stream-chat
Version:
JS SDK for the Stream Chat API
161 lines (136 loc) • 4.35 kB
text/typescript
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';
}