@account-kit/signer 4.0.0-beta.8 → 4.0.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 (52) hide show
  1. package/dist/esm/base.d.ts +9 -4
  2. package/dist/esm/base.js +124 -30
  3. package/dist/esm/base.js.map +1 -1
  4. package/dist/esm/client/base.d.ts +7 -12
  5. package/dist/esm/client/base.js +11 -1
  6. package/dist/esm/client/base.js.map +1 -1
  7. package/dist/esm/client/index.d.ts +15 -6
  8. package/dist/esm/client/index.js +23 -12
  9. package/dist/esm/client/index.js.map +1 -1
  10. package/dist/esm/client/types.d.ts +6 -1
  11. package/dist/esm/client/types.js.map +1 -1
  12. package/dist/esm/metrics.d.ts +17 -0
  13. package/dist/esm/metrics.js +7 -0
  14. package/dist/esm/metrics.js.map +1 -0
  15. package/dist/esm/session/manager.js +1 -0
  16. package/dist/esm/session/manager.js.map +1 -1
  17. package/dist/esm/signer.d.ts +1 -0
  18. package/dist/esm/signer.js +46 -3
  19. package/dist/esm/signer.js.map +1 -1
  20. package/dist/esm/types.d.ts +8 -1
  21. package/dist/esm/types.js +3 -1
  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 +9 -4
  27. package/dist/types/base.d.ts.map +1 -1
  28. package/dist/types/client/base.d.ts +7 -12
  29. package/dist/types/client/base.d.ts.map +1 -1
  30. package/dist/types/client/index.d.ts +15 -6
  31. package/dist/types/client/index.d.ts.map +1 -1
  32. package/dist/types/client/types.d.ts +6 -1
  33. package/dist/types/client/types.d.ts.map +1 -1
  34. package/dist/types/metrics.d.ts +18 -0
  35. package/dist/types/metrics.d.ts.map +1 -0
  36. package/dist/types/session/manager.d.ts.map +1 -1
  37. package/dist/types/signer.d.ts +1 -0
  38. package/dist/types/signer.d.ts.map +1 -1
  39. package/dist/types/types.d.ts +8 -1
  40. package/dist/types/types.d.ts.map +1 -1
  41. package/dist/types/version.d.ts +1 -1
  42. package/dist/types/version.d.ts.map +1 -1
  43. package/package.json +6 -5
  44. package/src/base.ts +198 -62
  45. package/src/client/base.ts +19 -2
  46. package/src/client/index.ts +29 -11
  47. package/src/client/types.ts +7 -1
  48. package/src/metrics.ts +23 -0
  49. package/src/session/manager.ts +2 -1
  50. package/src/signer.ts +60 -5
  51. package/src/types.ts +9 -1
  52. package/src/version.ts +1 -1
package/src/base.ts CHANGED
@@ -4,10 +4,14 @@ import {
4
4
  hashTypedData,
5
5
  keccak256,
6
6
  serializeTransaction,
7
- type CustomSource,
7
+ type GetTransactionType,
8
8
  type Hex,
9
+ type IsNarrowable,
9
10
  type LocalAccount,
11
+ type SerializeTransactionFn,
10
12
  type SignableMessage,
13
+ type TransactionSerializable,
14
+ type TransactionSerialized,
11
15
  type TypedData,
12
16
  type TypedDataDefinition,
13
17
  } from "viem";
@@ -18,6 +22,7 @@ import { createStore } from "zustand/vanilla";
18
22
  import type { BaseSignerClient } from "./client/base";
19
23
  import type { OauthConfig, OauthParams, User } from "./client/types";
20
24
  import { NotAuthenticatedError } from "./errors.js";
