From 6142c8aa171572e1e0cf1c7d77801f80ec524d93 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Mon, 27 Oct 2025 11:15:49 +0000 Subject: [PATCH 1/2] feat(core): signInWithCustomToken --- packages/core/src/auth.test.ts | 77 +++++++++++++++++++++++++++++++--- packages/core/src/auth.ts | 13 ++++++ 2 files changed, 85 insertions(+), 5 deletions(-) diff --git a/packages/core/src/auth.test.ts b/packages/core/src/auth.test.ts index 2526c0e6c..9fa500d35 100644 --- a/packages/core/src/auth.test.ts +++ b/packages/core/src/auth.test.ts @@ -10,6 +10,7 @@ import { signInWithCredential, signInAnonymously, signInWithProvider, + signInWithCustomToken, generateTotpQrCode, } from "./auth"; @@ -19,6 +20,7 @@ vi.mock("firebase/auth", () => ({ sendPasswordResetEmail: vi.fn(), sendSignInLinkToEmail: vi.fn(), signInAnonymously: vi.fn(), + signInWithCustomToken: vi.fn(), signInWithRedirect: vi.fn(), isSignInWithEmailLink: vi.fn(), EmailAuthProvider: { @@ -49,6 +51,7 @@ import { sendPasswordResetEmail as _sendPasswordResetEmail, sendSignInLinkToEmail as _sendSignInLinkToEmail, signInAnonymously as _signInAnonymously, + signInWithCustomToken as _signInWithCustomToken, isSignInWithEmailLink as _isSignInWithEmailLink, UserCredential, Auth, @@ -958,14 +961,11 @@ describe("signInAnonymously", () => { const result = await signInAnonymously(mockUI); - // Verify state management expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]); - // Verify the Firebase function was called with correct parameters expect(_signInAnonymously).toHaveBeenCalledWith(mockUI.auth); expect(_signInAnonymously).toHaveBeenCalledTimes(1); - // Verify the result expect(result).toEqual(mockUserCredential); }); @@ -977,10 +977,77 @@ describe("signInAnonymously", () => { await signInAnonymously(mockUI); - // Verify error handling expect(handleFirebaseError).toHaveBeenCalledWith(mockUI, error); - // Verify state management still happens + expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]); + }); +}); + +describe("signInWithCustomToken", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should update state and call signInWithCustomToken successfully", async () => { + const mockUI = createMockUI(); + const customToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."; + const mockUserCredential = { + user: { uid: "custom-user-uid", email: "user@example.com" }, + providerId: "custom", + operationType: "signIn", + } as UserCredential; + + vi.mocked(_signInWithCustomToken).mockResolvedValue(mockUserCredential); + + const result = await signInWithCustomToken(mockUI, customToken); + + expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]); + + expect(_signInWithCustomToken).toHaveBeenCalledWith(mockUI.auth, customToken); + expect(_signInWithCustomToken).toHaveBeenCalledTimes(1); + + expect(result).toEqual(mockUserCredential); + }); + + it("should call handleFirebaseError if an error is thrown", async () => { + const mockUI = createMockUI(); + const customToken = "invalid-token"; + const error = new FirebaseError("auth/invalid-custom-token", "Invalid custom token"); + + vi.mocked(_signInWithCustomToken).mockRejectedValue(error); + + await signInWithCustomToken(mockUI, customToken); + + expect(handleFirebaseError).toHaveBeenCalledWith(mockUI, error); + + expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]); + }); + + it("should handle network errors", async () => { + const mockUI = createMockUI(); + const customToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."; + const error = new Error("Network error"); + + vi.mocked(_signInWithCustomToken).mockRejectedValue(error); + + await signInWithCustomToken(mockUI, customToken); + + expect(handleFirebaseError).toHaveBeenCalledWith(mockUI, error); + + expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]); + }); + + it("should handle expired custom token", async () => { + const mockUI = createMockUI(); + const customToken = "expired-token"; + const error = new FirebaseError("auth/custom-token-mismatch", "Custom token expired"); + + vi.mocked(_signInWithCustomToken).mockRejectedValue(error); + + await signInWithCustomToken(mockUI, customToken); + + expect(handleFirebaseError).toHaveBeenCalledWith(mockUI, error); + expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]); }); }); diff --git a/packages/core/src/auth.ts b/packages/core/src/auth.ts index 8458a1cd8..2fbb6b85d 100644 --- a/packages/core/src/auth.ts +++ b/packages/core/src/auth.ts @@ -21,6 +21,7 @@ import { sendSignInLinkToEmail as _sendSignInLinkToEmail, signInAnonymously as _signInAnonymously, signInWithCredential as _signInWithCredential, + signInWithCustomToken as _signInWithCustomToken, EmailAuthProvider, linkWithCredential, PhoneAuthProvider, @@ -220,6 +221,18 @@ export async function signInWithCredential(ui: FirebaseUI, credential: AuthCrede } } +export async function signInWithCustomToken(ui: FirebaseUI, customToken: string): Promise { + try { + ui.setState("pending"); + const result = await _signInWithCustomToken(ui.auth, customToken); + return handlePendingCredential(ui, result); + } catch (error) { + handleFirebaseError(ui, error); + } finally { + ui.setState("idle"); + } +} + export async function signInAnonymously(ui: FirebaseUI): Promise { try { ui.setState("pending"); From 5f46b6347b6a183157e8b2f707c5760961b6645b Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Mon, 27 Oct 2025 11:27:38 +0000 Subject: [PATCH 2/2] chore: Update with main changes --- packages/core/src/auth.test.ts | 6 ++++++ packages/core/src/auth.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/core/src/auth.test.ts b/packages/core/src/auth.test.ts index 9fa500d35..99670a2f7 100644 --- a/packages/core/src/auth.test.ts +++ b/packages/core/src/auth.test.ts @@ -1001,6 +1001,7 @@ describe("signInWithCustomToken", () => { const result = await signInWithCustomToken(mockUI, customToken); + expect(mockUI.setRedirectError).toHaveBeenCalledWith(undefined); expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]); expect(_signInWithCustomToken).toHaveBeenCalledWith(mockUI.auth, customToken); @@ -1018,6 +1019,7 @@ describe("signInWithCustomToken", () => { await signInWithCustomToken(mockUI, customToken); + expect(mockUI.setRedirectError).toHaveBeenCalledWith(undefined); expect(handleFirebaseError).toHaveBeenCalledWith(mockUI, error); expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]); @@ -1032,6 +1034,8 @@ describe("signInWithCustomToken", () => { await signInWithCustomToken(mockUI, customToken); + // Verify redirect error is cleared even when network error occurs + expect(mockUI.setRedirectError).toHaveBeenCalledWith(undefined); expect(handleFirebaseError).toHaveBeenCalledWith(mockUI, error); expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]); @@ -1046,6 +1050,8 @@ describe("signInWithCustomToken", () => { await signInWithCustomToken(mockUI, customToken); + // Verify redirect error is cleared even when token is expired + expect(mockUI.setRedirectError).toHaveBeenCalledWith(undefined); expect(handleFirebaseError).toHaveBeenCalledWith(mockUI, error); expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]); diff --git a/packages/core/src/auth.ts b/packages/core/src/auth.ts index f9c9d1294..3724a7cc6 100644 --- a/packages/core/src/auth.ts +++ b/packages/core/src/auth.ts @@ -257,7 +257,7 @@ export async function signInWithCredential(ui: FirebaseUI, credential: AuthCrede export async function signInWithCustomToken(ui: FirebaseUI, customToken: string): Promise { try { - ui.setState("pending"); + setPendingState(ui); const result = await _signInWithCustomToken(ui.auth, customToken); return handlePendingCredential(ui, result); } catch (error) {