@logto/client 2.4.0 → 2.5.1

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.
@@ -39,16 +39,17 @@ export type InferStorageKey<S> = S extends Storage<infer Key> ? Key : never;
39
39
  * @param url The URL to navigate to.
40
40
  * @param parameters The parameters for the navigation.
41
41
  * @param parameters.redirectUri The redirect URI that the user will be redirected to after the
42
- * flow is completed. That is, the "redirect URI" for sign-in and "post-logout redirect URI" for
43
- * sign-out.
44
- * @param parameters.for The purpose of the navigation. It can be either "sign-in" or "sign-out".
42
+ * flow is completed. That is, the "redirect URI" for "sign-in" and "post-logout redirect URI" for
43
+ * "sign-out". For the "post-sign-in" navigation, it should be ignored.
44
+ * @param parameters.for The purpose of the navigation. It can be either "sign-in", "sign-out", or
45
+ * "post-sign-in".
45
46
  * @remarks Usually, the `redirectUri` parameter can be ignored unless the client needs to pass the
46
47
  * redirect scheme or other parameters to the native app, such as `ASWebAuthenticationSession` in
47
48
  * iOS.
48
49
  */
49
50
  export type Navigate = (url: string, parameters: {
50
51
  redirectUri?: string;
51
- for: 'sign-in' | 'sign-out';
52
+ for: 'sign-in' | 'sign-out' | 'post-sign-in';
52
53
  }) => void | Promise<void>;
