refactor(web): 主页面登录状态的管理

pull/13/head
moonrailgun 4 years ago
parent 0cdd7a10bb
commit 66e158d86d

@ -28,6 +28,7 @@ export { setTokenGetter } from './manager/request';
export { loginWithEmail, registerWithEmail } from './model/user';
// redux
export { userActions } from './redux/slices';
export { setupRedux } from './redux/setup';
export { createStore } from './redux/store';
export type { AppStore, AppDispatch } from './redux/store';

@ -1,6 +1,6 @@
import { request } from '../api/request';
interface UserLoginInfo {
export interface UserLoginInfo {
_id: string;
email: string;
password: string;
@ -26,6 +26,18 @@ export async function loginWithEmail(
return data;
}
/**
* 使 Token
* @param token JWT
*/
export async function loginWithToken(token: string): Promise<UserLoginInfo> {
const { data } = await request.post('/api/user/resolveToken', {
token,
});
return data;
}
/**
*
* @param email

@ -6,3 +6,5 @@ export const appReducer = combineReducers({
});
export type AppState = ReturnType<typeof appReducer>;
export { userActions } from './user';

@ -1,11 +1,21 @@
import { createSlice } from '@reduxjs/toolkit';
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import type { UserLoginInfo } from '../../model/user';
const initialState = {};
interface UserState {
info: UserLoginInfo | null;
}
const initialState: UserState = { info: null };
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {},
reducers: {
setUserInfo(state, action: PayloadAction<UserLoginInfo>) {
state.info = action.payload;
},
},
});
export const userActions = userSlice.actions;
export const userReducer = userSlice.reducer;

@ -7,6 +7,8 @@
"isolatedModules": true,
"module": "ESNext",
"moduleResolution": "node",
"strict": true,
"importsNotUsedAsValues": "error",
"typeRoots": ["./node_modules/@types", "../node_modules/@types", "./types"]
}
}

@ -1,28 +0,0 @@
import { createStore } from 'pawchat-shared';
import React, { useMemo } from 'react';
import { useEnsureSocket } from '../hooks/useEnsureSocket';
import { LoadingSpinner } from './LoadingSpinner';
import { Provider as ReduxProvider } from 'react-redux';
import { useSetupRedux } from '../hooks/useSetupRedux';
/**
* Provider
*
*/
export const MainProvider: React.FC = React.memo((props) => {
const store = useMemo(() => createStore(), []);
const { socket, loading } = useEnsureSocket();
useSetupRedux(socket, store);
if (loading) {
return (
<div className="w-screen h-screen flex items-center justify-center bg-gray-700 text-white text-xl">
<LoadingSpinner tip="正在连接到聊天服务器..." />
</div>
);
}
return <ReduxProvider store={store}>{props.children}</ReduxProvider>;
});
MainProvider.displayName = 'MainProvider';

@ -0,0 +1,11 @@
import type { AppState } from 'pawchat-shared/redux/slices';
import { useSelector, useDispatch } from 'react-redux';
export function useAppSelector<T>(
selector: (state: AppState) => T,
equalityFn?: (left: T, right: T) => boolean
) {
return useSelector<AppState, T>(selector, equalityFn);
}
export const useAppDispatch = useDispatch;

@ -1,25 +0,0 @@
import { createSocket, useAsync } from 'pawchat-shared';
import { getUserJWT } from '../utils/jwt-helper';
/**
* Socket
*/
export function useEnsureSocket() {
const {
value: socket,
loading,
error,
} = useAsync(async () => {
const token = await getUserJWT();
if (typeof token !== 'string') {
throw new Error('Token不合法');
}
const socket = await createSocket(token);
console.log('当前socket连接成功');
return socket;
}, []);
return { loading, error, socket };
}

@ -1,13 +0,0 @@
import { AppSocket, AppStore, setupRedux } from 'pawchat-shared';
import { useEffect } from 'react';
/**
*
*/
export function useSetupRedux(socket: AppSocket, store: AppStore) {
useEffect(() => {
if (socket !== undefined && store !== undefined) {
setupRedux(socket, store);
}
}, [socket, store]);
}

@ -6,6 +6,7 @@ import { Spinner } from '../../components/Spinner';
import { string } from 'yup';
import { useHistory } from 'react-router';
import { setUserJWT } from '../../utils/jwt-helper';
import { setGlobalUserLoginInfo } from '../../utils/user-helper';
/**
* TODO:
@ -45,6 +46,7 @@ export const LoginView: React.FC = React.memo(() => {
const data = await loginWithEmail(email, password);
setGlobalUserLoginInfo(data);
await setUserJWT(data.token);
history.push('/main');
}, [email, password, history]);

@ -5,6 +5,7 @@ import { string } from 'yup';
import { Icon } from '@iconify/react';
import { useHistory } from 'react-router';
import { setUserJWT } from '../../utils/jwt-helper';
import { setGlobalUserLoginInfo } from '../../utils/user-helper';
/**
*
@ -27,6 +28,7 @@ export const RegisterView: React.FC = React.memo(() => {
const data = await registerWithEmail(email, password);
setGlobalUserLoginInfo(data);
await setUserJWT(data.token);
history.push('/main');
}, [email, password]);

@ -0,0 +1,12 @@
import React from 'react';
import { useAppSelector } from '../../hooks/useAppSelector';
/**
*
*/
const Navbar: React.FC = React.memo(() => {
const userInfo = useAppSelector((state) => state.user.info);
return <div></div>;
});
Navbar.displayName = 'Navbar';

@ -0,0 +1,81 @@
import {
createSocket,
createStore,
setupRedux,
useAsync,
userActions,
} from 'pawchat-shared';
import React, { useMemo } from 'react';
import { LoadingSpinner } from '../../components/LoadingSpinner';
import { Provider as ReduxProvider } from 'react-redux';
import { getGlobalUserLoginInfo } from '../../utils/user-helper';
import _isNil from 'lodash/isNil';
import { loginWithToken } from 'pawchat-shared/model/user';
import { getUserJWT } from '../../utils/jwt-helper';
import { useHistory } from 'react-router';
/**
* hooks
*/
function useAppState() {
const history = useHistory();
const { value, loading } = useAsync(async () => {
let userLoginInfo = getGlobalUserLoginInfo();
if (_isNil(userLoginInfo)) {
// 如果没有全局缓存的数据, 则尝试自动登录
try {
const token = await getUserJWT();
if (typeof token !== 'string') {
throw new Error('Token 不合法');
}
userLoginInfo = await loginWithToken(token);
} catch (e) {
// 当前 Token 不存在或已过期
history.replace('/entry/login');
return;
}
}
// 到这里 userLoginInfo 必定存在
// 创建Redux store
const store = createStore();
store.dispatch(userActions.setUserInfo(userLoginInfo));
// 创建 websocket 连接
const socket = await createSocket(userLoginInfo.token);
// 初始化Redux
setupRedux(socket, store);
return { store, socket };
}, [history]);
const store = value?.store;
const socket = value?.socket;
return { loading, store, socket };
}
/**
* Provider
*
*/
export const MainProvider: React.FC = React.memo((props) => {
const { loading, store } = useAppState();
if (loading) {
return (
<div className="w-screen h-screen flex items-center justify-center bg-gray-700 text-white text-xl">
<LoadingSpinner tip="正在连接到聊天服务器..." />
</div>
);
}
if (_isNil(store)) {
return <div>, Store </div>;
}
return <ReduxProvider store={store}>{props.children}</ReduxProvider>;
});
MainProvider.displayName = 'MainProvider';

@ -1,9 +1,7 @@
import { Icon } from '@iconify/react';
import clsx, { ClassValue } from 'clsx';
import React, { useLayoutEffect } from 'react';
import { LoadingSpinner } from '../components/LoadingSpinner';
import { MainProvider } from '../components/MainProvider';
import { useEnsureSocket } from '../hooks/useEnsureSocket';
import React from 'react';
import { MainProvider } from './Provider';
const NavbarNavItem: React.FC<{
className?: ClassValue;

@ -0,0 +1,11 @@
import { UserLoginInfo } from 'pawchat-shared/model/user';
let _userLoginInfo: UserLoginInfo;
export function setGlobalUserLoginInfo(loginInfo: UserLoginInfo) {
_userLoginInfo = loginInfo;
}
export function getGlobalUserLoginInfo() {
return _userLoginInfo;
}
Loading…
Cancel
Save