From 3bee61b295cc429cd9e246b590ed781603a5068e Mon Sep 17 00:00:00 2001 From: Dylan Staley <88163+dstaley@users.noreply.github.com> Date: Thu, 13 Nov 2025 12:17:25 -0600 Subject: [PATCH 1/2] fix(clerk-js): Type method returns with ClerkError --- .changeset/deep-wombats-retire.md | 5 +++ .../clerk-js/src/core/resources/SignIn.ts | 42 +++++++++---------- .../clerk-js/src/core/resources/SignUp.ts | 24 +++++------ .../src/utils/runAsyncResourceTask.ts | 4 +- 4 files changed, 41 insertions(+), 34 deletions(-) create mode 100644 .changeset/deep-wombats-retire.md diff --git a/.changeset/deep-wombats-retire.md b/.changeset/deep-wombats-retire.md new file mode 100644 index 00000000000..60b2e5b2508 --- /dev/null +++ b/.changeset/deep-wombats-retire.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +[Experimental] Type method returns using ClerkError diff --git a/packages/clerk-js/src/core/resources/SignIn.ts b/packages/clerk-js/src/core/resources/SignIn.ts index 367db6a1a36..f5e9979de6c 100644 --- a/packages/clerk-js/src/core/resources/SignIn.ts +++ b/packages/clerk-js/src/core/resources/SignIn.ts @@ -1,5 +1,5 @@ import { inBrowser } from '@clerk/shared/browser'; -import { ClerkWebAuthnError } from '@clerk/shared/error'; +import { type ClerkError, ClerkWebAuthnError } from '@clerk/shared/error'; import { Poller } from '@clerk/shared/poller'; import type { AttemptFirstFactorParams, @@ -686,7 +686,7 @@ class SignInFuture implements SignInFutureResource { return this.resource.secondFactorVerification; } - async sendResetPasswordEmailCode(): Promise<{ error: unknown }> { + async sendResetPasswordEmailCode(): Promise<{ error: ClerkError | null }> { return runAsyncResourceTask(this.resource, async () => { if (!this.resource.id) { throw new Error('Cannot reset password without a sign in.'); @@ -708,7 +708,7 @@ class SignInFuture implements SignInFutureResource { }); } - async verifyResetPasswordEmailCode(params: SignInFutureEmailCodeVerifyParams): Promise<{ error: unknown }> { + async verifyResetPasswordEmailCode(params: SignInFutureEmailCodeVerifyParams): Promise<{ error: ClerkError | null }> { const { code } = params; return runAsyncResourceTask(this.resource, async () => { await this.resource.__internal_basePost({ @@ -718,7 +718,7 @@ class SignInFuture implements SignInFutureResource { }); } - async submitResetPassword(params: SignInFutureResetPasswordSubmitParams): Promise<{ error: unknown }> { + async submitResetPassword(params: SignInFutureResetPasswordSubmitParams): Promise<{ error: ClerkError | null }> { const { password, signOutOfOtherSessions = true } = params; return runAsyncResourceTask(this.resource, async () => { await this.resource.__internal_basePost({ @@ -736,13 +736,13 @@ class SignInFuture implements SignInFutureResource { }); } - async create(params: SignInFutureCreateParams): Promise<{ error: unknown }> { + async create(params: SignInFutureCreateParams): Promise<{ error: ClerkError | null }> { return runAsyncResourceTask(this.resource, async () => { await this._create(params); }); } - async password(params: SignInFuturePasswordParams): Promise<{ error: unknown }> { + async password(params: SignInFuturePasswordParams): Promise<{ error: ClerkError | null }> { if ([params.identifier, params.emailAddress, params.phoneNumber].filter(Boolean).length > 1) { throw new Error('Only one of identifier, emailAddress, or phoneNumber can be provided'); } @@ -763,7 +763,7 @@ class SignInFuture implements SignInFutureResource { }); } - async sendEmailCode(params: SignInFutureEmailCodeSendParams = {}): Promise<{ error: unknown }> { + async sendEmailCode(params: SignInFutureEmailCodeSendParams = {}): Promise<{ error: ClerkError | null }> { const { emailAddress, emailAddressId } = params; if (!this.resource.id && emailAddressId) { throw new Error( @@ -794,7 +794,7 @@ class SignInFuture implements SignInFutureResource { }); } - async verifyEmailCode(params: SignInFutureEmailCodeVerifyParams): Promise<{ error: unknown }> { + async verifyEmailCode(params: SignInFutureEmailCodeVerifyParams): Promise<{ error: ClerkError | null }> { const { code } = params; return runAsyncResourceTask(this.resource, async () => { await this.resource.__internal_basePost({ @@ -804,7 +804,7 @@ class SignInFuture implements SignInFutureResource { }); } - async sendEmailLink(params: SignInFutureEmailLinkSendParams): Promise<{ error: unknown }> { + async sendEmailLink(params: SignInFutureEmailLinkSendParams): Promise<{ error: ClerkError | null }> { const { emailAddress, verificationUrl, emailAddressId } = params; if (!this.resource.id && emailAddressId) { throw new Error( @@ -846,7 +846,7 @@ class SignInFuture implements SignInFutureResource { }); } - async waitForEmailLinkVerification(): Promise<{ error: unknown }> { + async waitForEmailLinkVerification(): Promise<{ error: ClerkError | null }> { return runAsyncResourceTask(this.resource, async () => { const { run, stop } = Poller(); await new Promise((resolve, reject) => { @@ -867,7 +867,7 @@ class SignInFuture implements SignInFutureResource { }); } - async sendPhoneCode(params: SignInFuturePhoneCodeSendParams = {}): Promise<{ error: unknown }> { + async sendPhoneCode(params: SignInFuturePhoneCodeSendParams = {}): Promise<{ error: ClerkError | null }> { const { phoneNumber, phoneNumberId, channel = 'sms' } = params; if (!this.resource.id && phoneNumberId) { throw new Error( @@ -898,7 +898,7 @@ class SignInFuture implements SignInFutureResource { }); } - async verifyPhoneCode(params: SignInFuturePhoneCodeVerifyParams): Promise<{ error: unknown }> { + async verifyPhoneCode(params: SignInFuturePhoneCodeVerifyParams): Promise<{ error: ClerkError | null }> { const { code } = params; return runAsyncResourceTask(this.resource, async () => { await this.resource.__internal_basePost({ @@ -908,7 +908,7 @@ class SignInFuture implements SignInFutureResource { }); } - async sso(params: SignInFutureSSOParams): Promise<{ error: unknown }> { + async sso(params: SignInFutureSSOParams): Promise<{ error: ClerkError | null }> { const { strategy, redirectUrl, redirectCallbackUrl, popup, oidcPrompt, enterpriseConnectionId } = params; return runAsyncResourceTask(this.resource, async () => { let actionCompleteRedirectUrl = redirectUrl; @@ -959,7 +959,7 @@ class SignInFuture implements SignInFutureResource { }); } - async web3(params: SignInFutureWeb3Params): Promise<{ error: unknown }> { + async web3(params: SignInFutureWeb3Params): Promise<{ error: ClerkError | null }> { const { strategy } = params; const provider = strategy.replace('web3_', '').replace('_signature', '') as Web3Provider; @@ -1030,7 +1030,7 @@ class SignInFuture implements SignInFutureResource { }); } - async passkey(params?: SignInFuturePasskeyParams): Promise<{ error: unknown }> { + async passkey(params?: SignInFuturePasskeyParams): Promise<{ error: ClerkError | null }> { const { flow } = params || {}; /** @@ -1101,7 +1101,7 @@ class SignInFuture implements SignInFutureResource { }); } - async sendMFAPhoneCode(): Promise<{ error: unknown }> { + async sendMFAPhoneCode(): Promise<{ error: ClerkError | null }> { return runAsyncResourceTask(this.resource, async () => { const phoneCodeFactor = this.resource.supportedSecondFactors?.find(f => f.strategy === 'phone_code'); @@ -1117,7 +1117,7 @@ class SignInFuture implements SignInFutureResource { }); } - async verifyMFAPhoneCode(params: SignInFutureMFAPhoneCodeVerifyParams): Promise<{ error: unknown }> { + async verifyMFAPhoneCode(params: SignInFutureMFAPhoneCodeVerifyParams): Promise<{ error: ClerkError | null }> { const { code } = params; return runAsyncResourceTask(this.resource, async () => { await this.resource.__internal_basePost({ @@ -1127,7 +1127,7 @@ class SignInFuture implements SignInFutureResource { }); } - async verifyTOTP(params: SignInFutureTOTPVerifyParams): Promise<{ error: unknown }> { + async verifyTOTP(params: SignInFutureTOTPVerifyParams): Promise<{ error: ClerkError | null }> { const { code } = params; return runAsyncResourceTask(this.resource, async () => { await this.resource.__internal_basePost({ @@ -1137,7 +1137,7 @@ class SignInFuture implements SignInFutureResource { }); } - async verifyBackupCode(params: SignInFutureBackupCodeVerifyParams): Promise<{ error: unknown }> { + async verifyBackupCode(params: SignInFutureBackupCodeVerifyParams): Promise<{ error: ClerkError | null }> { const { code } = params; return runAsyncResourceTask(this.resource, async () => { await this.resource.__internal_basePost({ @@ -1147,12 +1147,12 @@ class SignInFuture implements SignInFutureResource { }); } - async ticket(params?: SignInFutureTicketParams): Promise<{ error: unknown }> { + async ticket(params?: SignInFutureTicketParams): Promise<{ error: ClerkError | null }> { const ticket = params?.ticket ?? getClerkQueryParam('__clerk_ticket'); return this.create({ ticket: ticket ?? undefined }); } - async finalize(params?: SignInFutureFinalizeParams): Promise<{ error: unknown }> { + async finalize(params?: SignInFutureFinalizeParams): Promise<{ error: ClerkError | null }> { const { navigate } = params || {}; return runAsyncResourceTask(this.resource, async () => { if (!this.resource.createdSessionId) { diff --git a/packages/clerk-js/src/core/resources/SignUp.ts b/packages/clerk-js/src/core/resources/SignUp.ts index 9b01c5d406b..bef3afeecb7 100644 --- a/packages/clerk-js/src/core/resources/SignUp.ts +++ b/packages/clerk-js/src/core/resources/SignUp.ts @@ -1,4 +1,4 @@ -import { ClerkRuntimeError, isCaptchaError, isClerkAPIResponseError } from '@clerk/shared/error'; +import { type ClerkError, ClerkRuntimeError, isCaptchaError, isClerkAPIResponseError } from '@clerk/shared/error'; import { Poller } from '@clerk/shared/poller'; import type { AttemptEmailAddressVerificationParams, @@ -707,13 +707,13 @@ class SignUpFuture implements SignUpFutureResource { await this.resource.__internal_basePost({ path: this.resource.pathRoot, body }); } - async create(params: SignUpFutureCreateParams): Promise<{ error: unknown }> { + async create(params: SignUpFutureCreateParams): Promise<{ error: ClerkError | null }> { return runAsyncResourceTask(this.resource, async () => { await this._create(params); }); } - async update(params: SignUpFutureUpdateParams): Promise<{ error: unknown }> { + async update(params: SignUpFutureUpdateParams): Promise<{ error: ClerkError | null }> { return runAsyncResourceTask(this.resource, async () => { const body: Record = { ...params, @@ -724,7 +724,7 @@ class SignUpFuture implements SignUpFutureResource { }); } - async password(params: SignUpFuturePasswordParams): Promise<{ error: unknown }> { + async password(params: SignUpFuturePasswordParams): Promise<{ error: ClerkError | null }> { return runAsyncResourceTask(this.resource, async () => { const { captchaToken, captchaWidgetType, captchaError } = await this.getCaptchaToken(); @@ -741,7 +741,7 @@ class SignUpFuture implements SignUpFutureResource { }); } - async sendEmailCode(): Promise<{ error: unknown }> { + async sendEmailCode(): Promise<{ error: ClerkError | null }> { return runAsyncResourceTask(this.resource, async () => { await this.resource.__internal_basePost({ body: { strategy: 'email_code' }, @@ -750,7 +750,7 @@ class SignUpFuture implements SignUpFutureResource { }); } - async verifyEmailCode(params: SignUpFutureEmailCodeVerifyParams): Promise<{ error: unknown }> { + async verifyEmailCode(params: SignUpFutureEmailCodeVerifyParams): Promise<{ error: ClerkError | null }> { const { code } = params; return runAsyncResourceTask(this.resource, async () => { await this.resource.__internal_basePost({ @@ -760,7 +760,7 @@ class SignUpFuture implements SignUpFutureResource { }); } - async sendPhoneCode(params: SignUpFuturePhoneCodeSendParams): Promise<{ error: unknown }> { + async sendPhoneCode(params: SignUpFuturePhoneCodeSendParams): Promise<{ error: ClerkError | null }> { const { phoneNumber, channel = 'sms' } = params; return runAsyncResourceTask(this.resource, async () => { if (!this.resource.id) { @@ -778,7 +778,7 @@ class SignUpFuture implements SignUpFutureResource { }); } - async verifyPhoneCode(params: SignUpFuturePhoneCodeVerifyParams): Promise<{ error: unknown }> { + async verifyPhoneCode(params: SignUpFuturePhoneCodeVerifyParams): Promise<{ error: ClerkError | null }> { const { code } = params; return runAsyncResourceTask(this.resource, async () => { await this.resource.__internal_basePost({ @@ -788,7 +788,7 @@ class SignUpFuture implements SignUpFutureResource { }); } - async sso(params: SignUpFutureSSOParams): Promise<{ error: unknown }> { + async sso(params: SignUpFutureSSOParams): Promise<{ error: ClerkError | null }> { const { strategy, redirectUrl, redirectCallbackUrl } = params; return runAsyncResourceTask(this.resource, async () => { const { captchaToken, captchaWidgetType, captchaError } = await this.getCaptchaToken(); @@ -822,7 +822,7 @@ class SignUpFuture implements SignUpFutureResource { }); } - async web3(params: SignUpFutureWeb3Params): Promise<{ error: unknown }> { + async web3(params: SignUpFutureWeb3Params): Promise<{ error: ClerkError | null }> { const { strategy, unsafeMetadata, legalAccepted } = params; const provider = strategy.replace('web3_', '').replace('_signature', '') as Web3Provider; @@ -888,12 +888,12 @@ class SignUpFuture implements SignUpFutureResource { }); } - async ticket(params?: SignUpFutureTicketParams): Promise<{ error: unknown }> { + async ticket(params?: SignUpFutureTicketParams): Promise<{ error: ClerkError | null }> { const ticket = params?.ticket ?? getClerkQueryParam('__clerk_ticket'); return this.create({ ...params, ticket: ticket ?? undefined }); } - async finalize(params?: SignUpFutureFinalizeParams): Promise<{ error: unknown }> { + async finalize(params?: SignUpFutureFinalizeParams): Promise<{ error: ClerkError | null }> { const { navigate } = params || {}; return runAsyncResourceTask(this.resource, async () => { if (!this.resource.createdSessionId) { diff --git a/packages/clerk-js/src/utils/runAsyncResourceTask.ts b/packages/clerk-js/src/utils/runAsyncResourceTask.ts index 9d05f9fad41..5a2200a8288 100644 --- a/packages/clerk-js/src/utils/runAsyncResourceTask.ts +++ b/packages/clerk-js/src/utils/runAsyncResourceTask.ts @@ -1,3 +1,5 @@ +import type { ClerkError } from '@clerk/shared/error'; + import { eventBus } from '../core/events'; import type { BaseResource } from '../core/resources/internal'; @@ -8,7 +10,7 @@ import type { BaseResource } from '../core/resources/internal'; export async function runAsyncResourceTask( resource: BaseResource, task: () => Promise, -): Promise<{ result?: T; error: unknown }> { +): Promise<{ result?: T; error: ClerkError | null }> { eventBus.emit('resource:error', { resource, error: null }); eventBus.emit('resource:fetch', { resource, From f9ad025aae3ce0ebe025bc7ed53e00427723c7e7 Mon Sep 17 00:00:00 2001 From: Dylan Staley <88163+dstaley@users.noreply.github.com> Date: Thu, 13 Nov 2025 13:41:38 -0600 Subject: [PATCH 2/2] feat(clerk-js): Update errors to ClerkRuntimeError --- .../clerk-js/src/core/resources/SignIn.ts | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/packages/clerk-js/src/core/resources/SignIn.ts b/packages/clerk-js/src/core/resources/SignIn.ts index f5e9979de6c..8c56dcacb36 100644 --- a/packages/clerk-js/src/core/resources/SignIn.ts +++ b/packages/clerk-js/src/core/resources/SignIn.ts @@ -1,5 +1,5 @@ import { inBrowser } from '@clerk/shared/browser'; -import { type ClerkError, ClerkWebAuthnError } from '@clerk/shared/error'; +import { type ClerkError, ClerkRuntimeError, ClerkWebAuthnError } from '@clerk/shared/error'; import { Poller } from '@clerk/shared/poller'; import type { AttemptFirstFactorParams, @@ -687,17 +687,18 @@ class SignInFuture implements SignInFutureResource { } async sendResetPasswordEmailCode(): Promise<{ error: ClerkError | null }> { + if (!this.resource.id) { + throw new Error('Cannot reset password without a sign in.'); + } return runAsyncResourceTask(this.resource, async () => { - if (!this.resource.id) { - throw new Error('Cannot reset password without a sign in.'); - } - const resetPasswordEmailCodeFactor = this.resource.supportedFirstFactors?.find( f => f.strategy === 'reset_password_email_code', ); if (!resetPasswordEmailCodeFactor) { - throw new Error('Reset password email code factor not found'); + throw new ClerkRuntimeError('Reset password email code factor not found', { + code: 'factor_not_found', + }); } const { emailAddressId } = resetPasswordEmailCodeFactor; @@ -784,7 +785,7 @@ class SignInFuture implements SignInFutureResource { const emailCodeFactor = this.selectFirstFactor({ strategy: 'email_code', emailAddressId }); if (!emailCodeFactor) { - throw new Error('Email code factor not found'); + throw new ClerkRuntimeError('Email code factor not found', { code: 'factor_not_found' }); } await this.resource.__internal_basePost({ @@ -825,7 +826,7 @@ class SignInFuture implements SignInFutureResource { const emailLinkFactor = this.selectFirstFactor({ strategy: 'email_link', emailAddressId }); if (!emailLinkFactor) { - throw new Error('Email link factor not found'); + throw new ClerkRuntimeError('Email link factor not found', { code: 'factor_not_found' }); } let absoluteVerificationUrl = verificationUrl; @@ -888,7 +889,7 @@ class SignInFuture implements SignInFutureResource { const phoneCodeFactor = this.selectFirstFactor({ strategy: 'phone_code', phoneNumberId }); if (!phoneCodeFactor) { - throw new Error('Phone code factor not found'); + throw new ClerkRuntimeError('Phone code factor not found', { code: 'factor_not_found' }); } await this.resource.__internal_basePost({ @@ -993,7 +994,7 @@ class SignInFuture implements SignInFutureResource { f => f.strategy === strategy, ) as Web3SignatureFactor; if (!web3FirstFactor) { - throw new Error('Web3 first factor not found'); + throw new ClerkRuntimeError('Web3 first factor not found', { code: 'factor_not_found' }); } await this.resource.__internal_basePost({ @@ -1003,7 +1004,7 @@ class SignInFuture implements SignInFutureResource { const { message } = this.firstFactorVerification; if (!message) { - throw new Error('Web3 nonce not found'); + throw new ClerkRuntimeError('Web3 nonce not found', { code: 'web3_nonce_not_found' }); } let signature: string; @@ -1056,7 +1057,7 @@ class SignInFuture implements SignInFutureResource { const passKeyFactor = this.supportedFirstFactors.find(f => f.strategy === 'passkey') as PasskeyFactor; if (!passKeyFactor) { - clerkVerifyPasskeyCalledBeforeCreate(); + throw new ClerkRuntimeError('Passkey factor not found', { code: 'factor_not_found' }); } await this.resource.__internal_basePost({ body: { strategy: 'passkey' }, @@ -1068,7 +1069,7 @@ class SignInFuture implements SignInFutureResource { const publicKeyOptions = nonce ? convertJSONToPublicKeyRequestOptions(JSON.parse(nonce)) : null; if (!publicKeyOptions) { - clerkMissingWebAuthnPublicKeyOptions('get'); + throw new ClerkRuntimeError('Missing public key options', { code: 'missing_public_key_options' }); } let canUseConditionalUI = false; @@ -1088,7 +1089,7 @@ class SignInFuture implements SignInFutureResource { }); if (!publicKeyCredential) { - throw error; + throw new ClerkWebAuthnError(error.message, { code: 'passkey_retrieval_failed' }); } await this.resource.__internal_basePost({ @@ -1106,7 +1107,7 @@ class SignInFuture implements SignInFutureResource { const phoneCodeFactor = this.resource.supportedSecondFactors?.find(f => f.strategy === 'phone_code'); if (!phoneCodeFactor) { - throw new Error('Phone code factor not found'); + throw new ClerkRuntimeError('Phone code factor not found', { code: 'factor_not_found' }); } const { phoneNumberId } = phoneCodeFactor; @@ -1154,11 +1155,12 @@ class SignInFuture implements SignInFutureResource { async finalize(params?: SignInFutureFinalizeParams): Promise<{ error: ClerkError | null }> { const { navigate } = params || {}; - return runAsyncResourceTask(this.resource, async () => { - if (!this.resource.createdSessionId) { - throw new Error('Cannot finalize sign-in without a created session.'); - } + if (!this.resource.createdSessionId) { + throw new Error('Cannot finalize sign-in without a created session.'); + } + + return runAsyncResourceTask(this.resource, async () => { // Reload the client to prevent an issue where the created session is not picked up. await SignIn.clerk.client?.reload();