@amityco/ts-sdk-react-native
Version:
Amity Social Cloud Typescript SDK
271 lines (217 loc) • 8.02 kB
text/typescript
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 */