@auth0/auth0-spa-js 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/auth0-spa-js.development.js +615 -17
- package/dist/auth0-spa-js.development.js.map +1 -1
- package/dist/auth0-spa-js.production.esm.js +1 -1
- package/dist/auth0-spa-js.production.esm.js.map +1 -1
- package/dist/auth0-spa-js.production.js +1 -1
- package/dist/auth0-spa-js.production.js.map +1 -1
- package/dist/auth0-spa-js.worker.development.js +10 -2
- package/dist/auth0-spa-js.worker.development.js.map +1 -1
- package/dist/auth0-spa-js.worker.production.js +1 -1
- package/dist/auth0-spa-js.worker.production.js.map +1 -1
- package/dist/lib/auth0-spa-js.cjs.js +656 -17
- package/dist/lib/auth0-spa-js.cjs.js.map +1 -1
- package/dist/typings/Auth0Client.d.ts +54 -4
- package/dist/typings/Auth0Client.utils.d.ts +1 -1
- package/dist/typings/TokenExchange.d.ts +3 -2
- package/dist/typings/api.d.ts +1 -1
- package/dist/typings/cache/shared.d.ts +1 -0
- package/dist/typings/dpop/dpop.d.ts +17 -0
- package/dist/typings/dpop/storage.d.ts +27 -0
- package/dist/typings/dpop/utils.d.ts +15 -0
- package/dist/typings/errors.d.ts +7 -0
- package/dist/typings/fetcher.d.ts +48 -0
- package/dist/typings/global.d.ts +21 -0
- package/dist/typings/http.d.ts +2 -1
- package/dist/typings/index.d.ts +2 -1
- package/dist/typings/utils.d.ts +6 -0
- package/dist/typings/version.d.ts +1 -1
- package/package.json +22 -19
- package/src/Auth0Client.ts +126 -11
- package/src/Auth0Client.utils.ts +4 -2
- package/src/TokenExchange.ts +3 -2
- package/src/api.ts +17 -3
- package/src/cache/shared.ts +1 -0
- package/src/dpop/dpop.ts +56 -0
- package/src/dpop/storage.ts +134 -0
- package/src/dpop/utils.ts +66 -0
- package/src/errors.ts +11 -0
- package/src/fetcher.ts +224 -0
- package/src/global.ts +23 -0
- package/src/http.ts +70 -5
- package/src/index.ts +4 -1
- package/src/utils.ts +15 -0
- package/src/version.ts +1 -1
- package/src/worker/token.worker.ts +11 -5
package/src/fetcher.ts
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { DPOP_NONCE_HEADER } from './dpop/utils';
|
|
2
|
+
import { UseDpopNonceError } from './errors';
|
|
3
|
+
|
|
4
|
+
export type ResponseHeaders =
|
|
5
|
+
| Record<string, string | null | undefined>
|
|
6
|
+
| [string, string][]
|
|
7
|
+
| { get(name: string): string | null | undefined };
|
|
8
|
+
|
|
9
|
+
export type CustomFetchMinimalOutput = {
|
|
10
|
+
status: number;
|
|
11
|
+
headers: ResponseHeaders;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type CustomFetchImpl<TOutput extends CustomFetchMinimalOutput> = (
|
|
15
|
+
req: Request
|
|
16
|
+
) => Promise<TOutput>;
|
|
17
|
+
|
|
18
|
+
type AccessTokenFactory = () => Promise<string>;
|
|
19
|
+
|
|
20
|
+
export type FetcherConfig<TOutput extends CustomFetchMinimalOutput> = {
|
|
21
|
+
getAccessToken?: AccessTokenFactory;
|
|
22
|
+
baseUrl?: string;
|
|
23
|
+
fetch?: CustomFetchImpl<TOutput>;
|
|
24
|
+
dpopNonceId?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type FetcherHooks = {
|
|
28
|
+
isDpopEnabled: () => boolean;
|
|
29
|
+
getAccessToken: () => Promise<string>;
|
|
30
|
+
getDpopNonce: () => Promise<string | undefined>;
|
|
31
|
+
setDpopNonce: (nonce: string) => Promise<void>;
|
|
32
|
+
generateDpopProof: (params: {
|
|
33
|
+
url: string;
|
|
34
|
+
method: string;
|
|
35
|
+
nonce?: string;
|
|
36
|
+
accessToken: string;
|
|
37
|
+
}) => Promise<string>;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type FetchWithAuthCallbacks<TOutput> = {
|
|
41
|
+
onUseDpopNonceError?(): Promise<TOutput>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export class Fetcher<TOutput extends CustomFetchMinimalOutput> {
|
|
45
|
+
protected readonly config: Omit<FetcherConfig<TOutput>, 'fetch'> &
|
|
46
|
+
Required<Pick<FetcherConfig<TOutput>, 'fetch'>>;
|
|
47
|
+
|
|
48
|
+
protected readonly hooks: FetcherHooks;
|
|
49
|
+
|
|
50
|
+
constructor(config: FetcherConfig<TOutput>, hooks: FetcherHooks) {
|
|
51
|
+
this.hooks = hooks;
|
|
52
|
+
|
|
53
|
+
this.config = {
|
|
54
|
+
...config,
|
|
55
|
+
fetch:
|
|
56
|
+
config.fetch ||
|
|
57
|
+
// For easier testing and constructor compatibility with SSR.
|
|
58
|
+
((typeof window === 'undefined'
|
|
59
|
+
? fetch
|
|
60
|
+
: window.fetch.bind(window)) as () => Promise<any>)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
protected isAbsoluteUrl(url: string): boolean {
|
|
65
|
+
// `http://example.com`, `https://example.com` or `//example.com`
|
|
66
|
+
return /^(https?:)?\/\//i.test(url);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
protected buildUrl(
|
|
70
|
+
baseUrl: string | undefined,
|
|
71
|
+
url: string | undefined
|
|
72
|
+
): string {
|
|
73
|
+
if (url) {
|
|
74
|
+
if (this.isAbsoluteUrl(url)) {
|
|
75
|
+
return url;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (baseUrl) {
|
|
79
|
+
return `${baseUrl.replace(/\/?\/$/, '')}/${url.replace(/^\/+/, '')}`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
throw new TypeError('`url` must be absolute or `baseUrl` non-empty.');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
protected getAccessToken(): Promise<string> {
|
|
87
|
+
return this.config.getAccessToken
|
|
88
|
+
? this.config.getAccessToken()
|
|
89
|
+
: this.hooks.getAccessToken();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
protected buildBaseRequest(
|
|
93
|
+
info: RequestInfo | URL,
|
|
94
|
+
init: RequestInit | undefined
|
|
95
|
+
): Request {
|
|
96
|
+
// In the native `fetch()` behavior, `init` can override `info` and the result
|
|
97
|
+
// is the merge of both. So let's replicate that behavior by passing those into
|
|
98
|
+
// a fresh `Request` object.
|
|
99
|
+
const request = new Request(info, init);
|
|
100
|
+
|
|
101
|
+
// No `baseUrl` config, use whatever the URL the `Request` came with.
|
|
102
|
+
if (!this.config.baseUrl) {
|
|
103
|
+
return request;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return new Request(
|
|
107
|
+
this.buildUrl(this.config.baseUrl, request.url),
|
|
108
|
+
request
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
protected async setAuthorizationHeader(
|
|
113
|
+
request: Request,
|
|
114
|
+
accessToken: string
|
|
115
|
+
): Promise<void> {
|
|
116
|
+
request.headers.set(
|
|
117
|
+
'authorization',
|
|
118
|
+
`${this.config.dpopNonceId ? 'DPoP' : 'Bearer'} ${accessToken}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
protected async setDpopProofHeader(
|
|
123
|
+
request: Request,
|
|
124
|
+
accessToken: string
|
|
125
|
+
): Promise<void> {
|
|
126
|
+
if (!this.config.dpopNonceId) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const dpopNonce = await this.hooks.getDpopNonce();
|
|
131
|
+
|
|
132
|
+
const dpopProof = await this.hooks.generateDpopProof({
|
|
133
|
+
accessToken,
|
|
134
|
+
method: request.method,
|
|
135
|
+
nonce: dpopNonce,
|
|
136
|
+
url: request.url
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
request.headers.set('dpop', dpopProof);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
protected async prepareRequest(request: Request) {
|
|
143
|
+
const accessToken = await this.getAccessToken();
|
|
144
|
+
|
|
145
|
+
this.setAuthorizationHeader(request, accessToken);
|
|
146
|
+
|
|
147
|
+
await this.setDpopProofHeader(request, accessToken);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
protected getHeader(headers: ResponseHeaders, name: string): string {
|
|
151
|
+
if (Array.isArray(headers)) {
|
|
152
|
+
return new Headers(headers).get(name) || '';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (typeof headers.get === 'function') {
|
|
156
|
+
return headers.get(name) || '';
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return (headers as Record<string, string | null | undefined>)[name] || '';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
protected hasUseDpopNonceError(response: TOutput): boolean {
|
|
163
|
+
if (response.status !== 401) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const wwwAuthHeader = this.getHeader(response.headers, 'www-authenticate');
|
|
168
|
+
|
|
169
|
+
return wwwAuthHeader.includes('use_dpop_nonce');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
protected async handleResponse(
|
|
173
|
+
response: TOutput,
|
|
174
|
+
callbacks: FetchWithAuthCallbacks<TOutput>
|
|
175
|
+
): Promise<TOutput> {
|
|
176
|
+
const newDpopNonce = this.getHeader(response.headers, DPOP_NONCE_HEADER);
|
|
177
|
+
|
|
178
|
+
if (newDpopNonce) {
|
|
179
|
+
await this.hooks.setDpopNonce(newDpopNonce);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!this.hasUseDpopNonceError(response)) {
|
|
183
|
+
return response;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// After a `use_dpop_nonce` error, if we didn't get a new DPoP nonce or we
|
|
187
|
+
// did but it still got rejected for the same reason, we have to give up.
|
|
188
|
+
if (!newDpopNonce || !callbacks.onUseDpopNonceError) {
|
|
189
|
+
throw new UseDpopNonceError(newDpopNonce);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return callbacks.onUseDpopNonceError();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
protected async internalFetchWithAuth(
|
|
196
|
+
info: RequestInfo | URL,
|
|
197
|
+
init: RequestInit | undefined,
|
|
198
|
+
callbacks: FetchWithAuthCallbacks<TOutput>
|
|
199
|
+
): Promise<TOutput> {
|
|
200
|
+
const request = this.buildBaseRequest(info, init);
|
|
201
|
+
|
|
202
|
+
await this.prepareRequest(request);
|
|
203
|
+
|
|
204
|
+
const response = await this.config.fetch(request);
|
|
205
|
+
|
|
206
|
+
return this.handleResponse(response, callbacks);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
public fetchWithAuth(
|
|
210
|
+
info: RequestInfo | URL,
|
|
211
|
+
init?: RequestInit
|
|
212
|
+
): Promise<TOutput> {
|
|
213
|
+
const callbacks: FetchWithAuthCallbacks<TOutput> = {
|
|
214
|
+
onUseDpopNonceError: () =>
|
|
215
|
+
this.internalFetchWithAuth(info, init, {
|
|
216
|
+
...callbacks,
|
|
217
|
+
// Retry on a `use_dpop_nonce` error, but just once.
|
|
218
|
+
onUseDpopNonceError: undefined
|
|
219
|
+
})
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
return this.internalFetchWithAuth(info, init, callbacks);
|
|
223
|
+
}
|
|
224
|
+
}
|
package/src/global.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ICache } from './cache';
|
|
2
|
+
import type { Dpop } from './dpop/dpop';
|
|
2
3
|
|
|
3
4
|
export interface AuthorizationParams {
|
|
4
5
|
/**
|
|
@@ -84,6 +85,8 @@ export interface AuthorizationParams {
|
|
|
84
85
|
*
|
|
85
86
|
* - If you provide an Organization ID (a string with the prefix `org_`), it will be validated against the `org_id` claim of your user's ID Token. The validation is case-sensitive.
|
|
86
87
|
* - If you provide an Organization Name (a string *without* the prefix `org_`), it will be validated against the `org_name` claim of your user's ID Token. The validation is case-insensitive.
|
|
88
|
+
* To use an Organization Name you must have "Allow Organization Names in Authentication API" switched on in your Auth0 settings dashboard.
|
|
89
|
+
* More information is available on the [Auth0 documentation portal](https://auth0.com/docs/manage-users/organizations/configure-organizations/use-org-name-authentication-api)
|
|
87
90
|
*
|
|
88
91
|
*/
|
|
89
92
|
organization?: string;
|
|
@@ -269,6 +272,15 @@ export interface Auth0ClientOptions extends BaseLoginOptions {
|
|
|
269
272
|
* **Note**: The worker is only used when `useRefreshTokens: true`, `cacheLocation: 'memory'`, and the `cache` is not custom.
|
|
270
273
|
*/
|
|
271
274
|
workerUrl?: string;
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* If `true`, DPoP (OAuth 2.0 Demonstrating Proof of Possession, RFC9449)
|
|
278
|
+
* will be used to cryptographically bind tokens to this specific browser
|
|
279
|
+
* so they can't be used from a different device in case of a leak.
|
|
280
|
+
*
|
|
281
|
+
* The default setting is `false`.
|
|
282
|
+
*/
|
|
283
|
+
useDpop?: boolean;
|
|
272
284
|
}
|
|
273
285
|
|
|
274
286
|
/**
|
|
@@ -525,11 +537,13 @@ export interface TokenEndpointOptions {
|
|
|
525
537
|
timeout?: number;
|
|
526
538
|
auth0Client: any;
|
|
527
539
|
useFormData?: boolean;
|
|
540
|
+
dpop?: Pick<Dpop, 'generateProof' | 'getNonce' | 'setNonce'>;
|
|
528
541
|
[key: string]: any;
|
|
529
542
|
}
|
|
530
543
|
|
|
531
544
|
export type TokenEndpointResponse = {
|
|
532
545
|
id_token: string;
|
|
546
|
+
token_type: string;
|
|
533
547
|
access_token: string;
|
|
534
548
|
refresh_token?: string;
|
|
535
549
|
expires_in: number;
|
|
@@ -645,6 +659,15 @@ export type FetchOptions = {
|
|
|
645
659
|
signal?: AbortSignal;
|
|
646
660
|
};
|
|
647
661
|
|
|
662
|
+
/**
|
|
663
|
+
* @ignore
|
|
664
|
+
*/
|
|
665
|
+
export type FetchResponse = {
|
|
666
|
+
ok: boolean;
|
|
667
|
+
headers: Record<string, string | undefined>;
|
|
668
|
+
json: any;
|
|
669
|
+
};
|
|
670
|
+
|
|
648
671
|
export type GetTokenSilentlyVerboseResponse = Omit<
|
|
649
672
|
TokenEndpointResponse,
|
|
650
673
|
'refresh_token'
|
package/src/http.ts
CHANGED
|
@@ -3,13 +3,17 @@ import {
|
|
|
3
3
|
DEFAULT_SILENT_TOKEN_RETRY_COUNT
|
|
4
4
|
} from './constants';
|
|
5
5
|
|
|
6
|
+
import { fromEntries } from './utils';
|
|
6
7
|
import { sendMessage } from './worker/worker.utils';
|
|
7
|
-
import { FetchOptions } from './global';
|
|
8
|
+
import { FetchOptions, FetchResponse } from './global';
|
|
8
9
|
import {
|
|
9
10
|
GenericError,
|
|
10
11
|
MfaRequiredError,
|
|
11
|
-
MissingRefreshTokenError
|
|
12
|
+
MissingRefreshTokenError,
|
|
13
|
+
UseDpopNonceError
|
|
12
14
|
} from './errors';
|
|
15
|
+
import { Dpop } from './dpop/dpop';
|
|
16
|
+
import { DPOP_NONCE_HEADER } from './dpop/utils';
|
|
13
17
|
|
|
14
18
|
export const createAbortController = () => new AbortController();
|
|
15
19
|
|
|
@@ -18,7 +22,14 @@ const dofetch = async (fetchUrl: string, fetchOptions: FetchOptions) => {
|
|
|
18
22
|
|
|
19
23
|
return {
|
|
20
24
|
ok: response.ok,
|
|
21
|
-
json: await response.json()
|
|
25
|
+
json: await response.json(),
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* This is not needed, but do it anyway so the object shape is the
|
|
29
|
+
* same as when using a Web Worker (which *does* need this, see
|
|
30
|
+
* src/worker/token.worker.ts).
|
|
31
|
+
*/
|
|
32
|
+
headers: fromEntries(response.headers)
|
|
22
33
|
};
|
|
23
34
|
};
|
|
24
35
|
|
|
@@ -102,10 +113,22 @@ export async function getJSON<T>(
|
|
|
102
113
|
scope: string,
|
|
103
114
|
options: FetchOptions,
|
|
104
115
|
worker?: Worker,
|
|
105
|
-
useFormData?: boolean
|
|
116
|
+
useFormData?: boolean,
|
|
117
|
+
dpop?: Pick<Dpop, 'generateProof' | 'getNonce' | 'setNonce'>,
|
|
118
|
+
isDpopRetry?: boolean
|
|
106
119
|
): Promise<T> {
|
|
120
|
+
if (dpop) {
|
|
121
|
+
const dpopProof = await dpop.generateProof({
|
|
122
|
+
url,
|
|
123
|
+
method: options.method || 'GET',
|
|
124
|
+
nonce: await dpop.getNonce()
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
options.headers = { ...options.headers, dpop: dpopProof };
|
|
128
|
+
}
|
|
129
|
+
|
|
107
130
|
let fetchError: null | Error = null;
|
|
108
|
-
let response
|
|
131
|
+
let response!: FetchResponse;
|
|
109
132
|
|
|
110
133
|
for (let i = 0; i < DEFAULT_SILENT_TOKEN_RETRY_COUNT; i++) {
|
|
111
134
|
try {
|
|
@@ -135,9 +158,25 @@ export async function getJSON<T>(
|
|
|
135
158
|
|
|
136
159
|
const {
|
|
137
160
|
json: { error, error_description, ...data },
|
|
161
|
+
headers,
|
|
138
162
|
ok
|
|
139
163
|
} = response;
|
|
140
164
|
|
|
165
|
+
let newDpopNonce: string | undefined;
|
|
166
|
+
|
|
167
|
+
if (dpop) {
|
|
168
|
+
/**
|
|
169
|
+
* Note that a new DPoP nonce can appear in both error and success responses!
|
|
170
|
+
*
|
|
171
|
+
* @see {@link https://www.rfc-editor.org/rfc/rfc9449.html#section-8.2-3}
|
|
172
|
+
*/
|
|
173
|
+
newDpopNonce = headers[DPOP_NONCE_HEADER];
|
|
174
|
+
|
|
175
|
+
if (newDpopNonce) {
|
|
176
|
+
await dpop.setNonce(newDpopNonce);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
141
180
|
if (!ok) {
|
|
142
181
|
const errorMessage =
|
|
143
182
|
error_description || `HTTP error. Unable to fetch ${url}`;
|
|
@@ -150,6 +189,32 @@ export async function getJSON<T>(
|
|
|
150
189
|
throw new MissingRefreshTokenError(audience, scope);
|
|
151
190
|
}
|
|
152
191
|
|
|
192
|
+
/**
|
|
193
|
+
* When DPoP is used and we get a `use_dpop_nonce` error from the server,
|
|
194
|
+
* we must retry ONCE with any new nonce received in the rejected request.
|
|
195
|
+
*
|
|
196
|
+
* If a new nonce was not received or the retry fails again, we give up and
|
|
197
|
+
* throw the error as is.
|
|
198
|
+
*/
|
|
199
|
+
if (error === 'use_dpop_nonce') {
|
|
200
|
+
if (!dpop || !newDpopNonce || isDpopRetry) {
|
|
201
|
+
throw new UseDpopNonceError(newDpopNonce);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// repeat the call but with isDpopRetry=true to avoid any more retries
|
|
205
|
+
return getJSON(
|
|
206
|
+
url,
|
|
207
|
+
timeout,
|
|
208
|
+
audience,
|
|
209
|
+
scope,
|
|
210
|
+
options,
|
|
211
|
+
worker,
|
|
212
|
+
useFormData,
|
|
213
|
+
dpop,
|
|
214
|
+
true // !
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
153
218
|
throw new GenericError(error || 'request_error', errorMessage);
|
|
154
219
|
}
|
|
155
220
|
|
package/src/index.ts
CHANGED
|
@@ -29,7 +29,8 @@ export {
|
|
|
29
29
|
PopupTimeoutError,
|
|
30
30
|
PopupCancelledError,
|
|
31
31
|
MfaRequiredError,
|
|
32
|
-
MissingRefreshTokenError
|
|
32
|
+
MissingRefreshTokenError,
|
|
33
|
+
UseDpopNonceError
|
|
33
34
|
} from './errors';
|
|
34
35
|
|
|
35
36
|
export {
|
|
@@ -45,3 +46,5 @@ export {
|
|
|
45
46
|
CacheKey,
|
|
46
47
|
CacheKeyData
|
|
47
48
|
} from './cache';
|
|
49
|
+
|
|
50
|
+
export { type FetcherConfig } from './fetcher';
|
package/src/utils.ts
CHANGED
|
@@ -246,3 +246,18 @@ export const parseNumber = (value: any): number | undefined => {
|
|
|
246
246
|
}
|
|
247
247
|
return parseInt(value, 10) || undefined;
|
|
248
248
|
};
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Ponyfill for `Object.fromEntries()`, which is not available until ES2020.
|
|
252
|
+
*
|
|
253
|
+
* When the target of this project reaches ES2020, this can be removed.
|
|
254
|
+
*/
|
|
255
|
+
export const fromEntries = <T = any>(
|
|
256
|
+
iterable: Iterable<[PropertyKey, T]>
|
|
257
|
+
): Record<PropertyKey, T> => {
|
|
258
|
+
return [...iterable].reduce((obj, [key, val]) => {
|
|
259
|
+
obj[key] = val;
|
|
260
|
+
|
|
261
|
+
return obj;
|
|
262
|
+
}, {} as Record<PropertyKey, T>);
|
|
263
|
+
};
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '2.
|
|
1
|
+
export default '2.4.0';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MissingRefreshTokenError } from '../errors';
|
|
2
|
-
import {
|
|
2
|
+
import { FetchResponse } from '../global';
|
|
3
|
+
import { createQueryParams, fromEntries } from '../utils';
|
|
3
4
|
import { WorkerRefreshTokenMessage } from './worker.types';
|
|
4
5
|
|
|
5
6
|
let refreshTokens: Record<string, string> = {};
|
|
@@ -19,7 +20,7 @@ const deleteRefreshToken = (audience: string, scope: string) =>
|
|
|
19
20
|
delete refreshTokens[cacheKey(audience, scope)];
|
|
20
21
|
|
|
21
22
|
const wait = (time: number) =>
|
|
22
|
-
new Promise(resolve => setTimeout(resolve, time));
|
|
23
|
+
new Promise<void>(resolve => setTimeout(resolve, time));
|
|
23
24
|
|
|
24
25
|
const formDataToObject = (formData: string): Record<string, any> => {
|
|
25
26
|
const queryParams = new URLSearchParams(formData);
|
|
@@ -36,6 +37,8 @@ const messageHandler = async ({
|
|
|
36
37
|
data: { timeout, auth, fetchUrl, fetchOptions, useFormData },
|
|
37
38
|
ports: [port]
|
|
38
39
|
}: MessageEvent<WorkerRefreshTokenMessage>) => {
|
|
40
|
+
let headers: FetchResponse['headers'] = {};
|
|
41
|
+
|
|
39
42
|
let json: {
|
|
40
43
|
refresh_token?: string;
|
|
41
44
|
};
|
|
@@ -72,7 +75,7 @@ const messageHandler = async ({
|
|
|
72
75
|
fetchOptions.signal = abortController.signal;
|
|
73
76
|
}
|
|
74
77
|
|
|
75
|
-
let response:
|
|
78
|
+
let response: void | Response;
|
|
76
79
|
|
|
77
80
|
try {
|
|
78
81
|
response = await Promise.race([
|
|
@@ -99,6 +102,7 @@ const messageHandler = async ({
|
|
|
99
102
|
return;
|
|
100
103
|
}
|
|
101
104
|
|
|
105
|
+
headers = fromEntries(response.headers);
|
|
102
106
|
json = await response.json();
|
|
103
107
|
|
|
104
108
|
if (json.refresh_token) {
|
|
@@ -110,7 +114,8 @@ const messageHandler = async ({
|
|
|
110
114
|
|
|
111
115
|
port.postMessage({
|
|
112
116
|
ok: response.ok,
|
|
113
|
-
json
|
|
117
|
+
json,
|
|
118
|
+
headers
|
|
114
119
|
});
|
|
115
120
|
} catch (error) {
|
|
116
121
|
port.postMessage({
|
|
@@ -118,7 +123,8 @@ const messageHandler = async ({
|
|
|
118
123
|
json: {
|
|
119
124
|
error: error.error,
|
|
120
125
|
error_description: error.message
|
|
121
|
-
}
|
|
126
|
+
},
|
|
127
|
+
headers
|
|
122
128
|
});
|
|
123
129
|
}
|
|
124
130
|
};
|