@@ -15,11 +15,14 @@ import type {
1515 AuthenticateWithWeb3Params ,
1616 CreateEmailLinkFlowReturn ,
1717 EmailCodeConfig ,
18+ EmailCodeFactor ,
1819 EmailLinkConfig ,
20+ EmailLinkFactor ,
1921 EnterpriseSSOConfig ,
2022 PassKeyConfig ,
2123 PasskeyFactor ,
2224 PhoneCodeConfig ,
25+ PhoneCodeFactor ,
2326 PrepareFirstFactorParams ,
2427 PrepareSecondFactorParams ,
2528 ResetPasswordEmailCodeFactorConfig ,
@@ -544,6 +547,11 @@ export class SignIn extends BaseResource implements SignInResource {
544547 }
545548}
546549
550+ type SelectFirstFactorParams =
551+ | { strategy : 'email_code' ; emailAddressId ?: string ; phoneNumberId ?: never }
552+ | { strategy : 'email_link' ; emailAddressId ?: string ; phoneNumberId ?: never }
553+ | { strategy : 'phone_code' ; phoneNumberId ?: string ; emailAddressId ?: never } ;
554+
547555class SignInFuture implements SignInFutureResource {
548556 emailCode = {
549557 sendCode : this . sendEmailCode . bind ( this ) ,
@@ -692,22 +700,32 @@ class SignInFuture implements SignInFutureResource {
692700 } ) ;
693701 }
694702
695- async sendEmailCode ( params : SignInFutureEmailCodeSendParams ) : Promise < { error : unknown } > {
696- const { email } = params ;
703+ async sendEmailCode ( params : SignInFutureEmailCodeSendParams = { } ) : Promise < { error : unknown } > {
704+ const { emailAddress, emailAddressId } = params ;
705+ if ( ! this . resource . id && emailAddressId ) {
706+ throw new Error (
707+ 'signIn.emailCode.sendCode() cannot be called with an emailAddressId if an existing signIn does not exist.' ,
708+ ) ;
709+ }
710+
711+ if ( ! this . resource . id && ! emailAddress ) {
712+ throw new Error (
713+ 'signIn.emailCode.sendCode() cannot be called without an emailAddress if an existing signIn does not exist.' ,
714+ ) ;
715+ }
716+
697717 return runAsyncResourceTask ( this . resource , async ( ) => {
698- if ( ! this . resource . id ) {
699- await this . create ( { identifier : email } ) ;
718+ if ( emailAddress ) {
719+ await this . create ( { identifier : emailAddress } ) ;
700720 }
701721
702- const emailCodeFactor = this . resource . supportedFirstFactors ?. find ( f => f . strategy === 'email_code' ) ;
703-
722+ const emailCodeFactor = this . selectFirstFactor ( { strategy : 'email_code' , emailAddressId } ) ;
704723 if ( ! emailCodeFactor ) {
705724 throw new Error ( 'Email code factor not found' ) ;
706725 }
707726
708- const { emailAddressId } = emailCodeFactor ;
709727 await this . resource . __internal_basePost ( {
710- body : { emailAddressId, strategy : 'email_code' } ,
728+ body : { emailAddressId : emailCodeFactor . emailAddressId , strategy : 'email_code' } ,
711729 action : 'prepare_first_factor' ,
712730 } ) ;
713731 } ) ;
@@ -724,20 +742,29 @@ class SignInFuture implements SignInFutureResource {
724742 }
725743
726744 async sendEmailLink ( params : SignInFutureEmailLinkSendParams ) : Promise < { error : unknown } > {
727- const { email, verificationUrl } = params ;
745+ const { emailAddress, verificationUrl, emailAddressId } = params ;
746+ if ( ! this . resource . id && emailAddressId ) {
747+ throw new Error (
748+ 'signIn.emailLink.sendLink() cannot be called with an emailAddressId if an existing signIn does not exist.' ,
749+ ) ;
750+ }
751+
752+ if ( ! this . resource . id && ! emailAddress ) {
753+ throw new Error (
754+ 'signIn.emailLink.sendLink() cannot be called without an emailAddress if an existing signIn does not exist.' ,
755+ ) ;
756+ }
757+
728758 return runAsyncResourceTask ( this . resource , async ( ) => {
729- if ( ! this . resource . id ) {
730- await this . create ( { identifier : email } ) ;
759+ if ( emailAddress ) {
760+ await this . create ( { identifier : emailAddress } ) ;
731761 }
732762
733- const emailLinkFactor = this . resource . supportedFirstFactors ?. find ( f => f . strategy === 'email_link' ) ;
734-
763+ const emailLinkFactor = this . selectFirstFactor ( { strategy : 'email_link' , emailAddressId } ) ;
735764 if ( ! emailLinkFactor ) {
736765 throw new Error ( 'Email link factor not found' ) ;
737766 }
738767
739- const { emailAddressId } = emailLinkFactor ;
740-
741768 let absoluteVerificationUrl = verificationUrl ;
742769 try {
743770 new URL ( verificationUrl ) ;
@@ -746,7 +773,11 @@ class SignInFuture implements SignInFutureResource {
746773 }
747774
748775 await this . resource . __internal_basePost ( {
749- body : { emailAddressId, redirectUrl : absoluteVerificationUrl , strategy : 'email_link' } ,
776+ body : {
777+ emailAddressId : emailLinkFactor . emailAddressId ,
778+ redirectUrl : absoluteVerificationUrl ,
779+ strategy : 'email_link' ,
780+ } ,
750781 action : 'prepare_first_factor' ,
751782 } ) ;
752783 } ) ;
@@ -773,22 +804,32 @@ class SignInFuture implements SignInFutureResource {
773804 } ) ;
774805 }
775806
776- async sendPhoneCode ( params : SignInFuturePhoneCodeSendParams ) : Promise < { error : unknown } > {
777- const { phoneNumber, channel = 'sms' } = params ;
807+ async sendPhoneCode ( params : SignInFuturePhoneCodeSendParams = { } ) : Promise < { error : unknown } > {
808+ const { phoneNumber, phoneNumberId, channel = 'sms' } = params ;
809+ if ( ! this . resource . id && phoneNumberId ) {
810+ throw new Error (
811+ 'signIn.phoneCode.sendCode() cannot be called with an phoneNumberId if an existing signIn does not exist.' ,
812+ ) ;
813+ }
814+
815+ if ( ! this . resource . id && ! phoneNumber ) {
816+ throw new Error (
817+ 'signIn.phoneCode.sendCode() cannot be called without an phoneNumber if an existing signIn does not exist.' ,
818+ ) ;
819+ }
820+
778821 return runAsyncResourceTask ( this . resource , async ( ) => {
779- if ( ! this . resource . id ) {
822+ if ( phoneNumber ) {
780823 await this . create ( { identifier : phoneNumber } ) ;
781824 }
782825
783- const phoneCodeFactor = this . resource . supportedFirstFactors ?. find ( f => f . strategy === 'phone_code' ) ;
784-
826+ const phoneCodeFactor = this . selectFirstFactor ( { strategy : 'phone_code' , phoneNumberId } ) ;
785827 if ( ! phoneCodeFactor ) {
786828 throw new Error ( 'Phone code factor not found' ) ;
787829 }
788830
789- const { phoneNumberId } = phoneCodeFactor ;
790831 await this . resource . __internal_basePost ( {
791- body : { phoneNumberId, strategy : 'phone_code' , channel } ,
832+ body : { phoneNumberId : phoneCodeFactor . phoneNumberId , strategy : 'phone_code' , channel } ,
792833 action : 'prepare_first_factor' ,
793834 } ) ;
794835 } ) ;
@@ -886,4 +927,60 @@ class SignInFuture implements SignInFutureResource {
886927 await SignIn . clerk . setActive ( { session : this . resource . createdSessionId , navigate } ) ;
887928 } ) ;
888929 }
930+
931+ private selectFirstFactor (
932+ params : Extract < SelectFirstFactorParams , { strategy : 'email_code' } > ,
933+ ) : EmailCodeFactor | null ;
934+ private selectFirstFactor (
935+ params : Extract < SelectFirstFactorParams , { strategy : 'email_link' } > ,
936+ ) : EmailLinkFactor | null ;
937+ private selectFirstFactor (
938+ params : Extract < SelectFirstFactorParams , { strategy : 'phone_code' } > ,
939+ ) : PhoneCodeFactor | null ;
940+ private selectFirstFactor ( {
941+ strategy,
942+ emailAddressId,
943+ phoneNumberId,
944+ } : SelectFirstFactorParams ) : EmailCodeFactor | EmailLinkFactor | PhoneCodeFactor | null {
945+ if ( ! this . resource . supportedFirstFactors ) {
946+ return null ;
947+ }
948+
949+ if ( emailAddressId ) {
950+ const factor = this . resource . supportedFirstFactors . find (
951+ f => f . strategy === strategy && f . emailAddressId === emailAddressId ,
952+ ) as EmailCodeFactor | EmailLinkFactor ;
953+ if ( factor ) {
954+ return factor ;
955+ }
956+ }
957+
958+ if ( phoneNumberId ) {
959+ const factor = this . resource . supportedFirstFactors . find (
960+ f => f . strategy === strategy && f . phoneNumberId === phoneNumberId ,
961+ ) as PhoneCodeFactor ;
962+ if ( factor ) {
963+ return factor ;
964+ }
965+ }
966+
967+ // Try to find a factor that matches the identifier.
968+ const factorForIdentifier = this . resource . supportedFirstFactors . find (
969+ f => f . strategy === strategy && f . safeIdentifier === this . resource . identifier ,
970+ ) as EmailCodeFactor | EmailLinkFactor | PhoneCodeFactor ;
971+ if ( factorForIdentifier ) {
972+ return factorForIdentifier ;
973+ }
974+
975+ // If no factor is found matching the identifier, try to find a factor that matches the strategy.
976+ const factorForStrategy = this . resource . supportedFirstFactors . find ( f => f . strategy === strategy ) as
977+ | EmailCodeFactor
978+ | EmailLinkFactor
979+ | PhoneCodeFactor ;
980+ if ( factorForStrategy ) {
981+ return factorForStrategy ;
982+ }
983+
984+ return null ;
985+ }
889986}
0 commit comments