@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.
- package/dist/esm/base.d.ts +2 -0
- package/dist/esm/base.js +91 -25
- package/dist/esm/base.js.map +1 -1
- package/dist/esm/client/base.d.ts +6 -2
- package/dist/esm/client/base.js.map +1 -1
- package/dist/esm/client/index.d.ts +31 -1
- package/dist/esm/client/index.js +41 -4
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/client/types.d.ts +21 -1
- package/dist/esm/client/types.js.map +1 -1
- package/dist/esm/metrics.d.ts +1 -1
- package/dist/esm/metrics.js.map +1 -1
- package/dist/esm/session/manager.d.ts +7 -6
- package/dist/esm/session/manager.js +36 -15
- package/dist/esm/session/manager.js.map +1 -1
- package/dist/esm/session/types.d.ts +1 -1
- package/dist/esm/session/types.js.map +1 -1
- package/dist/esm/signer.d.ts +4 -0
- package/dist/esm/signer.js.map +1 -1
- package/dist/esm/types.d.ts +2 -1
- package/dist/esm/types.js +1 -0
- 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 +2 -0
- package/dist/types/base.d.ts.map +1 -1
- package/dist/types/client/base.d.ts +6 -2
- package/dist/types/client/base.d.ts.map +1 -1
- package/dist/types/client/index.d.ts +31 -1
- package/dist/types/client/index.d.ts.map +1 -1
- package/dist/types/client/types.d.ts +21 -1
- package/dist/types/client/types.d.ts.map +1 -1
- package/dist/types/metrics.d.ts +1 -1
- package/dist/types/metrics.d.ts.map +1 -1
- package/dist/types/session/manager.d.ts +7 -6
- package/dist/types/session/manager.d.ts.map +1 -1
- package/dist/types/session/types.d.ts +1 -1
- package/dist/types/session/types.d.ts.map +1 -1
- package/dist/types/signer.d.ts +4 -0
- package/dist/types/signer.d.ts.map +1 -1
- package/dist/types/types.d.ts +2 -1
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +4 -4
- package/src/base.ts +90 -30
- package/src/client/base.ts +6 -1
- package/src/client/index.ts +45 -4
- package/src/client/types.ts +21 -1
- package/src/metrics.ts +6 -1
- package/src/session/manager.ts +62 -29
- package/src/session/types.ts +1 -1
- package/src/signer.ts +10 -1
- package/src/types.ts +1 -0
- 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 =
|
|
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({
|
|
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:
|
|
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
|
|
808
|
-
this.sessionManager.
|
|
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
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
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
|
-
|
|
824
|
-
this.
|
|
825
|
-
|
|
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
|
}
|
package/src/client/base.ts
CHANGED
|
@@ -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>;
|
package/src/client/index.ts
CHANGED
|
@@ -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: "
|
|
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: "
|
|
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
|
package/src/client/types.ts
CHANGED
|
@@ -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:
|
|
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 };
|
package/src/session/manager.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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:
|
|
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 = ():
|
|
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<
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
) {
|
|
246
|
-
|
|
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
|
-
|
|
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
|
-
|
|
253
|
-
|
|
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
|
}) => {
|
package/src/session/types.ts
CHANGED
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
|
-
| {
|
|
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