UNPKG

amazon-cognito-identity-js

Version:

Amazon Cognito Identity Provider JavaScript SDK

225 lines (200 loc) • 5.62 kB
import 'isomorphic-unfetch'; import { getAmplifyUserAgent } from './UserAgent'; class CognitoError extends Error { constructor(message, code, name, statusCode) { super(message); this.code = code; this.name = name; this.statusCode = statusCode; } } /** @class */ export default class Client { /** * Constructs a new AWS Cognito Identity Provider client object * @param {string} region AWS region * @param {string} endpoint endpoint * @param {object} fetchOptions options for fetch API (only credentials is supported) */ constructor(region, endpoint, fetchOptions) { this.endpoint = endpoint || `https://cognito-idp.${region}.amazonaws.com/`; const { credentials } = fetchOptions || {}; this.fetchOptions = credentials ? { credentials } : {}; } /** * Makes an unauthenticated request on AWS Cognito Identity Provider API * using fetch * @param {string} operation API operation * @param {object} params Input parameters * @returns Promise<object> */ promisifyRequest(operation, params) { return new Promise((resolve, reject) => { this.request(operation, params, (err, data) => { if (err) { reject( new CognitoError(err.message, err.code, err.name, err.statusCode) ); } else { resolve(data); } }); }); } requestWithRetry(operation, params, callback) { const MAX_DELAY_IN_MILLIS = 5 * 1000; jitteredExponentialRetry( p => new Promise((res, rej) => { this.request(operation, p, (error, result) => { if (error) { rej(error); } else { res(result); } }); }), [params], MAX_DELAY_IN_MILLIS ) .then(result => callback(null, result)) .catch(error => callback(error)); } /** * Makes an unauthenticated request on AWS Cognito Identity Provider API * using fetch * @param {string} operation API operation * @param {object} params Input parameters * @param {function} callback Callback called when a response is returned * @returns {void} */ request(operation, params, callback) { const headers = { 'Content-Type': 'application/x-amz-json-1.1', 'X-Amz-Target': `AWSCognitoIdentityProviderService.${operation}`, 'X-Amz-User-Agent': getAmplifyUserAgent(), 'Cache-Control': 'no-store', }; const options = Object.assign({}, this.fetchOptions, { headers, method: 'POST', mode: 'cors', body: JSON.stringify(params), }); let response; let responseJsonData; fetch(this.endpoint, options) .then( resp => { response = resp; return resp; }, err => { // If error happens here, the request failed // if it is TypeError throw network error if (err instanceof TypeError) { throw new Error('Network error'); } throw err; } ) .then(resp => resp.json().catch(() => ({}))) .then(data => { // return parsed body stream if (response.ok) return callback(null, data); responseJsonData = data; // Taken from aws-sdk-js/lib/protocol/json.js // eslint-disable-next-line no-underscore-dangle const code = (data.__type || data.code).split('#').pop(); const error = new Error(data.message || data.Message || null); error.name = code; error.code = code; return callback(error); }) .catch(err => { // first check if we have a service error if ( response && response.headers && response.headers.get('x-amzn-errortype') ) { try { const code = response.headers.get('x-amzn-errortype').split(':')[0]; const error = new Error( response.status ? response.status.toString() : null ); error.code = code; error.name = code; error.statusCode = response.status; return callback(error); } catch (ex) { return callback(err); } // otherwise check if error is Network error } else if (err instanceof Error && err.message === 'Network error') { err.code = 'NetworkError'; } return callback(err); }); } } const logger = { debug: () => { // Intentionally blank. This package doesn't have logging }, }; /** * For now, all errors are retryable. */ class NonRetryableError extends Error { constructor(message) { super(message); this.nonRetryable = true; } } const isNonRetryableError = obj => { const key = 'nonRetryable'; return obj && obj[key]; }; function retry(functionToRetry, args, delayFn, attempt = 1) { if (typeof functionToRetry !== 'function') { throw Error('functionToRetry must be a function'); } logger.debug( `${functionToRetry.name} attempt #${attempt} with args: ${JSON.stringify( args )}` ); return functionToRetry(...args).catch(err => { logger.debug(`error on ${functionToRetry.name}`, err); if (isNonRetryableError(err)) { logger.debug(`${functionToRetry.name} non retryable error`, err); throw err; } const retryIn = delayFn(attempt, args, err); logger.debug(`${functionToRetry.name} retrying in ${retryIn} ms`); if (retryIn !== false) { return new Promise(res => setTimeout(res, retryIn)).then(() => retry(functionToRetry, args, delayFn, attempt + 1) ); } else { throw err; } }); } function jitteredBackoff(maxDelayMs) { const BASE_TIME_MS = 100; const JITTER_FACTOR = 100; return attempt => { const delay = 2 ** attempt * BASE_TIME_MS + JITTER_FACTOR * Math.random(); return delay > maxDelayMs ? false : delay; }; } const MAX_DELAY_MS = 5 * 60 * 1000; function jitteredExponentialRetry( functionToRetry, args, maxDelayMs = MAX_DELAY_MS ) { return retry(functionToRetry, args, jitteredBackoff(maxDelayMs)); }