25
+ import { SignerLogger } from "./metrics.js";
21
26
  import {
22
27
  SessionManager,
23
28
  type SessionManagerParams,
@@ -27,17 +32,20 @@ import {
27
32
  AlchemySignerStatus,
28
33
  type AlchemySignerEvent,
29
34
  type AlchemySignerEvents,
35
+ type ErrorInfo,
30
36
  } from "./types.js";
31
37
  import { assertNever } from "./utils/typeAssertions.js";
32
38
 
33
39
  export interface BaseAlchemySignerParams<TClient extends BaseSignerClient> {
34
40
  client: TClient;
35
41
  sessionConfig?: Omit<SessionManagerParams, "client">;
42
+ initialError?: ErrorInfo;
36
43
  }
37
44
 
38
45
  type AlchemySignerStore = {
39
46
  user: User | null;
40
47
  status: AlchemySignerStatus;
48
+ error: ErrorInfo | null;
41
49
  };
42
50
 
43
51
  type InternalStore = Mutate<
@@ -65,8 +73,13 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
65
73
  * @param {BaseAlchemySignerParams<TClient>} param0 Object containing the client and session configuration
66
74
  * @param {TClient} param0.client The client instance to be used internally
67
75
  * @param {SessionConfig} param0.sessionConfig Configuration for managing sessions
76
+ * @param {ErrorInfo | undefined} param0.initialError Error already present on the signer when initialized, if any
68
77
  */
69
- constructor({ client, sessionConfig }: BaseAlchemySignerParams<TClient>) {
78
+ constructor({
79
+ client,
80
+ sessionConfig,
81
+ initialError,
82
+ }: BaseAlchemySignerParams<TClient>) {
70
83
  this.inner = client;
71
84
  this.store = createStore(
72
85
  subscribeWithSelector(
@@ -74,6 +87,7 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
74
87
  ({
75
88
  user: null,
76
89
  status: AlchemySignerStatus.INITIALIZING,
90
+ error: initialError ?? null,
77
91
  } satisfies AlchemySignerStore)
78
92
  )
79
93
  );
@@ -84,15 +98,6 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
84
98
  ...sessionConfig,
85
99
  client: this.inner,
86
100
  });
87
- this.store = createStore(
88
- subscribeWithSelector(
89
- () =>
90
- ({
91
- user: null,
92
- status: AlchemySignerStatus.INITIALIZING,
93
- } satisfies AlchemySignerStore)
94
- )
95
- );
96
101
  // register listeners first
97
102
  this.registerListeners();
98
103
  // then initialize so that we can catch those events
@@ -138,8 +143,17 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
138
143
  listener as AlchemySignerEvents["statusChanged"],
139
144
  { fireImmediately: true }
140
145
  );
146
+ case "errorChanged":
147
+ return this.store.subscribe(
148
+ ({ error }) => error,
149
+ (error) =>
150
+ (listener as AlchemySignerEvents["errorChanged"])(
151
+ error ?? undefined
152
+ ),
153
+ { fireImmediately: true }
154
+ );
141
155
  default:
142
- throw new Error(`Unknown event type ${event}`);
156
+ assertNever(event, `Unknown event type ${event}`);
143
157
  }
144
158
  };
145
159
 
@@ -201,17 +215,78 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
201
215
  * @param {AuthParams} params - undefined if passkey login, otherwise an object with email and bundle to resolve
202
216
  * @returns {Promise<User>} the user that was authenticated
203
217
  */
