Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@shoutem/fetch-token-intercept

Package Overview
Dependencies
Maintainers
4
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@shoutem/fetch-token-intercept - npm Package Compare versions

Comparing version 0.0.1-alpha.3 to 0.0.1-alpha.4

lib/services/RetryCountExceededException.js

72

lib/accessTokenProvider.js

@@ -6,3 +6,2 @@ 'use strict';

});
exports.AccessTokenProvider = undefined;

@@ -17,3 +16,10 @@ var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var AccessTokenProvider = exports.AccessTokenProvider = function () {
/**
* Provides a way for renewing access token with correct renew token. It will automatically
* dispatch a call to server with request provided via config. It also ensures that
* renew token is fetched only once no matter how many requests are trying to get
* a renewed version of access token at the moment. All subsequent requests will be chained
* to renewing fetch promise and resolved once the response is received.
*/
var AccessTokenProvider = function () {
function AccessTokenProvider(fetch, config) {

@@ -25,3 +31,3 @@ _classCallCheck(this, AccessTokenProvider);

this.config = config;
this.refreshAccessTokenPromise = null;
this.renewAccessTokenPromise = null;
this.tokens = {

@@ -32,10 +38,12 @@ refreshToken: null,

this.isAuthorized = this.isAuthorized.bind(this);
this.refresh = this.refresh.bind(this);
this.renew = this.renew.bind(this);
this.authorize = this.authorize.bind(this);
this.getAuthorization = this.getAuthorization.bind(this);
this.clear = this.clear.bind(this);
this.isAuthorized = this.isAuthorized.bind(this);
this.resolveAccessToken = this.resolveAccessToken.bind(this);
this.fetchToken = this.fetchToken.bind(this);
this.handleFetchResolved = this.handleFetchResolved.bind(this);
this.handleTokenResolved = this.handleTokenResolved.bind(this);
this.fetchAccessToken = this.fetchAccessToken.bind(this);
this.handleFetchAccessTokenResponse = this.handleFetchAccessTokenResponse.bind(this);
this.handleAccessToken = this.handleAccessToken.bind(this);
this.handleError = this.handleError.bind(this);

@@ -45,3 +53,3 @@ }

/**
* Refreshes current access token with provided refresh token
* Renews current access token with provided refresh token
*/

@@ -51,6 +59,7 @@

_createClass(AccessTokenProvider, [{
key: 'refresh',
value: function refresh() {
key: 'renew',
value: function renew() {
// if token resolver is not authorized it should just resolve
if (!this.isAuthorized()) {
console.log('Please authorize provider before renewing or check shouldIntercept config.');
return Promise.resolve();

@@ -60,12 +69,12 @@ }

// if we are not running token promise, start it
if (!this.refreshAccessTokenPromise) {
this.refreshAccessTokenPromise = new Promise(this.resolveAccessToken);
if (!this.renewAccessTokenPromise) {
this.renewAccessTokenPromise = new Promise(this.resolveAccessToken);
}
// otherwise just return existing promise
return this.refreshAccessTokenPromise;
return this.renewAccessTokenPromise;
}
/**
* Authorizes intercept library with given refresh token
* Authorizes intercept library with given renew token
* @param refreshToken

@@ -91,2 +100,7 @@ * @param accessToken

}
/**
* Clears authorization tokens. Call this to effectively log out user from fetch interceptor.
*/
}, {

@@ -104,4 +118,4 @@ key: 'clear',

}, {
key: 'fetchToken',
value: function fetchToken(tokenRequest) {
key: 'fetchAccessToken',
value: function fetchAccessToken(tokenRequest) {
var fetch = this.fetch;

@@ -112,5 +126,5 @@

}, {
key: 'handleFetchResolved',
value: function handleFetchResolved(response) {
this.refreshAccessTokenPromise = null;
key: 'handleFetchAccessTokenResponse',
value: function handleFetchAccessTokenResponse(response) {
this.renewAccessTokenPromise = null;

@@ -125,4 +139,4 @@ if ((0, _http.isResponseUnauthorized)(response)) {

}, {
key: 'handleTokenResolved',
value: function handleTokenResolved(token, resolve) {
key: 'handleAccessToken',
value: function handleAccessToken(token, resolve) {
this.tokens.accessToken = token;

@@ -139,3 +153,3 @@

value: function handleError(error, reject) {
this.refreshAccessTokenPromise = null;
this.renewAccessTokenPromise = null;
this.clear();

@@ -150,4 +164,8 @@

return Promise.resolve(this.config.createAccessTokenRequest(this.tokens.refreshToken)).then(this.fetchToken).then(this.handleFetchResolved).then(function (token) {
return _this.handleTokenResolved(token, resolve);
var refreshToken = this.tokens.refreshToken;
var createAccessTokenRequest = this.config.createAccessTokenRequest;
return Promise.resolve(createAccessTokenRequest(refreshToken)).then(this.fetchAccessToken).then(this.handleFetchAccessTokenResponse).then(function (token) {
return _this.handleAccessToken(token, resolve);
}).catch(function (error) {

@@ -160,2 +178,4 @@ return _this.handleError(error, reject);

return AccessTokenProvider;
}();
}();
exports.default = AccessTokenProvider;

@@ -6,5 +6,4 @@ 'use strict';

});
exports.FetchInterceptor = undefined;
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };

@@ -19,7 +18,23 @@ var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var _accessTokenProvider = require('./accessTokenProvider');
var _TokenExpiredException = require('./services/TokenExpiredException');
var _TokenExpiredException2 = _interopRequireDefault(_TokenExpiredException);
var _RetryCountExceededException = require('./services/RetryCountExceededException');
var _RetryCountExceededException2 = _interopRequireDefault(_RetryCountExceededException);
var _AccessTokenProvider = require('./AccessTokenProvider');
var _AccessTokenProvider2 = _interopRequireDefault(_AccessTokenProvider);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var FetchInterceptor = exports.FetchInterceptor = function () {
/**
* Provides a default implementation for intercepting fetch requests. It will try to resolve
* unauthorized responses by renewing the access token and repeating the initial request.
*/
var FetchInterceptor = function () {
function FetchInterceptor(fetch) {

@@ -32,2 +47,3 @@ _classCallCheck(this, FetchInterceptor);

this.config = {
fetchRetryCount: 1,
createAccessTokenRequest: null,

@@ -42,7 +58,7 @@ shouldIntercept: null,

this.isConfigValid = this.isConfigValid.bind(this);
this.fetchWithRetry = this.fetchWithRetry.bind(this);
this.intercept = this.intercept.bind(this);
this.resolveIntercept = this.resolveIntercept.bind(this);
this.fetchWithRetry = this.fetchWithRetry.bind(this);
this.isConfigValid = this.isConfigValid.bind(this);
this.createRequestUnit = this.createRequestUnit.bind(this);

@@ -56,7 +72,35 @@ this.shouldIntercept = this.shouldIntercept.bind(this);

this.handleUnauthorizedRequest = this.handleUnauthorizedRequest.bind(this);
this.handleResponse = this.handleResponse.bind(this);
}
/**
* Configures fetch interceptor with given config object
* @param initConfig
* Configures fetch interceptor with given config object. All required properties can optionally
* return a promise which will be resolved by fetch interceptor automatically.
*
* @param config
*
* (Required) Prepare fetch request for renewing new access token
* createAccessTokenRequest: (refreshToken) => request,
*
* (Required) Parses access token from access token response
* parseAccessToken: (response) => accessToken,
*
* (Required) Defines whether interceptor will intercept this request or just let it pass through
* shouldIntercept: (request) => boolean,
*
* (Required) Defines whether access token will be invalidated after this response
* shouldInvalidateAccessToken: (response) => boolean,
*
* (Required) Adds authorization for intercepted requests
* authorizeRequest: (request) => authorizedRequest,
*
* Number of retries after initial request was unauthorized
* fetchRetryCount: 1,
*
* Event invoked when access token has changed
* onAccessTokenChange: null,
*
* Event invoked when response is resolved
* onResponse: null,
*
*/

@@ -67,4 +111,4 @@

key: 'configure',
value: function configure(initConfig) {
this.config = _extends({}, this.config, initConfig);
value: function configure(config) {
this.config = _extends({}, this.config, config);

@@ -75,7 +119,7 @@ if (!this.isConfigValid(this.config)) {

this.accessTokenProvider = new _accessTokenProvider.AccessTokenProvider(this.fetch, this.config);
this.accessTokenProvider = new _AccessTokenProvider2.default(this.fetch, this.config);
}
/**
* Authorizes fetch interceptor with given refresh token
* Authorizes fetch interceptor with given renew token
* @param refreshToken

@@ -101,2 +145,7 @@ * @param accessToken

}
/**
* Clears authorization tokens. Call this to effectively log out user from fetch interceptor.
*/
}, {

@@ -107,2 +156,9 @@ key: 'clear',

}
/**
* Main intercept method, you should chain this inside wrapped fetch call
* @param args Args initially provided to fetch method
* @returns {Promise} Promise which resolves the same way as fetch would
*/
}, {

@@ -135,8 +191,8 @@ key: 'intercept',

var requestUnit = this.createRequestUnit(request, resolve, reject);
// if access token is not resolved yet
if (!accessToken) {
return this.accessTokenProvider.refresh().then(function () {
return _this2.fetchWithRetry(request, resolve, reject);
return this.accessTokenProvider.renew().then(function () {
return _this2.fetchWithRetry(requestUnit);
}).catch(reject);

@@ -146,32 +202,30 @@ }

// attempt normal fetch operation
return this.fetchWithRetry(request, resolve, reject).catch(reject);
return this.fetchWithRetry(requestUnit).catch(reject);
}
}, {
key: 'fetchWithRetry',
value: function fetchWithRetry(request, outerResolve, outerReject) {
var _this3 = this;
return Promise.resolve(this.createRequestUnit(request)).then(this.shouldIntercept)
value: function fetchWithRetry(requestUnit) {
// prepare initial request unit
return Promise.resolve(requestUnit)
// resolve should intercept flag, when false, step is skipped
.then(this.shouldIntercept)
// authorize request
.then(this.authorizeRequest).then(this.shouldFetch)
// initial fetch
.then(this.fetchRequest).then(this.shouldInvalidateAccessToken).then(this.invalidateAccessToken).then(this.handleUnauthorizedRequest).then(function (requestUnit) {
var response = requestUnit.response;
// can only be empty on network errors
if (!response) {
outerReject();
return;
}
if (_this3.config.onResponse) {
_this3.config.onResponse(response);
}
outerResolve(response);
}).catch(outerReject);
.then(this.authorizeRequest)
// last minute check if fetch should be performed
// this is as close as it gets to canceling events since
// fetch spec does not support cancel at the moment
.then(this.shouldFetch)
// perform fetch
.then(this.fetchRequest)
// check if response invalidates current access token
.then(this.shouldInvalidateAccessToken)
// perform token invalidation if neccessary
.then(this.invalidateAccessToken)
// handle unauthorized response by requesting a new access token and
// repeating a request
.then(this.handleResponse).catch(this.handleUnauthorizedRequest);
}
}, {
key: 'createRequestUnit',
value: function createRequestUnit(request) {
value: function createRequestUnit(request, fetchResolve, fetchReject) {
return {

@@ -183,3 +237,6 @@ request: request,

shouldFetch: true,
accessToken: null
accessToken: null,
fetchCount: 0,
fetchResolve: fetchResolve,
fetchReject: fetchReject
};

@@ -191,9 +248,6 @@ }

var request = requestUnit.request;
var shouldIntercept = this.config.shouldIntercept;
return Promise.all([requestUnit, this.config.shouldIntercept(request)]).then(function (_ref) {
var _ref2 = _slicedToArray(_ref, 2),
requestUnit = _ref2[0],
shouldIntercept = _ref2[1];
return Promise.resolve(shouldIntercept(request)).then(function (shouldIntercept) {
return _extends({}, requestUnit, { shouldIntercept: shouldIntercept });

@@ -203,14 +257,22 @@ });

}, {
key: 'shouldFetch',
value: function shouldFetch(requestUnit) {
key: 'authorizeRequest',
value: function authorizeRequest(requestUnit) {
var shouldIntercept = requestUnit.shouldIntercept;
if (!shouldIntercept) {
return requestUnit;
}
var request = requestUnit.request;
var _accessTokenProvider$2 = this.accessTokenProvider.getAuthorization(),
accessToken = _accessTokenProvider$2.accessToken;
if (this.config.shouldFetch) {
return Promise.all([requestUnit, this.config.shouldFetch(request)]).then(function (_ref3) {
var _ref4 = _slicedToArray(_ref3, 2),
requestUnit = _ref4[0],
shouldFetch = _ref4[1];
var authorizeRequest = this.config.authorizeRequest;
return _extends({}, requestUnit, { shouldFetch: shouldFetch });
if (request && accessToken) {
return Promise.resolve(authorizeRequest(request, accessToken)).then(function (request) {
return _extends({}, requestUnit, { accessToken: accessToken, request: request });
});

@@ -222,26 +284,16 @@ }

}, {
key: 'authorizeRequest',
value: function authorizeRequest(requestUnit) {
var shouldIntercept = requestUnit.shouldIntercept;
key: 'shouldFetch',
value: function shouldFetch(requestUnit) {
var request = requestUnit.request;
var shouldFetch = this.config.shouldFetch;
// verifies all outside conditions from config are met
if (shouldIntercept) {
var request = requestUnit.request;
var _accessTokenProvider$2 = this.accessTokenProvider.getAuthorization(),
accessToken = _accessTokenProvider$2.accessToken;
if (request && accessToken) {
return Promise.all([requestUnit, accessToken, this.config.authorizeRequest(request, accessToken)]).then(function (_ref5) {
var _ref6 = _slicedToArray(_ref5, 3),
requestUnit = _ref6[0],
accessToken = _ref6[1],
request = _ref6[2];
return _extends({}, requestUnit, { accessToken: accessToken, request: request });
});
}
if (!shouldFetch) {
return requestUnit;
}
return requestUnit;
return Promise.resolve(shouldFetch(request)).then(function (shouldFetch) {
return _extends({}, requestUnit, { shouldFetch: shouldFetch });
});
}

@@ -251,2 +303,4 @@ }, {

value: function fetchRequest(requestUnit) {
var _this3 = this;
var shouldFetch = requestUnit.shouldFetch;

@@ -256,12 +310,26 @@

if (shouldFetch) {
var request = requestUnit.request;
var fetch = this.fetch;
var _ret = function () {
var request = requestUnit.request,
fetchCount = requestUnit.fetchCount;
var fetchRetryCount = _this3.config.fetchRetryCount;
return Promise.all([requestUnit, fetch(request)]).then(function (_ref7) {
var _ref8 = _slicedToArray(_ref7, 2),
requestUnit = _ref8[0],
response = _ref8[1];
// verifies that retry count has not been exceeded
return _extends({}, requestUnit, { response: response });
});
if (fetchCount > fetchRetryCount) {
throw new _RetryCountExceededException2.default(requestUnit);
}
var fetch = _this3.fetch;
return {
v: Promise.resolve(fetch(request)).then(function (response) {
return _extends({}, requestUnit, {
response: response,
fetchCount: fetchCount + 1
});
})
};
}();
if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v;
}

@@ -275,13 +343,10 @@

var shouldIntercept = requestUnit.shouldIntercept;
var shouldInvalidateAccessToken = this.config.shouldInvalidateAccessToken;
if (shouldIntercept && this.config.shouldInvalidateAccessToken) {
if (shouldIntercept && shouldInvalidateAccessToken) {
var response = requestUnit.response;
// check if response invalidates access token
return Promise.all([requestUnit, this.config.shouldInvalidateAccessToken(response)]).then(function (_ref9) {
var _ref10 = _slicedToArray(_ref9, 2),
requestUnit = _ref10[0],
shouldInvalidateAccessToken = _ref10[1];
return Promise.resolve(shouldInvalidateAccessToken(response)).then(function (shouldInvalidateAccessToken) {
return _extends({}, requestUnit, { shouldInvalidateAccessToken: shouldInvalidateAccessToken });

@@ -301,3 +366,3 @@ });

if (shouldIntercept && shouldInvalidateAccessToken) {
this.accessTokenProvider.refresh();
this.accessTokenProvider.renew();
}

@@ -308,29 +373,63 @@

}, {
key: 'handleResponse',
value: function handleResponse(requestUnit) {
var shouldIntercept = requestUnit.shouldIntercept,
response = requestUnit.response,
fetchResolve = requestUnit.fetchResolve,
fetchReject = requestUnit.fetchReject;
// can only be empty on network errors
if (!response) {
fetchReject();
return;
}
if (shouldIntercept && (0, _http.isResponseUnauthorized)(response)) {
throw new _TokenExpiredException2.default(_extends({}, requestUnit));
}
if (this.config.onResponse) {
this.config.onResponse(response);
}
return fetchResolve(response);
}
}, {
key: 'handleUnauthorizedRequest',
value: function handleUnauthorizedRequest(requestUnit) {
var shouldIntercept = requestUnit.shouldIntercept;
value: function handleUnauthorizedRequest(error) {
var _this4 = this;
// if expired token, we try to resolve it and retry operation
if (error instanceof _TokenExpiredException2.default) {
var _ret2 = function () {
var requestUnit = error.requestUnit;
var fetchReject = requestUnit.fetchReject;
if (shouldIntercept) {
var response = requestUnit.response;
// we only care for unauthorized responses
return {
v: Promise.resolve(_this4.accessTokenProvider.renew()).then(function () {
return _this4.fetchWithRetry(requestUnit);
}).catch(fetchReject)
};
}();
if ((0, _http.isResponseUnauthorized)(response)) {
return Promise.all([requestUnit, this.accessTokenProvider.refresh()]).then(function (_ref11) {
var _ref12 = _slicedToArray(_ref11, 2),
requestUnit = _ref12[0],
accessToken = _ref12[1];
if ((typeof _ret2 === 'undefined' ? 'undefined' : _typeof(_ret2)) === "object") return _ret2.v;
}
return _extends({}, requestUnit, {
accessToken: accessToken,
shouldFetch: !!accessToken
});
}).then(this.authorizeRequest).then(this.fetchRequest).catch(function (error) {
return new Error(error);
});
// if we failed to resolve token we just pass the last response
if (error instanceof _RetryCountExceededException2.default) {
var requestUnit = error.requestUnit;
var response = requestUnit.response,
fetchResolve = requestUnit.fetchResolve;
if (this.config.onResponse) {
this.config.onResponse(response);
}
return fetchResolve(response);
}
return requestUnit;
return fetchReject(error);
}

@@ -345,2 +444,4 @@ }, {

return FetchInterceptor;
}();
}();
exports.default = FetchInterceptor;

@@ -7,2 +7,4 @@ 'use strict';

exports.parseBearer = parseBearer;
var bearerRegex = /^Bearer (.+)$/;
function parseBearer(authorizationHeaderValue) {

@@ -13,4 +15,3 @@ if (!authorizationHeaderValue || typeof authorizationHeaderValue !== 'string') {

var bearerRegex = /^Bearer (.+)$/;
var matches = bearerRegex.exec(authorizationHeaderValue);
var matches = authorizationHeaderValue.match(bearerRegex);
// matches contains whole value and group, we are interested in group part

@@ -17,0 +18,0 @@ if (!matches || matches.length < 2) {

@@ -6,11 +6,73 @@ 'use strict';

});
exports.getAuthorization = exports.authorize = exports.configure = exports.clear = undefined;
exports.attach = attach;
exports.configure = configure;
exports.authorize = authorize;
exports.getAuthorization = getAuthorization;
exports.clear = clear;
var _environment = require('./environment');
var _environment = require('./services/environment');
(0, _environment.init)();
var _FetchInterceptor = require('./FetchInterceptor');
exports.clear = _environment.clear;
exports.configure = _environment.configure;
exports.authorize = _environment.authorize;
exports.getAuthorization = _environment.getAuthorization;
var _FetchInterceptor2 = _interopRequireDefault(_FetchInterceptor);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var interceptor = null;
function init() {
if ((0, _environment.isReactNative)()) {
attach(global);
} else if ((0, _environment.isWorker)()) {
attach(self);
} else if ((0, _environment.isWeb)()) {
attach(window);
} else if ((0, _environment.isNode)()) {
attach(global);
} else {
throw new Error('Unsupported environment for fetch-token-intercept');
}
}
function attach(env) {
if (!env.fetch) {
throw Error('No fetch available. Unable to register fetch-token-intercept');
}
if (interceptor) {
throw Error('You should attach only once.');
}
// for now add default interceptor
interceptor = new _FetchInterceptor2.default(env.fetch);
// monkey patch fetch
var fetchWrapper = function fetchWrapper(fetch) {
return function () {
var _interceptor;
return (_interceptor = interceptor).intercept.apply(_interceptor, arguments);
};
};
env.fetch = fetchWrapper(env.fetch);
}
function configure(config) {
interceptor.configure(config);
}
function authorize() {
var _interceptor2;
(_interceptor2 = interceptor).authorize.apply(_interceptor2, arguments);
}
function getAuthorization() {
return interceptor.getAuthorization();
}
function clear() {
return interceptor.clear();
}
init();
{
"name": "@shoutem/fetch-token-intercept",
"version": "0.0.1-alpha.3",
"version": "0.0.1-alpha.4",
"description": "Fetch interceptor for managing refresh token flow.",

@@ -17,3 +17,3 @@ "main": "lib/index.js",

"type": "git",
"url": ""
"url": "https://github.com/shoutem/fetch-token-intercept/"
},

@@ -28,5 +28,5 @@ "keywords": [

"author": "Shoutem",
"license": "MIT",
"license": "BSD",
"bugs": {
"url": ""
"url": "https://github.com/shoutem/fetch-token-intercept/issues"
},

@@ -33,0 +33,0 @@ "homepage": "",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc