UNPKG

react-native-mosquito-transport

Version:

React native javascript sdk for mosquito-transport (https://github.com/brainbehindx/mosquito-transport)

191 lines (160 loc) 7.95 kB
import { doSignOut, revokeAuthIntance } from "."; import EngineApi from "../../helpers/engine_api"; import { AuthTokenListener, TokenRefreshListener } from "../../helpers/listeners"; import { decodeBinary, deserializeE2E, listenReachableServer } from "../../helpers/peripherals"; import { awaitStore, buildFetchInterface, buildFetchResult, getPrefferTime, updateCacheStore } from "../../helpers/utils"; import { CacheStore, Scoped } from "../../helpers/variables"; import { simplifyError } from "simplify-error"; import { Validator } from "guard-object"; import { basicClone } from "../../helpers/basic_clone"; export const listenToken = (callback, projectUrl) => AuthTokenListener.listenTo(projectUrl, (t, n) => { if (t === undefined) return; callback?.(t || null, n); }, true); export const injectFreshToken = async (config, { token, refreshToken }) => { const { projectUrl } = config; await awaitStore(); CacheStore.AuthStore[projectUrl] = { token, refreshToken }; Scoped.AuthJWTToken[projectUrl] = token; const isEmulated = projectUrl in CacheStore.EmulatedAuth; if (isEmulated) delete CacheStore.EmulatedAuth[projectUrl]; updateCacheStore(['AuthStore', isEmulated ? 'EmulatedAuth' : ''].filter(v => v)); triggerAuthToken(projectUrl); initTokenRefresher(config); }; export const injectEmulatedAuth = async (config, emulatedURL) => { if (!Scoped.IsStoreReady) await awaitStore(); if (typeof emulatedURL !== 'string' || (!Validator.HTTPS(emulatedURL) && !Validator.HTTP(emulatedURL))) throw `Expected "projectUrl" to be valid https or http link but got "${emulatedURL}"`; const { projectUrl } = config; const { token } = CacheStore.AuthStore[emulatedURL] || {}; const depended = Object.entries(CacheStore.EmulatedAuth).find(([_, v]) => projectUrl === v); if (emulatedURL === projectUrl) throw `auth instance for ${emulatedURL} cannot emulate itself`; if (depended) throw `Chain Emulation Error: this auth instance (${projectUrl}) cannot be emulated as other auth instance (${depended[0]}) is already emulating it`; const thisAuthStore = basicClone(CacheStore.AuthStore[projectUrl]); revokeAuthIntance(config, thisAuthStore); CacheStore.AuthStore[projectUrl] = basicClone(CacheStore.AuthStore[emulatedURL]); Scoped.AuthJWTToken[projectUrl] = token; CacheStore.EmulatedAuth[projectUrl] = emulatedURL; updateCacheStore(['AuthStore', 'EmulatedAuth']); triggerAuthToken(projectUrl); initTokenRefresher(config); }; export const parseToken = (token) => JSON.parse(decodeBinary(token.split('.')[1])); export const triggerAuthToken = async (projectUrl, isInit) => { await awaitStore(); AuthTokenListener.dispatch(projectUrl, CacheStore.AuthStore[projectUrl]?.token || null, isInit); }; export const awaitRefreshToken = (projectUrl) => new Promise(resolve => { const l = TokenRefreshListener.listenTo(projectUrl, v => { if (v === 'ready') { l(); resolve(); } }, true); }); export const listenTokenReady = (callback, projectUrl) => TokenRefreshListener.listenTo(projectUrl, callback, true); export const initTokenRefresher = async (config, forceRefresh) => { const { projectUrl, maxRetries } = config; if (!Scoped.IsStoreReady) await awaitStore(); const { token } = CacheStore.AuthStore[projectUrl] || {}; const emulatedURL = CacheStore.EmulatedAuth[projectUrl]; const tokenInfo = token && parseToken(token); clearInterval(Scoped.TokenRefreshTimer[projectUrl]); if (emulatedURL) return; const notifyAuthReady = (value) => { TokenRefreshListener.dispatch(projectUrl, value); getEmulatedLinks(projectUrl).forEach(v => { TokenRefreshListener.dispatch(v, value); }); } if (token) { const expireOn = (tokenInfo.exp * 1000) - 60000; const hasExpire = getPrefferTime() >= expireOn; const rizz = () => refreshToken(config, ++Scoped.LastTokenRefreshRef[projectUrl], maxRetries, forceRefresh); if (hasExpire || forceRefresh) { notifyAuthReady(); return rizz(); } else { notifyAuthReady('ready'); Scoped.TokenRefreshTimer[projectUrl] = setInterval(() => { const countdown = expireOn - getPrefferTime(); if (countdown > 3000) return; clearInterval(Scoped.TokenRefreshTimer[projectUrl]); notifyAuthReady(); rizz(); }, 3000); } } else { notifyAuthReady('ready'); if (forceRefresh) { return simplifyError('no_token_yet', 'No token is available to initiate a refresh').simpleError; } } }; export const getEmulatedLinks = (projectUrl) => Object.entries(CacheStore.EmulatedAuth) .filter(([_, v]) => v === projectUrl) .map(v => v[0]); const refreshToken = (builder, processRef, remainRetries = 1, isForceRefresh) => new Promise(async (resolve, reject) => { const { projectUrl, serverE2E_PublicKey, uglify, extraHeaders } = builder; const lostProcess = simplifyError('process_lost', 'The token refresh process has been lost and replaced with another one'); try { const { token, refreshToken: r_token } = CacheStore.AuthStore[projectUrl]; const [reqBuilder, [privateKey]] = await buildFetchInterface({ body: { token, r_token }, uglify, serverE2E_PublicKey, extraHeaders }); let data; try { data = await buildFetchResult(await fetch(EngineApi._refreshAuthToken(projectUrl, uglify), reqBuilder), uglify); } finally { if (processRef !== Scoped.LastTokenRefreshRef[projectUrl]) { reject(lostProcess.simpleError); return; } } const f = uglify ? await deserializeE2E(data, serverE2E_PublicKey, privateKey) : data; if (CacheStore.AuthStore[projectUrl]) { CacheStore.AuthStore[projectUrl].token = f.result.token; Scoped.AuthJWTToken[projectUrl] = f.result.token; resolve(f.result.token); const isInit = !Scoped.InitiatedForcedToken[projectUrl] && isForceRefresh; triggerAuthToken(projectUrl, isInit); if (isForceRefresh) Scoped.InitiatedForcedToken[projectUrl] = true; getEmulatedLinks(projectUrl).forEach(v => { CacheStore.AuthStore[v] = basicClone(CacheStore.AuthStore[projectUrl]); Scoped.AuthJWTToken[v] = f.result.token; triggerAuthToken(v, isInit); if (isForceRefresh) Scoped.InitiatedForcedToken[v] = true; }); updateCacheStore(['AuthStore']); initTokenRefresher(builder); } else reject(lostProcess.simpleError); } catch (e) { if (e.simpleError) { console.error(`refreshToken error: ${e.simpleError?.message}`); doSignOut({ ...builder }); reject(e.simpleError); } else if (remainRetries <= 0) { reject( processRef === Scoped.LastTokenRefreshRef[projectUrl] ? lostProcess.simpleError : simplifyError('retry_limit_reached', 'The retry limit has been reach and execution prematurely stopped').simpleError ); console.error(`refreshToken retry limit exceeded`); } else { const l = listenReachableServer(c => { if (processRef !== Scoped.LastTokenRefreshRef[projectUrl]) { reject(lostProcess.simpleError); l(); } else if (c) { l(); refreshToken(builder, processRef, remainRetries - 1, isForceRefresh).then(resolve, reject); } }, projectUrl); } } });