Skip to content

Commit bfbf968

Browse files
committed
refactor(elements): Interim commit
1 parent 202bc8b commit bfbf968

1 file changed

Lines changed: 271 additions & 45 deletions

File tree

packages/elements/src/internals/machines/sign-in/router.machine.ts

Lines changed: 271 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
11
import { joinURL } from '@clerk/shared/url';
22
import { isWebAuthnAutofillSupported } from '@clerk/shared/webauthn';
3-
import type { LoadedClerk, SignInResource, SignInStatus, SignInStrategy, Web3Strategy } from '@clerk/types';
3+
import type {
4+
AttemptFirstFactorParams,
5+
EmailCodeAttempt,
6+
LoadedClerk,
7+
PasswordAttempt,
8+
PhoneCodeAttempt,
9+
PrepareFirstFactorParams,
10+
PrepareSecondFactorParams,
11+
ResetPasswordEmailCodeAttempt,
12+
ResetPasswordPhoneCodeAttempt,
13+
SignInFirstFactor,
14+
SignInResource,
15+
SignInSecondFactor,
16+
SignInStatus,
17+
SignInStrategy,
18+
Web3Attempt,
19+
Web3Strategy,
20+
} from '@clerk/types';
421
import type { NonReducibleUnknown } from 'xstate';
522
import { and, assertEvent, assign, enqueueActions, fromPromise, log, not, or, raise, sendTo, setup } from 'xstate';
623

@@ -16,7 +33,7 @@ import { ClerkElementsError, ClerkElementsRuntimeError } from '~/internals/error
1633
import { type FormFields, FormMachine } from '~/internals/machines/form';
1734
import { ThirdPartyMachine, ThirdPartyMachineId } from '~/internals/machines/third-party';
1835
import type { BaseRouterLoadingStep } from '~/internals/machines/types';
19-
import { assertActorEventError } from '~/internals/machines/utils/assert';
36+
import { assertActorEventError, assertIsDefined } from '~/internals/machines/utils/assert';
2037
import { shouldUseVirtualRouting } from '~/internals/machines/utils/next';
2138

