Compare commits

...

2 commits

Author SHA1 Message Date
hippoz
5eb1c2d0ae
feat: message history from message-history branch 2021-10-04 22:43:20 +03:00
hippoz
8f4e3980c7
improvement!: clean up and optimize imports 2021-10-04 22:35:40 +03:00
19 changed files with 104 additions and 60 deletions

View file

@ -1,14 +1,10 @@
import Logger from '../common/util/Logger'; import Logger from '../common/util/logger';
import gateway from "./gateway/globalGatewayConnection"; import gateway from "./gateway/globalGatewayConnection";
import token from "./tokenManager"; import { getToken } from "./tokenManager";
const { log: authLog } = Logger([ 'Authenticator' ]); const { log: authLog } = Logger([ 'Authenticator' ]);
const Authenticator = { export function login() {
login: async function() { authLog('Logging in through gateway...');
authLog('Logging in through gateway...'); return gateway.connect(getToken());
return gateway.connect(token.getToken());
}
}; };
export default Authenticator;

View file

@ -1,4 +1,4 @@
import logger from "../../common/util/Logger"; import logger from "../../common/util/logger";
const { log: logGateway } = logger([ "Gateway" ]); const { log: logGateway } = logger([ "Gateway" ]);
const { log: logRtc } = logger([ "Gateway", "RTC" ]); const { log: logRtc } = logger([ "Gateway", "RTC" ]);

View file

