@account-kit/signer 4.6.1 → 4.8.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.
Files changed (55) hide show
  1. package/dist/esm/base.d.ts +2 -0
  2. package/dist/esm/base.js +91 -25
  3. package/dist/esm/base.js.map +1 -1
  4. package/dist/esm/client/base.d.ts +6 -2
  5. package/dist/esm/client/base.js.map +1 -1
  6. package/dist/esm/client/index.d.ts +31 -1
  7. package/dist/esm/client/index.js +41 -4
  8. package/dist/esm/client/index.js.map +1 -1
  9. package/dist/esm/client/types.d.ts +21 -1
  10. package/dist/esm/client/types.js.map +1 -1
  11. package/dist/esm/metrics.d.ts +1 -1
  12. package/dist/esm/metrics.js.map +1 -1
  13. package/dist/esm/session/manager.d.ts +7 -6
  14. package/dist/esm/session/manager.js +36 -15
  15. package/dist/esm/session/manager.js.map +1 -1
  16. package/dist/esm/session/types.d.ts +1 -1
  17. package/dist/esm/session/types.js.map +1 -1
  18. package/dist/esm/signer.d.ts +4 -0
  19. package/dist/esm/signer.js.map +1 -1
  20. package/dist/esm/types.d.ts +2 -1
  21. package/dist/esm/types.js +1 -0
  22. package/dist/esm/types.js.map +1 -1
  23. package/dist/esm/version.d.ts +1 -1
  24. package/dist/esm/version.js +1 -1
  25. package/dist/esm/version.js.map +1 -1
  26. package/dist/types/base.d.ts +2 -0
  27. package/dist/types/base.d.ts.map +1 -1
  28. package/dist/types/client/base.d.ts +6 -2
  29. package/dist/types/client/base.d.ts.map +1 -1
  30. package/dist/types/client/index.d.ts +31 -1
  31. package/dist/types/client/index.d.ts.map +1 -1
  32. package/dist/types/client/types.d.ts +21 -1
  33. package/dist/types/client/types.d.ts.map +1 -1
  34. package/dist/types/metrics.d.ts +1 -1
  35. package/dist/types/metrics.d.ts.map +1 -1
  36. package/dist/types/session/manager.d.ts +7 -6
  37. package/dist/types/session/manager.d.ts.map +1 -1
  38. package/dist/types/session/types.d.ts +1 -1
  39. package/dist/types/session/types.d.ts.map +1 -1
  40. package/dist/types/signer.d.ts +4 -0
  41. package/dist/types/signer.d.ts.map +1 -1
  42. package/dist/types/types.d.ts +2 -1
  43. package/dist/types/types.d.ts.map +1 -1
  44. package/dist/types/version.d.ts +1 -1
  45. package/package.json +4 -4
  46. package/src/base.ts +90 -30
  47. package/src/client/base.ts +6 -1
  48. package/src/client/index.ts +45 -4
  49. package/src/client/types.ts +21 -1
  50. package/src/metrics.ts +6 -1
  51. package/src/session/manager.ts +62 -29
  52. package/src/session/types.ts +1 -1
  53. package/src/signer.ts +10 -1
  54. package/src/types.ts +1 -0
  55. package/src/version.ts +1 -1
package/src/base.ts CHANGED
@@ -35,6 +35,7 @@ import {
35
35
  type ErrorInfo,
36
36
  } from "./types.js";
37
37
  import { assertNever } from "./utils/typeAssertions.js";
38
+ import type { SessionManagerEvents } from "./session/types";
38
39
 
39
40
  export interface BaseAlchemySignerParams<TClient extends BaseSignerClient> {
40
41
  client: TClient;
@@ -46,6 +47,7 @@ type AlchemySignerStore = {
46
47
  user: User | null;
47
48
  status: AlchemySignerStatus;
48
49
  error: ErrorInfo | null;
50
+ otpId?: string;
49
51
  isNewUser?: boolean;
50
52
  };
51
53
 
@@ -238,6 +240,8 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
238
240
  return this.authenticateWithOauth(params);
239
241
  case "oauthReturn":
240
242
  return this.handleOauthReturn(params);
243
+ case "otp":
244
+ return this.authenticateWithOtp(params);
241
245
  default:
242
246
  assertNever(type, `Unknown auth type: ${type}`);
243
247
  }
@@ -296,6 +300,12 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
296
300
  break;
297
301
  case "oauthReturn":
298
302
  break;
