UNPKG

@amityco/ts-sdk-react-native

Version:

Amity Social Cloud Typescript SDK

271 lines (217 loc) 8.02 kB
import jwtDecode from 'jwt-decode'; /* eslint-disable no-param-reassign */ import { modifyMqttConnection } from '~/client/utils/modifyMqttConnection'; /* eslint-disable require-atomic-updates */ import { proxyWebsocketEvents } from '~/core/events'; import { onChannelDeleted } from '~/channelRepository/events/onChannelDeleted'; import { onChannelMemberBanned } from '~/channelRepository/events/onChannelMemberBanned'; import { markReadEngineOnLoginHandler } from '~/subChannelRepository/utils/markReadEngine'; import { onUserDeleted } from '~/userRepository/events/onUserDeleted'; import analyticsEngineOnLoginHandler from '~/analytic/utils/analyticsEngineOnLoginHandler'; import readReceiptSyncEngineOnLoginHandler from '~/client/utils/ReadReceiptSync/readReceiptSyncEngineOnLoginHandler'; import legacyReadReceiptSyncEngineOnLoginHandler from '~/client/utils/ReadReceiptSync/legacyReadReceiptSyncEngineOnLoginHandler'; import objectResolverEngineOnLoginHandler from '~/client/utils/ObjectResolver/objectResolverEngineOnLoginHandler'; import { logout } from './logout'; import { getActiveClient } from './activeClient'; import { terminateClient } from './terminateClient'; import { setActiveUser } from './activeUser'; import { setSessionState } from './setSessionState'; import { onClientBanned } from '../events'; import { onTokenExpired } from '../events/onTokenExpired'; import { onTokenTerminated } from '../events/onTokenTerminated'; import { removeChannelMarkerCache } from '../utils/removeChannelMarkerCache'; import { initializeMessagePreviewSetting } from '../utils/messagePreviewEngine'; import { ASCError } from '~/core/errors'; import SessionWatcher from '../utils/SessionWatcher'; /* * declared earlier to accomodate case when logging in with a different user * than the one already connected, in which case the existing subscriptions need * to be cleared */ let subscriptions: Amity.Unsubscriber[] = []; async function runMqtt() { await modifyMqttConnection(); } interface Params { userId: string; token: { issuedAt: string; expiresAt: string; accessToken: string; }; } const isSameUserId = (token: Params['token']['accessToken']) => { const client = getActiveClient(); const decoded = jwtDecode<{ user: { userId: string; publicUserId: string; deviceInfo: Amity.Device['deviceInfo']; networkId: string; displayName: string; refreshToken: string; }; }>(token); return decoded?.user?.publicUserId === client.userId; }; const validateAccessToken = async ({ token, userId }: Params) => { const client = getActiveClient(); // begin establishing session setSessionState(Amity.SessionStates.ESTABLISHING); const { data: { users }, } = await client.http.get(`/api/v3/users/${userId}`, { headers: { Authorization: `Bearer ${token.accessToken}`, }, }); const user = users.find((u: Amity.User) => u.userId === userId); client.http.defaults.headers.common.Authorization = `Bearer ${token.accessToken}`; client.http.defaults.metadata = { tokenExpiry: token.expiresAt, isGlobalBanned: false, isUserDeleted: false, }; client.upload.defaults.headers.common.Authorization = `Bearer ${token.accessToken}`; client.upload.defaults.metadata = { tokenExpiry: token.expiresAt, isGlobalBanned: false, isUserDeleted: false, }; client.token = token; setSessionState(Amity.SessionStates.ESTABLISHED); return user; }; /* begin_public_function id: client.resumeSession */ /** * ```js * import { resumeSession } from '@amityco/ts-sdk/client/api' * const success = await resumeSession({ * userId: 'XYZ123456789', * token: { accessToken: 'abc123', issuedAt: '2023-01-01T00:00:00Z', expiresAt: '2023-01-02T00:00:00Z' } * }) * ``` * * Connects an {@link Amity.Client} instance to ASC servers using an existing access token * * @param params the connect parameters * @param params.userId the user ID for the current session * @param params.token the existing access token with its metadata * @param sessionHandler the session handler for token renewal * @param config optional configuration * @returns a success boolean if connected * * @category Client API * @async */ export const resumeSession = async ( params: Params, sessionHandler: Amity.SessionHandler, config?: Amity.ConnectClientConfig, ): Promise<boolean> => { const client = getActiveClient(); let unsubWatcher: Amity.Unsubscriber; client.log('client/api/resumeSession', { apiKey: client.apiKey, sessionState: client.sessionState, ...params, }); // Handle existing connected user if (client.userId) { if (client.userId === params.userId && isSameUserId(params.token.accessToken)) { // Clear connections and listeners but preserve cache if (client.mqtt && client.mqtt.connected) { client.mqtt.disconnect(); } // Clear existing subscriptions subscriptions.forEach(fn => fn()); subscriptions = []; } else { // Different user - do full logout await logout(); // Remove subscription to ban and delete subscriptions.forEach(fn => fn()); subscriptions = []; } } try { const user = await validateAccessToken(params); if (user == null) { throw new ASCError( `${params.userId} has not been found`, Amity.ClientError.UNKNOWN_ERROR, Amity.ErrorLevel.ERROR, ); } if (user.isDeleted) { terminateClient(Amity.TokenTerminationReason.USER_DELETED); return false; } if (user.isGlobalBanned) { terminateClient(Amity.TokenTerminationReason.GLOBAL_BAN); return false; } client.userId = user.userId; client.sessionHandler = sessionHandler; /* * Cannot push to subscriptions as watcher needs to continue working even if * token expires */ unsubWatcher = client.accessTokenExpiryWatcher(sessionHandler); setActiveUser(user); } catch (error) { /* * if getting token failed session state reverts to initial state when app * is first launched */ SessionWatcher.getInstance().setSessionState(Amity.SessionStates.NOT_LOGGED_IN); // pass error down tree so the calling function handle it throw error; } if (config?.disableRTE !== true) { runMqtt(); } await initializeMessagePreviewSetting(); if (subscriptions.length === 0) { subscriptions.push( // GLOBAL_BAN onClientBanned((_: Amity.UserPayload) => { terminateClient(Amity.TokenTerminationReason.GLOBAL_BAN); subscriptions.forEach(fn => fn()); unsubWatcher(); }), onTokenTerminated(_ => { terminateClient(); subscriptions.forEach(fn => fn()); unsubWatcher(); }), onUserDeleted((user: Amity.InternalUser) => { if (user.userId === client.userId) { terminateClient(Amity.TokenTerminationReason.USER_DELETED); subscriptions.forEach(fn => fn()); unsubWatcher(); } }), onTokenExpired(state => { SessionWatcher.getInstance().setSessionState(state); logout(); subscriptions.forEach(fn => fn()); }), // NOTE: This is a temporary solution to handle the channel marker when the user is forced to leave // the channel because currently backend can't handle this, so every time a user is banned from // a channel or the channel is deleted the channel's unread count will not be reset to zero onChannelDeleted(removeChannelMarkerCache), onChannelMemberBanned(removeChannelMarkerCache), markReadEngineOnLoginHandler(), analyticsEngineOnLoginHandler(), objectResolverEngineOnLoginHandler(), ); if (client.useLegacyUnreadCount) { subscriptions.push(readReceiptSyncEngineOnLoginHandler()); } else subscriptions.push(legacyReadReceiptSyncEngineOnLoginHandler()); } return true; }; /* end_public_function */