UNPKG

@amityco/ts-sdk-react-native

Version:

Amity Social Cloud Typescript SDK

197 lines (166 loc) 5.6 kB
import axios, { AxiosResponse } from 'axios'; import HttpAgent, { HttpsAgent } from 'agentkeepalive'; import { ASCError } from '~/core/errors'; import { fireEvent } from '../events'; import { unwrapPayload } from './utils'; import NetworkActivitiesWatcher from '~/client/utils/NetworkActivitiesWatcher'; /* * Defined here as transport is a private module. Also, outside of this module * this enum holds no meaning. * * @private (exported for testing) */ export enum RequestCancelation { UserDeleted = 'User Deleted', UserGlobalBanned = 'User Global Banned', TokenExpired = 'Token Expired', } /* * Axios no longer supports `cancelToken`, since v0.22.0, and is marked as * deprecated. The doc asks to use the abort controller that fetch uses instead * * More Info: * https://axios-http.com/docs/cancellation */ const controller = new AbortController(); /** * Handle Request Cancellation * * @param cancel Reason for cancelation * * @category Transport Util * @hidden */ const handleRequestCancelation = (cancel: RequestCancelation) => { switch (cancel) { case RequestCancelation.UserGlobalBanned: /* * Note: * Firing a virtual event (fireEvent) is not required as the metadata in * the request (isGlobalBanned) is only true once the user has been banned * and the SDK has recieved a global ban event */ throw new ASCError(cancel, Amity.ServerError.GLOBAL_BAN, Amity.ErrorLevel.FATAL); case RequestCancelation.UserDeleted: /* * Note: * Firing a virtual event (fireEvent) is not required. For same reason as * above. Only difference is that the event recieved is user deleted */ throw new ASCError(cancel, Amity.ServerError.UNAUTHORIZED, Amity.ErrorLevel.FATAL); case RequestCancelation.TokenExpired: fireEvent('tokenExpired', Amity.SessionStates.TOKEN_EXPIRED); throw new ASCError(cancel, Amity.ClientError.TOKEN_EXPIRED, Amity.ErrorLevel.FATAL); default: throw new ASCError( 'Request Aborted', Amity.ClientError.UNKNOWN_ERROR, Amity.ErrorLevel.ERROR, ); } }; /** * Creates a pre-configured axios instance * * @param endpoint The ASC rest api server's URL * @returns A pre-configured axios instance * * @category Transport * @hidden */ export const createHttpTransport = (endpoint: string) => { const options = { maxSockets: 100, maxFreeSockets: 10, timeout: 60000, freeSocketTimeout: 30000, }; const instance = axios.create({ baseURL: endpoint, httpAgent: new HttpAgent(options), httpsAgent: new HttpsAgent(options), signal: controller.signal, /* * If paramSerializer is required, use the serialize option to stringify * params. The encode option will pass complex params as string only whereas * the serialize option will pass params in the format Record<string, any> * * For more details: * https://github.com/axios/axios#request-config */ }); instance.defaults.withCredentials = false; instance.interceptors.request.use(config => { // do not check expiration for token creation url if (config.url === '/api/v5/sessions') { return config; } // check global.ts for the extension of AxiosRequestConfig if (config.metadata) { const { tokenExpiry, isGlobalBanned, isUserDeleted } = config.metadata; if (isGlobalBanned) { controller.abort(); handleRequestCancelation(RequestCancelation.UserGlobalBanned); } if (isUserDeleted) { controller.abort(); handleRequestCancelation(RequestCancelation.UserDeleted); } if (tokenExpiry) { if (Date.now() >= Date.parse(tokenExpiry)) { controller.abort(RequestCancelation.UserDeleted); handleRequestCancelation(RequestCancelation.TokenExpired); } } } return config; }); instance.interceptors.response.use( response => { const responseHeaders = new Headers(); Object.entries(response.headers).forEach(([key, value]) => { if (typeof value === 'string') { responseHeaders.append(key, value); } }); const url = (response.config.baseURL || '') + (response.config.url || ''); const method = response.config.method?.toUpperCase(); const requestInit: RequestInit = { method, headers: response.config.headers, }; if (method !== 'GET' && method !== 'HEAD') { requestInit.body = response.config.data; } NetworkActivitiesWatcher.getInstance().setNetworkActivities(new Request(url, requestInit), { data: response.data, status: response.status, statusText: response.statusText, headers: responseHeaders, }); return response; }, error => { const { response } = error; // handle unauthorized request if (response?.data.code === Amity.ServerError.UNAUTHORIZED) { fireEvent('tokenTerminated', Amity.SessionStates.TERMINATED); } // if it's an error with status in the response payload, // then it's an expected error. if (response?.data?.status) { unwrapPayload(response?.data); } // as on request cancellation error is returned if (controller.signal.aborted) { throw error; } // unexpected error. throw new Error(response?.data ?? error); }, ); instance.defaults.paramsSerializer = { encode: params => encodeURIComponent(params), }; return instance; };