53
54
  export type JwtVerifier = {
54
55
  verifyIdToken(idToken: string): Promise<void>;
package/lib/client.cjs CHANGED
@@ -9,6 +9,7 @@ var memoize = require('./utils/memoize.cjs');
9
9
  var once = require('./utils/once.cjs');
10
10
  var types = require('./adapter/types.cjs');
11
11
 
12
+ /* eslint-disable max-lines */
12
13
  /**
13
14
  * The Logto base client class that provides the essential methods for
14
15
  * interacting with the Logto server.
@@ -139,23 +140,17 @@ class StandardLogtoClient {
139
140
  }
140
141
  return js.fetchUserInfo(userinfoEndpoint, accessToken, this.adapter.requester);
141
142
  }
142
- /**
143
- * Start the sign-in flow with the specified redirect URI. The URI must be
144
- * registered in the Logto Console.
145
- *
146
- * The user will be redirected to that URI after the sign-in flow is completed,
147
- * and the client will be able to get the authorization code from the URI.
148
- * To fetch the tokens from the authorization code, use {@link handleSignInCallback}
149
- * after the user is redirected in the callback URI.
150
- *
151
- * @param redirectUri The redirect URI that the user will be redirected to after the sign-in flow is completed.
152
- * @param interactionMode The interaction mode to be used for the authorization request. Note it's not
153
- * a part of the OIDC standard, but a Logto-specific extension. Defaults to `signIn`.
154
- *
155
- * @see {@link https://docs.logto.io/docs/recipes/integrate-logto/vanilla-js/#sign-in | Sign in} for more information.
156
- * @see {@link InteractionMode}
157
- */
158
- async signIn(redirectUri, interactionMode) {
143
+ async signIn(options, mode, hint) {
144
+ const { redirectUri: redirectUriUrl, postRedirectUri: postRedirectUriUrl, interactionMode, loginHint, } = typeof options === 'string' || options instanceof URL
145
+ ? {
146
+ redirectUri: options,
147
+ postRedirectUri: undefined,
148
+ interactionMode: mode,
149
+ loginHint: hint,
150
+ }
151
+ : options;
152
+ const redirectUri = redirectUriUrl.toString();
153
+ const postRedirectUri = postRedirectUriUrl?.toString();
159
154
  const { appId: clientId, prompt, resources, scopes } = this.logtoConfig;
160
155
  const { authorizationEndpoint } = await this.getOidcConfig();
161
156
  const [codeVerifier, state] = await Promise.all([
@@ -166,16 +161,17 @@ class StandardLogtoClient {
166
161
  const signInUri = js.generateSignInUri({
167
162
  authorizationEndpoint,
168
163
  clientId,
169
- redirectUri,
164
+ redirectUri: redirectUri.toString(),
170
165
  codeChallenge,
171
166
  state,
172
167
  scopes,
173
168
  resources,
174
169
  prompt,
175
170
  interactionMode,
171
+ loginHint,
176
172
  });
177
173
  await Promise.all([
178
- this.setSignInSession({ redirectUri, codeVerifier, state }),
174
+ this.setSignInSession({ redirectUri, postRedirectUri, codeVerifier, state }),
179
175
  this.setRefreshToken(null),
180
176
  this.setIdToken(null),
181
177
  ]);
@@ -349,7 +345,7 @@ class StandardLogtoClient {
349
345
  if (!signInSession) {
350
346
  throw new errors.LogtoClientError('sign_in_session.not_found');
351
347
  }
352
- const { redirectUri, state, codeVerifier } = signInSession;
348
+ const { redirectUri, postRedirectUri, state, codeVerifier } = signInSession;
353
349
  const code = js.verifyAndParseCodeFromCallbackUri(callbackUri, redirectUri, state);
354
350
  // NOTE: Will add scope to accessTokenKey when needed. (Linear issue LOG-1589)
355
351
  const accessTokenKey = index$2.buildAccessTokenKey();
@@ -377,7 +373,11 @@ class StandardLogtoClient {
377
373
  });
378
374
  await this.saveAccessTokenMap();
379
375
  await this.setSignInSession(null);
376
+ if (postRedirectUri) {
377
+ await this.adapter.navigate(postRedirectUri, { for: 'post-sign-in' });
378
+ }
380
379
  }
381
380
  }
381
+ /* eslint-enable max-lines */
382
382
 
383
383
  exports.StandardLogtoClient = StandardLogtoClient;
package/lib/client.d.ts CHANGED
@@ -2,6 +2,31 @@ import { type IdTokenClaims, type UserInfoResponse, type InteractionMode, type A
2
2
  import { type Nullable } from '@silverhand/essentials';
3
3
  import { ClientAdapterInstance, type ClientAdapter, type JwtVerifier } from './adapter/index.js';
4
4
  import type { AccessToken, LogtoConfig, LogtoSignInSessionItem } from './types/index.js';
5
+ export type SignInOptions = {
6
+ /**
7
+ * The redirect URI that the user will be redirected to after the sign-in flow is completed.
8
+ */
9
+ redirectUri: string | URL;
10
+ /**
11
+ * The URI that the user will be redirected to after `redirectUri` successfully handled the
12
+ * sign-in callback. If not specified, the user will stay on the `redirectUri` page.
13
+ */
14
+ postRedirectUri?: string | URL;
15
+ /**
16
+ * The interaction mode to be used for the authorization request. It determines the first page
17
+ * that the user will see in the sign-in flow.
18
+ *
19
+ * Note it's not a part of the OIDC standard, but a Logto-specific extension.
20
+ *
21
+ * @default InteractionMode.SignIn
22
+ * @see {@link InteractionMode}
23
+ */
24
+ interactionMode?: InteractionMode;
25
+ /**
26
+ * Login hint indicates the current user (usually an email address).
27
+ */
28
+ loginHint?: string;
29
+ };
5
30
  /**
6
31
  * The Logto base client class that provides the essential methods for
7
32
  * interacting with the Logto server.
@@ -106,6 +131,7 @@ export declare class StandardLogtoClient {
106
131
  * @throws LogtoClientError if the user is not authenticated.
107
132
  */
108
133
  fetchUserInfo(): Promise<UserInfoResponse>;
134
+ signIn(options: SignInOptions): Promise<void>;
109
135
  /**
110
136
  * Start the sign-in flow with the specified redirect URI. The URI must be
111
137
  * registered in the Logto Console.
@@ -115,14 +141,10 @@ export declare class StandardLogtoClient {
115
141
  * To fetch the tokens from the authorization code, use {@link handleSignInCallback}
116
142
  * after the user is redirected in the callback URI.
117
143
  *
118
- * @param redirectUri The redirect URI that the user will be redirected to after the sign-in flow is completed.
119
- * @param interactionMode The interaction mode to be used for the authorization request. Note it's not
120
- * a part of the OIDC standard, but a Logto-specific extension. Defaults to `signIn`.
121
- *
122
- * @see {@link https://docs.logto.io/docs/recipes/integrate-logto/vanilla-js/#sign-in | Sign in} for more information.
123
- * @see {@link InteractionMode}
144
+ * @param redirectUri See {@link SignInOptions.redirectUri}.
145
+ * @param interactionMode See {@link SignInOptions.interactionMode}.
124
146
  */
125
- signIn(redirectUri: string, interactionMode?: InteractionMode): Promise<void>;
147
+ signIn(redirectUri: SignInOptions['redirectUri'], interactionMode?: SignInOptions['interactionMode'], loginHint?: SignInOptions['loginHint']): Promise<void>;
126
148
  /**
127
149
  * Check if the user is redirected from the sign-in page by checking if the
128
150
  * current URL matches the redirect URI in the sign-in session.
package/lib/client.js CHANGED
@@ -7,6 +7,7 @@ import { memoize } from './utils/memoize.js';
7
7
  import { once } from './utils/once.js';
8
8
  import { PersistKey, CacheKey } from './adapter/types.js';
9
9
 
10
+ /* eslint-disable max-lines */
10
11
  /**
11
12
  * The Logto base client class that provides the essential methods for
12
13
  * interacting with the Logto server.
@@ -137,23 +138,17 @@ class StandardLogtoClient {
137
138
  }
138
139
  return fetchUserInfo(userinfoEndpoint, accessToken, this.adapter.requester);
139
140
  }
140
- /**
141
- * Start the sign-in flow with the specified redirect URI. The URI must be
142
- * registered in the Logto Console.
143
- *
144
- * The user will be redirected to that URI after the sign-in flow is completed,
145
- * and the client will be able to get the authorization code from the URI.
146
- * To fetch the tokens from the authorization code, use {@link handleSignInCallback}
147
- * after the user is redirected in the callback URI.
148
- *
149
- * @param redirectUri The redirect URI that the user will be redirected to after the sign-in flow is completed.
150
- * @param interactionMode The interaction mode to be used for the authorization request. Note it's not
151
- * a part of the OIDC standard, but a Logto-specific extension. Defaults to `signIn`.
152
- *
153
- * @see {@link https://docs.logto.io/docs/recipes/integrate-logto/vanilla-js/#sign-in | Sign in} for more information.
154
- * @see {@link InteractionMode}
155
- */
156
- async signIn(redirectUri, interactionMode) {
141
+ async signIn(options, mode, hint) {
142
+ const { redirectUri: redirectUriUrl, postRedirectUri: postRedirectUriUrl, interactionMode, loginHint, } = typeof options === 'string' || options instanceof URL
143
+ ? {
144
+ redirectUri: options,
145
+ postRedirectUri: undefined,
146
+ interactionMode: mode,
147
+ loginHint: hint,
148
+ }
149
+ : options;
150
+ const redirectUri = redirectUriUrl.toString();
151
+ const postRedirectUri = postRedirectUriUrl?.toString();
157
152
  const { appId: clientId, prompt, resources, scopes } = this.logtoConfig;
158
153
  const { authorizationEndpoint } = await this.getOidcConfig();
159
154
  const [codeVerifier, state] = await Promise.all([
@@ -164,16 +159,17 @@ class StandardLogtoClient {
164
159
  const signInUri = generateSignInUri({
165
160
  authorizationEndpoint,
166
161
  clientId,
167
- redirectUri,
162
+ redirectUri: redirectUri.toString(),
168
163
  codeChallenge,
169
164
  state,
170
165
  scopes,
171
166
  resources,
172
167
  prompt,
173
168
  interactionMode,
169
+ loginHint,
174
170
  });
175
171
  await Promise.all([
176
- this.setSignInSession({ redirectUri, codeVerifier, state }),
172
+ this.setSignInSession({ redirectUri, postRedirectUri, codeVerifier, state }),
177
173
  this.setRefreshToken(null),
178
174
  this.setIdToken(null),
179
175
  ]);
@@ -347,7 +343,7 @@ class StandardLogtoClient {
347
343
  if (!signInSession) {
348
344
  throw new LogtoClientError('sign_in_session.not_found');
349
345
  }
350
- const { redirectUri, state, codeVerifier } = signInSession;
346
+ const { redirectUri, postRedirectUri, state, codeVerifier } = signInSession;
351
347
  const code = verifyAndParseCodeFromCallbackUri(callbackUri, redirectUri, state);
352
348
  // NOTE: Will add scope to accessTokenKey when needed. (Linear issue LOG-1589)
353
349
  const accessTokenKey = buildAccessTokenKey();
@@ -375,7 +371,11 @@ class StandardLogtoClient {
375
371
  });
376
372
  await this.saveAccessTokenMap();
377
373
  await this.setSignInSession(null);
374
+ if (postRedirectUri) {
375
+ await this.adapter.navigate(postRedirectUri, { for: 'post-sign-in' });
376
+ }
378
377
  }
379
378
  }
379
+ /* eslint-enable max-lines */
380
380
 
381
381
  export { StandardLogtoClient };
package/lib/mock.d.ts CHANGED
@@ -26,9 +26,11 @@ export declare const postSignOutRedirectUri = "http://localhost:3000";
26
26
  export declare const mockCodeChallenge = "code_challenge_value";
27
27
  export declare const mockedCodeVerifier = "code_verifier_value";
28
28
  export declare const mockedState = "state_value";
29
+ export declare const mockedUserHint = "johndoe@example.com";
29
30
  export declare const mockedSignInUri: string;
30
31
  export declare const mockedSignInUriWithLoginPrompt: string;
31
32
  export declare const mockedSignUpUri: string;
33
+ export declare const mockedSignInUriWithLoginHint: string;
32
34
  export declare const accessToken = "access_token_value";
33
35
  export declare const refreshToken = "new_refresh_token_value";
34
36
  export declare const idToken = "id_token_value";
@@ -62,6 +62,7 @@ export declare const isLogtoSignInSessionItem: (data: unknown) => data is LogtoS
62
62
  export declare const isLogtoAccessTokenMap: (data: unknown) => data is Record<string, AccessToken>;
63
63
  export type LogtoSignInSessionItem = {
64
64
  redirectUri: string;
65
+ postRedirectUri?: string;
65
66
  codeVerifier: string;
66
67
  state: string;
67
68
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logto/client",
3
- "version": "2.4.0",
3
+ "version": "2.5.1",
4
4
  "type": "module",
5
5
  "main": "./lib/index.cjs",
6
6
  "module": "./lib/index.js",
@@ -29,7 +29,7 @@
29
29
  "directory": "packages/client"
30
30
  },
31
31
  "dependencies": {
32
- "@logto/js": "^4.0.0",
32
+ "@logto/js": "^4.0.1",
33
33
  "@silverhand/essentials": "^2.8.7",
34
34
  "camelcase-keys": "^7.0.1",
35
35
  "jose": "^5.2.2"