@logto/browser 0.1.2 → 0.1.3

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/index.d.ts CHANGED
@@ -31,23 +31,25 @@ export default class LogtoClient {
31
31
  protected logtoStorageKey: string;
32
32
  protected requester: Requester;
33
33
  protected accessTokenMap: Map<string, AccessToken>;
34
+ private readonly getAccessTokenPromiseMap;
34
35
  private _refreshToken;
35
36
  private _idToken;
36
37
  constructor(logtoConfig: LogtoConfig, requester?: <T>(input: RequestInfo, init?: RequestInit | undefined) => Promise<T>);
37
38
  get isAuthenticated(): boolean;
38
39
  protected get signInSession(): Nullable<LogtoSignInSessionItem>;
39
40
  protected set signInSession(logtoSignInSessionItem: Nullable<LogtoSignInSessionItem>);
40
- private get refreshToken();
41
+ get refreshToken(): Nullable<string>;
41
42
  private set refreshToken(value);
42
- private get idToken();
43
+ get idToken(): Nullable<string>;
43
44
  private set idToken(value);
44
- getAccessToken(resource?: string): Promise<Nullable<string>>;
45
+ getAccessToken(resource?: string): Promise<string>;
45
46
  getIdTokenClaims(): IdTokenClaims;
46
47
  fetchUserInfo(): Promise<UserInfoResponse>;
47
48
  signIn(redirectUri: string): Promise<void>;
48
49
  isSignInRedirected(url: string): boolean;
49
50
  handleSignInCallback(callbackUri: string): Promise<void>;
50
51
  signOut(postLogoutRedirectUri?: string): Promise<void>;
52
+ private getAccessTokenByRefreshToken;
51
53
  private getOidcConfig;
52
54
  private verifyIdToken;
53
55
  private saveCodeToken;
package/lib/index.js CHANGED
@@ -25,6 +25,7 @@ exports.LogtoSignInSessionItemSchema = (0, superstruct_1.type)({
25
25
  class LogtoClient {
26
26
  constructor(logtoConfig, requester = (0, js_1.createRequester)()) {
27
27
  this.accessTokenMap = new Map();
28
+ this.getAccessTokenPromiseMap = new Map();
28
29
  this.logtoConfig = logtoConfig;
29
30
  this.logtoStorageKey = (0, utils_1.buildLogtoKey)(logtoConfig.clientId);
30
31
  this.requester = requester;
@@ -89,33 +90,24 @@ class LogtoClient {
89
90
  if (accessToken && accessToken.expiresAt > Date.now() / 1000) {
90
91
  return accessToken.token;
91
92
  }
92
- // Token expired, remove it from the map
93
- if (accessToken) {
94
- this.accessTokenMap.delete(accessTokenKey);
95
- }
96
- // Fetch new access token by refresh token
97
- const { clientId } = this.logtoConfig;
98
- if (!this.refreshToken) {
99
- throw new errors_1.LogtoClientError('not_authenticated');
100
- }
101
- try {
102
- const { tokenEndpoint } = await this.getOidcConfig();
103
- const { accessToken, refreshToken, idToken, scope, expiresIn } = await (0, js_1.fetchTokenByRefreshToken)({ clientId, tokenEndpoint, refreshToken: this.refreshToken, resource }, this.requester);
104
- this.accessTokenMap.set(accessTokenKey, {
105
- token: accessToken,
106
- scope,
107
- expiresAt: Math.round(Date.now() / 1000) + expiresIn,
108
- });
109
- this.refreshToken = refreshToken;
110
- if (idToken) {
111
- await this.verifyIdToken(idToken);
112
- this.idToken = idToken;
113
- }
114
- return accessToken;
115
- }
116
- catch (error) {
117
- throw new errors_1.LogtoClientError('get_access_token_by_refresh_token_failed', error);
118
- }
93
+ /**
94
+ * Token has expired, need to fetch a new one using refresh token.
95
+ * Reuse the cached promise if exists.
96
+ */
97
+ const cachedPromise = this.getAccessTokenPromiseMap.get(accessTokenKey);
98
+ if (cachedPromise) {
99
+ return cachedPromise;
100
+ }
101
+ /**
102
+ * Create a new promise and cache in map to avoid race condition.
103
+ * Since we enable "refresh token rotation" by default,
104
+ * it will be problematic when calling multiple `getAccessToken()` closely.
105
+ */
106
+ const promise = this.getAccessTokenByRefreshToken(resource);
107
+ this.getAccessTokenPromiseMap.set(accessTokenKey, promise);
108
+ const token = await promise;
109
+ this.getAccessTokenPromiseMap.delete(accessTokenKey);
110
+ return token;
119
111
  }
120
112
  getIdTokenClaims() {
121
113
  if (!this.idToken) {
@@ -202,6 +194,36 @@ class LogtoClient {
202
194
  this.idToken = null;
203
195
  window.location.assign(url);
204
196
  }
197
+ async getAccessTokenByRefreshToken(resource) {
198
+ const accessTokenKey = (0, utils_1.buildAccessTokenKey)(resource);
199
+ // Token expired, remove it from the map
200
+ if (this.accessTokenMap.has(accessTokenKey)) {
201
+ this.accessTokenMap.delete(accessTokenKey);
202
+ }
203
+ // Fetch new access token by refresh token
204
+ const { clientId } = this.logtoConfig;
205
+ if (!this.refreshToken) {
206
+ throw new errors_1.LogtoClientError('not_authenticated');
207
+ }
208
+ try {
209
+ const { tokenEndpoint } = await this.getOidcConfig();
210
+ const { accessToken, refreshToken, idToken, scope, expiresIn } = await (0, js_1.fetchTokenByRefreshToken)({ clientId, tokenEndpoint, refreshToken: this.refreshToken, resource }, this.requester);
211
+ this.accessTokenMap.set(accessTokenKey, {
212
+ token: accessToken,
213
+ scope,
214
+ expiresAt: Math.round(Date.now() / 1000) + expiresIn,
215
+ });
216
+ this.refreshToken = refreshToken;
217
+ if (idToken) {
218
+ await this.verifyIdToken(idToken);
219
+ this.idToken = idToken;
220
+ }
221
+ return accessToken;
222
+ }
223
+ catch (error) {
224
+ throw new errors_1.LogtoClientError('get_access_token_by_refresh_token_failed', error);
225
+ }
226
+ }
205
227
  async getOidcConfig() {
206
228
  if (!this.oidcConfig) {
207
229
  const { endpoint } = this.logtoConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logto/browser",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "main": "./lib/index.js",
5
5
  "exports": "./lib/index.js",
6
6
  "typings": "./lib/index.d.ts",
@@ -8,6 +8,11 @@
8
8
  "lib"
9
9
  ],
10
10
  "license": "MIT",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/logto-io/js.git",
14
+ "directory": "packages/browser"
15
+ },
11
16
  "scripts": {
12
17
  "dev:tsc": "tsc -p tsconfig.build.json -w --preserveWatchOutput",
13
18
  "preinstall": "npx only-allow pnpm",
@@ -19,7 +24,7 @@
19
24
  "prepack": "pnpm test && pnpm build"
20
25
  },
21
26
  "dependencies": {
22
- "@logto/js": "^0.1.2",
27
+ "@logto/js": "^0.1.3",
23
28
  "@silverhand/essentials": "^1.1.6",
24
29
  "jose": "^4.5.0",
25
30
  "lodash.get": "^4.4.2",
@@ -48,5 +53,5 @@
48
53
  "publishConfig": {
49
54
  "access": "public"
50
55
  },
51
- "gitHead": "74bd01ec73a5215b26a96f426b874de9ad474dae"
56
+ "gitHead": "2a59d35046744dd1284eb81804cf0ef9a35f41e6"
52
57
  }