303
+ case "otp":
304
+ SignerLogger.trackEvent({
305
+ name: "signer_authnticate",
306
+ data: { authType: "otp" },
307
+ });
308
+ break;
299
309
  default:
300
310
  assertNever(type, `Unknown auth type: ${type}`);
301
311
  }
@@ -673,26 +683,30 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
673
683
  ): Promise<User> => {
674
684
  if ("email" in params) {
675
685
  const existingUser = await this.getUser(params.email);
676
- const expirationSeconds = Math.floor(
677
- this.sessionManager.expirationTimeMs / 1000
678
- );
686
+ const expirationSeconds = this.getExpirationSeconds();
679
687
 
680
- const { orgId } = existingUser
688
+ const { orgId, otpId } = existingUser
681
689
  ? await this.inner.initEmailAuth({
682
690
  email: params.email,
691
+ emailMode: params.emailMode,
683
692
  expirationSeconds,
684
693
  redirectParams: params.redirectParams,
685
694
  })
686
695
  : await this.inner.createAccount({
687
696
  type: "email",
688
697
  email: params.email,
698
+ emailMode: params.emailMode,
689
699
  expirationSeconds,
690
700
  redirectParams: params.redirectParams,
691
701
  });
692
702
 
693
- this.sessionManager.setTemporarySession({ orgId });
703
+ this.sessionManager.setTemporarySession({
704
+ orgId,
705
+ isNewUser: !existingUser,
706
+ });
694
707
  this.store.setState({
695
708
  status: AlchemySignerStatus.AWAITING_EMAIL_AUTH,
709
+ otpId,
696
710
  error: null,
697
711
  });
698
712
 
@@ -774,9 +788,7 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
774
788
  ): Promise<User> => {
775
789
  const params: OauthParams = {
776
790
  ...args,
777
- expirationSeconds: Math.floor(
778
- this.sessionManager.expirationTimeMs / 1000
779
- ),
791
+ expirationSeconds: this.getExpirationSeconds(),
780
792
  };
781
793
  if (params.mode === "redirect") {
782
794
  return this.inner.oauthWithRedirect(params);
@@ -785,6 +797,42 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
785
797
  }
786
798
  };
787
799
 
800
+ private authenticateWithOtp = async (
801
+ args: Extract<AuthParams, { type: "otp" }>
802
+ ): Promise<User> => {
803
+ const tempSession = this.sessionManager.getTemporarySession();
804
+ const { orgId, isNewUser } = tempSession ?? {};
805
+ const { otpId } = this.store.getState();
806
+ if (!orgId) {
807
+ throw new Error("orgId not found in session");
808
+ }
809
+ if (!otpId) {
810
+ throw new Error("otpId not found in session");
811
+ }
812
+ const { bundle } = await this.inner.submitOtpCode({
813
+ orgId,
814
+ otpId,
815
+ otpCode: args.otpCode,
816
+ expirationSeconds: this.getExpirationSeconds(),
817
+ });
818
+ const user = await this.inner.completeAuthWithBundle({
819
+ bundle,
820
+ orgId,
821
+ connectedEventName: "connectedOtp",
822
+ authenticatingType: "otp",
823
+ });
824
+
825
+ this.emitNewUserEvent(isNewUser);
826
+ if (tempSession) {
827
+ this.sessionManager.setTemporarySession({
828
+ ...tempSession,
829
+ isNewUser: false,
830
+ });
831
+ }
832
+
833
+ return user;
834
+ };
835
+
788
836
  private handleOauthReturn = ({
789
837
  bundle,
790
838
  orgId,
@@ -804,30 +852,39 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
804
852
  return user;
805
853
  };
806
854
 
807
- private registerListeners = () => {
808
- this.sessionManager.on("connected", (session) => {
809
- this.store.setState({
810
- user: session.user,
811
- status: AlchemySignerStatus.CONNECTED,
812
- error: null,
813
- });
814
- });
855
+ private getExpirationSeconds = () =>
856
+ Math.floor(this.sessionManager.expirationTimeMs / 1000);
815
857
 
816
- this.sessionManager.on("disconnected", () => {
817
- this.store.setState({
818
- user: null,
819
- status: AlchemySignerStatus.DISCONNECTED,
820
- });
821
- });
858
+ private registerListeners = () => {
859
+ // Declare listeners in an object to typecheck that every event type is
860
+ // handled.
861
+ const listeners: SessionManagerEvents = {
862
+ connected: (session) => {
863
+ this.store.setState({
864
+ user: session.user,
865
+ status: AlchemySignerStatus.CONNECTED,
866
+ error: null,
867
+ });
868
+ },
869
+ disconnected: () => {
870
+ this.store.setState({
871
+ user: null,
872
+ status: AlchemySignerStatus.DISCONNECTED,
873
+ });
874
+ },
875
+ initialized: () => {
876
+ this.store.setState((state) => ({
877
+ status: state.user
878
+ ? AlchemySignerStatus.CONNECTED
879
+ : AlchemySignerStatus.DISCONNECTED,
880
+ ...(state.user ? { error: null } : undefined),
881
+ }));
882
+ },
883
+ };
822
884
 
823
- this.sessionManager.on("initialized", () => {
824
- this.store.setState((state) => ({
825
- status: state.user
826
- ? AlchemySignerStatus.CONNECTED
827
- : AlchemySignerStatus.DISCONNECTED,
828
- ...(state.user ? { error: null } : undefined),
829
- }));
830
- });
885
+ for (const [event, listener] of Object.entries(listeners)) {
886
+ this.sessionManager.on(event as keyof SessionManagerEvents, listener);
887
+ }
831
888
 
832
889
  this.inner.on("authenticating", ({ type }) => {
833
890
  const status = (() => {
@@ -838,6 +895,9 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
838
895
  return AlchemySignerStatus.AUTHENTICATING_PASSKEY;
839
896
  case "oauth":
840
897
  return AlchemySignerStatus.AUTHENTICATING_OAUTH;
898
+ case "otp":
899
+ case "otpVerify":
900
+ return AlchemySignerStatus.AWAITING_OTP_AUTH;
841
901
  default:
842
902
  assertNever(type, "unhandled authenticating type");
843
903
  }
@@ -15,6 +15,7 @@ import type {
15
15
  GetWebAuthnAttestationResult,
16
16
  OauthConfig,
17
17
  OauthParams,
18
+ OtpParams,
18
19
  SignerBody,
19
20
  SignerResponse,
20
21
  SignerRoutes,
@@ -126,7 +127,7 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
126
127
 
127
128
  public abstract initEmailAuth(
128
129
  params: Omit<EmailAuthParams, "targetPublicKey">
129
- ): Promise<{ orgId: string }>;
130
+ ): Promise<{ orgId: string; otpId?: string }>;
130
131
 
131
132
  public abstract completeAuthWithBundle(params: {
132
133
  bundle: string;
@@ -144,6 +145,10 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
144
145
  args: Extract<OauthParams, { mode: "popup" }>
145
146
  ): Promise<User>;
146
147
 
148
+ public abstract submitOtpCode(
149
+ args: Omit<OtpParams, "targetPublicKey">
150
+ ): Promise<{ bundle: string }>;
151
+
147
152
  public abstract disconnect(): Promise<void>;
148
153
 
149
154
  public abstract exportWallet(params: TExportWalletParams): Promise<boolean>;
@@ -18,6 +18,7 @@ import type {
18
18
  ExportWalletParams,
19
19
  OauthConfig,
20
20
  OauthParams,
21
+ OtpParams,
21
22
  User,
22
23
  } from "./types.js";
23
24
 
@@ -138,12 +139,13 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
138
139
  */
139
140
  public override createAccount = async (params: CreateAccountParams) => {
140
141
  if (params.type === "email") {
141
- this.eventEmitter.emit("authenticating", { type: "email" });
142
- const { email, expirationSeconds } = params;
142
+ this.eventEmitter.emit("authenticating", { type: "otp" });
143
+ const { email, emailMode, expirationSeconds } = params;
143
144
  const publicKey = await this.initIframeStamper();
144
145
 
145
146
  const response = await this.request("/v1/signup", {
146
147
  email,
148
+ emailMode,
147
149
  targetPublicKey: publicKey,
148
150
  expirationSeconds,
149
151
  redirectParams: params.redirectParams?.toString(),
@@ -205,18 +207,57 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
205
207
  public override initEmailAuth = async (
206
208
  params: Omit<EmailAuthParams, "targetPublicKey">
207
209
  ) => {
208
- this.eventEmitter.emit("authenticating", { type: "email" });
209
- const { email, expirationSeconds } = params;
210
+ this.eventEmitter.emit("authenticating", { type: "otp" });
211
+ const { email, emailMode, expirationSeconds } = params;
210
212
  const publicKey = await this.initIframeStamper();
211
213
 
212
214
  return this.request("/v1/auth", {
213
215
  email,
216
+ emailMode,
214
217
  targetPublicKey: publicKey,
215
218
  expirationSeconds,
216
219
  redirectParams: params.redirectParams?.toString(),
217
220
  });
218
221
  };
219
222
 
223
+ /**
224
+ * Authenticates using an OTP code which was previously received via email.
225
+ *
226
+ * @example
227
+ * ```ts
228
+ * import { AlchemySignerWebClient } from "@account-kit/signer";
229
+ *
230
+ * const client = new AlchemySignerWebClient({
231
+ * connection: {
232
+ * apiKey: "your-api-key",
233
+ * },
234
+ * iframeConfig: {
235
+ * iframeContainerId: "signer-iframe-container",
236
+ * },
237
+ * });
238
+ *
239
+ * const account = await client.submitOtpCode({
240
+ * orgId: "user-org-id",
241
+ * otpId: "opt-returned-from-initEmailAuth",
242
+ * otpCode: "otp-code-from-email",
243
+ * });
244
+ * ```
245
+ *
246
+ * @param {Omit<OtpParams, "targetPublicKey">} args The parameters for the OTP request, excluding the target public key.
247
+ * @returns {Promise<{ bundle: string }>} A promise that resolves to an object containing the credential bundle.
248
+ */
249
+ public override async submitOtpCode(
250
+ args: Omit<OtpParams, "targetPublicKey">
251
+ ): Promise<{ bundle: string }> {
252
+ this.eventEmitter.emit("authenticating", { type: "otpVerify" });
253
+ const targetPublicKey = await this.initIframeStamper();
254
+ const { credentialBundle } = await this.request("/v1/otp", {
255
+ ...args,
256
+ targetPublicKey,
257
+ });
258
+ return { bundle: credentialBundle };
259
+ }
260
+
220
261
  /**
221
262
  * Completes auth for the user by injecting a credential bundle and retrieving
222
263
  * the user information based on the provided organization ID. Emits events
@@ -28,6 +28,7 @@ export type CreateAccountParams =
28
28
  | {
29
29
  type: "email";
30
30
  email: string;
31
+ emailMode?: EmailType;
31
32
  expirationSeconds?: number;
32
33
  redirectParams?: URLSearchParams;
33
34
  }
@@ -42,8 +43,11 @@ export type CreateAccountParams =
42
43
  creationOpts?: CredentialCreationOptionOverrides;
43
44
  };
44
45
 
46
+ export type EmailType = "magicLink" | "otp";
47
+
45
48
  export type EmailAuthParams = {
46
49
  email: string;
50
+ emailMode?: EmailType;
47
51
  expirationSeconds?: number;
48
52
  targetPublicKey: string;
49
53
  redirectParams?: URLSearchParams;
@@ -53,10 +57,19 @@ export type OauthParams = Extract<AuthParams, { type: "oauth" }> & {
53
57
  expirationSeconds?: number;
54
58
  };
55
59
 
60
+ export type OtpParams = {
61
+ orgId: string;
62
+ otpId: string;
63
+ otpCode: string;
64
+ targetPublicKey: string;
65
+ expirationSeconds?: number;
66
+ };
67
+
56
68
  export type SignupResponse = {
57
69
  orgId: string;
58
70
  userId?: string;
59
71
  address?: Address;
72
+ otpId?: string;
60
73
  };
61
74
 
62
75
  export type OauthConfig = {
@@ -107,6 +120,7 @@ export type SignerEndpoints = [
107
120
  Body: Omit<EmailAuthParams, "redirectParams"> & { redirectParams?: string };
108
121
  Response: {
109
122
  orgId: string;
123
+ otpId?: string;
110
124
  };
111
125
  },
112
126
  {
@@ -133,11 +147,16 @@ export type SignerEndpoints = [
133
147
  nonce: string;
134
148
  };
135
149
  Response: OauthConfig;
150
+ },
151
+ {
152
+ Route: "/v1/otp";
153
+ Body: OtpParams;
154
+ Response: { credentialBundle: string };
136
155
  }
137
156
  ];
138
157
 
139
158
  export type AuthenticatingEventMetadata = {
140
- type: "email" | "passkey" | "oauth";
159
+ type: "email" | "passkey" | "oauth" | "otp" | "otpVerify";
141
160
  };
142
161
 
143
162
  export type AlchemySignerClientEvents = {
@@ -147,6 +166,7 @@ export type AlchemySignerClientEvents = {
147
166
  connectedEmail(user: User, bundle: string): void;
148
167
  connectedPasskey(user: User): void;
149
168
  connectedOauth(user: User, bundle: string): void;
169
+ connectedOtp(user: User, bundle: string): void;
150
170
  disconnected(): void;
151
171
  };
152
172
 
package/src/metrics.ts CHANGED
@@ -6,7 +6,12 @@ export type SignerEventsSchema = [
6
6
  EventName: "signer_authnticate";
7
7
  EventData:
8
8
  | {
9
- authType: "email" | "passkey_anon" | "passkey_email" | "oauthReturn";
9
+ authType:
10
+ | "email"
11
+ | "passkey_anon"
12
+ | "passkey_email"
13
+ | "otp"
14
+ | "oauthReturn";
10
15
  provider?: never;
11
16
  }
12
17
  | { authType: "oauth"; provider: string };
@@ -7,7 +7,11 @@ import {
7
7
  } from "zustand/middleware";
8
8
  import { createStore, type Mutate, type StoreApi } from "zustand/vanilla";
9
9
  import type { BaseSignerClient } from "../client/base";
10
- import type { User } from "../client/types";
10
+ import type {
11
+ AlchemySignerClientEvent,
12
+ AlchemySignerClientEvents,
13
+ User,
14
+ } from "../client/types";
11
15
  import { assertNever } from "../utils/typeAssertions.js";
12
16
  import type { Session, SessionManagerEvents } from "./types";
13
17
 
@@ -39,6 +43,8 @@ type Store = Mutate<
39
43
  [["zustand/subscribeWithSelector", never], ["zustand/persist", SessionState]]
40
44
  >;
41
45
 
46
+ type TemporarySession = { orgId: string; isNewUser?: boolean };
47
+
42
48
  export class SessionManager {
43
49
  private sessionKey: string;
44
50
  private client: BaseSignerClient;
@@ -85,11 +91,18 @@ export class SessionManager {
85
91
 
86
92
  switch (existingSession.type) {
87
93
  case "email":
88
- case "oauth": {
89
- const connectedEventName =
90
- existingSession.type === "email"
91
- ? "connectedEmail"
92
- : "connectedOauth";
94
+ case "oauth":
95
+ case "otp": {
96
+ const connectedEventName = (() => {
97
+ switch (existingSession.type) {
98
+ case "email":
99
+ return "connectedEmail";
100
+ case "oauth":
101
+ return "connectedOauth";
102
+ case "otp":
103
+ return "connectedOtp";
104
+ }
105
+ })();
93
106
  const result = await this.client
94
107
  .completeAuthWithBundle({
95
108
  bundle: existingSession.bundle,
@@ -133,7 +146,7 @@ export class SessionManager {
133
146
  }
134
147
  };
135
148
 
136
- public setTemporarySession = (session: { orgId: string }) => {
149
+ public setTemporarySession = (session: TemporarySession) => {
137
150
  // temporary session must be placed in localStorage so that it can be accessed across tabs
138
151
  localStorage.setItem(
139
152
  `${this.sessionKey}:temporary`,
@@ -141,7 +154,7 @@ export class SessionManager {
141
154
  );
142
155
  };
143
156
 
144
- public getTemporarySession = (): { orgId: string } | null => {
157
+ public getTemporarySession = (): TemporarySession | null => {
145
158
  // temporary session must be placed in localStorage so that it can be accessed across tabs
146
159
  const sessionStr = localStorage.getItem(`${this.sessionKey}:temporary`);
147
160
 
@@ -187,7 +200,10 @@ export class SessionManager {
187
200
 
188
201
  private setSession = (
189
202
  session_:
190
- | Omit<Extract<Session, { type: "email" | "oauth" }>, "expirationDateMs">
203
+ | Omit<
204
+ Extract<Session, { type: "email" | "oauth" | "otp" }>,
205
+ "expirationDateMs"
206
+ >
191
207
  | Omit<Extract<Session, { type: "passkey" }>, "expirationDateMs">
192
208
  ) => {
193
209
  const session = {
@@ -230,28 +246,45 @@ export class SessionManager {
230
246
  }
231
247
  );
232
248
 
233
- this.client.on("disconnected", () => this.clearSession());
234
-
235
- this.client.on("connectedEmail", (user, bundle) =>
236
- this.setSessionWithUserAndBundle({ type: "email", user, bundle })
237
- );
249
+ // Helper type to ensure that a listener is either defined or explicitly
250
+ // omitted for every event type.
251
+ type Listeners = {
252
+ [K in keyof AlchemySignerClientEvents]:
253
+ | AlchemySignerClientEvents[K]
254
+ | undefined;
255
+ };
238
256
 
239
- this.client.on("connectedPasskey", (user) => {
240
- const existingSession = this.getSession();
241
- if (
242
- existingSession != null &&
243
- existingSession.type === "passkey" &&
244
- existingSession.user.userId === user.userId
245
- ) {
246
- return;
247
- }
257
+ const listeners: Listeners = {
258
+ connected: undefined,
259
+ newUserSignup: undefined,
260
+ authenticating: undefined,
261
+ connectedEmail: (user, bundle) =>
262
+ this.setSessionWithUserAndBundle({ type: "email", user, bundle }),
263
+ connectedPasskey: (user) => {
264
+ const existingSession = this.getSession();
265
+ if (
266
+ existingSession != null &&
267
+ existingSession.type === "passkey" &&
268
+ existingSession.user.userId === user.userId
269
+ ) {
270
+ return;
271
+ }
248
272
 
249
- this.setSession({ type: "passkey", user });
250
- });
273
+ this.setSession({ type: "passkey", user });
274
+ },
275
+ connectedOauth: (user, bundle) =>
276
+ this.setSessionWithUserAndBundle({ type: "oauth", user, bundle }),
277
+ connectedOtp: (user, bundle) => {
278
+ this.setSessionWithUserAndBundle({ type: "otp", user, bundle });
279
+ },
280
+ disconnected: () => this.clearSession(),
281
+ };
251
282
 
252
- this.client.on("connectedOauth", (user, bundle) =>
253
- this.setSessionWithUserAndBundle({ type: "oauth", user, bundle })
254
- );
283
+ for (const [event, listener] of Object.entries(listeners)) {
284
+ if (listener) {
285
+ this.client.on(event as AlchemySignerClientEvent, listener);
286
+ }
287
+ }
255
288
 
256
289
  // sync local state if persisted state has changed from another tab
257
290
  // only do this in the browser
@@ -295,7 +328,7 @@ export class SessionManager {
295
328
  user,
296
329
  bundle,
297
330
  }: {
298
- type: "email" | "oauth";
331
+ type: "email" | "oauth" | "otp";
299
332
  user: User;
300
333
  bundle: string;
301
334
  }) => {
@@ -2,7 +2,7 @@ import type { User } from "../client/types";
2
2
 
3
3
  export type Session =
4
4
  | {
5
- type: "email" | "oauth";
5
+ type: "email" | "oauth" | "otp";
6
6
  bundle: string;
7
7
  expirationDateMs: number;
8
8
  user: User;
package/src/signer.ts CHANGED
@@ -8,7 +8,12 @@ import type { CredentialCreationOptionOverrides } from "./client/types.js";
8
8
  import { SessionManagerParamsSchema } from "./session/manager.js";
9
9
 
10
10
  export type AuthParams =
11
- | { type: "email"; email: string; redirectParams?: URLSearchParams }
11
+ | {
12
+ type: "email";
13
+ email: string;
14
+ emailMode?: "magicLink" | "otp";
15
+ redirectParams?: URLSearchParams;
16
+ }
12
17
  | { type: "email"; bundle: string; orgId?: string; isNewUser?: boolean }
13
18
  | {
14
19
  type: "passkey";
@@ -37,6 +42,10 @@ export type AuthParams =
37
42
  orgId: string;
38
43
  idToken: string;
39
44
  isNewUser?: boolean;
45
+ }
46
+ | {
47
+ type: "otp";
48
+ otpCode: string;
40
49
  };
41
50
 
42
51
  export type OauthProviderConfig =
package/src/types.ts CHANGED
@@ -18,6 +18,7 @@ export enum AlchemySignerStatus {
18
18
  AUTHENTICATING_EMAIL = "AUTHENTICATING_EMAIL",
19
19
  AUTHENTICATING_OAUTH = "AUTHENTICATING_OAUTH",
20
20
  AWAITING_EMAIL_AUTH = "AWAITING_EMAIL_AUTH",
21
+ AWAITING_OTP_AUTH = "AWAITING_OTP_AUTH",
21
22
  }
22
23
 
23
24
  export interface ErrorInfo {
package/src/version.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  // This file is autogenerated by inject-version.ts. Any changes will be
2
2
  // overwritten on commit!
3
- export const VERSION = "4.6.1";
3
+ export const VERSION = "4.8.0";