Skip to content

Commit 2a82737

Browse files
authored
feat(clerk-js,clerk-react,types): Signal phone code support (#6650)
1 parent 6f2cafa commit 2a82737

7 files changed

Lines changed: 115 additions & 7 deletions

File tree

.changeset/hungry-dogs-stick.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@clerk/clerk-js': minor
3+
'@clerk/clerk-react': minor
4+
'@clerk/types': minor
5+
---
6+
7+
[Experimental] Signal phone code support

packages/clerk-js/src/core/resources/SignIn.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import type {
3232
SignInFutureEmailCodeVerifyParams,
3333
SignInFutureFinalizeParams,
3434
SignInFuturePasswordParams,
35+
SignInFuturePhoneCodeSendParams,
36+
SignInFuturePhoneCodeVerifyParams,
3537
SignInFutureResetPasswordSubmitParams,
3638
SignInFutureResource,
3739
SignInFutureSSOParams,
@@ -508,6 +510,11 @@ class SignInFuture implements SignInFutureResource {
508510
submitPassword: this.submitResetPassword.bind(this),
509511
};
510512

513+
phoneCode = {
514+
sendCode: this.sendPhoneCode.bind(this),
515+
verifyCode: this.verifyPhoneCode.bind(this),
516+
};
517+
511518
constructor(readonly resource: SignIn) {}
512519

513520
get status() {
@@ -627,6 +634,37 @@ class SignInFuture implements SignInFutureResource {
627634
});
628635
}
629636

637+
async sendPhoneCode(params: SignInFuturePhoneCodeSendParams): Promise<{ error: unknown }> {
638+
const { phoneNumber, channel = 'sms' } = params;
639+
return runAsyncResourceTask(this.resource, async () => {
640+
if (!this.resource.id) {
641+
await this.create({ identifier: phoneNumber });
642+
}
643+
644+
const phoneCodeFactor = this.resource.supportedFirstFactors?.find(f => f.strategy === 'phone_code');
645+
646+
if (!phoneCodeFactor) {
647+
throw new Error('Phone code factor not found');
648+
}
649+
650+
const { phoneNumberId } = phoneCodeFactor;
651+
await this.resource.__internal_basePost({
652+
body: { phoneNumberId, strategy: 'phone_code', channel },
653+
action: 'prepare_first_factor',
654+
});
655+
});
656+
}
657+
658+
async verifyPhoneCode(params: SignInFuturePhoneCodeVerifyParams): Promise<{ error: unknown }> {
659+
const { code } = params;
660+
return runAsyncResourceTask(this.resource, async () => {
661+
await this.resource.__internal_basePost({
662+
body: { code, strategy: 'phone_code' },
663+
action: 'attempt_first_factor',
664+
});
665+
});
666+
}
667+
630668
async sso(params: SignInFutureSSOParams): Promise<{ error: unknown }> {
631669
const { flow = 'auto', strategy, redirectUrl, redirectCallbackUrl } = params;
632670
return runAsyncResourceTask(this.resource, async () => {

packages/clerk-js/src/core/resources/SignUp.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ import type {
2121
SignUpFutureEmailCodeVerifyParams,
2222
SignUpFutureFinalizeParams,
2323
SignUpFuturePasswordParams,
24+
SignUpFuturePhoneCodeSendParams,
25+
SignUpFuturePhoneCodeVerifyParams,
2426
SignUpFutureResource,
25-
SignUpFutureSSoParams,
27+
SignUpFutureSSOParams,
2628
SignUpIdentificationField,
2729
SignUpJSON,
2830
SignUpJSONSnapshot,
@@ -497,6 +499,8 @@ class SignUpFuture implements SignUpFutureResource {
497499
verifications = {
498500
sendEmailCode: this.sendEmailCode.bind(this),
499501
verifyEmailCode: this.verifyEmailCode.bind(this),
502+
sendPhoneCode: this.sendPhoneCode.bind(this),
503+
verifyPhoneCode: this.verifyPhoneCode.bind(this),
500504
};
501505

502506
constructor(readonly resource: SignUp) {}
@@ -594,7 +598,35 @@ class SignUpFuture implements SignUpFutureResource {
594598
});
595599
}
596600

597-
async sso(params: SignUpFutureSSoParams): Promise<{ error: unknown }> {
601+
async sendPhoneCode(params: SignUpFuturePhoneCodeSendParams): Promise<{ error: unknown }> {
602+
const { phoneNumber, channel = 'sms' } = params;
603+
return runAsyncResourceTask(this.resource, async () => {
604+
if (!this.resource.id) {
605+
const { captchaToken, captchaWidgetType, captchaError } = await this.getCaptchaToken();
606+
await this.resource.__internal_basePost({
607+
path: this.resource.pathRoot,
608+
body: { phoneNumber, captchaToken, captchaWidgetType, captchaError },
609+
});
610+
}
611+
612+
await this.resource.__internal_basePost({
613+
body: { strategy: 'phone_code', channel },
614+
action: 'prepare_verification',
615+
});
616+
});
617+
}
618+
619+
async verifyPhoneCode(params: SignUpFuturePhoneCodeVerifyParams): Promise<{ error: unknown }> {
620+
const { code } = params;
621+
return runAsyncResourceTask(this.resource, async () => {
622+
await this.resource.__internal_basePost({
623+
body: { strategy: 'phone_code', code },
624+
action: 'attempt_verification',
625+
});
626+
});
627+
}
628+
629+
async sso(params: SignUpFutureSSOParams): Promise<{ error: unknown }> {
598630
const { strategy, redirectUrl, redirectCallbackUrl } = params;
599631
return runAsyncResourceTask(this.resource, async () => {
600632
const { captchaToken, captchaWidgetType, captchaError } = await this.getCaptchaToken();

packages/react/src/stateProxy.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export class StateProxy implements State {
5656
'verifyCode',
5757
'submitPassword',
5858
] as const),
59+
phoneCode: this.wrapMethods(() => target().phoneCode, ['sendCode', 'verifyCode'] as const),
5960
},
6061
};
6162
}
@@ -76,7 +77,12 @@ export class StateProxy implements State {
7677
password: this.gateMethod(target, 'password'),
7778
finalize: this.gateMethod(target, 'finalize'),
7879

79-
verifications: this.wrapMethods(() => target().verifications, ['sendEmailCode', 'verifyEmailCode'] as const),
80+
verifications: this.wrapMethods(() => target().verifications, [
81+
'sendEmailCode',
82+
'verifyEmailCode',
83+
'sendPhoneCode',
84+
'verifyPhoneCode',
85+
] as const),
8086
},
8187
};
8288
}

packages/types/src/factors.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import type { PhoneCodeChannel } from 'phoneCodeChannel';
2-
31
import type { PublicKeyCredentialWithAuthenticatorAssertionResponse } from './passkey';
2+
import type { PhoneCodeChannel } from './phoneCodeChannel';
43
import type {
54
BackupCodeStrategy,
65
EmailCodeStrategy,

packages/types/src/signInFuture.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { SetActiveNavigate } from './clerk';
2+
import type { PhoneCodeChannel } from './phoneCodeChannel';
23
import type { SignInFirstFactor, SignInStatus } from './signInCommon';
34
import type { OAuthStrategy } from './strategies';
45

@@ -28,6 +29,15 @@ export interface SignInFutureResetPasswordSubmitParams {
2829
signOutOfOtherSessions?: boolean;
2930
}
3031

32+
export interface SignInFuturePhoneCodeSendParams {
33+
phoneNumber?: string;
34+
channel?: PhoneCodeChannel;
35+
}
36+
37+
export interface SignInFuturePhoneCodeVerifyParams {
38+
code: string;
39+
}
40+
3141
export interface SignInFutureSSOParams {
3242
flow?: 'auto' | 'modal';
3343
strategy: OAuthStrategy | 'saml' | 'enterprise_sso';
@@ -56,6 +66,10 @@ export interface SignInFutureResource {
5666
sendCode: (params: SignInFutureEmailCodeSendParams) => Promise<{ error: unknown }>;
5767
verifyCode: (params: SignInFutureEmailCodeVerifyParams) => Promise<{ error: unknown }>;
5868
};
69+
phoneCode: {
70+
sendCode: (params: SignInFuturePhoneCodeSendParams) => Promise<{ error: unknown }>;
71+
verifyCode: (params: SignInFuturePhoneCodeVerifyParams) => Promise<{ error: unknown }>;
72+
};
5973
resetPasswordEmailCode: {
6074
sendCode: () => Promise<{ error: unknown }>;
6175
verifyCode: (params: SignInFutureEmailCodeVerifyParams) => Promise<{ error: unknown }>;

packages/types/src/signUpFuture.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { SetActiveNavigate } from './clerk';
2+
import type { PhoneCodeChannel } from './phoneCodeChannel';
23
import type { SignUpIdentificationField, SignUpStatus } from './signUpCommon';
34

45
export interface SignUpFutureCreateParams {
@@ -14,7 +15,16 @@ export interface SignUpFuturePasswordParams {
1415
password: string;
1516
}
1617

17-
export interface SignUpFutureSSoParams {
18+
export interface SignUpFuturePhoneCodeSendParams {
19+
phoneNumber?: string;
20+
channel?: PhoneCodeChannel;
21+
}
22+
23+
export interface SignUpFuturePhoneCodeVerifyParams {
24+
code: string;
25+
}
26+
27+
export interface SignUpFutureSSOParams {
1828
strategy: string;
1929
/**
2030
* The URL to redirect to after the user has completed the SSO flow.
@@ -39,8 +49,10 @@ export interface SignUpFutureResource {
3949
verifications: {
4050
sendEmailCode: () => Promise<{ error: unknown }>;
4151
verifyEmailCode: (params: SignUpFutureEmailCodeVerifyParams) => Promise<{ error: unknown }>;
52+
sendPhoneCode: (params: SignUpFuturePhoneCodeSendParams) => Promise<{ error: unknown }>;
53+
verifyPhoneCode: (params: SignUpFuturePhoneCodeVerifyParams) => Promise<{ error: unknown }>;
4254
};
4355
password: (params: SignUpFuturePasswordParams) => Promise<{ error: unknown }>;
44-
sso: (params: SignUpFutureSSoParams) => Promise<{ error: unknown }>;
56+
sso: (params: SignUpFutureSSOParams) => Promise<{ error: unknown }>;
4557
finalize: (params?: SignUpFutureFinalizeParams) => Promise<{ error: unknown }>;
4658
}

0 commit comments

Comments
 (0)