204
- authenticate: (params: AuthParams) => Promise<User> = async (params) => {
218
+ authenticate: (params: AuthParams) => Promise<User> = SignerLogger.profiled(
219
+ "BaseAlchemySigner.authenticate",
220
+ async (params) => {
221
+ const { type } = params;
222
+ const result = (() => {
223
+ switch (type) {
224
+ case "email":
225
+ return this.authenticateWithEmail(params);
226
+ case "passkey":
227
+ return this.authenticateWithPasskey(params);
228
+ case "oauth":
229
+ return this.authenticateWithOauth(params);
230
+ case "oauthReturn":
231
+ return this.handleOauthReturn(params);
232
+ default:
233
+ assertNever(type, `Unknown auth type: ${type}`);
234
+ }
235
+ })();
236
+
237
+ this.trackAuthenticateType(params);
238
+
239
+ return result.catch((error) => {
240
+ /**
241
+ * 2 things going on here:
242
+ * 1. for oauth flows we expect the status to remain in authenticating
243
+ * 2. we do the ternary, because if we explicitly pass in `undefined` for the status, zustand will set the value of status to `undefined`.
244
+ * However, if we omit it, then it will not override the current value of status.
245
+ */
246
+ this.store.setState({
247
+ error: toErrorInfo(error),
248
+ ...(type === "oauthReturn" || type === "oauth"
249
+ ? {}
250
+ : { status: AlchemySignerStatus.DISCONNECTED }),
251
+ });
252
+ throw error;
253
+ });
254
+ }
255
+ );
256
+
257
+ private trackAuthenticateType = (params: AuthParams) => {
205
258
  const { type } = params;
206
259
  switch (type) {
207
- case "email":
208
- return this.authenticateWithEmail(params);
209
- case "passkey":
210
- return this.authenticateWithPasskey(params);
260
+ case "email": {
261
+ // we just want to track the start of email auth
262
+ if ("bundle" in params) return;
263
+ SignerLogger.trackEvent({
264
+ name: "signer_authnticate",
265
+ data: { authType: "email" },
266
+ });
267
+ return;
268
+ }
269
+ case "passkey": {
270
+ const isAnon = !("email" in params) && params.createNew == null;
271
+ SignerLogger.trackEvent({
272
+ name: "signer_authnticate",
273
+ data: {
274
+ authType: isAnon ? "passkey_anon" : "passkey_email",
275
+ },
276
+ });
277
+ return;
278
+ }
211
279
  case "oauth":
212
- return this.authenticateWithOauth(params);
280
+ SignerLogger.trackEvent({
281
+ name: "signer_authnticate",
282
+ data: {
283
+ authType: "oauth",
284
+ provider: params.authProviderId,
285
+ },
286
+ });
287
+ break;
213
288
  case "oauthReturn":
214
- return this.handleOauthReturn(params);
289
+ break;
215
290
  default:
216
291
  assertNever(type, `Unknown auth type: ${type}`);
217
292
  }
@@ -285,11 +360,14 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
285
360
  *
286
361
  * @returns {Promise<string>} A promise that resolves to the address of the current user.
287
362
  */
288
- getAddress: () => Promise<`0x${string}`> = async () => {
289
- const { address } = await this.inner.whoami();
363
+ getAddress: () => Promise<`0x${string}`> = SignerLogger.profiled(
364
+ "BaseAlchemySigner.getAddress",
365
+ async () => {
366
+ const { address } = await this.inner.whoami();
290
367
 
291
- return address;
292
- };
368
+ return address;
369
+ }
370
+ );
293
371
 
294
372
  /**
295
373
  * Signs a raw message after hashing it.
@@ -315,13 +393,18 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
315
393
  * @param {string} msg the message to be hashed and then signed
316
394
  * @returns {Promise<string>} a promise that resolves to the signed message
317
395
  */
318
- signMessage: (msg: SignableMessage) => Promise<`0x${string}`> = async (
319
- msg
320
- ) => {
321
- const messageHash = hashMessage(msg);
396
+ signMessage: (msg: SignableMessage) => Promise<`0x${string}`> =
397
+ SignerLogger.profiled("BaseAlchemySigner.signMessage", async (msg) => {
398
+ const messageHash = hashMessage(msg);
322
399
 
323
- return this.inner.signRawMessage(messageHash);
324
- };
400
+ const result = await this.inner.signRawMessage(messageHash);
401
+
402
+ SignerLogger.trackEvent({
403
+ name: "signer_sign_message",
404
+ });
405
+
406
+ return result;
407
+ });
325
408
 
326
409
  /**
327
410
  * Signs a typed message by first hashing it and then signing the hashed message using the `signRawMessage` method.
@@ -357,11 +440,14 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
357
440
  TPrimaryType extends keyof TTypedData | "EIP712Domain" = keyof TTypedData
358
441
  >(
359
442
  params: TypedDataDefinition<TTypedData, TPrimaryType>
360
- ) => Promise<Hex> = async (params) => {
361
- const messageHash = hashTypedData(params);
443
+ ) => Promise<Hex> = SignerLogger.profiled(
444
+ "BaseAlchemySigner.signTypedData",
445
+ async (params) => {
446
+ const messageHash = hashTypedData(params);
362
447
 
363
- return this.inner.signRawMessage(messageHash);
364
- };
448
+ return this.inner.signRawMessage(messageHash);
449
+ }
450
+ );
365
451
 
366
452
  /**
367
453
  * Serializes a transaction, signs it with a raw message, and then returns the serialized transaction with the signature.
@@ -393,21 +479,41 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
393
479
  * @param {() => Hex} [args.serializer] an optional serializer function. If not provided, the default `serializeTransaction` function will be used
394
480
  * @returns {Promise<string>} a promise that resolves to the serialized transaction with the signature
395
481
  */
396
- signTransaction: CustomSource["signTransaction"] = async (tx, args) => {
397
- const serializeFn = args?.serializer ?? serializeTransaction;
398
- const serializedTx = serializeFn(tx);
399
- const signatureHex = await this.inner.signRawMessage(
400
- keccak256(serializedTx)
401
- );
482
+ signTransaction: <
483
+ serializer extends SerializeTransactionFn<TransactionSerializable> = SerializeTransactionFn<TransactionSerializable>,
484
+ transaction extends Parameters<serializer>[0] = Parameters<serializer>[0]
485
+ >(
486
+ transaction: transaction,
487
+ options?:
488
+ | {
489
+ serializer?: serializer | undefined;
490
+ }
491
+ | undefined
492
+ ) => Promise<
493
+ IsNarrowable<
494
+ TransactionSerialized<GetTransactionType<transaction>>,
495
+ Hex
496
+ > extends true
497
+ ? TransactionSerialized<GetTransactionType<transaction>>
498
+ : Hex
499
+ > = SignerLogger.profiled(
500
+ "BaseAlchemySigner.signTransaction",
501
+ async (tx, args) => {
502
+ const serializeFn = args?.serializer ?? serializeTransaction;
503
+ const serializedTx = serializeFn(tx);
504
+ const signatureHex = await this.inner.signRawMessage(
505
+ keccak256(serializedTx)
506
+ );
402
507
 
403
- const signature = {
404
- r: takeBytes(signatureHex, { count: 32 }),
405
- s: takeBytes(signatureHex, { count: 32, offset: 32 }),
406
- v: BigInt(takeBytes(signatureHex, { count: 1, offset: 64 })),
407
- };
508
+ const signature = {
509
+ r: takeBytes(signatureHex, { count: 32 }),
510
+ s: takeBytes(signatureHex, { count: 32, offset: 32 }),
511
+ v: BigInt(takeBytes(signatureHex, { count: 1, offset: 64 })),
512
+ };
408
513
 
409
- return serializeFn(tx, signature);
410
- };
514
+ return serializeFn(tx, signature);
515
+ }
516
+ );
411
517
 
412
518
  /**
413
519
  * Unauthenticated call to look up a user's organizationId by email
@@ -433,19 +539,18 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
433
539
  * @param {string} email the email to lookup
434
540
  * @returns {Promise<{orgId: string}>} the organization id for the user if they exist
435
541
  */
436
- getUser: (email: string) => Promise<{ orgId: string } | null> = async (
437
- email
438
- ) => {
439
- const result = await this.inner.lookupUserByEmail(email);
542
+ getUser: (email: string) => Promise<{ orgId: string } | null> =
543
+ SignerLogger.profiled("BaseAlchemySigner.getUser", async (email) => {
544
+ const result = await this.inner.lookupUserByEmail(email);
440
545
 
441
- if (result.orgId == null) {
442
- return null;
443
- }
546
+ if (result.orgId == null) {
547
+ return null;
548
+ }
444
549
 
445
- return {
446
- orgId: result.orgId,
447
- };
448
- };
550
+ return {
551
+ orgId: result.orgId,
552
+ };
553
+ });
449
554
 
450
555
  /**
451
556
  * Adds a passkey to the user's account
@@ -472,9 +577,9 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
472
577
  * @returns {Promise<string[]>} an array of the authenticator ids added to the user
473
578
  */
474
579
  addPasskey: (params?: CredentialCreationOptions) => Promise<string[]> =
475
- async (params) => {
580
+ SignerLogger.profiled("BaseAlchemySigner.addPasskey", async (params) => {
476
581
  return this.inner.addPasskey(params ?? {});
477
- };
582
+ });
478
583
 
479
584
  /**
480
585
  * Used to export the wallet for a given user
@@ -577,7 +682,10 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
577
682
  });
578
683
 
579
684
  this.sessionManager.setTemporarySession({ orgId });
580
- this.store.setState({ status: AlchemySignerStatus.AWAITING_EMAIL_AUTH });
685
+ this.store.setState({
686
+ status: AlchemySignerStatus.AWAITING_EMAIL_AUTH,
687
+ error: null,
688
+ });
581
689
 
582
690
  // We wait for the session manager to emit a connected event if
583
691
  // cross tab sessions are permitted
@@ -604,6 +712,7 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
604
712
  bundle: params.bundle,
605
713
  orgId: temporarySession.orgId,
606
714
  connectedEventName: "connectedEmail",
715
+ authenticatingType: "email",
607
716
  });
608
717
 
609
718
  return user;
@@ -667,11 +776,14 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
667
776
  private handleOauthReturn = ({
668
777
  bundle,
669
778
  orgId,
779
+ idToken,
670
780
  }: Extract<AuthParams, { type: "oauthReturn" }>): Promise<User> =>
671
781
  this.inner.completeAuthWithBundle({
672
782
  bundle,
673
783
  orgId,
674
784
  connectedEventName: "connectedOauth",
785
+ authenticatingType: "oauth",
786
+ idToken,
675
787
  });
676
788
 
677
789
  private registerListeners = () => {
@@ -679,6 +791,7 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
679
791
  this.store.setState({
680
792
  user: session.user,
681
793
  status: AlchemySignerStatus.CONNECTED,
794
+ error: null,
682
795
  });
683
796
  });
684
797
 
@@ -694,11 +807,34 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
694
807
  status: state.user
695
808
  ? AlchemySignerStatus.CONNECTED
696
809
  : AlchemySignerStatus.DISCONNECTED,
810
+ ...(state.user ? { error: null } : undefined),
697
811
  }));
698
812
  });
699
813
 
700
- this.inner.on("authenticating", () => {
701
- this.store.setState({ status: AlchemySignerStatus.AUTHENTICATING });
814
+ this.inner.on("authenticating", ({ type }) => {
815
+ const status = (() => {
816
+ switch (type) {
817
+ case "email":
818
+ return AlchemySignerStatus.AUTHENTICATING_EMAIL;
819
+ case "passkey":
820
+ return AlchemySignerStatus.AUTHENTICATING_PASSKEY;
821
+ case "oauth":
822
+ return AlchemySignerStatus.AUTHENTICATING_OAUTH;
823
+ default:
824
+ assertNever(type, "unhandled authenticating type");
825
+ }
826
+ })();
827
+
828
+ this.store.setState({
829
+ status,
830
+ error: null,
831
+ });
702
832
  });
703
833
  };
704
834
  }
835
+
836
+ function toErrorInfo(error: unknown): ErrorInfo {
837
+ return error instanceof Error
838
+ ? { name: error.name, message: error.message }
839
+ : { name: "Error", message: "Unknown error" };
840
+ }
@@ -1,12 +1,15 @@
1
1
  import { ConnectionConfigSchema, type ConnectionConfig } from "@aa-sdk/core";
2
2
  import { TurnkeyClient, type TSignedRequest } from "@turnkey/http";
3
3
  import EventEmitter from "eventemitter3";
4
+ import { jwtDecode } from "jwt-decode";
4
5
  import type { Hex } from "viem";
5
6
  import { NotAuthenticatedError } from "../errors.js";
6
7
  import { base64UrlEncode } from "../utils/base64UrlEncode.js";
8
+ import { assertNever } from "../utils/typeAssertions.js";
7
9
  import type {
8
10
  AlchemySignerClientEvent,
9
11
  AlchemySignerClientEvents,
12
+ AuthenticatingEventMetadata,
10
13
  CreateAccountParams,
11
14
  EmailAuthParams,
12
15
  GetWebAuthnAttestationResult,
@@ -18,7 +21,6 @@ import type {
18
21
  SignupResponse,
19
22
  User,
20
23
  } from "./types.js";
21
- import { assertNever } from "../utils/typeAssertions.js";
22
24
 
23
25
  export interface BaseSignerClientParams {
24
26
  stamper: TurnkeyClient["stamper"];
@@ -130,6 +132,8 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
130
132
  bundle: string;
131
133
  orgId: string;
132
134
  connectedEventName: keyof AlchemySignerClientEvents;
135
+ authenticatingType: AuthenticatingEventMetadata["type"];
136
+ idToken?: string;
133
137
  }): Promise<User>;
134
138
 
135
139
  public abstract oauthWithRedirect(
@@ -217,10 +221,14 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
217
221
  * Retrieves the current user or fetches the user information if not already available.
218
222
  *
219
223
  * @param {string} [orgId] optional organization ID, defaults to the user's organization ID
224
+ * @param {string} idToken an OIDC ID token containing additional user information
220
225
  * @returns {Promise<User>} A promise that resolves to the user object
221
226
  * @throws {Error} if no organization ID is provided when there is no current user
222
227
  */
223
- public whoami = async (orgId = this.user?.orgId): Promise<User> => {
228
+ public whoami = async (
229
+ orgId = this.user?.orgId,
230
+ idToken?: string
231
+ ): Promise<User> => {
224
232
  if (this.user) {
225
233
  return this.user;
226
234
  }
@@ -237,6 +245,15 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
237
245
  stampedRequest,
238
246
  });
239
247
 
248
+ if (idToken) {
249
+ const claims: Record<string, unknown> = jwtDecode(idToken);
250
+ user.idToken = idToken;
251
+ user.claims = claims;
252
+ if (typeof claims.email === "string") {
253
+ user.email = claims.email;
254
+ }
255
+ }
256
+
240
257
  const credentialId = (() => {
241
258
  try {
242
259
  return JSON.parse(stampedRequest?.stamp.stampHeaderValue)
@@ -3,11 +3,14 @@ import { getWebAuthnAttestation } from "@turnkey/http";
3
3
  import { IframeStamper } from "@turnkey/iframe-stamper";
4
4
  import { WebauthnStamper } from "@turnkey/webauthn-stamper";
5
5
  import { z } from "zod";
6
+ import { getDefaultScopeAndClaims, getOauthNonce } from "../oauth.js";
7
+ import type { AuthParams, OauthMode } from "../signer.js";
6
8
  import { base64UrlEncode } from "../utils/base64UrlEncode.js";
7
9
  import { generateRandomBuffer } from "../utils/generateRandomBuffer.js";
8
10
  import { BaseSignerClient } from "./base.js";
9
11
  import type {
10
12
  AlchemySignerClientEvents,
13
+ AuthenticatingEventMetadata,
11
14
  CreateAccountParams,
12
15
  CredentialCreationOptionOverrides,
13
16
  EmailAuthParams,
@@ -16,8 +19,6 @@ import type {
16
19
  OauthParams,
17
20
  User,
18
21
  } from "./types.js";
19
- import { getDefaultScopeAndClaims, getOauthNonce } from "../oauth.js";
20
- import type { AuthParams, OauthMode } from "../signer.js";
21
22
 
22
23
  const CHECK_CLOSE_INTERVAL = 500;
23
24
 
@@ -135,8 +136,8 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
135
136
  * @returns {Promise<SignupResponse>} A promise that resolves with the response object containing the account creation result.
136
137
  */
137
138
  public override createAccount = async (params: CreateAccountParams) => {
138
- this.eventEmitter.emit("authenticating");
139
139
  if (params.type === "email") {
140
+ this.eventEmitter.emit("authenticating", { type: "email" });
140
141
  const { email, expirationSeconds } = params;
141
142
  const publicKey = await this.initIframeStamper();
142
143
 
@@ -150,6 +151,7 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
150
151
  return response;
151
152
  }
152
153
 
154
+ this.eventEmitter.emit("authenticating", { type: "passkey" });
153
155
  // Passkey account creation flow
154
156
  const { attestation, challenge } = await this.getWebAuthnAttestation(
155
157
  params.creationOpts,
@@ -202,7 +204,7 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
202
204
  public override initEmailAuth = async (
203
205
  params: Omit<EmailAuthParams, "targetPublicKey">
204
206
  ) => {
205
- this.eventEmitter.emit("authenticating");
207
+ this.eventEmitter.emit("authenticating", { type: "email" });
206
208
  const { email, expirationSeconds } = params;
207
209
  const publicKey = await this.initIframeStamper();
208
210
 
@@ -215,7 +217,9 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
215
217
  };
216
218
 
217
219
  /**
218
- * Completes auth for the user by injecting a credential bundle and retrieving the user information based on the provided organization ID. Emits events during the process.
220
+ * Completes auth for the user by injecting a credential bundle and retrieving
221
+ * the user information based on the provided organization ID. Emits events
222
+ * during the process.
219
223
  *
220
224
  * @example
221
225
  * ```ts
@@ -233,19 +237,28 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
233
237
  * const account = await client.completeAuthWithBundle({ orgId: "user-org-id", bundle: "bundle-from-email", connectedEventName: "connectedEmail" });
234
238
  * ```
235
239
  *
236
- * @param {{ bundle: string; orgId: string }} config The configuration object for the authentication function containing the credential bundle to inject and the organization id associated with the user
237
- * @returns {Promise<User>} A promise that resolves to the authenticated user information
240
+ * @param {{ bundle: string; orgId: string, connectedEventName: keyof AlchemySignerClientEvents, idToken?: string }} config
241
+ * The configuration object for the authentication function containing the
242
+ * credential bundle to inject and the organization id associated with the
243
+ * user, as well as the event to be emitted on success and optionally an OIDC
244
+ * ID token with extra user information
245
+ * @returns {Promise<User>} A promise that resolves to the authenticated user
246
+ * information
238
247
  */
239
248
  public override completeAuthWithBundle = async ({
240
249
  bundle,
241
250
  orgId,
242
251
  connectedEventName,
252
+ idToken,
253
+ authenticatingType,
243
254
  }: {
244
255
  bundle: string;
245
256
  orgId: string;
246
257
  connectedEventName: keyof AlchemySignerClientEvents;
258
+ authenticatingType: AuthenticatingEventMetadata["type"];
259
+ idToken?: string;
247
260
  }): Promise<User> => {
248
- this.eventEmitter.emit("authenticating");
261
+ this.eventEmitter.emit("authenticating", { type: authenticatingType });
249
262
  await this.initIframeStamper();
250
263
 
251
264
  const result = await this.iframeStamper.injectCredentialBundle(bundle);
@@ -254,7 +267,8 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
254
267
  throw new Error("Failed to inject credential bundle");
255
268
  }
256
269
 
257
- const user = await this.whoami(orgId);
270
+ const user = await this.whoami(orgId, idToken);
271
+
258
272
  this.eventEmitter.emit(connectedEventName, user, bundle);
259
273
 
260
274
  return user;
@@ -285,7 +299,7 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
285
299
  public override lookupUserWithPasskey = async (
286
300
  user: User | undefined = undefined
287
301
  ) => {
288
- this.eventEmitter.emit("authenticating");
302
+ this.eventEmitter.emit("authenticating", { type: "passkey" });
289
303
  await this.initWebauthnStamper(user);
290
304
  if (user) {
291
305
  this.user = user;
@@ -372,6 +386,7 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
372
386
  public override disconnect = async () => {
373
387
  this.user = undefined;
374
388
  this.iframeStamper.clear();
389
+ await this.iframeStamper.init();
375
390
  };
376
391
 
377
392
  /**
@@ -454,15 +469,18 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
454
469
  const {
455
470
  alchemyBundle: bundle,
456
471
  alchemyOrgId: orgId,
472
+ alchemyIdToken: idToken,
457
473
  alchemyError,
458
474
  } = event.data;
459
- if (bundle && orgId) {
475
+ if (bundle && orgId && idToken) {
460
476
  cleanup();
461
477
  popup?.close();
462
478
  this.completeAuthWithBundle({
463
479
  bundle,
464
480
  orgId,
465
481
  connectedEventName: "connectedOauth",
482
+ idToken,
483
+ authenticatingType: "oauth",
466
484
  }).then(resolve, reject);
467
485
  } else if (alchemyError) {
468
486
  cleanup();
@@ -14,6 +14,8 @@ export type User = {
14
14
  userId: string;
15
15
  address: Address;
16
16
  credentialId?: string;
17
+ idToken?: string;
18
+ claims?: Record<string, unknown>;
17
19
  };
18
20
  // [!endregion User]
19
21
 
@@ -134,9 +136,13 @@ export type SignerEndpoints = [
134
136
  }
135
137
  ];
136
138
 
139
+ export type AuthenticatingEventMetadata = {
140
+ type: "email" | "passkey" | "oauth";
141
+ };
142
+
137
143
  export type AlchemySignerClientEvents = {
138
144
  connected(user: User): void;
139
- authenticating(): void;
145
+ authenticating(data: AuthenticatingEventMetadata): void;
140
146
  connectedEmail(user: User, bundle: string): void;
141
147
  connectedPasskey(user: User): void;
142
148
  connectedOauth(user: User, bundle: string): void;
package/src/metrics.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { createLogger } from "@account-kit/logging";
2
+ import { VERSION } from "./version.js";
3
+
4
+ export type SignerEventsSchema = [
5
+ {
6
+ EventName: "signer_authnticate";
7
+ EventData:
8
+ | {
9
+ authType: "email" | "passkey_anon" | "passkey_email" | "oauthReturn";
10
+ provider?: never;
11
+ }
12
+ | { authType: "oauth"; provider: string };
13
+ },
14
+ {
15
+ EventName: "signer_sign_message";
16
+ EventData: undefined;
17
+ }
18
+ ];
19
+
20
+ export const SignerLogger = createLogger<SignerEventsSchema>({
21
+ package: "@account-kit/signer",
22
+ version: VERSION,
23
+ });