@logto/client 2.1.0 → 2.2.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/lib/adapter/index.cjs +63 -0
- package/lib/adapter/index.d.ts +29 -0
- package/lib/adapter/index.js +53 -0
- package/lib/adapter/index.test.d.ts +1 -0
- package/lib/adapter/types.cjs +24 -0
- package/lib/adapter/types.d.ts +45 -0
- package/lib/adapter/types.js +24 -0
- package/lib/index.cache.test.d.ts +1 -0
- package/lib/index.cjs +38 -35
- package/lib/index.constructor.test.d.ts +1 -0
- package/lib/index.d.ts +18 -9
- package/lib/index.js +28 -33
- package/lib/index.sign-in.test.d.ts +1 -0
- package/lib/index.sign-out.test.d.ts +1 -0
- package/lib/mock.d.ts +27 -7
- package/lib/remote-jwk-set.cjs +62 -0
- package/lib/remote-jwk-set.d.ts +10 -0
- package/lib/remote-jwk-set.js +60 -0
- package/lib/remote-jwk-set.test.d.ts +1 -0
- package/lib/utils/memoize.cjs +25 -0
- package/lib/utils/memoize.d.ts +1 -0
- package/lib/utils/memoize.js +23 -0
- package/package.json +1 -1
- package/lib/adapter.d.ts +0 -17
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var essentials = require('@silverhand/essentials');
|
|
4
|
+
var types = require('./types.cjs');
|
|
5
|
+
|
|
6
|
+
class ClientAdapterInstance {
|
|
7
|
+
/* END OF IMPLEMENTATION */
|
|
8
|
+
constructor(adapter) {
|
|
9
|
+
// eslint-disable-next-line @silverhand/fp/no-mutating-assign
|
|
10
|
+
Object.assign(this, adapter);
|
|
11
|
+
}
|
|
12
|
+
async setStorageItem(key, value) {
|
|
13
|
+
if (!value) {
|
|
14
|
+
await this.storage.removeItem(key);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
await this.storage.setItem(key, value);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Try to get the string value from the cache and parse as JSON.
|
|
21
|
+
* Return the parsed value if it is an object, return `undefined` otherwise.
|
|
22
|
+
*
|
|
23
|
+
* @param key The cache key to get value from.
|
|
24
|
+
*/
|
|
25
|
+
async getCachedObject(key) {
|
|
26
|
+
const cached = await essentials.trySafe(async () => {
|
|
27
|
+
const data = await this.unstable_cache?.getItem(key);
|
|
28
|
+
// It's actually `unknown`
|
|
29
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
30
|
+
return essentials.conditional(data && JSON.parse(data));
|
|
31
|
+
});
|
|
32
|
+
if (cached && typeof cached === 'object') {
|
|
33
|
+
// Trust cache for now
|
|
34
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
35
|
+
return cached;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Try to get the value from the cache first, if it doesn't exist in cache,
|
|
40
|
+
* run the getter function and store the result into cache.
|
|
41
|
+
*
|
|
42
|
+
* @param key The cache key to get value from.
|
|
43
|
+
*/
|
|
44
|
+
async getWithCache(key, getter) {
|
|
45
|
+
const cached = await this.getCachedObject(key);
|
|
46
|
+
if (cached) {
|
|
47
|
+
return cached;
|
|
48
|
+
}
|
|
49
|
+
const result = await getter();
|
|
50
|
+
await this.unstable_cache?.setItem(key, JSON.stringify(result));
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
Object.defineProperty(exports, 'CacheKey', {
|
|
56
|
+
enumerable: true,
|
|
57
|
+
get: function () { return types.CacheKey; }
|
|
58
|
+
});
|
|
59
|
+
Object.defineProperty(exports, 'PersistKey', {
|
|
60
|
+
enumerable: true,
|
|
61
|
+
get: function () { return types.PersistKey; }
|
|
62
|
+
});
|
|
63
|
+
exports.ClientAdapterInstance = ClientAdapterInstance;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Requester } from '@logto/js';
|
|
2
|
+
import { type Nullable } from '@silverhand/essentials';
|
|
3
|
+
import { type CacheKey, type Navigate, type PersistKey, type Storage, type StorageKey, type ClientAdapter, type InferStorageKey } from './types.js';
|
|
4
|
+
export declare class ClientAdapterInstance implements ClientAdapter {
|
|
5
|
+
requester: Requester;
|
|
6
|
+
storage: Storage<StorageKey | PersistKey>;
|
|
7
|
+
unstable_cache?: Storage<CacheKey> | undefined;
|
|
8
|
+
navigate: Navigate;
|
|
9
|
+
generateState: () => string;
|
|
10
|
+
generateCodeVerifier: () => string;
|
|
11
|
+
generateCodeChallenge: (codeVerifier: string) => Promise<string>;
|
|
12
|
+
constructor(adapter: ClientAdapter);
|
|
13
|
+
setStorageItem(key: InferStorageKey<typeof this.storage>, value: Nullable<string>): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Try to get the string value from the cache and parse as JSON.
|
|
16
|
+
* Return the parsed value if it is an object, return `undefined` otherwise.
|
|
17
|
+
*
|
|
18
|
+
* @param key The cache key to get value from.
|
|
19
|
+
*/
|
|
20
|
+
getCachedObject<T>(key: CacheKey): Promise<T | undefined>;
|
|
21
|
+
/**
|
|
22
|
+
* Try to get the value from the cache first, if it doesn't exist in cache,
|
|
23
|
+
* run the getter function and store the result into cache.
|
|
24
|
+
*
|
|
25
|
+
* @param key The cache key to get value from.
|
|
26
|
+
*/
|
|
27
|
+
getWithCache<T>(key: CacheKey, getter: () => Promise<T>): Promise<T>;
|
|
28
|
+
}
|
|
29
|
+
export * from './types.js';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { trySafe, conditional } from '@silverhand/essentials';
|
|
2
|
+
export { CacheKey, PersistKey } from './types.js';
|
|
3
|
+
|
|
4
|
+
class ClientAdapterInstance {
|
|
5
|
+
/* END OF IMPLEMENTATION */
|
|
6
|
+
constructor(adapter) {
|
|
7
|
+
// eslint-disable-next-line @silverhand/fp/no-mutating-assign
|
|
8
|
+
Object.assign(this, adapter);
|
|
9
|
+
}
|
|
10
|
+
async setStorageItem(key, value) {
|
|
11
|
+
if (!value) {
|
|
12
|
+
await this.storage.removeItem(key);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
await this.storage.setItem(key, value);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Try to get the string value from the cache and parse as JSON.
|
|
19
|
+
* Return the parsed value if it is an object, return `undefined` otherwise.
|
|
20
|
+
*
|
|
21
|
+
* @param key The cache key to get value from.
|
|
22
|
+
*/
|
|
23
|
+
async getCachedObject(key) {
|
|
24
|
+
const cached = await trySafe(async () => {
|
|
25
|
+
const data = await this.unstable_cache?.getItem(key);
|
|
26
|
+
// It's actually `unknown`
|
|
27
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
28
|
+
return conditional(data && JSON.parse(data));
|
|
29
|
+
});
|
|
30
|
+
if (cached && typeof cached === 'object') {
|
|
31
|
+
// Trust cache for now
|
|
32
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
33
|
+
return cached;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Try to get the value from the cache first, if it doesn't exist in cache,
|
|
38
|
+
* run the getter function and store the result into cache.
|
|
39
|
+
*
|
|
40
|
+
* @param key The cache key to get value from.
|
|
41
|
+
*/
|
|
42
|
+
async getWithCache(key, getter) {
|
|
43
|
+
const cached = await this.getCachedObject(key);
|
|
44
|
+
if (cached) {
|
|
45
|
+
return cached;
|
|
46
|
+
}
|
|
47
|
+
const result = await getter();
|
|
48
|
+
await this.unstable_cache?.setItem(key, JSON.stringify(result));
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { ClientAdapterInstance };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
exports.PersistKey = void 0;
|
|
4
|
+
(function (PersistKey) {
|
|
5
|
+
PersistKey["IdToken"] = "idToken";
|
|
6
|
+
PersistKey["RefreshToken"] = "refreshToken";
|
|
7
|
+
PersistKey["AccessToken"] = "accessToken";
|
|
8
|
+
PersistKey["SignInSession"] = "signInSession";
|
|
9
|
+
})(exports.PersistKey || (exports.PersistKey = {}));
|
|
10
|
+
exports.CacheKey = void 0;
|
|
11
|
+
(function (CacheKey) {
|
|
12
|
+
/**
|
|
13
|
+
* OpenID Configuration endpoint response.
|
|
14
|
+
*
|
|
15
|
+
* @see {@link https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse | OpenID Connect Discovery 1.0}
|
|
16
|
+
*/
|
|
17
|
+
CacheKey["OpenidConfig"] = "openidConfiguration";
|
|
18
|
+
/**
|
|
19
|
+
* The content of OpenID Provider's `jwks_uri` (JSON Web Key Set).
|
|
20
|
+
*
|
|
21
|
+
* @see {@link https://openid.net/specs/openid-connect-discovery-1_0-21.html#ProviderMetadata | OpenID Connect Discovery 1.0}
|
|
22
|
+
*/
|
|
23
|
+
CacheKey["Jwks"] = "jwks";
|
|
24
|
+
})(exports.CacheKey || (exports.CacheKey = {}));
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Requester } from '@logto/js';
|
|
2
|
+
import type { Nullable } from '@silverhand/essentials';
|
|
3
|
+
/** @deprecated Use {@link PersistKey} instead. */
|
|
4
|
+
export type StorageKey = 'idToken' | 'refreshToken' | 'accessToken' | 'signInSession';
|
|
5
|
+
export declare enum PersistKey {
|
|
6
|
+
IdToken = "idToken",
|
|
7
|
+
RefreshToken = "refreshToken",
|
|
8
|
+
AccessToken = "accessToken",
|
|
9
|
+
SignInSession = "signInSession"
|
|
10
|
+
}
|
|
11
|
+
export declare enum CacheKey {
|
|
12
|
+
/**
|
|
13
|
+
* OpenID Configuration endpoint response.
|
|
14
|
+
*
|
|
15
|
+
* @see {@link https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse | OpenID Connect Discovery 1.0}
|
|
16
|
+
*/
|
|
17
|
+
OpenidConfig = "openidConfiguration",
|
|
18
|
+
/**
|
|
19
|
+
* The content of OpenID Provider's `jwks_uri` (JSON Web Key Set).
|
|
20
|
+
*
|
|
21
|
+
* @see {@link https://openid.net/specs/openid-connect-discovery-1_0-21.html#ProviderMetadata | OpenID Connect Discovery 1.0}
|
|
22
|
+
*/
|
|
23
|
+
Jwks = "jwks"
|
|
24
|
+
}
|
|
25
|
+
export type Storage<Keys extends string> = {
|
|
26
|
+
getItem(key: Keys): Promise<Nullable<string>>;
|
|
27
|
+
setItem(key: Keys, value: string): Promise<void>;
|
|
28
|
+
removeItem(key: Keys): Promise<void>;
|
|
29
|
+
};
|
|
30
|
+
export type InferStorageKey<S> = S extends Storage<infer Key> ? Key : never;
|
|
31
|
+
export type Navigate = (url: string) => void;
|
|
32
|
+
export type ClientAdapter = {
|
|
33
|
+
requester: Requester;
|
|
34
|
+
storage: Storage<StorageKey | PersistKey>;
|
|
35
|
+
/**
|
|
36
|
+
* An optional storage for caching well-known data.
|
|
37
|
+
*
|
|
38
|
+
* @see {@link CacheKey}
|
|
39
|
+
*/
|
|
40
|
+
unstable_cache?: Storage<CacheKey>;
|
|
41
|
+
navigate: Navigate;
|
|
42
|
+
generateState: () => string;
|
|
43
|
+
generateCodeVerifier: () => string;
|
|
44
|
+
generateCodeChallenge: (codeVerifier: string) => Promise<string>;
|
|
45
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
var PersistKey;
|
|
2
|
+
(function (PersistKey) {
|
|
3
|
+
PersistKey["IdToken"] = "idToken";
|
|
4
|
+
PersistKey["RefreshToken"] = "refreshToken";
|
|
5
|
+
PersistKey["AccessToken"] = "accessToken";
|
|
6
|
+
PersistKey["SignInSession"] = "signInSession";
|
|
7
|
+
})(PersistKey || (PersistKey = {}));
|
|
8
|
+
var CacheKey;
|
|
9
|
+
(function (CacheKey) {
|
|
10
|
+
/**
|
|
11
|
+
* OpenID Configuration endpoint response.
|
|
12
|
+
*
|
|
13
|
+
* @see {@link https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse | OpenID Connect Discovery 1.0}
|
|
14
|
+
*/
|
|
15
|
+
CacheKey["OpenidConfig"] = "openidConfiguration";
|
|
16
|
+
/**
|
|
17
|
+
* The content of OpenID Provider's `jwks_uri` (JSON Web Key Set).
|
|
18
|
+
*
|
|
19
|
+
* @see {@link https://openid.net/specs/openid-connect-discovery-1_0-21.html#ProviderMetadata | OpenID Connect Discovery 1.0}
|
|
20
|
+
*/
|
|
21
|
+
CacheKey["Jwks"] = "jwks";
|
|
22
|
+
})(CacheKey || (CacheKey = {}));
|
|
23
|
+
|
|
24
|
+
export { CacheKey, PersistKey };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/index.cjs
CHANGED
|
@@ -4,23 +4,27 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
var js = require('@logto/js');
|
|
6
6
|
var jose = require('jose');
|
|
7
|
+
var index$1 = require('./adapter/index.cjs');
|
|
7
8
|
var errors = require('./errors.cjs');
|
|
9
|
+
var remoteJwkSet = require('./remote-jwk-set.cjs');
|
|
8
10
|
var index = require('./types/index.cjs');
|
|
9
|
-
var index$
|
|
11
|
+
var index$2 = require('./utils/index.cjs');
|
|
12
|
+
var memoize = require('./utils/memoize.cjs');
|
|
10
13
|
var once = require('./utils/once.cjs');
|
|
14
|
+
var types = require('./adapter/types.cjs');
|
|
11
15
|
var requester = require('./utils/requester.cjs');
|
|
12
16
|
|
|
13
17
|
class LogtoClient {
|
|
14
18
|
constructor(logtoConfig, adapter) {
|
|
15
|
-
this.getOidcConfig =
|
|
16
|
-
this.getJwtVerifyGetKey = once.once(this
|
|
19
|
+
this.getOidcConfig = memoize.memoize(this.#getOidcConfig);
|
|
20
|
+
this.getJwtVerifyGetKey = once.once(this.#getJwtVerifyGetKey);
|
|
17
21
|
this.accessTokenMap = new Map();
|
|
18
22
|
this.logtoConfig = {
|
|
19
23
|
...logtoConfig,
|
|
20
24
|
prompt: logtoConfig.prompt ?? js.Prompt.Consent,
|
|
21
25
|
scopes: js.withDefaultScopes(logtoConfig.scopes).split(' '),
|
|
22
26
|
};
|
|
23
|
-
this.adapter = adapter;
|
|
27
|
+
this.adapter = new index$1.ClientAdapterInstance(adapter);
|
|
24
28
|
void this.loadAccessTokenMap();
|
|
25
29
|
}
|
|
26
30
|
async isAuthenticated() {
|
|
@@ -36,7 +40,7 @@ class LogtoClient {
|
|
|
36
40
|
if (!(await this.getIdToken())) {
|
|
37
41
|
throw new errors.LogtoClientError('not_authenticated');
|
|
38
42
|
}
|
|
39
|
-
const accessTokenKey = index$
|
|
43
|
+
const accessTokenKey = index$2.buildAccessTokenKey(resource);
|
|
40
44
|
const accessToken = this.accessTokenMap.get(accessTokenKey);
|
|
41
45
|
if (accessToken && accessToken.expiresAt > Date.now() / 1000) {
|
|
42
46
|
return accessToken.token;
|
|
@@ -156,34 +160,21 @@ class LogtoClient {
|
|
|
156
160
|
}
|
|
157
161
|
return item;
|
|
158
162
|
}
|
|
159
|
-
async setSignInSession(
|
|
160
|
-
|
|
161
|
-
await this.adapter.storage.removeItem('signInSession');
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
const jsonItem = JSON.stringify(logtoSignInSessionItem);
|
|
165
|
-
await this.adapter.storage.setItem('signInSession', jsonItem);
|
|
163
|
+
async setSignInSession(value) {
|
|
164
|
+
return this.adapter.setStorageItem(types.PersistKey.SignInSession, value && JSON.stringify(value));
|
|
166
165
|
}
|
|
167
|
-
async setIdToken(
|
|
168
|
-
|
|
169
|
-
await this.adapter.storage.removeItem('idToken');
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
await this.adapter.storage.setItem('idToken', idToken);
|
|
166
|
+
async setIdToken(value) {
|
|
167
|
+
return this.adapter.setStorageItem(types.PersistKey.IdToken, value);
|
|
173
168
|
}
|
|
174
|
-
async setRefreshToken(
|
|
175
|
-
|
|
176
|
-
await this.adapter.storage.removeItem('refreshToken');
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
await this.adapter.storage.setItem('refreshToken', refreshToken);
|
|
169
|
+
async setRefreshToken(value) {
|
|
170
|
+
return this.adapter.setStorageItem(types.PersistKey.RefreshToken, value);
|
|
180
171
|
}
|
|
181
172
|
async getAccessTokenByRefreshToken(resource) {
|
|
182
173
|
const currentRefreshToken = await this.getRefreshToken();
|
|
183
174
|
if (!currentRefreshToken) {
|
|
184
175
|
throw new errors.LogtoClientError('not_authenticated');
|
|
185
176
|
}
|
|
186
|
-
const accessTokenKey = index$
|
|
177
|
+
const accessTokenKey = index$2.buildAccessTokenKey(resource);
|
|
187
178
|
const { appId: clientId } = this.logtoConfig;
|
|
188
179
|
const { tokenEndpoint } = await this.getOidcConfig();
|
|
189
180
|
const { accessToken, refreshToken, idToken, scope, expiresIn } = await js.fetchTokenByRefreshToken({
|
|
@@ -205,15 +196,6 @@ class LogtoClient {
|
|
|
205
196
|
}
|
|
206
197
|
return accessToken;
|
|
207
198
|
}
|
|
208
|
-
async _getOidcConfig() {
|
|
209
|
-
const { endpoint } = this.logtoConfig;
|
|
210
|
-
const discoveryEndpoint = index$1.getDiscoveryEndpoint(endpoint);
|
|
211
|
-
return js.fetchOidcConfig(discoveryEndpoint, this.adapter.requester);
|
|
212
|
-
}
|
|
213
|
-
async _getJwtVerifyGetKey() {
|
|
214
|
-
const { jwksUri } = await this.getOidcConfig();
|
|
215
|
-
return jose.createRemoteJWKSet(new URL(jwksUri));
|
|
216
|
-
}
|
|
217
199
|
async verifyIdToken(idToken) {
|
|
218
200
|
const { appId } = this.logtoConfig;
|
|
219
201
|
const { issuer } = await this.getOidcConfig();
|
|
@@ -224,7 +206,7 @@ class LogtoClient {
|
|
|
224
206
|
await this.setRefreshToken(refreshToken ?? null);
|
|
225
207
|
await this.setIdToken(idToken);
|
|
226
208
|
// NOTE: Will add scope to accessTokenKey when needed. (Linear issue LOG-1589)
|
|
227
|
-
const accessTokenKey = index$
|
|
209
|
+
const accessTokenKey = index$2.buildAccessTokenKey();
|
|
228
210
|
const expiresAt = Date.now() / 1000 + expiresIn;
|
|
229
211
|
this.accessTokenMap.set(accessTokenKey, { token: accessToken, scope, expiresAt });
|
|
230
212
|
await this.saveAccessTokenMap();
|
|
@@ -256,6 +238,19 @@ class LogtoClient {
|
|
|
256
238
|
console.warn(error);
|
|
257
239
|
}
|
|
258
240
|
}
|
|
241
|
+
async #getOidcConfig() {
|
|
242
|
+
return this.adapter.getWithCache(types.CacheKey.OpenidConfig, async () => {
|
|
243
|
+
return js.fetchOidcConfig(index$2.getDiscoveryEndpoint(this.logtoConfig.endpoint), this.adapter.requester);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
async #getJwtVerifyGetKey() {
|
|
247
|
+
const { jwksUri } = await this.getOidcConfig();
|
|
248
|
+
if (!this.adapter.unstable_cache) {
|
|
249
|
+
return jose.createRemoteJWKSet(new URL(jwksUri));
|
|
250
|
+
}
|
|
251
|
+
const cachedJwkSet = new remoteJwkSet.CachedRemoteJwkSet(new URL(jwksUri), this.adapter);
|
|
252
|
+
return async (...args) => cachedJwkSet.getKey(...args);
|
|
253
|
+
}
|
|
259
254
|
}
|
|
260
255
|
|
|
261
256
|
Object.defineProperty(exports, 'LogtoError', {
|
|
@@ -285,5 +280,13 @@ Object.defineProperty(exports, 'UserScope', {
|
|
|
285
280
|
exports.LogtoClientError = errors.LogtoClientError;
|
|
286
281
|
exports.isLogtoAccessTokenMap = index.isLogtoAccessTokenMap;
|
|
287
282
|
exports.isLogtoSignInSessionItem = index.isLogtoSignInSessionItem;
|
|
283
|
+
Object.defineProperty(exports, 'CacheKey', {
|
|
284
|
+
enumerable: true,
|
|
285
|
+
get: function () { return types.CacheKey; }
|
|
286
|
+
});
|
|
287
|
+
Object.defineProperty(exports, 'PersistKey', {
|
|
288
|
+
enumerable: true,
|
|
289
|
+
get: function () { return types.PersistKey; }
|
|
290
|
+
});
|
|
288
291
|
exports.createRequester = requester.createRequester;
|
|
289
292
|
exports.default = LogtoClient;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/index.d.ts
CHANGED
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
import { type IdTokenClaims, type UserInfoResponse, type InteractionMode, type AccessTokenClaims } from '@logto/js';
|
|
2
|
-
import type
|
|
3
|
-
import type
|
|
2
|
+
import { type Nullable } from '@silverhand/essentials';
|
|
3
|
+
import { type JWTVerifyGetKey } from 'jose';
|
|
4
|
+
import { ClientAdapterInstance, type ClientAdapter } from './adapter/index.js';
|
|
4
5
|
import type { AccessToken, LogtoConfig, LogtoSignInSessionItem } from './types/index.js';
|
|
5
6
|
export type { IdTokenClaims, LogtoErrorCode, UserInfoResponse, InteractionMode } from '@logto/js';
|
|
6
7
|
export { LogtoError, OidcError, Prompt, LogtoRequestError, ReservedScope, UserScope, } from '@logto/js';
|
|
7
8
|
export * from './errors.js';
|
|
8
|
-
export type { Storage, StorageKey, ClientAdapter } from './adapter.js';
|
|
9
|
+
export type { Storage, StorageKey, ClientAdapter } from './adapter/index.js';
|
|
10
|
+
export { PersistKey, CacheKey } from './adapter/index.js';
|
|
9
11
|
export { createRequester } from './utils/index.js';
|
|
10
12
|
export * from './types/index.js';
|
|
11
13
|
export default class LogtoClient {
|
|
14
|
+
#private;
|
|
12
15
|
protected readonly logtoConfig: LogtoConfig;
|
|
13
|
-
protected readonly getOidcConfig:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
protected readonly getOidcConfig: (this: unknown) => Promise<import("@silverhand/essentials").KeysToCamelCase<{
|
|
17
|
+
authorization_endpoint: string;
|
|
18
|
+
token_endpoint: string;
|
|
19
|
+
userinfo_endpoint: string;
|
|
20
|
+
end_session_endpoint: string;
|
|
21
|
+
revocation_endpoint: string;
|
|
22
|
+
jwks_uri: string;
|
|
23
|
+
issuer: string;
|
|
24
|
+
}>>;
|
|
25
|
+
protected readonly getJwtVerifyGetKey: (...args: unknown[]) => Promise<JWTVerifyGetKey>;
|
|
26
|
+
protected readonly adapter: ClientAdapterInstance;
|
|
16
27
|
protected readonly accessTokenMap: Map<string, AccessToken>;
|
|
17
28
|
constructor(logtoConfig: LogtoConfig, adapter: ClientAdapter);
|
|
18
29
|
isAuthenticated(): Promise<boolean>;
|
|
@@ -27,12 +38,10 @@ export default class LogtoClient {
|
|
|
27
38
|
handleSignInCallback(callbackUri: string): Promise<void>;
|
|
28
39
|
signOut(postLogoutRedirectUri?: string): Promise<void>;
|
|
29
40
|
protected getSignInSession(): Promise<Nullable<LogtoSignInSessionItem>>;
|
|
30
|
-
protected setSignInSession(
|
|
41
|
+
protected setSignInSession(value: Nullable<LogtoSignInSessionItem>): Promise<void>;
|
|
31
42
|
private setIdToken;
|
|
32
43
|
private setRefreshToken;
|
|
33
44
|
private getAccessTokenByRefreshToken;
|
|
34
|
-
private _getOidcConfig;
|
|
35
|
-
private _getJwtVerifyGetKey;
|
|
36
45
|
private verifyIdToken;
|
|
37
46
|
private saveCodeToken;
|
|
38
47
|
private saveAccessTokenMap;
|
package/lib/index.js
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
|
-
import { Prompt, withDefaultScopes, decodeIdToken, decodeAccessToken, fetchUserInfo, generateSignInUri, verifyAndParseCodeFromCallbackUri, fetchTokenByAuthorizationCode, revoke, generateSignOutUri, fetchTokenByRefreshToken,
|
|
1
|
+
import { Prompt, withDefaultScopes, decodeIdToken, decodeAccessToken, fetchUserInfo, generateSignInUri, verifyAndParseCodeFromCallbackUri, fetchTokenByAuthorizationCode, revoke, generateSignOutUri, fetchTokenByRefreshToken, verifyIdToken, fetchOidcConfig } from '@logto/js';
|
|
2
2
|
export { LogtoError, LogtoRequestError, OidcError, Prompt, ReservedScope, UserScope } from '@logto/js';
|
|
3
3
|
import { createRemoteJWKSet } from 'jose';
|
|
4
|
+
import { ClientAdapterInstance } from './adapter/index.js';
|
|
4
5
|
import { LogtoClientError } from './errors.js';
|
|
6
|
+
import { CachedRemoteJwkSet } from './remote-jwk-set.js';
|
|
5
7
|
import { isLogtoSignInSessionItem, isLogtoAccessTokenMap } from './types/index.js';
|
|
6
8
|
import { buildAccessTokenKey, getDiscoveryEndpoint } from './utils/index.js';
|
|
9
|
+
import { memoize } from './utils/memoize.js';
|
|
7
10
|
import { once } from './utils/once.js';
|
|
11
|
+
import { PersistKey, CacheKey } from './adapter/types.js';
|
|
8
12
|
export { createRequester } from './utils/requester.js';
|
|
9
13
|
|
|
10
14
|
class LogtoClient {
|
|
11
15
|
constructor(logtoConfig, adapter) {
|
|
12
|
-
this.getOidcConfig =
|
|
13
|
-
this.getJwtVerifyGetKey = once(this
|
|
16
|
+
this.getOidcConfig = memoize(this.#getOidcConfig);
|
|
17
|
+
this.getJwtVerifyGetKey = once(this.#getJwtVerifyGetKey);
|
|
14
18
|
this.accessTokenMap = new Map();
|
|
15
19
|
this.logtoConfig = {
|
|
16
20
|
...logtoConfig,
|
|
17
21
|
prompt: logtoConfig.prompt ?? Prompt.Consent,
|
|
18
22
|
scopes: withDefaultScopes(logtoConfig.scopes).split(' '),
|
|
19
23
|
};
|
|
20
|
-
this.adapter = adapter;
|
|
24
|
+
this.adapter = new ClientAdapterInstance(adapter);
|
|
21
25
|
void this.loadAccessTokenMap();
|
|
22
26
|
}
|
|
23
27
|
async isAuthenticated() {
|
|
@@ -153,27 +157,14 @@ class LogtoClient {
|
|
|
153
157
|
}
|
|
154
158
|
return item;
|
|
155
159
|
}
|
|
156
|
-
async setSignInSession(
|
|
157
|
-
|
|
158
|
-
await this.adapter.storage.removeItem('signInSession');
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
const jsonItem = JSON.stringify(logtoSignInSessionItem);
|
|
162
|
-
await this.adapter.storage.setItem('signInSession', jsonItem);
|
|
160
|
+
async setSignInSession(value) {
|
|
161
|
+
return this.adapter.setStorageItem(PersistKey.SignInSession, value && JSON.stringify(value));
|
|
163
162
|
}
|
|
164
|
-
async setIdToken(
|
|
165
|
-
|
|
166
|
-
await this.adapter.storage.removeItem('idToken');
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
await this.adapter.storage.setItem('idToken', idToken);
|
|
163
|
+
async setIdToken(value) {
|
|
164
|
+
return this.adapter.setStorageItem(PersistKey.IdToken, value);
|
|
170
165
|
}
|
|
171
|
-
async setRefreshToken(
|
|
172
|
-
|
|
173
|
-
await this.adapter.storage.removeItem('refreshToken');
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
await this.adapter.storage.setItem('refreshToken', refreshToken);
|
|
166
|
+
async setRefreshToken(value) {
|
|
167
|
+
return this.adapter.setStorageItem(PersistKey.RefreshToken, value);
|
|
177
168
|
}
|
|
178
169
|
async getAccessTokenByRefreshToken(resource) {
|
|
179
170
|
const currentRefreshToken = await this.getRefreshToken();
|
|
@@ -202,15 +193,6 @@ class LogtoClient {
|
|
|
202
193
|
}
|
|
203
194
|
return accessToken;
|
|
204
195
|
}
|
|
205
|
-
async _getOidcConfig() {
|
|
206
|
-
const { endpoint } = this.logtoConfig;
|
|
207
|
-
const discoveryEndpoint = getDiscoveryEndpoint(endpoint);
|
|
208
|
-
return fetchOidcConfig(discoveryEndpoint, this.adapter.requester);
|
|
209
|
-
}
|
|
210
|
-
async _getJwtVerifyGetKey() {
|
|
211
|
-
const { jwksUri } = await this.getOidcConfig();
|
|
212
|
-
return createRemoteJWKSet(new URL(jwksUri));
|
|
213
|
-
}
|
|
214
196
|
async verifyIdToken(idToken) {
|
|
215
197
|
const { appId } = this.logtoConfig;
|
|
216
198
|
const { issuer } = await this.getOidcConfig();
|
|
@@ -253,6 +235,19 @@ class LogtoClient {
|
|
|
253
235
|
console.warn(error);
|
|
254
236
|
}
|
|
255
237
|
}
|
|
238
|
+
async #getOidcConfig() {
|
|
239
|
+
return this.adapter.getWithCache(CacheKey.OpenidConfig, async () => {
|
|
240
|
+
return fetchOidcConfig(getDiscoveryEndpoint(this.logtoConfig.endpoint), this.adapter.requester);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
async #getJwtVerifyGetKey() {
|
|
244
|
+
const { jwksUri } = await this.getOidcConfig();
|
|
245
|
+
if (!this.adapter.unstable_cache) {
|
|
246
|
+
return createRemoteJWKSet(new URL(jwksUri));
|
|
247
|
+
}
|
|
248
|
+
const cachedJwkSet = new CachedRemoteJwkSet(new URL(jwksUri), this.adapter);
|
|
249
|
+
return async (...args) => cachedJwkSet.getKey(...args);
|
|
250
|
+
}
|
|
256
251
|
}
|
|
257
252
|
|
|
258
|
-
export { LogtoClientError, LogtoClient as default, isLogtoAccessTokenMap, isLogtoSignInSessionItem };
|
|
253
|
+
export { CacheKey, LogtoClientError, PersistKey, LogtoClient as default, isLogtoAccessTokenMap, isLogtoSignInSessionItem };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/mock.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/// <reference types="jest" />
|
|
2
2
|
import { Prompt } from '@logto/js';
|
|
3
|
-
import type
|
|
4
|
-
import type { Storage } from './adapter.js';
|
|
3
|
+
import { type Nullable } from '@silverhand/essentials';
|
|
4
|
+
import type { Storage } from './adapter/index.js';
|
|
5
5
|
import type { AccessToken, LogtoConfig, LogtoSignInSessionItem } from './index.js';
|
|
6
6
|
import LogtoClient from './index.js';
|
|
7
7
|
export declare const appId = "app_id_value";
|
|
8
8
|
export declare const endpoint = "https://logto.dev";
|
|
9
|
-
export declare class MockedStorage implements Storage {
|
|
9
|
+
export declare class MockedStorage implements Storage<string> {
|
|
10
10
|
private storage;
|
|
11
11
|
constructor(values?: Record<string, string>);
|
|
12
12
|
getItem(key: string): Promise<string | null>;
|
|
@@ -33,6 +33,15 @@ export declare const accessToken = "access_token_value";
|
|
|
33
33
|
export declare const refreshToken = "new_refresh_token_value";
|
|
34
34
|
export declare const idToken = "id_token_value";
|
|
35
35
|
export declare const currentUnixTimeStamp: number;
|
|
36
|
+
export declare const mockFetchOidcConfig: (delay?: number) => jest.Mock<Promise<{
|
|
37
|
+
authorizationEndpoint: string;
|
|
38
|
+
tokenEndpoint: string;
|
|
39
|
+
userinfoEndpoint: string;
|
|
40
|
+
endSessionEndpoint: string;
|
|
41
|
+
revocationEndpoint: string;
|
|
42
|
+
jwksUri: string;
|
|
43
|
+
issuer: string;
|
|
44
|
+
}>, [], any>;
|
|
36
45
|
export declare const fetchOidcConfig: jest.Mock<Promise<{
|
|
37
46
|
authorizationEndpoint: string;
|
|
38
47
|
tokenEndpoint: string;
|
|
@@ -48,19 +57,30 @@ export declare const navigate: jest.Mock<any, any, any>;
|
|
|
48
57
|
export declare const generateCodeChallenge: jest.Mock<Promise<string>, [], any>;
|
|
49
58
|
export declare const generateCodeVerifier: jest.Mock<string, [], any>;
|
|
50
59
|
export declare const generateState: jest.Mock<string, [], any>;
|
|
51
|
-
export declare const createAdapters: () => {
|
|
60
|
+
export declare const createAdapters: (withCache?: boolean) => {
|
|
52
61
|
requester: jest.Mock<any, any, any>;
|
|
53
62
|
storage: MockedStorage;
|
|
63
|
+
unstable_cache: import("@silverhand/essentials").Optional<MockedStorage>;
|
|
54
64
|
navigate: jest.Mock<any, any, any>;
|
|
55
65
|
generateCodeChallenge: jest.Mock<Promise<string>, [], any>;
|
|
56
66
|
generateCodeVerifier: jest.Mock<string, [], any>;
|
|
57
67
|
generateState: jest.Mock<string, [], any>;
|
|
58
68
|
};
|
|
59
|
-
export declare const createClient: (prompt?: Prompt, storage?: MockedStorage) =>
|
|
69
|
+
export declare const createClient: (prompt?: Prompt, storage?: MockedStorage, withCache?: boolean) => LogtoClientWithAccessors;
|
|
60
70
|
/**
|
|
61
|
-
* Make
|
|
71
|
+
* Make protected fields accessible for test
|
|
62
72
|
*/
|
|
63
|
-
export declare class
|
|
73
|
+
export declare class LogtoClientWithAccessors extends LogtoClient {
|
|
74
|
+
runGetOidcConfig(): Promise<import("@silverhand/essentials").KeysToCamelCase<{
|
|
75
|
+
authorization_endpoint: string;
|
|
76
|
+
token_endpoint: string;
|
|
77
|
+
userinfo_endpoint: string;
|
|
78
|
+
end_session_endpoint: string;
|
|
79
|
+
revocation_endpoint: string;
|
|
80
|
+
jwks_uri: string;
|
|
81
|
+
issuer: string;
|
|
82
|
+
}>>;
|
|
83
|
+
runGetJwtVerifyGetKey(): Promise<import("jose").JWTVerifyGetKey>;
|
|
64
84
|
getLogtoConfig(): Nullable<LogtoConfig>;
|
|
65
85
|
getSignInSessionItem(): Promise<Nullable<LogtoSignInSessionItem>>;
|
|
66
86
|
setSignInSessionItem(item: Nullable<LogtoSignInSessionItem>): Promise<void>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var essentials = require('@silverhand/essentials');
|
|
4
|
+
var jose = require('jose');
|
|
5
|
+
var types = require('./adapter/types.cjs');
|
|
6
|
+
|
|
7
|
+
// Edited from jose's internal util `isJWKSLike`
|
|
8
|
+
function isJwkSetLike(jwkSet) {
|
|
9
|
+
return Boolean(jwkSet &&
|
|
10
|
+
typeof jwkSet === 'object' &&
|
|
11
|
+
'keys' in jwkSet &&
|
|
12
|
+
Array.isArray(jwkSet.keys) &&
|
|
13
|
+
jwkSet.keys.every((element) => essentials.isObject(element)));
|
|
14
|
+
}
|
|
15
|
+
class CachedRemoteJwkSet {
|
|
16
|
+
constructor(url, adapter) {
|
|
17
|
+
this.url = url;
|
|
18
|
+
this.adapter = adapter;
|
|
19
|
+
if (!adapter.unstable_cache) {
|
|
20
|
+
throw new Error("No cache found in the client adapter. Use `createRemoteJWKSet()` from 'jose' instead.");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async getKey(...args) {
|
|
24
|
+
if (!this.jwkSet) {
|
|
25
|
+
this.jwkSet = await this.#load();
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return await this.#getLocalKey(...args);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
// Jose does not export the error definition
|
|
32
|
+
// Found in https://github.com/panva/jose/blob/d5b3cb672736112b1e1e31ac4d5e9cd641675206/src/util/errors.ts#L347
|
|
33
|
+
if (error instanceof Error && 'code' in error && error.code === 'ERR_JWKS_NO_MATCHING_KEY') {
|
|
34
|
+
this.jwkSet = await this.#load();
|
|
35
|
+
return this.#getLocalKey(...args);
|
|
36
|
+
}
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async #load() {
|
|
41
|
+
return this.adapter.getWithCache(types.CacheKey.Jwks, async () => {
|
|
42
|
+
const controller = new AbortController();
|
|
43
|
+
const response = await fetch(this.url, { signal: controller.signal, redirect: 'manual' });
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
throw new Error('Expected OK from the JSON Web Key Set HTTP response');
|
|
46
|
+
}
|
|
47
|
+
const json = await response.json();
|
|
48
|
+
if (!isJwkSetLike(json)) {
|
|
49
|
+
throw new Error('JSON Web Key Set malformed');
|
|
50
|
+
}
|
|
51
|
+
return json;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async #getLocalKey(...args) {
|
|
55
|
+
if (!this.jwkSet) {
|
|
56
|
+
throw new Error('No local JWK Set found.');
|
|
57
|
+
}
|
|
58
|
+
return jose.createLocalJWKSet(this.jwkSet)(...args);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
exports.CachedRemoteJwkSet = CachedRemoteJwkSet;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type JSONWebKeySet, type JWTVerifyGetKey } from 'jose';
|
|
2
|
+
import { type ClientAdapterInstance } from './adapter/index.js';
|
|
3
|
+
export declare class CachedRemoteJwkSet {
|
|
4
|
+
#private;
|
|
5
|
+
readonly url: URL;
|
|
6
|
+
private readonly adapter;
|
|
7
|
+
protected jwkSet?: JSONWebKeySet;
|
|
8
|
+
constructor(url: URL, adapter: ClientAdapterInstance);
|
|
9
|
+
getKey(...args: Parameters<JWTVerifyGetKey>): Promise<import("jose").KeyLike>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { isObject } from '@silverhand/essentials';
|
|
2
|
+
import { createLocalJWKSet } from 'jose';
|
|
3
|
+
import { CacheKey } from './adapter/types.js';
|
|
4
|
+
|
|
5
|
+
// Edited from jose's internal util `isJWKSLike`
|
|
6
|
+
function isJwkSetLike(jwkSet) {
|
|
7
|
+
return Boolean(jwkSet &&
|
|
8
|
+
typeof jwkSet === 'object' &&
|
|
9
|
+
'keys' in jwkSet &&
|
|
10
|
+
Array.isArray(jwkSet.keys) &&
|
|
11
|
+
jwkSet.keys.every((element) => isObject(element)));
|
|
12
|
+
}
|
|
13
|
+
class CachedRemoteJwkSet {
|
|
14
|
+
constructor(url, adapter) {
|
|
15
|
+
this.url = url;
|
|
16
|
+
this.adapter = adapter;
|
|
17
|
+
if (!adapter.unstable_cache) {
|
|
18
|
+
throw new Error("No cache found in the client adapter. Use `createRemoteJWKSet()` from 'jose' instead.");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async getKey(...args) {
|
|
22
|
+
if (!this.jwkSet) {
|
|
23
|
+
this.jwkSet = await this.#load();
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
return await this.#getLocalKey(...args);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
// Jose does not export the error definition
|
|
30
|
+
// Found in https://github.com/panva/jose/blob/d5b3cb672736112b1e1e31ac4d5e9cd641675206/src/util/errors.ts#L347
|
|
31
|
+
if (error instanceof Error && 'code' in error && error.code === 'ERR_JWKS_NO_MATCHING_KEY') {
|
|
32
|
+
this.jwkSet = await this.#load();
|
|
33
|
+
return this.#getLocalKey(...args);
|
|
34
|
+
}
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async #load() {
|
|
39
|
+
return this.adapter.getWithCache(CacheKey.Jwks, async () => {
|
|
40
|
+
const controller = new AbortController();
|
|
41
|
+
const response = await fetch(this.url, { signal: controller.signal, redirect: 'manual' });
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
throw new Error('Expected OK from the JSON Web Key Set HTTP response');
|
|
44
|
+
}
|
|
45
|
+
const json = await response.json();
|
|
46
|
+
if (!isJwkSetLike(json)) {
|
|
47
|
+
throw new Error('JSON Web Key Set malformed');
|
|
48
|
+
}
|
|
49
|
+
return json;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async #getLocalKey(...args) {
|
|
53
|
+
if (!this.jwkSet) {
|
|
54
|
+
throw new Error('No local JWK Set found.');
|
|
55
|
+
}
|
|
56
|
+
return createLocalJWKSet(this.jwkSet)(...args);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export { CachedRemoteJwkSet };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function memoize(run) {
|
|
4
|
+
const promiseCache = new Map();
|
|
5
|
+
const memoized = async function (...args) {
|
|
6
|
+
const promiseKey = args[0];
|
|
7
|
+
const cachedPromise = promiseCache.get(promiseKey);
|
|
8
|
+
if (cachedPromise) {
|
|
9
|
+
return cachedPromise;
|
|
10
|
+
}
|
|
11
|
+
const promise = (async () => {
|
|
12
|
+
try {
|
|
13
|
+
return await run.apply(this, args);
|
|
14
|
+
}
|
|
15
|
+
finally {
|
|
16
|
+
promiseCache.delete(promiseKey);
|
|
17
|
+
}
|
|
18
|
+
})();
|
|
19
|
+
promiseCache.set(promiseKey, promise);
|
|
20
|
+
return promise;
|
|
21
|
+
};
|
|
22
|
+
return memoized;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
exports.memoize = memoize;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function memoize<Args extends unknown[], Return>(run: (...args: Args) => Promise<Return>): (this: unknown, ...args: Args) => Promise<Return>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
function memoize(run) {
|
|
2
|
+
const promiseCache = new Map();
|
|
3
|
+
const memoized = async function (...args) {
|
|
4
|
+
const promiseKey = args[0];
|
|
5
|
+
const cachedPromise = promiseCache.get(promiseKey);
|
|
6
|
+
if (cachedPromise) {
|
|
7
|
+
return cachedPromise;
|
|
8
|
+
}
|
|
9
|
+
const promise = (async () => {
|
|
10
|
+
try {
|
|
11
|
+
return await run.apply(this, args);
|
|
12
|
+
}
|
|
13
|
+
finally {
|
|
14
|
+
promiseCache.delete(promiseKey);
|
|
15
|
+
}
|
|
16
|
+
})();
|
|
17
|
+
promiseCache.set(promiseKey, promise);
|
|
18
|
+
return promise;
|
|
19
|
+
};
|
|
20
|
+
return memoized;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { memoize };
|
package/package.json
CHANGED
package/lib/adapter.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { Requester } from '@logto/js';
|
|
2
|
-
import type { Nullable } from '@silverhand/essentials';
|
|
3
|
-
export type StorageKey = 'idToken' | 'refreshToken' | 'accessToken' | 'signInSession';
|
|
4
|
-
export type Storage = {
|
|
5
|
-
getItem(key: StorageKey): Promise<Nullable<string>>;
|
|
6
|
-
setItem(key: StorageKey, value: string): Promise<void>;
|
|
7
|
-
removeItem(key: StorageKey): Promise<void>;
|
|
8
|
-
};
|
|
9
|
-
export type Navigate = (url: string) => void;
|
|
10
|
-
export type ClientAdapter = {
|
|
11
|
-
requester: Requester;
|
|
12
|
-
storage: Storage;
|
|
13
|
-
navigate: Navigate;
|
|
14
|
-
generateState: () => string;
|
|
15
|
-
generateCodeVerifier: () => string;
|
|
16
|
-
generateCodeChallenge: (codeVerifier: string) => Promise<string>;
|
|
17
|
-
};
|