Skip to content

Commit e39fe02

Browse files
author
Domagoj Rukavina
committed
Improved jsdoc
Removed restoring of original fetch Updated tests
1 parent 8eeb9dc commit e39fe02

File tree

6 files changed

+136
-84
lines changed

6 files changed

+136
-84
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,7 @@
5757
"nock": "^8.0.0",
5858
"sinon": "^1.17.4"
5959
},
60-
"dependencies": {}
60+
"dependencies": {
61+
"lodash": "^4.17.4"
62+
}
6163
}

src/AccessTokenProvider.js

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
import { ERROR_INVALID_CONFIG } from './const';
2-
31
/**
42
* Provides a way for renewing access token with correct refresh token. It will automatically
53
* dispatch a call to server with request provided via config. It also ensures that
64
* access token is fetched only once no matter how many requests are trying to get
75
* a renewed version of access token at the moment. All subsequent requests will be chained
86
* to renewing fetch promise and resolved once the response is received.
97
*/
10-
export default class AccessTokenProvider {
8+
class AccessTokenProvider {
119
constructor(fetch, config) {
1210
this.fetch = fetch;
1311

@@ -29,20 +27,13 @@ export default class AccessTokenProvider {
2927
this.handleFetchAccessTokenResponse = this.handleFetchAccessTokenResponse.bind(this);
3028
this.handleAccessToken = this.handleAccessToken.bind(this);
3129
this.handleError = this.handleError.bind(this);
32-
this.isConfigValid = this.isConfigValid.bind(this);
3330
}
3431

3532
/**
3633
* Configures access token provider
3734
*/
3835
configure(config) {
3936
this.config = { ...this.config, ...config };
40-
41-
if (!this.isConfigValid(this.config)) {
42-
throw new Error(ERROR_INVALID_CONFIG);
43-
}
44-
45-
this.config = config;
4637
}
4738

4839
/**
@@ -135,10 +126,6 @@ export default class AccessTokenProvider {
135126
.then(token => this.handleAccessToken(token, resolve))
136127
.catch(error => this.handleError(error, reject));
137128
}
138-
139-
isConfigValid() {
140-
return this.config.isResponseUnauthorized &&
141-
this.config.createAccessTokenRequest &&
142-
this.config.parseAccessToken;
143-
}
144129
}
130+
131+
export default AccessTokenProvider;

src/FetchInterceptor.js

Lines changed: 96 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import isFunction from 'lodash/isFunction';
12
import {
23
ERROR_INVALID_CONFIG,
34
} from './const';
@@ -6,27 +7,70 @@ import TokenExpiredException from './services/TokenExpiredException';
67
import RetryCountExceededException from './services/RetryCountExceededException';
78
import AccessTokenProvider from './AccessTokenProvider';
89

10+
/**
11+
* Prepares signed request object which can be used for renewing access token
12+
*
13+
* @callback createAccessTokenRequest
14+
* @param {string} refreshToken Refresh token used to sign the request
15+
* @returns {Request} Signed request object which can be used to get access token
16+
*/
17+
18+
/**
19+
* Parses access token from access token response object
20+
*
21+
* @callback parseAccessToken
22+
* @param {Response} response Response object with access token
23+
* @returns {string} Access token parsed from response
24+
*/
25+
26+
/**
27+
* Checks whether interceptor will intercept this request or just let it pass through
28+
*
29+
* @callback shouldIntercept
30+
* @param {Request} request Request object
31+
* @returns {bool} A value indicating whether this request should be intercepted
32+
*/
33+
34+
/**
35+
* Checks whether provided response invalidates current access token
36+
*
37+
* @callback shouldInvalidateAccessToken
38+
* @param {Response} response Response object
39+
* @returns {bool} A value indicating whether token should be invalidated
40+
*/
41+
42+
/**
43+
* Adds authorization for intercepted requests
44+
*
45+
* @callback authorizeRequest
46+
* @param {Request} request Request object being intercepted
47+
* @param {string} accessToken Current access token
48+
* @returns {Request} Authorized request object
49+
*/
50+
51+
const getDefaultConfig = () => ({
52+
fetchRetryCount: 1,
53+
createAccessTokenRequest: null,
54+
shouldIntercept: () => false,
55+
shouldInvalidateAccessToken: () => false,
56+
isResponseUnauthorized: http.isResponseUnauthorized,
57+
parseAccessToken: null,
58+
authorizeRequest: null,
59+
onAccessTokenChange: null,
60+
onResponse: null,
61+
});
62+
963
/**
1064
* Provides a default implementation for intercepting fetch requests. It will try to resolve
1165
* unauthorized responses by renewing the access token and repeating the initial request.
1266
*/
13-
export default class FetchInterceptor {
67+
class FetchInterceptor {
1468
constructor(fetch) {
1569
// stores reference to vanilla fetch method
1670
this.fetch = fetch;
1771
this.accessTokenProvider = new AccessTokenProvider(this.fetch);
1872

19-
this.config = {
20-
fetchRetryCount: 1,
21-
createAccessTokenRequest: null,
22-
shouldIntercept: () => false,
23-
shouldInvalidateAccessToken: () => false,
24-
isResponseUnauthorized: http.isResponseUnauthorized,
25-
parseAccessToken: null,
26-
authorizeRequest: null,
27-
onAccessTokenChange: null,
28-
onResponse: null,
29-
};
73+
this.config = getDefaultConfig();
3074

3175
this.intercept = this.intercept.bind(this);
3276

@@ -49,37 +93,28 @@ export default class FetchInterceptor {
4993
* Configures fetch interceptor with given config object. All required properties can optionally
5094
* return a promise which will be resolved by fetch interceptor automatically.
5195
*
52-
* @param config
53-
*
54-
* (Required) Prepare fetch request for renewing new access token
55-
* createAccessTokenRequest: (refreshToken) => request,
56-
*
57-
* (Required) Parses access token from access token response
58-
* parseAccessToken: (response) => accessToken,
59-
*
60-
* (Required) Defines whether interceptor will intercept this request or just let it pass through
61-
* shouldIntercept: (request) => boolean,
62-
*
63-
* (Required) Defines whether access token will be invalidated after this response
64-
* shouldInvalidateAccessToken: (response) => boolean,
65-
*
66-
* (Required) Adds authorization for intercepted requests
67-
* authorizeRequest: (request, accessToken) => authorizedRequest,
68-
*
69-
* Checks if response should be considered unauthorized (by default only 401 responses are
70-
* considered unauthorized. Override this method if you need to trigger token renewal for
71-
* other response statuses.
72-
* isResponseUnauthorized: (response) => boolean,
73-
*
74-
* Number of retries after initial request was unauthorized
75-
* fetchRetryCount: 1,
76-
*
77-
* Event invoked when access token has changed
78-
* onAccessTokenChange: null,
79-
*
80-
* Event invoked when response is resolved
81-
* onResponse: null,
82-
*
96+
* @param {object} config
97+
* @param {createAccessTokenRequest} config.createAccessTokenRequest
98+
* Prepare fetch request for renewing new access token
99+
* @param {parseAccessToken} config.parseAccessToken
100+
* Parses access token from access token response
101+
* @param {shouldIntercept} config.shouldIntercept
102+
* Defines whether interceptor will intercept this request or just let it pass through
103+
* @param {shouldInvalidateAccessToken} config.shouldInvalidateAccessToken
104+
* Defines whether access token will be invalidated after this response
105+
* @param {authorizeRequest} config.authorizeRequest
106+
* Adds authorization for intercepted requests
107+
* @param {function} [config.isResponseUnauthorized=null]
108+
* Checks if response should be considered unauthorized (by default only 401 responses are
109+
* considered unauthorized. Override this method if you need to trigger token renewal for
110+
* other response statuses.
111+
* @param {number} [config.fetchRetryCount=1]
112+
* Number of retries after initial request was unauthorized
113+
* @param {number} [config.onAccessTokenChange=null]
114+
* Event invoked when access token has changed
115+
* @param {number} [config.onResponse=null]
116+
* Event invoked when response is resolved
117+
* </pre>
83118
*/
84119
configure(config) {
85120
this.config = { ...this.config, ...config };
@@ -93,8 +128,8 @@ export default class FetchInterceptor {
93128

94129
/**
95130
* Authorizes fetch interceptor with given refresh token
96-
* @param refreshToken
97-
* @param accessToken
131+
* @param {string} refreshToken Refresh token
132+
* @param {string} accessToken Access token
98133
*/
99134
authorize(refreshToken, accessToken) {
100135
this.accessTokenProvider.authorize(refreshToken, accessToken);
@@ -115,6 +150,15 @@ export default class FetchInterceptor {
115150
this.accessTokenProvider.clear();
116151
}
117152

153+
/**
154+
* Clears current authorization and restores default configuration, e.g. interceptor
155+
* will stop intercepting requests.
156+
*/
157+
unload() {
158+
this.clear();
159+
this.config = getDefaultConfig();
160+
}
161+
118162
/**
119163
* Main intercept method, you should chain this inside wrapped fetch call
120164
* @param args Args initially provided to fetch method
@@ -125,8 +169,11 @@ export default class FetchInterceptor {
125169
}
126170

127171
isConfigValid() {
128-
return this.config.shouldIntercept &&
129-
this.config.authorizeRequest;
172+
return this.config.shouldIntercept && isFunction(this.config.shouldIntercept) &&
173+
this.config.authorizeRequest && isFunction(this.config.authorizeRequest) &&
174+
this.config.isResponseUnauthorized && isFunction(this.config.isResponseUnauthorized) &&
175+
this.config.createAccessTokenRequest && isFunction(this.config.createAccessTokenRequest) &&
176+
this.config.parseAccessToken && isFunction(this.config.parseAccessToken);
130177
}
131178

132179
resolveIntercept(resolve, reject, ...args) {
@@ -356,3 +403,5 @@ export default class FetchInterceptor {
356403
throw new Error(error);
357404
}
358405
}
406+
407+
export default FetchInterceptor;

src/index.js

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { isResponseUnauthorized } from './services/http';
55
import FetchInterceptor from './FetchInterceptor';
66

77
let interceptor = null;
8-
let nativeFetch = null;
98
let environment = null;
109

1110
export function attach(env) {
@@ -17,8 +16,6 @@ export function attach(env) {
1716
throw Error('You should attach only once.');
1817
}
1918

20-
nativeFetch = env.fetch;
21-
2219
// for now add default interceptor
2320
interceptor = new FetchInterceptor(env.fetch);
2421

@@ -40,7 +37,8 @@ function initialize() {
4037

4138
/**
4239
* Initializes and configures interceptor
43-
* @param config
40+
* @param config Configuration object
41+
* @see FetchInterceptor#configure
4442
*/
4543
export function configure(config) {
4644
if (!interceptor) {
@@ -53,6 +51,7 @@ export function configure(config) {
5351
/**
5452
* Initializes tokens which will be used by interceptor
5553
* @param args
54+
* @see FetchInterceptor#authorize
5655
*/
5756
export function authorize(...args) {
5857
interceptor.authorize(...args);
@@ -86,12 +85,7 @@ export function isActive() {
8685
*/
8786
export function unload() {
8887
if (interceptor) {
89-
interceptor.clear();
90-
interceptor = null;
91-
}
92-
93-
if (environment) {
94-
environment.fetch = nativeFetch;
88+
interceptor.unload();
9589
}
9690
}
9791

test/FetchInterceptor.spec.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,35 @@ describe('fetch-intercept', function () {
8282
});
8383
});
8484

85+
describe('unload', function () {
86+
beforeEach(done => {
87+
server.start(done);
88+
});
89+
90+
afterEach(done => {
91+
server.stop(done);
92+
});
93+
94+
it('should clear authorization tokens and stop intercepting requests', done => {
95+
fetchInterceptor.configure(configuration());
96+
fetchInterceptor.authorize('refreshToken', 'accessToken');
97+
fetchInterceptor.unload();
98+
99+
// assert that authorization has been cleared
100+
const { refreshToken, accessToken } = fetchInterceptor.getAuthorization();
101+
expect(refreshToken).to.be.null;
102+
expect(accessToken).to.be.null;
103+
104+
// assert that fetch now works ok without interceptor
105+
fetch('http://localhost:5000/200').then((response) => {
106+
expect(response.status).to.be.equal(200);
107+
done();
108+
}).catch(err => {
109+
done(err);
110+
});
111+
});
112+
});
113+
85114
describe('should not change default fetch behaviour', () => {
86115
describe('server is running', () => {
87116
beforeEach(done => {

test/index.spec.js

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,4 @@ describe('index', function() {
5353
expect(accessToken).to.equal('access-token');
5454
});
5555
});
56-
57-
describe('unload', function() {
58-
it('should unload and detach interceptor', function (){
59-
fetchInterceptor.configure(config());
60-
fetchInterceptor.unload();
61-
62-
expect(fetchInterceptor.isActive()).to.be.false;
63-
});
64-
});
65-
});
56+
});

0 commit comments

Comments
 (0)