@logto/client 2.2.1 → 2.2.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/README.md CHANGED
@@ -5,8 +5,6 @@
5
5
 
6
6
  The Logto JavaScript Client SDK written in TypeScript. Check out our [docs](https://docs.logto.io/sdk/JavaScript/client/) for more information.
7
7
 
8
- We also provide [文档](https://docs.logto.io/zh-cn/sdk/JavaScript/sdk/client/) in Simplified Chinese.
9
-
10
8
  ## Installation
11
9
 
12
10
  ### Using npm
@@ -22,13 +22,23 @@ export declare enum CacheKey {
22
22
  */
23
23
  Jwks = "jwks"
24
24
  }
25
+ /**
26
+ * The storage object that allows the client to persist data.
27
+ *
28
+ * It's compatible with the `localStorage` API.
29
+ */
25
30
  export type Storage<Keys extends string> = {
26
31
  getItem(key: Keys): Promise<Nullable<string>>;
27
32
  setItem(key: Keys, value: string): Promise<void>;
28
33
  removeItem(key: Keys): Promise<void>;
29
34
  };
30
35
  export type InferStorageKey<S> = S extends Storage<infer Key> ? Key : never;
31
- export type Navigate = (url: string) => void;
36
+ /** The navigation function that redirects the user to the specified URL. */
37
+ export type Navigate = (url: string) => void | Promise<void>;
38
+ /**
39
+ * The adapter object that allows the customizations of the client behavior
40
+ * for different environments.
41
+ */
32
42
  export type ClientAdapter = {
33
43
  requester: Requester;
34
44
  storage: Storage<StorageKey | PersistKey>;
@@ -39,7 +49,26 @@ export type ClientAdapter = {
39
49
  */
40
50
  unstable_cache?: Storage<CacheKey>;
41
51
  navigate: Navigate;
52
+ /**
53
+ * The function that generates a random state string.
54
+ *
55
+ * @returns The state string.
56
+ */
42
57
  generateState: () => string;
58
+ /**
59
+ * The function that generates a random code verifier string for PKCE.
60
+ *
61
+ * @see {@link https://www.rfc-editor.org/rfc/rfc7636.html| RFC 7636}
62
+ * @returns The code verifier string.
63
+ */
43
64
  generateCodeVerifier: () => string;
65
+ /**
66
+ * The function that generates a code challenge string based on the code verifier
67
+ * for PKCE.
68
+ *
69
+ * @see {@link https://www.rfc-editor.org/rfc/rfc7636.html| RFC 7636}
70
+ * @param codeVerifier The code verifier string.
71
+ * @returns The code challenge string.
72
+ */
44
73
  generateCodeChallenge: (codeVerifier: string) => Promise<string>;
45
74
  };
package/lib/errors.cjs CHANGED
@@ -5,10 +5,12 @@ const logtoClientErrorCodes = Object.freeze({
5
5
  'sign_in_session.not_found': 'Sign-in session not found.',
6
6
  not_authenticated: 'Not authenticated.',
7
7
  fetch_user_info_failed: 'Unable to fetch user info. The access token may be invalid.',
8
+ user_cancelled: 'The user cancelled the action.',
8
9
  });
9
10
  class LogtoClientError extends Error {
10
11
  constructor(code, data) {
11
12
  super(logtoClientErrorCodes[code]);
13
+ this.name = 'LogtoClientError';
12
14
  this.code = code;
13
15
  this.data = data;
14
16
  }
package/lib/errors.d.ts CHANGED
@@ -3,9 +3,11 @@ declare const logtoClientErrorCodes: Readonly<{
3
3
  'sign_in_session.not_found': "Sign-in session not found.";
4
4
  not_authenticated: "Not authenticated.";
5
5
  fetch_user_info_failed: "Unable to fetch user info. The access token may be invalid.";
6
+ user_cancelled: "The user cancelled the action.";
6
7
  }>;
7
8
  export type LogtoClientErrorCode = keyof typeof logtoClientErrorCodes;
8
9
  export declare class LogtoClientError extends Error {
10
+ name: string;
9
11
  code: LogtoClientErrorCode;
10
12
  data: unknown;
11
13
  constructor(code: LogtoClientErrorCode, data?: unknown);
package/lib/errors.js CHANGED
@@ -3,10 +3,12 @@ const logtoClientErrorCodes = Object.freeze({
3
3
  'sign_in_session.not_found': 'Sign-in session not found.',
4
4
  not_authenticated: 'Not authenticated.',
5
5
  fetch_user_info_failed: 'Unable to fetch user info. The access token may be invalid.',
6
+ user_cancelled: 'The user cancelled the action.',
6
7
  });
7
8
  class LogtoClientError extends Error {
8
9
  constructor(code, data) {
9
10
  super(logtoClientErrorCodes[code]);
11
+ this.name = 'LogtoClientError';
10
12
  this.code = code;
11
13
  this.data = data;
12
14
  }
package/lib/index.cjs CHANGED
@@ -14,6 +14,13 @@ var once = require('./utils/once.cjs');
14
14
  var types = require('./adapter/types.cjs');
15
15
  var requester = require('./utils/requester.cjs');
16
16
 
17
+ /**
18
+ * The Logto base client class that provides the essential methods for
19
+ * interacting with the Logto server.
20
+ *
21
+ * It also provides an adapter object that allows the customizations of the
22
+ * client behavior for different environments.
23
+ */
17
24
  class LogtoClient {
18
25
  constructor(logtoConfig, adapter) {
19
26
  this.getOidcConfig = memoize.memoize(this.#getOidcConfig);
@@ -27,15 +34,37 @@ class LogtoClient {
27
34
  this.adapter = new index$1.ClientAdapterInstance(adapter);
28
35
  void this.loadAccessTokenMap();
29
36
  }
37
+ /**
38
+ * Check if the user is authenticated by checking if the ID token exists.
39
+ */
30
40
  async isAuthenticated() {
31
41
  return Boolean(await this.getIdToken());
32
42
  }
43
+ /**
44
+ * Get the Refresh Token from the storage.
45
+ */
33
46
  async getRefreshToken() {
34
47
  return this.adapter.storage.getItem('refreshToken');
35
48
  }
49
+ /**
50
+ * Get the ID Token from the storage. If you want to get the ID Token claims,
51
+ * use {@link getIdTokenClaims} instead.
52
+ */
36
53
  async getIdToken() {
37
54
  return this.adapter.storage.getItem('idToken');
38
55
  }
56
+ /**
57
+ * Get the Access Token from the storage. If the Access Token has expired, it
58
+ * will try to fetch a new one using the Refresh Token.
59
+ *
60
+ * If you want to get the Access Token claims, use {@link getAccessTokenClaims} instead.
61
+ *
62
+ * @param resource The resource that the Access Token is granted for. If not
63
+ * specified, the Access Token will be used for OpenID Connect or the default
64
+ * resource, as specified in the Logto Console.
65
+ * @returns The Access Token string.
66
+ * @throws LogtoClientError if the user is not authenticated.
67
+ */
39
68
  async getAccessToken(resource) {
40
69
  if (!(await this.getIdToken())) {
41
70
  throw new errors.LogtoClientError('not_authenticated');
@@ -54,6 +83,9 @@ class LogtoClient {
54
83
  */
55
84
  return this.getAccessTokenByRefreshToken(resource);
56
85
  }
86
+ /**
87
+ * Get the ID Token claims.
88
+ */
57
89
  async getIdTokenClaims() {
58
90
  const idToken = await this.getIdToken();
59
91
  if (!idToken) {
@@ -61,10 +93,27 @@ class LogtoClient {
61
93
  }
62
94
  return js.decodeIdToken(idToken);
63
95
  }
96
+ /**
97
+ * Get the Access Token claims for the specified resource.
98
+ *
99
+ * @param resource The resource that the Access Token is granted for. If not
100
+ * specified, the Access Token will be used for OpenID Connect or the default
101
+ * resource, as specified in the Logto Console.
102
+ */
64
103
  async getAccessTokenClaims(resource) {
65
104
  const accessToken = await this.getAccessToken(resource);
66
105
  return js.decodeAccessToken(accessToken);
67
106
  }
107
+ /**
108
+ * Get the user information from the Userinfo Endpoint.
109
+ *
110
+ * Note the Userinfo Endpoint will return more claims than the ID Token. See
111
+ * {@link https://docs.logto.io/docs/recipes/integrate-logto/vanilla-js/#fetch-user-information | Fetch user information}
112
+ * for more information.
113
+ *
114
+ * @returns The user information.
115
+ * @throws LogtoClientError if the user is not authenticated.
116
+ */
68
117
  async fetchUserInfo() {
69
118
  const { userinfoEndpoint } = await this.getOidcConfig();
70
119
  const accessToken = await this.getAccessToken();
@@ -73,6 +122,22 @@ class LogtoClient {
73
122
  }
74
123
  return js.fetchUserInfo(userinfoEndpoint, accessToken, this.adapter.requester);
75
124
  }
125
+ /**
126
+ * Start the sign-in flow with the specified redirect URI. The URI must be
127
+ * registered in the Logto Console.
128
+ *
129
+ * The user will be redirected to that URI after the sign-in flow is completed,
130
+ * and the client will be able to get the authorization code from the URI.
131
+ * To fetch the tokens from the authorization code, use {@link handleSignInCallback}
132
+ * after the user is redirected in the callback URI.
133
+ *
134
+ * @param redirectUri The redirect URI that the user will be redirected to after the sign-in flow is completed.
135
+ * @param interactionMode The interaction mode to be used for the authorization request. Note it's not
136
+ * a part of the OIDC standard, but a Logto-specific extension. Defaults to `signIn`.
137
+ *
138
+ * @see {@link https://docs.logto.io/docs/recipes/integrate-logto/vanilla-js/#sign-in | Sign in} for more information.
139
+ * @see {@link InteractionMode}
140
+ */
76
141
  async signIn(redirectUri, interactionMode) {
77
142
  const { appId: clientId, prompt, resources, scopes } = this.logtoConfig;
78
143
  const { authorizationEndpoint } = await this.getOidcConfig();
@@ -90,11 +155,21 @@ class LogtoClient {
90
155
  prompt,
91
156
  interactionMode,
92
157
  });
93
- await this.setSignInSession({ redirectUri, codeVerifier, state });
94
- await this.setRefreshToken(null);
95
- await this.setIdToken(null);
96
- this.adapter.navigate(signInUri);
158
+ await Promise.all([
159
+ this.setSignInSession({ redirectUri, codeVerifier, state }),
160
+ this.setRefreshToken(null),
161
+ this.setIdToken(null),
162
+ ]);
163
+ await this.adapter.navigate(signInUri);
97
164
  }
165
+ /**
166
+ * Check if the user is redirected from the sign-in page by checking if the
167
+ * current URL matches the redirect URI in the sign-in session.
168
+ *
169
+ * If there's no sign-in session, it will return `false`.
170
+ *
171
+ * @param url The current URL.
172
+ */
98
173
  async isSignInRedirected(url) {
99
174
  const signInSession = await this.getSignInSession();
100
175
  if (!signInSession) {
@@ -104,6 +179,15 @@ class LogtoClient {
104
179
  const { origin, pathname } = new URL(url);
105
180
  return `${origin}${pathname}` === redirectUri;
106
181
  }
182
+ /**
183
+ * Handle the sign-in callback by parsing the authorization code from the
184
+ * callback URI and exchanging it for the tokens.
185
+ *
186
+ * @param callbackUri The callback URI, including the search params, that the user is redirected to after the sign-in flow is completed.
187
+ * The origin and pathname of this URI must match the origin and pathname of the redirect URI specified in {@link signIn}.
188
+ * In many cases you'll probably end up passing `window.location.href` as the argument to this function.
189
+ * @throws LogtoClientError if the sign-in session is not found.
190
+ */
107
191
  async handleSignInCallback(callbackUri) {
108
192
  const { requester } = this.adapter;
109
193
  const signInSession = await this.getSignInSession();
@@ -139,6 +223,16 @@ class LogtoClient {
139
223
  await this.saveAccessTokenMap();
140
224
  await this.setSignInSession(null);
141
225
  }
226
+ /**
227
+ * Start the sign-out flow with the specified redirect URI. The URI must be
228
+ * registered in the Logto Console.
229
+ *
230
+ * It will also revoke all the tokens and clean up the storage.
231
+ *
232
+ * The user will be redirected that URI after the sign-out flow is completed.
233
+ * If the `postLogoutRedirectUri` is not specified, the user will be redirected
234
+ * to a default page.
235
+ */
142
236
  async signOut(postLogoutRedirectUri) {
143
237
  const { appId: clientId } = this.logtoConfig;
144
238
  const { endSessionEndpoint, revocationEndpoint } = await this.getOidcConfig();
@@ -157,10 +251,12 @@ class LogtoClient {
157
251
  clientId,
158
252
  });
159
253
  this.accessTokenMap.clear();
160
- await this.setRefreshToken(null);
161
- await this.setIdToken(null);
162
- await this.adapter.storage.removeItem('accessToken');
163
- this.adapter.navigate(url);
254
+ await Promise.all([
255
+ this.setRefreshToken(null),
256
+ this.setIdToken(null),
257
+ this.adapter.storage.removeItem('accessToken'),
258
+ ]);
259
+ await this.adapter.navigate(url);
164
260
  }
165
261
  async getSignInSession() {
166
262
  const jsonItem = await this.adapter.storage.getItem('signInSession');
@@ -207,7 +303,9 @@ class LogtoClient {
207
303
  expiresAt: requestedAt + expiresIn,
208
304
  });
209
305
  await this.saveAccessTokenMap();
210
- await this.setRefreshToken(refreshToken);
306
+ if (refreshToken) {
307
+ await this.setRefreshToken(refreshToken);
308
+ }
211
309
  if (idToken) {
212
310
  await this.verifyIdToken(idToken);
213
311
  await this.setIdToken(idToken);
package/lib/index.d.ts CHANGED
@@ -4,12 +4,19 @@ import { type JWTVerifyGetKey } from 'jose';
4
4
  import { ClientAdapterInstance, type ClientAdapter } from './adapter/index.js';
5
5
  import type { AccessToken, LogtoConfig, LogtoSignInSessionItem } from './types/index.js';
6
6
  export type { IdTokenClaims, LogtoErrorCode, UserInfoResponse, InteractionMode } from '@logto/js';
7
- export { LogtoError, OidcError, Prompt, LogtoRequestError, ReservedScope, UserScope, } from '@logto/js';
7
+ export { LogtoError, LogtoRequestError, OidcError, Prompt, ReservedScope, UserScope, } from '@logto/js';
8
8
  export * from './errors.js';
9
9
  export type { Storage, StorageKey, ClientAdapter } from './adapter/index.js';
10
10
  export { PersistKey, CacheKey } from './adapter/index.js';
11
11
  export { createRequester } from './utils/index.js';
12
12
  export * from './types/index.js';
13
+ /**
14
+ * The Logto base client class that provides the essential methods for
15
+ * interacting with the Logto server.
16
+ *
17
+ * It also provides an adapter object that allows the customizations of the
18
+ * client behavior for different environments.
19
+ */
13
20
  export default class LogtoClient {
14
21
  #private;
15
22
  protected readonly logtoConfig: LogtoConfig;
@@ -26,16 +33,101 @@ export default class LogtoClient {
26
33
  protected readonly adapter: ClientAdapterInstance;
27
34
  protected readonly accessTokenMap: Map<string, AccessToken>;
28
35
  constructor(logtoConfig: LogtoConfig, adapter: ClientAdapter);
36
+ /**
37
+ * Check if the user is authenticated by checking if the ID token exists.
38
+ */
29
39
  isAuthenticated(): Promise<boolean>;
40
+ /**
41
+ * Get the Refresh Token from the storage.
42
+ */
30
43
  getRefreshToken(): Promise<Nullable<string>>;
44
+ /**
45
+ * Get the ID Token from the storage. If you want to get the ID Token claims,
46
+ * use {@link getIdTokenClaims} instead.
47
+ */
31
48
  getIdToken(): Promise<Nullable<string>>;
49
+ /**
50
+ * Get the Access Token from the storage. If the Access Token has expired, it
51
+ * will try to fetch a new one using the Refresh Token.
52
+ *
53
+ * If you want to get the Access Token claims, use {@link getAccessTokenClaims} instead.
54
+ *
55
+ * @param resource The resource that the Access Token is granted for. If not
56
+ * specified, the Access Token will be used for OpenID Connect or the default
57
+ * resource, as specified in the Logto Console.
58
+ * @returns The Access Token string.
59
+ * @throws LogtoClientError if the user is not authenticated.
60
+ */
32
61
  getAccessToken(resource?: string): Promise<string>;
62
+ /**
63
+ * Get the ID Token claims.
64
+ */
33
65
  getIdTokenClaims(): Promise<IdTokenClaims>;
66
+ /**
67
+ * Get the Access Token claims for the specified resource.
68
+ *
69
+ * @param resource The resource that the Access Token is granted for. If not
70
+ * specified, the Access Token will be used for OpenID Connect or the default
71
+ * resource, as specified in the Logto Console.
72
+ */
34
73
  getAccessTokenClaims(resource?: string): Promise<AccessTokenClaims>;
74
+ /**
75
+ * Get the user information from the Userinfo Endpoint.
76
+ *
77
+ * Note the Userinfo Endpoint will return more claims than the ID Token. See
78
+ * {@link https://docs.logto.io/docs/recipes/integrate-logto/vanilla-js/#fetch-user-information | Fetch user information}
79
+ * for more information.
80
+ *
81
+ * @returns The user information.
82
+ * @throws LogtoClientError if the user is not authenticated.
83
+ */
35
84
  fetchUserInfo(): Promise<UserInfoResponse>;
85
+ /**
86
+ * Start the sign-in flow with the specified redirect URI. The URI must be
87
+ * registered in the Logto Console.
88
+ *
89
+ * The user will be redirected to that URI after the sign-in flow is completed,
90
+ * and the client will be able to get the authorization code from the URI.
91
+ * To fetch the tokens from the authorization code, use {@link handleSignInCallback}
92
+ * after the user is redirected in the callback URI.
93
+ *
94
+ * @param redirectUri The redirect URI that the user will be redirected to after the sign-in flow is completed.
95
+ * @param interactionMode The interaction mode to be used for the authorization request. Note it's not
96
+ * a part of the OIDC standard, but a Logto-specific extension. Defaults to `signIn`.
97
+ *
98
+ * @see {@link https://docs.logto.io/docs/recipes/integrate-logto/vanilla-js/#sign-in | Sign in} for more information.
99
+ * @see {@link InteractionMode}
100
+ */
36
101
  signIn(redirectUri: string, interactionMode?: InteractionMode): Promise<void>;
102
+ /**
103
+ * Check if the user is redirected from the sign-in page by checking if the
104
+ * current URL matches the redirect URI in the sign-in session.
105
+ *
106
+ * If there's no sign-in session, it will return `false`.
107
+ *
108
+ * @param url The current URL.
109
+ */
37
110
  isSignInRedirected(url: string): Promise<boolean>;
111
+ /**
112
+ * Handle the sign-in callback by parsing the authorization code from the
113
+ * callback URI and exchanging it for the tokens.
114
+ *
115
+ * @param callbackUri The callback URI, including the search params, that the user is redirected to after the sign-in flow is completed.
116
+ * The origin and pathname of this URI must match the origin and pathname of the redirect URI specified in {@link signIn}.
117
+ * In many cases you'll probably end up passing `window.location.href` as the argument to this function.
118
+ * @throws LogtoClientError if the sign-in session is not found.
119
+ */
38
120
  handleSignInCallback(callbackUri: string): Promise<void>;
121
+ /**
122
+ * Start the sign-out flow with the specified redirect URI. The URI must be
123
+ * registered in the Logto Console.
124
+ *
125
+ * It will also revoke all the tokens and clean up the storage.
126
+ *
127
+ * The user will be redirected that URI after the sign-out flow is completed.
128
+ * If the `postLogoutRedirectUri` is not specified, the user will be redirected
129
+ * to a default page.
130
+ */
39
131
  signOut(postLogoutRedirectUri?: string): Promise<void>;
40
132
  protected getSignInSession(): Promise<Nullable<LogtoSignInSessionItem>>;
41
133
  protected setSignInSession(value: Nullable<LogtoSignInSessionItem>): Promise<void>;
package/lib/index.js CHANGED
@@ -11,6 +11,13 @@ import { once } from './utils/once.js';
11
11
  import { PersistKey, CacheKey } from './adapter/types.js';
12
12
  export { createRequester } from './utils/requester.js';
13
13
 
14
+ /**
15
+ * The Logto base client class that provides the essential methods for
16
+ * interacting with the Logto server.
17
+ *
18
+ * It also provides an adapter object that allows the customizations of the
19
+ * client behavior for different environments.
20
+ */
14
21
  class LogtoClient {
15
22
  constructor(logtoConfig, adapter) {
16
23
  this.getOidcConfig = memoize(this.#getOidcConfig);
@@ -24,15 +31,37 @@ class LogtoClient {
24
31
  this.adapter = new ClientAdapterInstance(adapter);
25
32
  void this.loadAccessTokenMap();
26
33
  }
34
+ /**
35
+ * Check if the user is authenticated by checking if the ID token exists.
36
+ */
27
37
  async isAuthenticated() {
28
38
  return Boolean(await this.getIdToken());
29
39
  }
40
+ /**
41
+ * Get the Refresh Token from the storage.
42
+ */
30
43
  async getRefreshToken() {
31
44
  return this.adapter.storage.getItem('refreshToken');
32
45
  }
46
+ /**
47
+ * Get the ID Token from the storage. If you want to get the ID Token claims,
48
+ * use {@link getIdTokenClaims} instead.
49
+ */
33
50
  async getIdToken() {
34
51
  return this.adapter.storage.getItem('idToken');
35
52
  }
53
+ /**
54
+ * Get the Access Token from the storage. If the Access Token has expired, it
55
+ * will try to fetch a new one using the Refresh Token.
56
+ *
57
+ * If you want to get the Access Token claims, use {@link getAccessTokenClaims} instead.
58
+ *
59
+ * @param resource The resource that the Access Token is granted for. If not
60
+ * specified, the Access Token will be used for OpenID Connect or the default
61
+ * resource, as specified in the Logto Console.
62
+ * @returns The Access Token string.
63
+ * @throws LogtoClientError if the user is not authenticated.
64
+ */
36
65
  async getAccessToken(resource) {
37
66
  if (!(await this.getIdToken())) {
38
67
  throw new LogtoClientError('not_authenticated');
@@ -51,6 +80,9 @@ class LogtoClient {
51
80
  */
52
81
  return this.getAccessTokenByRefreshToken(resource);
53
82
  }
83
+ /**
84
+ * Get the ID Token claims.
85
+ */
54
86
  async getIdTokenClaims() {
55
87
  const idToken = await this.getIdToken();
56
88
  if (!idToken) {
@@ -58,10 +90,27 @@ class LogtoClient {
58
90
  }
59
91
  return decodeIdToken(idToken);
60
92
  }
93
+ /**
94
+ * Get the Access Token claims for the specified resource.
95
+ *
96
+ * @param resource The resource that the Access Token is granted for. If not
97
+ * specified, the Access Token will be used for OpenID Connect or the default
98
+ * resource, as specified in the Logto Console.
99
+ */
61
100
  async getAccessTokenClaims(resource) {
62
101
  const accessToken = await this.getAccessToken(resource);
63
102
  return decodeAccessToken(accessToken);
64
103
  }
104
+ /**
105
+ * Get the user information from the Userinfo Endpoint.
106
+ *
107
+ * Note the Userinfo Endpoint will return more claims than the ID Token. See
108
+ * {@link https://docs.logto.io/docs/recipes/integrate-logto/vanilla-js/#fetch-user-information | Fetch user information}
109
+ * for more information.
110
+ *
111
+ * @returns The user information.
112
+ * @throws LogtoClientError if the user is not authenticated.
113
+ */
65
114
  async fetchUserInfo() {
66
115
  const { userinfoEndpoint } = await this.getOidcConfig();
67
116
  const accessToken = await this.getAccessToken();
@@ -70,6 +119,22 @@ class LogtoClient {
70
119
  }
71
120
  return fetchUserInfo(userinfoEndpoint, accessToken, this.adapter.requester);
72
121
  }
122
+ /**
123
+ * Start the sign-in flow with the specified redirect URI. The URI must be
124
+ * registered in the Logto Console.
125
+ *
126
+ * The user will be redirected to that URI after the sign-in flow is completed,
127
+ * and the client will be able to get the authorization code from the URI.
128
+ * To fetch the tokens from the authorization code, use {@link handleSignInCallback}
129
+ * after the user is redirected in the callback URI.
130
+ *
131
+ * @param redirectUri The redirect URI that the user will be redirected to after the sign-in flow is completed.
132
+ * @param interactionMode The interaction mode to be used for the authorization request. Note it's not
133
+ * a part of the OIDC standard, but a Logto-specific extension. Defaults to `signIn`.
134
+ *
135
+ * @see {@link https://docs.logto.io/docs/recipes/integrate-logto/vanilla-js/#sign-in | Sign in} for more information.
136
+ * @see {@link InteractionMode}
137
+ */
73
138
  async signIn(redirectUri, interactionMode) {
74
139
  const { appId: clientId, prompt, resources, scopes } = this.logtoConfig;
75
140
  const { authorizationEndpoint } = await this.getOidcConfig();
@@ -87,11 +152,21 @@ class LogtoClient {
87
152
  prompt,
88
153
  interactionMode,
89
154
  });
90
- await this.setSignInSession({ redirectUri, codeVerifier, state });
91
- await this.setRefreshToken(null);
92
- await this.setIdToken(null);
93
- this.adapter.navigate(signInUri);
155
+ await Promise.all([
156
+ this.setSignInSession({ redirectUri, codeVerifier, state }),
157
+ this.setRefreshToken(null),
158
+ this.setIdToken(null),
159
+ ]);
160
+ await this.adapter.navigate(signInUri);
94
161
  }
162
+ /**
163
+ * Check if the user is redirected from the sign-in page by checking if the
164
+ * current URL matches the redirect URI in the sign-in session.
165
+ *
166
+ * If there's no sign-in session, it will return `false`.
167
+ *
168
+ * @param url The current URL.
169
+ */
95
170
  async isSignInRedirected(url) {
96
171
  const signInSession = await this.getSignInSession();
97
172
  if (!signInSession) {
@@ -101,6 +176,15 @@ class LogtoClient {
101
176
  const { origin, pathname } = new URL(url);
102
177
  return `${origin}${pathname}` === redirectUri;
103
178
  }
179
+ /**
180
+ * Handle the sign-in callback by parsing the authorization code from the
181
+ * callback URI and exchanging it for the tokens.
182
+ *
183
+ * @param callbackUri The callback URI, including the search params, that the user is redirected to after the sign-in flow is completed.
184
+ * The origin and pathname of this URI must match the origin and pathname of the redirect URI specified in {@link signIn}.
185
+ * In many cases you'll probably end up passing `window.location.href` as the argument to this function.
186
+ * @throws LogtoClientError if the sign-in session is not found.
187
+ */
104
188
  async handleSignInCallback(callbackUri) {
105
189
  const { requester } = this.adapter;
106
190
  const signInSession = await this.getSignInSession();
@@ -136,6 +220,16 @@ class LogtoClient {
136
220
  await this.saveAccessTokenMap();
137
221
  await this.setSignInSession(null);
138
222
  }
223
+ /**
224
+ * Start the sign-out flow with the specified redirect URI. The URI must be
225
+ * registered in the Logto Console.
226
+ *
227
+ * It will also revoke all the tokens and clean up the storage.
228
+ *
229
+ * The user will be redirected that URI after the sign-out flow is completed.
230
+ * If the `postLogoutRedirectUri` is not specified, the user will be redirected
231
+ * to a default page.
232
+ */
139
233
  async signOut(postLogoutRedirectUri) {
140
234
  const { appId: clientId } = this.logtoConfig;
141
235
  const { endSessionEndpoint, revocationEndpoint } = await this.getOidcConfig();
@@ -154,10 +248,12 @@ class LogtoClient {
154
248
  clientId,
155
249
  });
156
250
  this.accessTokenMap.clear();
157
- await this.setRefreshToken(null);
158
- await this.setIdToken(null);
159
- await this.adapter.storage.removeItem('accessToken');
160
- this.adapter.navigate(url);
251
+ await Promise.all([
252
+ this.setRefreshToken(null),
253
+ this.setIdToken(null),
254
+ this.adapter.storage.removeItem('accessToken'),
255
+ ]);
256
+ await this.adapter.navigate(url);
161
257
  }
162
258
  async getSignInSession() {
163
259
  const jsonItem = await this.adapter.storage.getItem('signInSession');
@@ -204,7 +300,9 @@ class LogtoClient {
204
300
  expiresAt: requestedAt + expiresIn,
205
301
  });
206
302
  await this.saveAccessTokenMap();
207
- await this.setRefreshToken(refreshToken);
303
+ if (refreshToken) {
304
+ await this.setRefreshToken(refreshToken);
305
+ }
208
306
  if (idToken) {
209
307
  await this.verifyIdToken(idToken);
210
308
  await this.setIdToken(idToken);
@@ -1,15 +1,51 @@
1
1
  import type { Prompt } from '@logto/js';
2
+ /** The configuration object for the Logto client. */
2
3
  export type LogtoConfig = {
4
+ /**
5
+ * The endpoint for the Logto server, you can get it from the integration guide
6
+ * or the team settings page of the Logto Console.
7
+ *
8
+ * @example https://foo.logto.app
9
+ */
3
10
  endpoint: string;
11
+ /**
12
+ * The client ID of your application, you can get it from the integration guide
13
+ * or the application details page of the Logto Console.
14
+ */
4
15
  appId: string;
16
+ /**
17
+ * The client secret of your application, you can get it from the application
18
+ * details page of the Logto Console.
19
+ */
5
20
  appSecret?: string;
21
+ /**
22
+ * The scopes (permissions) that your application needs to access.
23
+ * Scopes that will be added by default: `openid`, `offline_access` and `profile`.
24
+ *
25
+ * If resources are specified, scopes will be applied to every resource.
26
+ *
27
+ * @see {@link https://docs.logto.io/docs/recipes/integrate-logto/vanilla-js/#fetch-user-information | Fetch user information}
28
+ * for more information of available scopes for user information.
29
+ */
6
30
  scopes?: string[];
31
+ /**
32
+ * The API resources that your application needs to access. You can specify
33
+ * multiple resources by providing an array of strings.
34
+ *
35
+ * @see {@link https://docs.logto.io/docs/recipes/rbac/ | RBAC} to learn more about how to use role-based access control (RBAC) to protect API resources.
36
+ */
7
37
  resources?: string[];
38
+ /**
39
+ * The prompt parameter to be used for the authorization request.
40
+ */
8
41
  prompt?: Prompt;
9
42
  };
10
43
  export type AccessToken = {
44
+ /** The access token string. */
11
45
  token: string;
46
+ /** The scopes that the access token is granted for. */
12
47
  scope: string;
48
+ /** The timestamp of the access token expiration. */
13
49
  expiresAt: number;
14
50
  };
15
51
  export declare const isLogtoSignInSessionItem: (data: unknown) => data is LogtoSignInSessionItem;
@@ -2,6 +2,13 @@
2
2
 
3
3
  var js = require('@logto/js');
4
4
 
5
+ /**
6
+ * A factory function that creates a requester by accepting a `fetch`-like function.
7
+ *
8
+ * @param fetchFunction A `fetch`-like function.
9
+ * @returns A requester function.
10
+ * @see {@link Requester}
11
+ */
5
12
  const createRequester = (fetchFunction) => {
6
13
  return async (...args) => {
7
14
  const response = await fetchFunction(...args);
@@ -1,2 +1,9 @@
1
1
  import type { Requester } from '@logto/js';
2
+ /**
3
+ * A factory function that creates a requester by accepting a `fetch`-like function.
4
+ *
5
+ * @param fetchFunction A `fetch`-like function.
6
+ * @returns A requester function.
7
+ * @see {@link Requester}
8
+ */
2
9
  export declare const createRequester: (fetchFunction: typeof fetch) => Requester;
@@ -1,5 +1,12 @@
1
1
  import { isLogtoRequestError, LogtoError, LogtoRequestError } from '@logto/js';
2
2
 
3
+ /**
4
+ * A factory function that creates a requester by accepting a `fetch`-like function.
5
+ *
6
+ * @param fetchFunction A `fetch`-like function.
7
+ * @returns A requester function.
8
+ * @see {@link Requester}
9
+ */
3
10
  const createRequester = (fetchFunction) => {
4
11
  return async (...args) => {
5
12
  const response = await fetchFunction(...args);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logto/client",
3
- "version": "2.2.1",
3
+ "version": "2.2.3",
4
4
  "type": "module",
5
5
  "main": "./lib/index.cjs",
6
6
  "module": "./lib/index.js",
@@ -21,7 +21,7 @@
21
21
  "directory": "packages/client"
22
22
  },
23
23
  "dependencies": {
24
- "@logto/js": "^2.1.0",
24
+ "@logto/js": "^2.1.3",
25
25
  "@silverhand/essentials": "^2.6.2",
26
26
  "camelcase-keys": "^7.0.1",
27
27
  "jose": "^4.13.2"
@@ -36,11 +36,11 @@
36
36
  "eslint": "^8.44.0",
37
37
  "jest": "^29.5.0",
38
38
  "jest-matcher-specific-error": "^1.0.0",
39
- "lint-staged": "^13.0.0",
39
+ "lint-staged": "^14.0.0",
40
40
  "nock": "^13.3.0",
41
41
  "prettier": "^3.0.0",
42
42
  "text-encoder": "^0.0.4",
43
- "type-fest": "^3.0.0",
43
+ "type-fest": "^4.0.0",
44
44
  "typescript": "^5.0.0"
45
45
  },
46
46
  "eslintConfig": {