@ringcentral/sdk
Version:
- [Installation](#installation) - [Getting Started](#getting-started) - [API Calls](#api-calls) - [Advanced SDK Configuration & Polyfills](#advanced-sdk-configuration--polyfills) - [Making telephony calls](#making-telephony-calls) - [Call mana
1,005 lines (858 loc) • 34 kB
text/typescript
import {
createHash,
randomBytes,
} from 'crypto';
import {EventEmitter} from 'events';
import Cache from '../core/Cache';
import * as Constants from '../core/Constants';
import Externals from '../core/Externals';
import Client, {ApiError} from '../http/Client';
import {objectToUrlParams} from '../http/utils';
import Auth, {AuthOptions} from './Auth';
import Discovery from './Discovery';
import {delay} from './utils';
declare const screen: any; //FIXME TS Crap
const getParts = (url, separator) => url.split(separator).reverse()[0];
export enum events {
beforeLogin = 'beforeLogin',
loginSuccess = 'loginSuccess',
loginError = 'loginError',
beforeRefresh = 'beforeRefresh',
refreshSuccess = 'refreshSuccess',
refreshError = 'refreshError',
beforeLogout = 'beforeLogout',
logoutSuccess = 'logoutSuccess',
logoutError = 'logoutError',
rateLimitError = 'rateLimitError',
}
function checkPathHasHttp(path) {
return path.startsWith('http://') || path.startsWith('https://');
}
export default class Platform extends EventEmitter {
public static _cacheId = 'platform';
public static _discoveryCacheId = 'platform-discovery';
public events = events;
private _server: string;
private _rcvServer: string;
private _clientId: string;
private _clientSecret: string;
private _redirectUri: string;
private _brandId: string;
private _refreshDelayMs: number;
private _clearCacheOnRefreshError: boolean;
private _userAgent: string;
private _externals: Externals;
private _cache: Cache;
private _client: Client;
private _refreshPromise: Promise<any>;
private _auth: Auth;
private _tokenEndpoint;
private _revokeEndpoint;
private _authorizeEndpoint;
private _authProxy;
private _urlPrefix;
private _handleRateLimit: boolean | number;
private _codeVerifier: string;
private _discovery?: Discovery;
private _discoveryInitPromise: Promise<void>;
private _loginWindowCheckTimeout?: ReturnType<typeof setTimeout>;
private _loginWindowEventListener?: (event: MessageEvent) => void;
public constructor({
server,
clientId,
clientSecret,
brandId,
redirectUri = '',
refreshDelayMs = 100,
clearCacheOnRefreshError = true,
appName = '',
appVersion = '',
additionalUserAgent = '',
externals,
cache,
client,
refreshHandicapMs,
tokenEndpoint = '/restapi/oauth/token',
revokeEndpoint = '/restapi/oauth/revoke',
authorizeEndpoint = '/restapi/oauth/authorize',
enableDiscovery = false,
discoveryServer,
discoveryInitialEndpoint = '/.well-known/entry-points/initial',
discoveryAutoInit = true,
authProxy = false,
urlPrefix = '',
handleRateLimit,
}: PlatformOptionsConstructor) {
super();
this._server = server;
this._rcvServer = server;
this._clientId = clientId;
this._clientSecret = clientSecret;
this._brandId = brandId;
this._redirectUri = redirectUri;
this._refreshDelayMs = refreshDelayMs;
this._clearCacheOnRefreshError = clearCacheOnRefreshError;
this._authProxy = authProxy;
this._urlPrefix = urlPrefix;
this._userAgent = `${appName ? `${appName + (appVersion ? `/${appVersion}` : '')} ` : ''}RCJSSDK/${
Constants.version
}${additionalUserAgent ? ` ${additionalUserAgent}` : ''}`;
this._externals = externals;
this._cache = cache;
this._client = client;
this._refreshPromise = null;
this._auth = new Auth({
cache: this._cache,
cacheId: Platform._cacheId,
refreshHandicapMs,
});
this._tokenEndpoint = tokenEndpoint;
this._revokeEndpoint = revokeEndpoint;
this._authorizeEndpoint = authorizeEndpoint;
this._handleRateLimit = handleRateLimit;
this._codeVerifier = '';
if (enableDiscovery) {
const initialEndpoint = discoveryServer
? `${discoveryServer}${discoveryInitialEndpoint}`
: discoveryInitialEndpoint;
this._discovery = new Discovery({
clientId,
brandId,
cache: this._cache,
cacheId: Platform._discoveryCacheId,
initialEndpoint,
fetchGet: this.get.bind(this),
});
this._discovery.on(this._discovery.events.initialized, discoveryData => {
this._authorizeEndpoint = discoveryData.authApi.authorizationUri;
});
this._client.on(this._client.events.requestSuccess, response => {
if (response.headers.get('discovery-required')) {
this._discovery.refreshExternalData();
}
});
if (discoveryAutoInit) {
this._discoveryInitPromise = this.initDiscovery();
}
}
}
public on(event: events.beforeLogin, listener: () => void);
public on(event: events.loginSuccess, listener: (response: Response) => void);
public on(event: events.loginError, listener: (error: ApiError | Error) => void);
public on(event: events.beforeRefresh, listener: () => void);
public on(event: events.refreshSuccess, listener: (response: Response) => void);
public on(event: events.refreshError, listener: (error: ApiError | Error) => void);
public on(event: events.beforeLogout, listener: () => void);
public on(event: events.logoutSuccess, listener: (response: Response) => void);
public on(event: events.logoutError, listener: (error: ApiError | Error) => void);
public on(event: events.rateLimitError, listener: (error: ApiError | Error) => void);
public on(event: string, listener: (...args) => void) {
return super.on(event, listener);
}
public auth() {
return this._auth;
}
public discovery() {
return this._discovery;
}
public createUrl(path = '', options: CreateUrlOptions = {}) {
let builtUrl = '';
const hasHttp = checkPathHasHttp(path);
if (options.addServer && !hasHttp) {
if (path.indexOf('/rcvideo') === 0 || (this._urlPrefix && this._urlPrefix.indexOf('/rcvideo') === 0)) {
builtUrl += this._rcvServer;
} else {
builtUrl += this._server;
}
}
if (this._urlPrefix) {builtUrl += this._urlPrefix;}
builtUrl += path;
if (options.addMethod) {builtUrl += `${path.includes('?') ? '&' : '?'}_method=${options.addMethod}`;}
return builtUrl;
}
public async signUrl(path: string) {
return `${path + (path.includes('?') ? '&' : '?')}access_token=${(await this._auth.data()).access_token}`;
}
public async loginUrlWithDiscovery(options: LoginUrlOptions = {}) {
if (this._discovery) {
try {
// fetch new discovery when generate login url
const discoveryData = await this._discovery.fetchInitialData();
this._authorizeEndpoint = discoveryData.authApi.authorizationUri;
if (this._discoveryInitPromise) {
// await init discovery if it's not initialized
await this._discoveryInitPromise;
}
} catch (e) {
const discoveryData = await this._discovery.initialData();
if (!discoveryData) {
throw e;
}
// feedback to use the cached data
this._authorizeEndpoint = discoveryData.authApi.authorizationUri;
}
}
return this.loginUrl(options);
}
public async initDiscovery() {
if (!this._discovery) {
throw new Error('Discovery is not enabled!');
}
try {
await this._discovery.init();
this._discoveryInitPromise = null;
} catch (e) {
this._discoveryInitPromise = null;
throw e;
}
}
public loginUrl({
implicit,
state,
brandId,
display,
prompt,
uiOptions,
uiLocales,
localeId,
usePKCE,
responseHint,
redirectUri,
loginHint,
}: LoginUrlOptions = {}) {
const query: AuthorizationQuery = {
response_type: implicit ? 'token' : 'code',
redirect_uri: redirectUri ? redirectUri : this._redirectUri,
client_id: this._clientId,
state,
brand_id: brandId ? brandId : this._brandId,
display,
prompt,
ui_options: uiOptions,
ui_locales: uiLocales,
localeId,
};
if (responseHint) {
query.response_hint = responseHint;
}
if (!!loginHint) {
query.login_hint = loginHint;
}
if (this._discovery) {
if (!this._discovery.initialized) {
throw new Error('Discovery is not initialized');
}
query.discovery = true;
}
if (usePKCE && implicit) {
throw new Error('PKCE only works with Authorization Code Flow');
}
this._codeVerifier = '';
if (usePKCE) {
this._codeVerifier = this._generateCodeVerifier();
query.code_challenge = createHash('sha256')
.update(this._codeVerifier)
.digest()
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
query.code_challenge_method = 'S256';
}
return this.createUrl(`${this._authorizeEndpoint}?${objectToUrlParams(query)}`, {addServer: true});
}
/**
* @return {string}
*/
private _generateCodeVerifier() {
let codeVerifier: any = randomBytes(32);
codeVerifier = codeVerifier
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
return codeVerifier;
}
/**
* @param {string} url
* @return {Object}
*/
public parseLoginRedirect(url: string) {
const response =
(url.startsWith('#') && getParts(url, '#')) || (url.startsWith('?') && getParts(url, '?')) || null;
if (!response) {throw new Error('Unable to parse response');}
const queryString = new URLSearchParams(response);
if (!queryString) {throw new Error('Unable to parse response');}
const error = queryString.get('error_description') || queryString.get('error');
if (error) {
const e: any = new Error(error.toString());
e.error = queryString.get('error');
throw e;
}
return Object.fromEntries(queryString);
}
/**
* Convenience method to handle 3-legged OAuth
*
* Attention! This is an experimental method and it's signature and behavior may change without notice.
*/
public loginWindow({
url,
width = 400,
height = 600,
origin = window.location.origin,
property = Constants.authResponseProperty,
target = 'RingCentralLoginWindow',
}: LoginWindowOptions): Promise<LoginOptions> {
// clear check last timeout when user open loginWindow twice to avoid leak
this._clearLoginWindowCheckTimeout();
return new Promise((resolve, reject) => {
if (typeof window === 'undefined') {throw new Error('This method can be used only in browser');}
if (!url) {throw new Error('Missing mandatory URL parameter');}
const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : 0;
const dualScreenTop = window.screenTop !== undefined ? window.screenTop : 0;
const screenWidth = screen.width;
const screenHeight = screen.height;
const left = screenWidth / 2 - width / 2 + dualScreenLeft;
const top = screenHeight / 2 - height / 2 + dualScreenTop;
const win = window.open(
url,
target,
target === '_blank'
? `scrollbars=yes, status=yes, width=${width}, height=${height}, left=${left}, top=${top}`
: '',
);
if (!win) {
throw new Error('Could not open login window. Please allow popups for this site');
}
if (win.focus) {win.focus();}
// clear listener when user open loginWindow twice to avoid leak
if (this._loginWindowEventListener) {
window.removeEventListener('message', this._loginWindowEventListener);
}
this._loginWindowEventListener = e => {
try {
if (e.origin !== origin) {return;}
if (!e.data || !e.data[property]) {return;} // keep waiting
this._clearLoginWindowCheckTimeout();
win.close();
window.removeEventListener('message', this._loginWindowEventListener);
this._loginWindowEventListener = null;
const loginOptions = this.parseLoginRedirect(e.data[property]);
if (!loginOptions.code && !loginOptions.access_token)
{throw new Error('No authorization code or token');}
resolve(loginOptions);
} catch (e1) {
reject(e1);
}
};
window.addEventListener('message', this._loginWindowEventListener, false);
this._createLoginWindowCheckTimeout(win, reject);
});
}
private _createLoginWindowCheckTimeout(win, reject) {
this._loginWindowCheckTimeout = setTimeout(() => {
if (win.closed) {
if (this._loginWindowEventListener) {
window.removeEventListener('message', this._loginWindowEventListener);
this._loginWindowEventListener = null;
}
this._loginWindowCheckTimeout = null;
reject(new Error('Login window is closed'));
return;
}
this._createLoginWindowCheckTimeout(win, reject);
}, 2000);
}
private _clearLoginWindowCheckTimeout() {
if (this._loginWindowCheckTimeout) {
clearTimeout(this._loginWindowCheckTimeout);
this._loginWindowCheckTimeout = null;
}
}
/**
* @return {Promise<boolean>}
*/
public async loggedIn() {
try {
if (this._authProxy) {
await this.get('/restapi/v1.0/client-info'); // we only can determine the status if we actually make request
} else {
await this.ensureLoggedIn();
}
return true;
} catch (e) {
return false;
}
}
public async login({
username,
password,
extension = '',
code,
jwt,
access_token_ttl,
refresh_token_ttl,
access_token,
endpoint_id,
token_uri,
discovery_uri,
code_verifier,
redirect_uri,
...options
}: LoginOptions = {}): Promise<Response> {
try {
this.emit(this.events.beforeLogin);
const body: any = {};
let response = null;
let json;
let tokenEndpoint = this._tokenEndpoint;
let discoveryEndpoint;
if (this._discovery) {
const discovery = await this._getTokenAndDiscoveryUriOnLogin({token_uri, discovery_uri});
tokenEndpoint = discovery.tokenEndpoint;
discoveryEndpoint = discovery.discoveryEndpoint;
}
const codeVerifier = code_verifier ? code_verifier : this._codeVerifier;
if (access_token) {
//TODO Potentially make a request to /oauth/tokeninfo
json = {access_token, ...options};
} else {
if (!code && !jwt) {
body.grant_type = 'password';
if (extension && extension.length > 0) {
body.username = `${username}*${extension}`;
} else {
body.username = username;
}
body.password = password;
// eslint-disable-next-line no-console
console.warn(
'Username/password authentication is deprecated. Please migrate to the JWT grant type.',
);
} else if (jwt) {
body.grant_type = 'urn:ietf:params:oauth:grant-type:jwt-bearer';
body.assertion = jwt;
} else if (code) {
//@see https://developers.ringcentral.com/legacy-api-reference/index.html#!#RefAuthorizationCodeFlow
body.grant_type = 'authorization_code';
body.code = code;
body.redirect_uri = redirect_uri ? redirect_uri : this._redirectUri;
if (codeVerifier && codeVerifier.length > 0) {
body.code_verifier = codeVerifier;
}
}
if (access_token_ttl) {body.access_token_ttl = access_token_ttl;}
if (refresh_token_ttl) {body.refresh_token_ttl = refresh_token_ttl;}
if (endpoint_id) {body.endpoint_id = endpoint_id;}
response = await this._tokenRequest(tokenEndpoint, body);
json = await response.clone().json();
}
await this._auth.setData({
...json,
code_verifier: codeVerifier,
});
if (discoveryEndpoint) {
await this._discovery.fetchExternalData(discoveryEndpoint);
}
this.emit(this.events.loginSuccess, response);
return response;
} catch (e) {
if (this._clearCacheOnRefreshError) {
await this._cache.clean();
if (this._discovery) {
this._discoveryInitPromise = this.initDiscovery(); // request new init data after refresh error and cache cleaned
}
}
this.emit(this.events.loginError, e);
throw e;
}
}
private async _getTokenAndDiscoveryUriOnLogin({token_uri, discovery_uri}) {
let tokenEndpoint = token_uri;
let discoveryEndpoint = discovery_uri;
if (tokenEndpoint && discoveryEndpoint) {
return {tokenEndpoint, discoveryEndpoint};
}
// wait discovery initial finished
if (this._discoveryInitPromise) {
await this._discoveryInitPromise;
}
let discoveryData = await this._discovery.initialData();
// check if discovery data is initialized successfully
if (!discoveryData) {
// discovery request fail in previous init, try re-fetch discovery
discoveryData = await this._discovery.fetchInitialData();
}
if (!tokenEndpoint) {
tokenEndpoint = discoveryData.authApi.defaultTokenUri;
}
if (!discoveryEndpoint) {
discoveryEndpoint = discoveryData.discoveryApi.defaultExternalUri;
}
return {tokenEndpoint, discoveryEndpoint};
}
private async _refresh(): Promise<Response> {
try {
this.emit(this.events.beforeRefresh);
await delay(this._refreshDelayMs);
const authData = await this.auth().data();
// Perform sanity checks
if (!authData.refresh_token) {throw new Error('Refresh token is missing');}
const refreshTokenValid = await this._auth.refreshTokenValid();
if (!refreshTokenValid) {throw new Error('Refresh token has expired');}
const body: RefreshTokenBody = {
grant_type: 'refresh_token',
refresh_token: authData.refresh_token,
access_token_ttl: parseInt(authData.expires_in),
refresh_token_ttl: parseInt(authData.refresh_token_expires_in),
};
let tokenEndpoint = this._tokenEndpoint;
if (this._discovery) {
const discoveryData = await this._discovery.externalData();
if (discoveryData) {
tokenEndpoint = discoveryData.authApi.tokenUri;
} else {
// For user who logged before discovery enabled. Need to refresh token firstly, then get discovery data
if (this._discoveryInitPromise) {
await this._discoveryInitPromise;
}
const initialDiscoveryData = await this._discovery.initialData();
tokenEndpoint = initialDiscoveryData.authApi.defaultTokenUri;
}
}
const res = await this._tokenRequest(tokenEndpoint, body);
const json = await res.clone().json();
if (!json.access_token) {
throw await this._client.makeError(new Error('Malformed OAuth response'), res);
}
await this._auth.setData(json);
this.emit(this.events.refreshSuccess, res);
return res;
} catch (e) {
if (this._clearCacheOnRefreshError) {
await this._cache.clean();
if (this._discovery) {
this._discoveryInitPromise = this.initDiscovery(); // request new init data after refresh error and cache cleaned
}
}
this.emit(this.events.refreshError, e);
throw e;
}
}
public async refresh(): Promise<Response> {
if (this._authProxy) {
throw new Error('Refresh is not supported in Auth Proxy mode');
}
if (!this._refreshPromise) {
this._refreshPromise = (async () => {
try {
const res = await this._refresh();
this._refreshPromise = null;
return res;
} catch (e) {
this._refreshPromise = null;
throw e;
}
})();
}
return this._refreshPromise;
}
public async logout(): Promise<Response> {
if (this._authProxy) {
throw new Error('Logout is not supported in Auth Proxy mode');
}
try {
this.emit(this.events.beforeLogout);
let res = null;
const revokeEndpoint = await this._getRevokeEndpoint();
if (revokeEndpoint) {
const authData = await this._auth.data();
const body: RevokeTokenBody = {
token: authData.access_token,
};
// Support to revoke token without client secret with client id in body
res = await this._tokenRequest(revokeEndpoint, body);
}
await this._cache.clean();
if (this._discovery) {
this._discoveryInitPromise = this.initDiscovery(); // request new init data after logout
}
this.emit(this.events.logoutSuccess, res);
return res;
} catch (e) {
if (this._discovery) {
this._discoveryInitPromise = this.initDiscovery(); // request new init data after logout error
}
this.emit(this.events.logoutError, e);
throw e;
}
}
private async _getRevokeEndpoint() {
let revokeEndpoint = this._revokeEndpoint;
if (!this._discovery || checkPathHasHttp(revokeEndpoint)) {
return revokeEndpoint;
}
const discoveryData = await this._discovery.externalData();
const baseUri = discoveryData.authApi.baseUri;
revokeEndpoint = `${baseUri}${revokeEndpoint}`;
return revokeEndpoint;
}
public async inflateRequest(request: Request, options: SendOptions = {}): Promise<Request> {
options = options || {};
let userAgent = this._userAgent;
if (options.userAgent) {
userAgent = `${options.userAgent} ${userAgent}`;
}
request.headers.set('X-User-Agent', userAgent);
if (options.skipAuthCheck) {return request;}
await this.ensureLoggedIn();
request.headers.set('Client-Id', this._clientId);
if (!this._authProxy) {request.headers.set('Authorization', await this.authHeader());}
return request;
}
public async sendRequest(request: Request, options: SendOptions = {}): Promise<Response> {
try {
request = await this.inflateRequest(request, options);
return await this._client.sendRequest(request);
} catch (e) {
let {retry, handleRateLimit} = options;
// Guard is for errors that come from polling
if (!e.response || retry) {throw e;}
const {response} = e;
const {status} = response;
if ((status !== Client._unauthorizedStatus && status !== Client._rateLimitStatus) || this._authProxy)
{throw e;}
options.retry = true;
let retryAfter = 0;
if (status === Client._unauthorizedStatus) {
await this._auth.cancelAccessToken();
}
if (status === Client._rateLimitStatus) {
handleRateLimit = handleRateLimit || this._handleRateLimit;
const defaultRetryAfter =
!handleRateLimit || typeof handleRateLimit === 'boolean' ? 60 : handleRateLimit;
// FIXME retry-after is custom header, by default, it can't be retrieved. Server should add header: 'Access-Control-Expose-Headers: retry-after'.
retryAfter = parseFloat(response.headers.get('retry-after') || defaultRetryAfter) * 1000;
e.retryAfter = retryAfter;
this.emit(this.events.rateLimitError, e);
if (!handleRateLimit) {throw e;}
}
await delay(retryAfter);
return this.sendRequest(this._client.createRequest(options), options);
}
}
public async send(options: SendOptions = {}) {
if (!options.skipAuthCheck && !options.skipDiscoveryCheck && this._discovery) {
if (this._discoveryInitPromise) {
await this._discoveryInitPromise;
}
const discoveryExpired = await this._discovery.externalDataExpired();
if (discoveryExpired) {
await this._discovery.refreshExternalData();
}
const discoveryData = await this._discovery.externalData();
if (!discoveryData) {
throw new Error('Discovery data is missing');
}
this._server = discoveryData.coreApi.baseUri;
this._rcvServer = discoveryData.rcv.baseApiUri;
if (discoveryData.tag) {
options.headers = options.headers || {};
options.headers['Discovery-Tag'] = discoveryData.tag;
}
}
//FIXME https://github.com/bitinn/node-fetch/issues/43
options.url = this.createUrl(options.url, {addServer: true});
return this.sendRequest(this._client.createRequest(options), options);
}
/**
* These methods refresh access token automatically if it's expired.
* If you want to handle it yourself and disable auto refresh, place below code snippet in your code.
* platform.ensureLoggedIn = async () => null;
* For more details, please refer to https://medium.com/ringcentral-developers/how-to-disable-auto-token-refreshment-for-ringcentral-javascript-sdk-461d7982ed35
* You can also follow demo https://github.com/tylerlong/rc-js-sdk-no-auto-refresh-token-demo
*/
public async get(url, query?, options?: SendOptions): Promise<Response> {
return this.send({method: 'GET', url, query, ...options});
}
public async post(url, body?, query?, options?: SendOptions): Promise<Response> {
return this.send({method: 'POST', url, query, body, ...options});
}
public async put(url, body?, query?, options?: SendOptions): Promise<Response> {
return this.send({method: 'PUT', url, query, body, ...options});
}
public async patch(url, body?, query?, options?: SendOptions): Promise<Response> {
return this.send({method: 'PATCH', url, query, body, ...options});
}
public async delete(url, query?, options?: SendOptions): Promise<Response> {
return this.send({method: 'DELETE', url, query, ...options});
}
public async ensureLoggedIn(): Promise<Response | null> {
if (this._authProxy) {return null;}
if (await this._auth.accessTokenValid()) {return null;}
await this.refresh();
return null;
}
protected async _tokenRequest(url, body): Promise<Response> {
const headers: TokenRequestHeaders = {
'Content-Type': Client._urlencodedContentType,
};
if (this._clientSecret && this._clientSecret.length > 0) {
headers.Authorization = this.basicAuthHeader();
} else {
// Put client_id in body when no app secret
body.client_id = this._clientId;
}
return this.send({
url,
body,
skipAuthCheck: true,
method: 'POST',
headers,
});
}
public basicAuthHeader(): string {
const apiKey = this._clientId + (this._clientSecret ? `:${this._clientSecret}` : '');
return `Basic ${typeof btoa === 'function' ? btoa(apiKey) : Buffer.from(apiKey).toString('base64')}`;
}
public async authHeader(): Promise<string> {
const data = await this._auth.data();
return (data.token_type || 'Bearer') + (data.access_token ? ` ${data.access_token}` : '');
}
public get discoveryInitPromise() {
return this._discoveryInitPromise;
}
public get codeVerifier() {
return this._codeVerifier;
}
public get userAgent() {
return this._userAgent;
}
}
export interface PlatformOptions extends AuthOptions {
server?: string;
clientId?: string;
clientSecret?: string;
redirectUri?: string;
refreshDelayMs?: number;
refreshHandicapMs?: number;
clearCacheOnRefreshError?: boolean;
appName?: string;
appVersion?: string;
additionalUserAgent?: string;
tokenEndpoint?: string;
revokeEndpoint?: string;
authorizeEndpoint?: string;
authProxy?: boolean;
urlPrefix?: string;
handleRateLimit?: boolean | number;
enableDiscovery?: boolean;
discoveryServer?: string;
discoveryInitialEndpoint?: string;
discoveryAuthorizedEndpoint?: string;
discoveryAutoInit?: boolean;
brandId?: string;
}
export interface PlatformOptionsConstructor extends PlatformOptions {
externals: Externals;
cache: Cache;
client: Client;
}
export interface SendOptions {
url?: any;
body?: any;
method?: string;
query?: any;
headers?: any;
userAgent?: string;
skipAuthCheck?: boolean;
skipDiscoveryCheck?: boolean;
handleRateLimit?: boolean | number;
retry?: boolean; // Will be set by this method if SDK makes second request
}
export interface LoginOptions {
username?: string;
password?: string;
extension?: string;
code?: string;
jwt?: string;
access_token?: string;
access_token_ttl?: number;
refresh_token_ttl?: number;
endpoint_id?: string;
token_uri?: string;
discovery_uri?: string;
code_verifier?: string;
redirect_uri?: string;
}
export interface LoginUrlOptions {
state?: string;
brandId?: string;
display?: LoginUrlDisplay | string;
prompt?: LoginUrlPrompt | string;
implicit?: boolean;
uiOptions?: string | string[];
uiLocales?: string;
localeId?: string;
usePKCE?: boolean;
responseHint?: string | string[];
redirectUri?: string;
loginHint?: string | string[];
}
export enum LoginUrlPrompt {
login = 'login',
sso = 'sso',
consent = 'consent',
none = 'none',
}
export enum LoginUrlDisplay {
page = 'page',
popup = 'popup',
touch = 'touch',
mobile = 'mobile',
}
export interface CreateUrlOptions {
addServer?: boolean;
addMethod?: string;
}
export interface LoginWindowOptions {
url: string;
width?: number;
height?: number;
origin?: string;
property?: string;
target?: string;
}
export interface AuthorizationQuery {
response_type: 'token' | 'code';
response_hint?: string | string[];
redirect_uri: string;
client_id: string;
state?: string;
brand_id?: string;
display?: LoginUrlDisplay | string;
prompt?: LoginUrlPrompt | string;
ui_options?: string | string[];
ui_locales?: string;
localeId?: string;
code_challenge?: string;
code_challenge_method?: string;
discovery?: boolean;
login_hint?: string | string[];
}
export interface TokenRequestHeaders {
'Content-Type': string;
Authorization?: string;
}
export interface RefreshTokenBody {
grant_type: 'refresh_token';
refresh_token: string;
access_token_ttl: number;
refresh_token_ttl: number;
client_id?: string;
}
export interface RevokeTokenBody {
token: string;
client_id?: string;
}