import { Feature } from 'oak-frontend-base/es/types/Feature'; import { OakRowInconsistencyException, OakUnloggedInException, OakUserUnpermittedException, OakNetworkException, OakServerProxyException, } from 'oak-domain/lib/types/Exception'; import { Cache } from 'oak-frontend-base/es/features/cache'; import { LocalStorage } from 'oak-frontend-base/es/features/localStorage'; import { Environment } from 'oak-frontend-base/es/features/environment'; import { WebEnv, WechatMpEnv } from 'oak-domain/lib/types/Environment'; import { EntityDict } from '../oak-app-domain'; import { tokenProjection } from '../types/Projection'; import { OakUserInfoLoadingException } from '../types/Exception'; import { LOCAL_STORAGE_KEYS } from '../config/constants'; import { cloneDeep } from 'oak-domain/lib/utils/lodash'; export class Token extends Feature { private tokenValue?: string; private environment: Environment; private cache: Cache; private storage: LocalStorage; private async loadSavedToken() { this.tokenValue = await this.storage.load(LOCAL_STORAGE_KEYS.token); await this.refreshTokenData(this.tokenValue); this.publish(); } constructor( cache: Cache, storage: LocalStorage, environment: Environment ) { super(); this.cache = cache; this.storage = storage; this.environment = environment; this.tokenValue = ''; // 置个空字符串代表还在load storage缓存的数据 this.loadSavedToken(); if ( process.env.OAK_PLATFORM === 'web' && (process.env.NODE_ENV !== 'development' || process.env.OAK_DEV_MODE === 'server') ) { // 纯前台模式 多窗口时不监听storage // 在web下可能多窗口,一个窗口更新了token,其它窗口应跟着变 window.addEventListener('storage', async (e) => { if (e.key === LOCAL_STORAGE_KEYS.token) { this.tokenValue = e.newValue ? JSON.parse(e.newValue) : undefined; await this.refreshTokenData(this.tokenValue); this.publish(); } }); } } async refreshTokenData(tokenValue?: string) { if (!tokenValue) { this.tokenValue = undefined; return; } const env = await this.environment.getEnv(); try { const { result } = await this.cache.exec( 'refreshToken', { tokenValue, env, }, undefined, true, true ); if (tokenValue !== result) { // 如果返回空字符串,token被disabled,tokenValue置为undefined if (result) { this.tokenValue = result; await this.storage.save(LOCAL_STORAGE_KEYS.token, result); } else { this.removeToken(true); } } } catch (err) { // refresh出了任何错都无视(排除网络异常),直接放弃此token console.warn(err); if ( err instanceof OakNetworkException || err instanceof OakServerProxyException ) { return; } this.removeToken(true); } } async loginByMobile( mobile: string, password?: string, captcha?: string, disableRegister?: boolean ) { const env = await this.environment.getEnv(); const { result } = await this.cache.exec( 'loginByMobile', { password, mobile, captcha, disableRegister, env, }, undefined, true ); this.tokenValue = result; await this.storage.save(LOCAL_STORAGE_KEYS.token, result); this.publish(); } async loginByWechatInWebEnv(wechatLoginId: string) { const env = await this.environment.getEnv(); const { result } = await this.cache.exec('loginByWechat', { env: env as WebEnv, wechatLoginId, }); this.tokenValue = result; await this.storage.save(LOCAL_STORAGE_KEYS.token, result); this.publish(); } async loginWechat(code: string, params?: { wechatLoginId?: string }) { const env = await this.environment.getEnv(); const { result } = await this.cache.exec('loginWechat', { code, env: env as WebEnv, wechatLoginId: params?.wechatLoginId, }); this.tokenValue = result; await this.storage.save(LOCAL_STORAGE_KEYS.token, result); this.publish(); } async loginWechatMp() { const { code } = await wx.login(); const env = await this.environment.getEnv(); const { result } = await this.cache.exec('loginWechatMp', { code, env: env as WechatMpEnv, }); this.tokenValue = result; await this.storage.save(LOCAL_STORAGE_KEYS.token, result); this.publish(); } async syncUserInfoWechatMp() { const info = await wx.getUserProfile({ desc: '同步微信昵称和头像信息', }); const { userInfo: { nickName: nickname, avatarUrl }, encryptedData, signature, iv, } = info; await this.cache.exec('syncUserInfoWechatMp', { nickname, avatarUrl, encryptedData, signature, iv, }); this.publish(); } async logout(dontPublish?: boolean) { await this.cache.exec( 'logout', { tokenValue: this.tokenValue!, }, undefined, undefined, true ); this.removeToken(dontPublish); } removeToken(dontPublish?: boolean) { this.tokenValue = undefined; this.storage.remove(LOCAL_STORAGE_KEYS.token); if (!dontPublish) { this.publish(); } } getTokenValue() { if (this.tokenValue === '') { throw new OakUserInfoLoadingException(); } return this.tokenValue; } getToken(allowUnloggedIn?: boolean) { if (this.tokenValue === '') { throw new OakUserInfoLoadingException(); } if (this.tokenValue) { const token = this.cache.get('token', { data: cloneDeep(tokenProjection), filter: { value: this.tokenValue!, }, })[0]; if (!token) { if (allowUnloggedIn) { return undefined; } throw new OakUserInfoLoadingException(); } return token; } if (allowUnloggedIn) { return undefined; } throw new OakUnloggedInException(); } getUserId(allowUnloggedIn?: boolean) { const token = this.getToken(allowUnloggedIn); if (token?.userId) { return token.userId!; } } // getUserInfo 不要求登录 getUserInfo() { const token = this.getToken(true); if (token?.user) { return token.user; } } isRoot(): boolean { const token = this.getToken(true); return !!token?.user?.isRoot; } /** * 这个是指token的player到底是不是root * @returns */ isReallyRoot(): boolean { const token = this.getToken(true); return !!token?.player?.isRoot; } async sendCaptcha( mobile: string, type: 'login' | 'changePassword' | 'confirm' ) { const env = await this.environment.getEnv(); const { result } = await this.cache.exec('sendCaptcha', { mobile, env: env as WebEnv, type, }); return result as string; } async switchTo(userId: string) { const currentUserId = this.getUserId(); if (currentUserId === userId) { throw new OakRowInconsistencyException( undefined, '您已经是当前用户' ); } await this.cache.exec('switchTo', { userId, }); this.publish(); } async refreshWechatPublicUserInfo() { await this.cache.exec('refreshWechatPublicUserInfo', {}); } async getWechatMpUserPhoneNumber(code: string) { const env = await this.environment.getEnv(); await this.cache.exec('getWechatMpUserPhoneNumber', { code, env: env as WechatMpEnv, }); } async wakeupParasite(id: string) { const env = await this.environment.getEnv(); const { result } = await this.cache.exec('wakeupParasite', { id, env: env as WechatMpEnv, }); this.tokenValue = result; await this.storage.save(LOCAL_STORAGE_KEYS.token, result); this.publish(); } }