@ -1,7 +1,7 @@
import GatewayConnection from './GatewayConnection'; import GatewayConnection from './GatewayConnection';
import config from '../../config'; import config from '../../config';
import store from '../../common/store'; import store from '../../common/store';
import logger from '../../common/util/Logger'; import logger from '../../common/util/logger';
const { log } = logger(["globalGatewayConnection"]); const { log } = logger(["globalGatewayConnection"]);
const { warn: experimentsWarn } = logger(["globalGatewayConnection", "Experiments"]); const { warn: experimentsWarn } = logger(["globalGatewayConnection", "Experiments"]);
@ -18,6 +18,7 @@ const wsCloseCodes = {
NOT_AUTHORIZED: [4006, "Not authorized"], NOT_AUTHORIZED: [4006, "Not authorized"],
FLOODING: [4007, "Flooding"], FLOODING: [4007, "Flooding"],
NO_PING: [4008, "No ping"], NO_PING: [4008, "No ping"],
UNSUPPORTED_ATTRIBUTE: [4009, "Unsupported attribute."],
}; };
const cancelReconnectionForCodes = [ const cancelReconnectionForCodes = [
wsCloseCodes.NOT_AUTHENTICATED[0], wsCloseCodes.NOT_AUTHENTICATED[0],
@ -25,6 +26,7 @@ const cancelReconnectionForCodes = [
wsCloseCodes.SERVER_DENIED_CONNECTION[0], wsCloseCodes.SERVER_DENIED_CONNECTION[0],
wsCloseCodes.TOO_MANY_SESSIONS[0], wsCloseCodes.TOO_MANY_SESSIONS[0],
wsCloseCodes.FLOODING[0], wsCloseCodes.FLOODING[0],
wsCloseCodes.UNSUPPORTED_ATTRIBUTE[0],
]; ];
globalGatewayConnection.onopen = (sessionData) => { globalGatewayConnection.onopen = (sessionData) => {

View file

@ -1,7 +1,7 @@
import config from '../config'; import config from '../config';
import token from "./tokenManager"; import { getToken } from "./tokenManager";
async function APIRequest(endpoint, options) { export async function APIRequest(endpoint, options) {
let res; let res;
let json; let json;
let isOK = false; let isOK = false;
@ -28,7 +28,7 @@ async function APIRequest(endpoint, options) {
return { res, json, isOK, err }; return { res, json, isOK, err };
} }
APIRequest.authenticated = async function(endpoint, options) { export async function authenticated(endpoint, options) {
let res; let res;
let json; let json;
let isOK = false; let isOK = false;
@ -38,7 +38,7 @@ APIRequest.authenticated = async function(endpoint, options) {
if (!options.headers) options.headers = {}; if (!options.headers) options.headers = {};
options.headers = { options.headers = {
"Authorization": token.getToken(), "Authorization": getToken(),
...options.headers ...options.headers
}; };

View file

@ -1,6 +1,7 @@
const tokenManager = { export function getToken() {
getToken: () => localStorage.getItem("token"), return localStorage.getItem("token");
setToken: (token) => localStorage.setItem("token", token)
}; };
export default tokenManager; export function setToken(token) {
localStorage.setItem("token", token);
};

View file

@ -54,6 +54,19 @@ const reducer = (state = intitialState, payload) => {
}; };
} }
case 'messagestore/addmessagesback': {
return {
...state,
messages: {
...state.messages,
[payload.channelId]: [
...payload.messages,
...state.messages[payload.channelId] || []
]
}
};
}
case 'channels/selectchannel': { case 'channels/selectchannel': {
return { return {
...state, ...state,

View file

@ -1,11 +1,13 @@
export default function FullMessage({ glyph, title, content }) { export default function FullMessage({ glyph, title, content }) {
return (<div className="center grow col-flex"> return (
<div className="center grow col-flex">
<div className="greeter-logo"> <div className="greeter-logo">
<span className="greeter-logo-text">{ glyph }</span> <span className="greeter-logo-text">{glyph}</span>
</div> </div>
<span className="greeter-branding-name">{ title }</span> <span className="greeter-branding-name">{title}</span>
<div className="center"> <div className="center">
{ content } {content}
</div> </div>
</div>) </div>
);
} }

View file

@ -3,7 +3,7 @@ import { useHistory } from 'react-router-dom';
import Notification from '../Notification'; import Notification from '../Notification';
import APIRequest from '../../api/request'; import APIRequest from '../../api/request';
import { getSignupMessageFromError } from '../../common/util/Errors' import { getSignupMessageFromError } from '../../common/util/errors'
export default function Create() { export default function Create() {
const history = useHistory(); const history = useHistory();
@ -11,7 +11,6 @@ export default function Create() {
const [ usernameInput, setUsernameInput ] = useState(); const [ usernameInput, setUsernameInput ] = useState();
const [ passwordInput, setPasswordInput ] = useState(); const [ passwordInput, setPasswordInput ] = useState();
const [ specialCodeInput, setSpecialCodeInput ] = useState(); const [ specialCodeInput, setSpecialCodeInput ] = useState();
const [ specialCodePrompt, setSpecialCodePrompt ] = useState(); const [ specialCodePrompt, setSpecialCodePrompt ] = useState();
const [ info, setInfo ] = useState(); const [ info, setInfo ] = useState();

View file

@ -3,9 +3,9 @@ import { useHistory } from 'react-router-dom';
import Notification from '../Notification'; import Notification from '../Notification';
import APIRequest from '../../api/request'; import APIRequest from '../../api/request';
import Authenticator from '../../api/authenticator'; import { login } from '../../api/authenticator';
import token from "../../api/tokenManager"; import { setToken } from "../../api/tokenManager";
import { getLoginMessageFromError } from '../../common/util/Errors' import { getLoginMessageFromError } from '../../common/util/errors'
export default function Login() { export default function Login() {
const history = useHistory(); const history = useHistory();
@ -42,8 +42,8 @@ export default function Login() {
return; return;
} }
token.setToken(json.token); setToken(json.token);
await Authenticator.login(); await login();
history.push('/'); history.push('/');
} }

View file

@ -1,8 +1,8 @@
import { useHistory } from 'react-router-dom';
import ChannelProfile from './ChannelProfileLink'; import ChannelProfile from './ChannelProfileLink';
import gatewayConnection from '../../api/gateway/globalGatewayConnection'; import gatewayConnection from '../../api/gateway/globalGatewayConnection';
import { useHistory } from 'react-router-dom';
export default function ChannelButton({ channel, selected }) { export default function ChannelButton({ channel, selected }) {
const history = useHistory(); const history = useHistory();

View file

@ -1,8 +1,8 @@
import { connect } from 'react-redux'
import ChannelListLoader from './ChannelListLoader'; import ChannelListLoader from './ChannelListLoader';
import ChannelButton from './ChannelButton'; import ChannelButton from './ChannelButton';
import { connect } from 'react-redux'
function ChannelList({ selectedChannelId, channels }) { function ChannelList({ selectedChannelId, channels }) {
if (!channels) { if (!channels) {
return ( return (

View file

@ -1,21 +1,51 @@
import Message from "../Message"; import { useRef, useEffect, useState } from 'react';
import { useRef, useEffect } from 'react'; import { useDispatch } from "react-redux";
export default function ChannelMessageView({messages}) { import { authenticated } from "../../api/request";
import Message from "../Message";
export default function ChannelMessageView({ messages, channelId }) {
const invisibleBottomMessageRef = useRef(); const invisibleBottomMessageRef = useRef();
const [isLoading, setIsLoading] = useState(false);
const dispatch = useDispatch();
const scroller = useRef();
const loadOlderMessages = () => {
if (isLoading) return;
if (!channelId) return;
setIsLoading(true);
const request = messages[0] ? `/api/v1/content/channel/${channelId}/messages?before=${messages[0]._id}` : `/api/v1/content/channel/${channelId}/messages`;
authenticated(request)
.then((res) => {
if (res.json.channelMessages)
dispatch({
type: "messagestore/addmessagesback",
messages: res.json.channelMessages.reverse(),
channelId
})
})
.then(() => {
setIsLoading(false);
});
};
const onScroll = () => {
if (scroller.current.scrollTop === 0) {
loadOlderMessages();
}
};
useEffect(loadOlderMessages, [channelId, dispatch]);
useEffect(() => { useEffect(() => {
invisibleBottomMessageRef.current.scrollIntoView(true); invisibleBottomMessageRef.current.scrollIntoView(true);
}, [messages]); }, [messages]);
let messagesView = messages.map((message) => (<Message key={message._id} message={message} />)); return <div className="message-list" id="message-list" ref={ scroller } onScroll={ onScroll }>
if (messagesView === undefined || messagesView.length <= 0) {messages.map(message => <Message message={message} key={message._id} />)}
messagesView = (<div className='no-messages-warning'>
<span style={{ margin: "12px" }}>No messages yet...</span>
</div>);
return <div className="message-list">
{ messagesView }
<div className="messages-scroll-div" ref={ invisibleBottomMessageRef }></div> <div className="messages-scroll-div" ref={ invisibleBottomMessageRef }></div>
</div>; </div>;
} }

View file

@ -1,10 +1,10 @@
import { connect, useDispatch } from 'react-redux';
import { useState, useRef, useEffect } from 'react';
import ChannelProfile from './ChannelProfileLink'; import ChannelProfile from './ChannelProfileLink';
import gatewayConnection from '../../api/gateway/globalGatewayConnection'; import gatewayConnection from '../../api/gateway/globalGatewayConnection';
import ChannelUserList from "./ChannelUserList"; import ChannelUserList from "./ChannelUserList";
import ProfileLinkLoader from "../ProfileLinkLoader"; import ProfileLinkLoader from "../ProfileLinkLoader";
import { connect, useDispatch } from 'react-redux';
import { useState, useRef, useEffect } from 'react';
import ChannelMessageView from './ChannelMessageView'; import ChannelMessageView from './ChannelMessageView';
const ChannelView = ({ messages, channel, channelPresenceClientList }) => { const ChannelView = ({ messages, channel, channelPresenceClientList }) => {
@ -41,7 +41,7 @@ const ChannelView = ({ messages, channel, channelPresenceClientList }) => {
<div className="row-flex hidden-overflow"> <div className="row-flex hidden-overflow">
<div className="channel-message-panel"> <div className="channel-message-panel">
<ChannelMessageView messages={messages}></ChannelMessageView> <ChannelMessageView messages={messages} channelId={channel._id}></ChannelMessageView>
<div className="col-flex"> <div className="col-flex">
<input className="text-input message-input" type="text" placeholder="Go on, type something interesting!" ref={ textInputRef } onKeyDown={ handleTextboxKeydown } onChange={ ({ target }) => setTextInput(target.value) }></input> <input className="text-input message-input" type="text" placeholder="Go on, type something interesting!" ref={ textInputRef } onKeyDown={ handleTextboxKeydown } onChange={ ({ target }) => setTextInput(target.value) }></input>
</div> </div>

View file

@ -2,12 +2,12 @@ import { useEffect, useState } from 'react';
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { BrowserRouter, Switch, Route } from 'react-router-dom'; import { BrowserRouter, Switch, Route } from 'react-router-dom';
import './../../styles/App.scss';
import Login from '../auth/Login'; import Login from '../auth/Login';
import Create from '../auth/Create'; import Create from '../auth/Create';
import Root from '../main/Root'; import Root from '../main/Root';
import Authenticator from '../../api/authenticator'; import { login } from '../../api/authenticator';
import Notification from '../Notification'; import Notification from '../Notification';
import './../../styles/App.scss';
import LoggedInMount from './LoggedInMount'; import LoggedInMount from './LoggedInMount';
import FullMessage from '../FullMessage'; import FullMessage from '../FullMessage';
@ -16,7 +16,7 @@ function App({ user, fullscreenMessage }) {
const [ hasError ] = useState(false); const [ hasError ] = useState(false);
useEffect(() => { useEffect(() => {
Authenticator.login(); login();
}, []); }, []);
if (user === null && !hasError) { if (user === null && !hasError) {

View file

@ -1,9 +1,10 @@
import { connect } from "react-redux";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import Sidebar from "../Sidebar"; import Sidebar from "../Sidebar";
import ChannelView from "../channel/ChannelView"; import ChannelView from "../channel/ChannelView";
import GradientBanner from "../GradientBanner"; import GradientBanner from "../GradientBanner";
import UserView from "../user/UserView"; import UserView from "../user/UserView";
import { connect } from "react-redux";
function LoggedInMount({ gradientBannerNotificationText }) { function LoggedInMount({ gradientBannerNotificationText }) {
const { channelId, userId } = useParams(); const { channelId, userId } = useParams();

View file

@ -1,5 +1,5 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import APIRequest from "../../api/request"; import { authenticated } from "../../api/request";
import UserProfile from "./UserProfileLink"; import UserProfile from "./UserProfileLink";
import ProfileLinkLoader from "../ProfileLinkLoader"; import ProfileLinkLoader from "../ProfileLinkLoader";
@ -7,7 +7,7 @@ export default function UserView({ userId }) {
const [userObject, setUserObject] = useState(null); const [userObject, setUserObject] = useState(null);
useEffect(() => { useEffect(() => {
APIRequest.authenticated(`/api/v1/users/user/${userId}/info`, { authenticated(`/api/v1/users/user/${userId}/info`, {
method: 'GET', method: 'GET',
headers: { headers: {
"Accept": "application/json" "Accept": "application/json"
@ -23,8 +23,8 @@ export default function UserView({ userId }) {
if (userObject) { if (userObject) {
view = <> view = <>
<UserProfile user={ userObject } size="32" /> <UserProfile user={ userObject } size="32" />
{(userObject.role === "ADMIN") && <span>Admin</span>} {(userObject.role === "ADMIN") && <span style={{ padding: "12px" }}>Admin</span>}
{(userObject.role === "USER") && <span>User</span>} {(userObject.role === "USER") && <span style={{ padding: "12px" }}>User</span>}
</> </>
} else { } else {
view = <> view = <>

View file

@ -69,7 +69,7 @@
} }
.profile-username { .profile-username {
font-weight: 500; font-weight: 600;
font-size: 1rem; font-size: 1rem;
line-height: 1.256rem; line-height: 1.256rem;
} }