From 35371de53b3f1c0aff7bab9320788a0837040d3f Mon Sep 17 00:00:00 2001 From: Jacek Date: Fri, 15 May 2026 07:32:20 -0500 Subject: [PATCH] fix(clerk-js): patch active signup resource in future api --- .changeset/signup-future-resource-path.md | 5 + .../clerk-js/src/core/resources/SignUp.ts | 4 +- .../core/resources/__tests__/SignUp.test.ts | 137 ++++++++++++++++++ 3 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 .changeset/signup-future-resource-path.md diff --git a/.changeset/signup-future-resource-path.md b/.changeset/signup-future-resource-path.md new file mode 100644 index 00000000000..7090f6514af --- /dev/null +++ b/.changeset/signup-future-resource-path.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Fix Future `signUp.update()` and `signUp.sso()` to patch the active sign-up resource URL when continuing an existing sign-up. diff --git a/packages/clerk-js/src/core/resources/SignUp.ts b/packages/clerk-js/src/core/resources/SignUp.ts index d0c884509ed..bac2bcc9183 100644 --- a/packages/clerk-js/src/core/resources/SignUp.ts +++ b/packages/clerk-js/src/core/resources/SignUp.ts @@ -886,7 +886,7 @@ class SignUpFuture implements SignUpFutureResource { unsafeMetadata: params.unsafeMetadata ? normalizeUnsafeMetadata(params.unsafeMetadata) : undefined, }; - await this.#resource.__internal_basePatch({ path: this.#resource.pathRoot, body }); + await this.#resource.__internal_basePatch({ body }); }); } @@ -1039,7 +1039,7 @@ class SignUpFuture implements SignUpFutureResource { captchaError, }; if (this.#resource.id) { - return this.#resource.__internal_basePatch({ path: this.#resource.pathRoot, body }); + return this.#resource.__internal_basePatch({ body }); } return this.#resource.__internal_basePost({ path: this.#resource.pathRoot, body }); }; diff --git a/packages/clerk-js/src/core/resources/__tests__/SignUp.test.ts b/packages/clerk-js/src/core/resources/__tests__/SignUp.test.ts index 96257b65b73..0ed3f278136 100644 --- a/packages/clerk-js/src/core/resources/__tests__/SignUp.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/SignUp.test.ts @@ -364,6 +364,35 @@ describe('SignUp', () => { }); }); + describe('update', () => { + afterEach(() => { + vi.clearAllMocks(); + vi.unstubAllGlobals(); + SignUp.clerk = {} as any; + }); + + it('patches the active sign up resource', async () => { + const mockFetch = vi.fn().mockResolvedValue({ + client: null, + response: { id: 'signup_123', first_name: 'Ada' }, + }); + BaseResource._fetch = mockFetch; + + const signUp = new SignUp({ id: 'signup_123' } as any); + await signUp.__internal_future.update({ firstName: 'Ada' }); + + expect(mockFetch).toHaveBeenCalledWith( + expect.objectContaining({ + method: 'PATCH', + path: '/client/sign_ups/signup_123', + body: expect.objectContaining({ + firstName: 'Ada', + }), + }), + ); + }); + }); + describe('sendPhoneCode', () => { afterEach(() => { vi.clearAllMocks(); @@ -669,6 +698,114 @@ describe('SignUp', () => { ); }); + it('continues an existing sign up via the resource URL', async () => { + vi.stubGlobal('window', { location: { origin: 'https://example.com' } }); + + const mockBuildUrlWithAuth = vi.fn().mockReturnValue('https://example.com/sso-callback'); + SignUp.clerk = { + buildUrlWithAuth: mockBuildUrlWithAuth, + __internal_environment: { + displayConfig: { + captchaOauthBypass: [], + }, + }, + } as any; + + const mockFetch = vi.fn().mockResolvedValue({ + client: null, + response: { + id: 'signup_123', + verifications: { + external_account: { + status: 'complete', + }, + }, + }, + }); + BaseResource._fetch = mockFetch; + + const signUp = new SignUp({ id: 'signup_123' } as any); + await signUp.__internal_future.sso({ + strategy: 'oauth_google', + redirectUrl: '/complete', + redirectCallbackUrl: '/sso-callback', + }); + + expect(mockFetch).toHaveBeenCalledWith( + expect.objectContaining({ + method: 'PATCH', + path: '/client/sign_ups/signup_123', + body: expect.objectContaining({ + strategy: 'oauth_google', + redirectUrl: 'https://example.com/sso-callback', + actionCompleteRedirectUrl: 'https://example.com/complete', + }), + }), + ); + }); + + it('continues a ticket sign up with sso via the resource URL', async () => { + vi.stubGlobal('window', { location: { origin: 'https://example.com' } }); + + const mockBuildUrlWithAuth = vi.fn().mockReturnValue('https://example.com/sso-callback'); + SignUp.clerk = { + buildUrlWithAuth: mockBuildUrlWithAuth, + __internal_environment: { + displayConfig: { + captchaOauthBypass: [], + }, + }, + } as any; + + const mockFetch = vi + .fn() + .mockResolvedValueOnce({ + client: null, + response: { id: 'signup_123' }, + }) + .mockResolvedValueOnce({ + client: null, + response: { + id: 'signup_123', + verifications: { + external_account: { + status: 'complete', + }, + }, + }, + }); + BaseResource._fetch = mockFetch; + + const signUp = new SignUp(); + await signUp.__internal_future.ticket({ ticket: 'provided_ticket' }); + await signUp.__internal_future.sso({ + strategy: 'oauth_google', + redirectUrl: '/complete', + redirectCallbackUrl: '/sso-callback', + }); + + expect(mockFetch).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + method: 'POST', + path: '/client/sign_ups', + body: expect.objectContaining({ + ticket: 'provided_ticket', + }), + }), + ); + expect(mockFetch).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + method: 'PATCH', + path: '/client/sign_ups/signup_123', + body: expect.objectContaining({ + strategy: 'oauth_google', + }), + }), + ); + }); + it('uses popup when provided', async () => { vi.stubGlobal('window', { location: { origin: 'https://example.com' } });