UNPKG

stream-chat

Version:

JS SDK for the Stream Chat API

161 lines (136 loc) 4.35 kB
import type jwt from 'jsonwebtoken'; import { JWTServerToken, JWTUserToken, UserFromToken } from './signing'; import { isFunction } from './utils'; import type { TokenOrProvider, UserResponse } from './types'; /** * TokenManager * * Handles all the operations around user token. */ export class TokenManager { loadTokenPromise: Promise<string> | null; type: 'static' | 'provider'; secret?: jwt.Secret; token?: string; tokenProvider?: TokenOrProvider; user?: UserResponse; /** * Constructor * * @param {Secret} secret */ constructor(secret?: jwt.Secret) { this.loadTokenPromise = null; if (secret) { this.secret = secret; } this.type = 'static'; if (this.secret) { this.token = JWTServerToken(this.secret); } } /** * Set the static string token or token provider. * Token provider should return a token string or a promise which resolves to string token. * * @param {TokenOrProvider} tokenOrProvider * @param {UserResponse} user */ setTokenOrProvider = async (tokenOrProvider: TokenOrProvider, user: UserResponse) => { this.validateToken(tokenOrProvider, user); this.user = user; if (isFunction(tokenOrProvider)) { this.tokenProvider = tokenOrProvider; this.type = 'provider'; } if (typeof tokenOrProvider === 'string') { this.token = tokenOrProvider; this.type = 'static'; } if (!tokenOrProvider && this.user && this.secret) { this.token = JWTUserToken(this.secret, user.id, {}, {}); this.type = 'static'; } await this.loadToken(); }; /** * Resets the token manager. * Useful for client disconnection or switching user. */ reset = () => { this.token = undefined; this.tokenProvider = undefined; this.type = 'static'; this.user = undefined; this.loadTokenPromise = null; }; // Validates the user token. validateToken = (tokenOrProvider: TokenOrProvider, user: UserResponse) => { // allow empty token for anon user if (user && user.anon && !tokenOrProvider) return; // Don't allow empty token for non-server side client. if (!this.secret && !tokenOrProvider) { throw new Error('User token can not be empty'); } if ( tokenOrProvider && typeof tokenOrProvider !== 'string' && !isFunction(tokenOrProvider) ) { throw new Error('user token should either be a string or a function'); } if (typeof tokenOrProvider === 'string') { // Allow empty token for anonymous users if (user.anon && tokenOrProvider === '') return; const tokenUserId = UserFromToken(tokenOrProvider); if ( tokenOrProvider != null && (tokenUserId == null || tokenUserId === '' || tokenUserId !== user.id) ) { throw new Error( 'userToken does not have a user_id or is not matching with user.id', ); } } }; // Resolves when token is ready. This function is simply to check if loadToken is in progress, in which // case a function should wait. tokenReady = () => this.loadTokenPromise; // Fetches a token from tokenProvider function and sets in tokenManager. // In case of static token, it will simply resolve to static token. loadToken = () => { // eslint-disable-next-line no-async-promise-executor this.loadTokenPromise = new Promise(async (resolve, reject) => { if (this.type === 'static') { return resolve(this.token as string); } if (this.tokenProvider && typeof this.tokenProvider !== 'string') { try { this.token = await this.tokenProvider(); } catch (e) { return reject( new Error(`Call to tokenProvider failed with message: ${e}`, { cause: e }), ); } resolve(this.token); } }); return this.loadTokenPromise; }; // Returns a current token getToken = () => { if (this.token) { return this.token; } if (this.user && this.user.anon && !this.token) { return this.token; } if (this.secret) { return JWTServerToken(this.secret); } throw new Error( `Both secret and user tokens are not set. Either client.connectUser wasn't called or client.disconnect was called`, ); }; isStatic = () => this.type === 'static'; }