2239
import type {
@@ -26,7 +43,7 @@ import type {
2643
SignInRouterSchema,
2744
SignInRouterSessionSetActiveEvent,
2845
} from './router.types';
29-
import { SignInFirstFactorMachine, SignInSecondFactorMachine } from './verification.machine';
46+
import { determineStartingSignInFactor, determineStartingSignInSecondFactor } from './utils/starting-factors';
3047

3148
export type TSignInRouterMachine = typeof SignInRouterMachine;
3249

@@ -43,56 +60,265 @@ const needsStatus =
4360

4461
export const SignInRouterMachineId = 'SignInRouter';
4562

63+
export type PrepareFirstFactorInput = {
64+
clerk: LoadedClerk;
65+
params: PrepareFirstFactorParams;
66+
resendable: boolean;
67+
};
68+
export type PrepareSecondFactorInput = {
69+
clerk: LoadedClerk;
70+
params: PrepareSecondFactorParams;
71+
resendable: boolean;
72+
};
73+
74+
export type AttemptFirstFactorInput = {
75+
clerk: LoadedClerk;
76+
currentFactor: SignInFirstFactor | null;
77+
fields: FormFields;
78+
};
79+
80+
export type AttemptSecondFactorInput = {
81+
clerk: LoadedClerk;
82+
currentFactor: SignInSecondFactor | null;
83+
fields: FormFields;
84+
};
85+
86+
export type ResetPasswordAttemptInput = { clerk: LoadedClerk; fields: FormFields };
87+
export type ClerkInput = { clerk: LoadedClerk };
88+
89+
export type AttemptPasskeyInput = { clerk: LoadedClerk; flow: 'autofill' | 'discoverable' | undefined };
90+
export type AttemptWeb3Input = { clerk: LoadedClerk; strategy: Web3Strategy };
91+
export type StartAttemptInput = { clerk: LoadedClerk; fields: FormFields };
92+
93+
const isNonPreparableStrategy = (strategy?: SignInFirstFactor['strategy'] | SignInSecondFactor['strategy']) => {
94+
if (!strategy) {
95+
return false;
96+
}
97+
98+
return ['passkey', 'password'].includes(strategy);
99+
};
100+
46101
export const SignInRouterMachine = setup({
47102
actors: {
48-
attemptPasskey: fromPromise<SignInResource, { clerk: LoadedClerk; flow: 'autofill' | 'discoverable' | undefined }>(
49-
({ input: { clerk, flow } }) => {
50-
return clerk.client.signIn.authenticateWithPasskey({
51-
flow,
52-
});
53-
},
54-
),
55-
attemptWeb3: fromPromise<SignInResource, { clerk: LoadedClerk; strategy: Web3Strategy }>(
56-
({ input: { clerk, strategy } }) => {
57-
if (strategy === 'web3_metamask_signature') {
58-
return clerk.client.signIn.authenticateWithMetamask();
103+
// ----------------
104+
// Global
105+
// ----------------
106+
107+
attemptPasskey: fromPromise<SignInResource, AttemptPasskeyInput>(({ input }) => {
108+
const { clerk, flow } = input;
109+
110+
return clerk.client.signIn.authenticateWithPasskey({
111+
flow,
112+
});
113+
}),
114+
115+
attemptWeb3: fromPromise<SignInResource, AttemptWeb3Input>(({ input }) => {
116+
const { clerk, strategy } = input;
117+
118+
if (strategy === 'web3_metamask_signature') {
119+
return clerk.client.signIn.authenticateWithMetamask();
120+
}
121+
122+
if (strategy === 'web3_coinbase_wallet_signature') {
123+
return clerk.client.signIn.authenticateWithCoinbaseWallet();
124+
}
125+
126+
throw new ClerkElementsRuntimeError(`Unsupported Web3 strategy: ${strategy}`);
127+
}),
128+
129+
// ----------------
130+
// Start
131+
// ----------------
132+
133+
startAttempt: fromPromise<SignInResource, StartAttemptInput>(({ input }) => {
134+
const { clerk, fields } = input;
135+
136+
const password = fields.get('password');
137+
const identifier = fields.get('identifier');
138+
139+
const passwordParams = password?.value
140+
? {
141+
password: password.value,
142+
strategy: 'password',
143+
}
144+
: {};
145+
146+
return clerk.client.signIn.create({
147+
identifier: (identifier?.value as string) || '',
148+
...passwordParams,
149+
});
150+
}),
151+
152+
// ----------------
153+
// First Factor
154+
// ----------------
155+
156+
firstFactorAttempt: fromPromise<SignInResource, AttemptFirstFactorInput>(async ({ input }) => {
157+
const { clerk, currentFactor, fields } = input;
158+
assertIsDefined(currentFactor, 'Current factor');
159+
160+
let attemptParams: AttemptFirstFactorParams;
161+
162+
const strategy = currentFactor.strategy;
163+
const code = fields.get('code')?.value as string | undefined;
164+
const password = fields.get('password')?.value as string | undefined;
165+
166+
switch (strategy) {
167+
case 'passkey': {
168+
return await clerk.client.signIn.authenticateWithPasskey();
59169
}
60-
if (strategy === 'web3_coinbase_wallet_signature') {
61-
return clerk.client.signIn.authenticateWithCoinbaseWallet();
170+
case 'password': {
171+
assertIsDefined(password, 'Password');
172+
173+
attemptParams = {
174+
strategy,
175+
password,
176+
} satisfies PasswordAttempt;
177+
178+
break;
62179
}
63-
throw new ClerkElementsRuntimeError(`Unsupported Web3 strategy: ${strategy}`);
64-
},
65-
),
66-
resetPasswordAttempt: fromPromise<SignInResource, { clerk: LoadedClerk; fields: FormFields }>(
67-
({ input: { clerk, fields } }) => {
68-
const password = (fields.get('password')?.value as string) || '';
69-
const signOutOfOtherSessions = fields.get('signOutOfOtherSessions')?.checked || false;
70-
return clerk.client.signIn.resetPassword({ password, signOutOfOtherSessions });
71-
},
72-
),
73-
startAttempt: fromPromise<SignInResource, { clerk: LoadedClerk; fields: FormFields }>(
74-
({ input: { clerk, fields } }) => {
75-
const password = fields.get('password');
76-
const identifier = fields.get('identifier');
77-
78-
const passwordParams = password?.value
79-
? {
80-
password: password.value,
81-
strategy: 'password',
82-
}
83-
: {};
84-
85-
return clerk.client.signIn.create({
86-
identifier: (identifier?.value as string) || '',
87-
...passwordParams,
88-
});
89-
},
180+
case 'reset_password_phone_code':
181+
case 'reset_password_email_code': {
182+
assertIsDefined(code, 'Code for resetting phone/email');
183+
184+
attemptParams = {
185+
strategy,
186+
code,
187+
password,
188+
} satisfies ResetPasswordPhoneCodeAttempt | ResetPasswordEmailCodeAttempt;
189+
190+
break;
191+
}
192+
case 'phone_code':
193+
case 'email_code': {
194+
assertIsDefined(code, 'Code for phone/email');
195+
196+
attemptParams = {
197+
strategy,
198+
code,
199+
} satisfies PhoneCodeAttempt | EmailCodeAttempt;
200+
201+
break;
202+
}
203+
case 'web3_metamask_signature': {
204+
const signature = fields.get('signature')?.value as string | undefined;
205+
assertIsDefined(signature, 'Web3 Metamask signature');
206+
207+
attemptParams = {
208+
strategy,
209+
signature,
210+
} satisfies Web3Attempt;
211+
212+
break;
213+
}
214+
case 'web3_coinbase_wallet_signature': {
215+
const signature = fields.get('signature')?.value as string | undefined;
216+
assertIsDefined(signature, 'Web3 Coinbase Wallet signature');
217+
218+
attemptParams = {
219+
strategy,
220+
signature,
221+
} satisfies Web3Attempt;
222+
223+
break;
224+
}
225+
default:
226+
throw new ClerkElementsRuntimeError(`Invalid strategy: ${strategy}`);
227+
}
228+
229+
return await clerk.client.signIn.attemptFirstFactor(attemptParams);
230+
}),
231+
232+
firstFactorDetermineStartingFactor: fromPromise<SignInFirstFactor | null, ClerkInput>(async ({ input }) => {
233+
return Promise.resolve(
234+
determineStartingSignInFactor(
235+
input.clerk.client.signIn.supportedFirstFactors,
236+
input.clerk.client.signIn.identifier,
237+
input.clerk.__unstable__environment?.displayConfig.preferredSignInStrategy,
238+
),
239+
);
240+
}),
241+
242+
firstFactorPrepare: fromPromise<SignInResource, PrepareFirstFactorInput>(async ({ input }) => {
243+
const { clerk, params, resendable } = input;
244+
245+
// If a prepare call has already been fired recently, don't re-send
246+
const currentVerificationExpiration = clerk.client.signIn.firstFactorVerification.expireAt;
247+
const needsPrepare = resendable || !currentVerificationExpiration || currentVerificationExpiration < new Date();
248+
249+
if (isNonPreparableStrategy(params?.strategy) || !needsPrepare) {
250+
return Promise.resolve(clerk.client.signIn);
251+
}
252+
253+
assertIsDefined(params, 'First factor params');
254+
return await clerk.client.signIn.prepareFirstFactor(params);
255+
}),
256+
257+
// ----------------
258+
// Second Factor
259+
// ----------------
260+
261+
secondFactorAttempt: fromPromise<SignInResource, AttemptSecondFactorInput>(async ({ input }) => {
262+
const { clerk, fields, currentFactor } = input;
263+
264+
const code = fields.get('code')?.value as string;
265+
266+
assertIsDefined(currentFactor, 'Current factor');
267+
assertIsDefined(code, 'Code');
268+
269+
return await clerk.client.signIn.attemptSecondFactor({
270+
strategy: currentFactor.strategy,
271+
code,
272+
});
273+
}),
274+
275+
secondFactorDetermineStartingFactor: fromPromise<SignInSecondFactor | null, ClerkInput>(async ({ input }) =>
276+
Promise.resolve(determineStartingSignInSecondFactor(input.clerk.client.signIn.supportedSecondFactors)),
90277
),
91278

92-
firstFactorMachine: SignInFirstFactorMachine,
279+
secondFactorPrepare: fromPromise<SignInResource, PrepareSecondFactorInput>(async ({ input }) => {
280+
const { clerk, params, resendable } = input;
281+
282+
// If a prepare call has already been fired recently, don't re-send
283+
const currentVerificationExpiration = clerk.client.signIn.secondFactorVerification.expireAt;
284+
const needsPrepare = resendable || !currentVerificationExpiration || currentVerificationExpiration < new Date();
285+
286+
assertIsDefined(params, 'Second factor params');
287+
288+
if (params.strategy !== 'phone_code' || !needsPrepare) {
289+
return Promise.resolve(clerk.client.signIn);
290+
}
291+
292+
return await clerk.client.signIn.prepareSecondFactor({
293+
strategy: params.strategy,
294+
phoneNumberId: params.phoneNumberId,
295+
});
296+
}),
297+
298+
// ----------------
299+
// Reset Password
300+
// ----------------
301+
302+
resetPasswordAttempt: fromPromise<SignInResource, ResetPasswordAttemptInput>(({ input }) => {
303+
const { clerk, fields } = input;
304+
305+
const password = (fields.get('password')?.value as string) || '';
306+
const signOutOfOtherSessions = fields.get('signOutOfOtherSessions')?.checked || false;
307+
308+
return clerk.client.signIn.resetPassword({ password, signOutOfOtherSessions });
309+
}),
310+
311+
// ----------------
312+
// Shared Machines
313+
// ----------------
314+
93315
formMachine: FormMachine,
94-
secondFactorMachine: SignInSecondFactorMachine,
95316
thirdPartyMachine: ThirdPartyMachine,
317+
318+
// ----------------
319+
// Other
320+
// ----------------
321+
96322
webAuthnAutofillSupport: fromPromise(() => isWebAuthnAutofillSupported()),
97323
},
98324
actions: {

0 commit comments

Comments
 (0)