@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.
- package/dist/esm/base.d.ts +9 -4
- package/dist/esm/base.js +124 -30
- package/dist/esm/base.js.map +1 -1
- package/dist/esm/client/base.d.ts +7 -12
- package/dist/esm/client/base.js +11 -1
- package/dist/esm/client/base.js.map +1 -1
- package/dist/esm/client/index.d.ts +15 -6
- package/dist/esm/client/index.js +23 -12
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/client/types.d.ts +6 -1
- package/dist/esm/client/types.js.map +1 -1
- package/dist/esm/metrics.d.ts +17 -0
- package/dist/esm/metrics.js +7 -0
- package/dist/esm/metrics.js.map +1 -0
- package/dist/esm/session/manager.js +1 -0
- package/dist/esm/session/manager.js.map +1 -1
- package/dist/esm/signer.d.ts +1 -0
- package/dist/esm/signer.js +46 -3
- package/dist/esm/signer.js.map +1 -1
- package/dist/esm/types.d.ts +8 -1
- package/dist/esm/types.js +3 -1
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/version.js.map +1 -1
- package/dist/types/base.d.ts +9 -4
- package/dist/types/base.d.ts.map +1 -1
- package/dist/types/client/base.d.ts +7 -12
- package/dist/types/client/base.d.ts.map +1 -1
- package/dist/types/client/index.d.ts +15 -6
- package/dist/types/client/index.d.ts.map +1 -1
- package/dist/types/client/types.d.ts +6 -1
- package/dist/types/client/types.d.ts.map +1 -1
- package/dist/types/metrics.d.ts +18 -0
- package/dist/types/metrics.d.ts.map +1 -0
- package/dist/types/session/manager.d.ts.map +1 -1
- package/dist/types/signer.d.ts +1 -0
- package/dist/types/signer.d.ts.map +1 -1
- package/dist/types/types.d.ts +8 -1
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/version.d.ts +1 -1
- package/dist/types/version.d.ts.map +1 -1
- package/package.json +6 -5
- package/src/base.ts +198 -62
- package/src/client/base.ts +19 -2
- package/src/client/index.ts +29 -11
- package/src/client/types.ts +7 -1
- package/src/metrics.ts +23 -0
- package/src/session/manager.ts +2 -1
- package/src/signer.ts +60 -5
- package/src/types.ts +9 -1
- 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
|
|
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({
|
|
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
|
-
|
|
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> =
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}`> =
|
|
289
|
-
|
|
363
|
+
getAddress: () => Promise<`0x${string}`> = SignerLogger.profiled(
|
|
364
|
+
"BaseAlchemySigner.getAddress",
|
|
365
|
+
async () => {
|
|
366
|
+
const { address } = await this.inner.whoami();
|
|
290
367
|
|
|
291
|
-
|
|
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}`> =
|
|
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
|
-
|
|
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> =
|
|
361
|
-
|
|
443
|
+
) => Promise<Hex> = SignerLogger.profiled(
|
|
444
|
+
"BaseAlchemySigner.signTypedData",
|
|
445
|
+
async (params) => {
|
|
446
|
+
const messageHash = hashTypedData(params);
|
|
362
447
|
|
|
363
|
-
|
|
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:
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
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> =
|
|
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
|
-
|
|
442
|
-
|
|
443
|
-
|
|
546
|
+
if (result.orgId == null) {
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
444
549
|
|
|
445
|
-
|
|
446
|
-
|
|
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({
|
|
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
|
-
|
|
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
|
+
}
|
package/src/client/base.ts
CHANGED
|
@@ -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 (
|
|
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)
|
package/src/client/index.ts
CHANGED
|
@@ -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
|
|
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
|
|
237
|
-
*
|
|
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();
|
package/src/client/types.ts
CHANGED
|
@@ -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
|
+
});
|