diff --git a/examples/react/src/firebase/firebase.ts b/examples/react/src/firebase/firebase.ts index 45240ad2..57118dca 100644 --- a/examples/react/src/firebase/firebase.ts +++ b/examples/react/src/firebase/firebase.ts @@ -19,7 +19,7 @@ import { initializeApp, getApps } from "firebase/app"; import { firebaseConfig } from "./config"; import { connectAuthEmulator, getAuth } from "firebase/auth"; -import { autoAnonymousLogin, initializeUI } from "@firebase-ui/core"; +import { autoAnonymousLogin, initializeUI, oneTapSignIn } from "@firebase-ui/core"; export const firebaseApp = getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0]; @@ -27,7 +27,9 @@ export const auth = getAuth(firebaseApp); export const ui = initializeUI({ app: firebaseApp, - behaviors: [autoAnonymousLogin()], + behaviors: [autoAnonymousLogin(), oneTapSignIn({ + clientId: '200312857118-lscdui98fkaq7ffr81446blafjn5o6r0.apps.googleusercontent.com', + })], }); if (import.meta.env.MODE === "development") { diff --git a/packages/core/package.json b/packages/core/package.json index 9f5768df..10d03584 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -52,6 +52,7 @@ "zod": "catalog:" }, "devDependencies": { + "@types/google-one-tap": "^1.2.6", "@types/jsdom": "catalog:", "firebase": "catalog:", "jsdom": "catalog:", @@ -60,7 +61,7 @@ "tsup": "catalog:", "typescript": "catalog:", "vite": "catalog:", - "vitest-tsconfig-paths": "catalog:", - "vitest": "catalog:" + "vitest": "catalog:", + "vitest-tsconfig-paths": "catalog:" } } diff --git a/packages/core/src/auth.test.ts b/packages/core/src/auth.test.ts index 53649a10..dd4f025d 100644 --- a/packages/core/src/auth.test.ts +++ b/packages/core/src/auth.test.ts @@ -1,8 +1,6 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { signInWithEmailAndPassword, createUserWithEmailAndPassword, signInWithPhoneNumber, confirmPhoneNumber, sendPasswordResetEmail, sendSignInLinkToEmail, signInWithEmailLink, signInAnonymously, signInWithProvider, completeEmailLinkSignIn, } from "./auth"; -import type { FirebaseUIConfiguration } from "./config"; +import { signInWithEmailAndPassword, createUserWithEmailAndPassword, signInWithPhoneNumber, confirmPhoneNumber, sendPasswordResetEmail, sendSignInLinkToEmail, signInWithEmailLink, signInWithCredential, signInAnonymously, signInWithProvider, completeEmailLinkSignIn, } from "./auth"; -// Mock the external dependencies vi.mock("firebase/auth", () => ({ signInWithCredential: vi.fn(), createUserWithEmailAndPassword: vi.fn(), @@ -32,7 +30,7 @@ vi.mock("./errors", () => ({ })); // Import the mocked functions -import { signInWithCredential, EmailAuthProvider, PhoneAuthProvider, createUserWithEmailAndPassword as _createUserWithEmailAndPassword, signInWithPhoneNumber as _signInWithPhoneNumber, sendPasswordResetEmail as _sendPasswordResetEmail, sendSignInLinkToEmail as _sendSignInLinkToEmail, signInAnonymously as _signInAnonymously, signInWithRedirect, isSignInWithEmailLink as _isSignInWithEmailLink, UserCredential, Auth, ConfirmationResult, AuthProvider } from "firebase/auth"; +import { signInWithCredential as _signInWithCredential, EmailAuthProvider, PhoneAuthProvider, createUserWithEmailAndPassword as _createUserWithEmailAndPassword, signInWithPhoneNumber as _signInWithPhoneNumber, sendPasswordResetEmail as _sendPasswordResetEmail, sendSignInLinkToEmail as _sendSignInLinkToEmail, signInAnonymously as _signInAnonymously, signInWithRedirect, isSignInWithEmailLink as _isSignInWithEmailLink, UserCredential, Auth, ConfirmationResult, AuthProvider } from "firebase/auth"; import { hasBehavior, getBehavior } from "./behaviors"; import { handleFirebaseError } from "./errors"; import { FirebaseError } from "firebase/app"; @@ -46,7 +44,7 @@ describe("signInWithEmailAndPassword", () => { vi.clearAllMocks(); }); - it("should update state and call signInWithCredential with no behavior", async () => { + it("should update state and call _signInWithCredential with no behavior", async () => { const mockUI = createMockUI(); const email = "test@example.com"; const password = "password123"; @@ -54,17 +52,17 @@ describe("signInWithEmailAndPassword", () => { const credential = EmailAuthProvider.credential(email, password); vi.mocked(hasBehavior).mockReturnValue(false); vi.mocked(EmailAuthProvider.credential).mockReturnValue(credential); - vi.mocked(signInWithCredential).mockResolvedValue({ providerId: "password" } as UserCredential); + vi.mocked(_signInWithCredential).mockResolvedValue({ providerId: "password" } as UserCredential); const result = await signInWithEmailAndPassword(mockUI, email, password); expect(hasBehavior).toHaveBeenCalledWith(mockUI, "autoUpgradeAnonymousCredential"); - // Calls pending pre-signInWithCredential call, then idle after. + // Calls pending pre-_signInWithCredential call, then idle after. expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]); - expect(signInWithCredential).toHaveBeenCalledWith(mockUI.auth, credential); - expect(signInWithCredential).toHaveBeenCalledTimes(1); + expect(_signInWithCredential).toHaveBeenCalledWith(mockUI.auth, credential); + expect(_signInWithCredential).toHaveBeenCalledTimes(1); // Assert that the result is a valid UserCredential. expect(result.providerId).toBe("password"); @@ -111,10 +109,10 @@ describe("signInWithEmailAndPassword", () => { expect(mockBehavior).toHaveBeenCalledWith(mockUI, credential); - expect(signInWithCredential).toHaveBeenCalledWith(mockUI.auth, credential); - expect(signInWithCredential).toHaveBeenCalledTimes(1); + expect(_signInWithCredential).toHaveBeenCalledWith(mockUI.auth, credential); + expect(_signInWithCredential).toHaveBeenCalledTimes(1); - // Calls pending pre-signInWithCredential call, then idle after. + // Calls pending pre-_signInWithCredential call, then idle after. expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]); }); @@ -127,7 +125,7 @@ describe("signInWithEmailAndPassword", () => { const error = new FirebaseError('foo/bar', 'Foo bar'); - vi.mocked(signInWithCredential).mockRejectedValue(error); + vi.mocked(_signInWithCredential).mockRejectedValue(error); await signInWithEmailAndPassword(mockUI, email, password); @@ -299,7 +297,7 @@ describe("confirmPhoneNumber", () => { vi.clearAllMocks(); }); - it("should update state and call signInWithCredential with no behavior", async () => { + it("should update state and call _signInWithCredential with no behavior", async () => { const mockUI = createMockUI({ auth: { currentUser: null } as Auth }); @@ -309,18 +307,18 @@ describe("confirmPhoneNumber", () => { const credential = PhoneAuthProvider.credential(confirmationResult.verificationId, verificationCode); vi.mocked(hasBehavior).mockReturnValue(false); vi.mocked(PhoneAuthProvider.credential).mockReturnValue(credential); - vi.mocked(signInWithCredential).mockResolvedValue({ providerId: "phone" } as UserCredential); + vi.mocked(_signInWithCredential).mockResolvedValue({ providerId: "phone" } as UserCredential); const result = await confirmPhoneNumber(mockUI, confirmationResult, verificationCode); // Since currentUser is null, the behavior should not called. expect(hasBehavior).toHaveBeenCalledTimes(0); - // Calls pending pre-signInWithCredential call, then idle after. + // Calls pending pre-_signInWithCredential call, then idle after. expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]); - expect(signInWithCredential).toHaveBeenCalledWith(mockUI.auth, credential); - expect(signInWithCredential).toHaveBeenCalledTimes(1); + expect(_signInWithCredential).toHaveBeenCalledWith(mockUI.auth, credential); + expect(_signInWithCredential).toHaveBeenCalledTimes(1); // Assert that the result is a valid UserCredential. expect(result.providerId).toBe("phone"); @@ -360,7 +358,7 @@ describe("confirmPhoneNumber", () => { const credential = PhoneAuthProvider.credential(confirmationResult.verificationId, verificationCode); vi.mocked(PhoneAuthProvider.credential).mockReturnValue(credential); - vi.mocked(signInWithCredential).mockResolvedValue({ providerId: "phone" } as UserCredential); + vi.mocked(_signInWithCredential).mockResolvedValue({ providerId: "phone" } as UserCredential); const result = await confirmPhoneNumber(mockUI, confirmationResult, verificationCode); @@ -368,7 +366,7 @@ describe("confirmPhoneNumber", () => { expect(hasBehavior).not.toHaveBeenCalled(); // Should proceed with normal sign-in flow - expect(signInWithCredential).toHaveBeenCalledWith(mockUI.auth, credential); + expect(_signInWithCredential).toHaveBeenCalledWith(mockUI.auth, credential); expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]); expect(result.providerId).toBe("phone"); }); @@ -382,7 +380,7 @@ describe("confirmPhoneNumber", () => { const credential = PhoneAuthProvider.credential(confirmationResult.verificationId, verificationCode); vi.mocked(PhoneAuthProvider.credential).mockReturnValue(credential); - vi.mocked(signInWithCredential).mockResolvedValue({ providerId: "phone" } as UserCredential); + vi.mocked(_signInWithCredential).mockResolvedValue({ providerId: "phone" } as UserCredential); const result = await confirmPhoneNumber(mockUI, confirmationResult, verificationCode); @@ -390,7 +388,7 @@ describe("confirmPhoneNumber", () => { expect(hasBehavior).not.toHaveBeenCalled(); // Should proceed with normal sign-in flow - expect(signInWithCredential).toHaveBeenCalledWith(mockUI.auth, credential); + expect(_signInWithCredential).toHaveBeenCalledWith(mockUI.auth, credential); expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]); expect(result.providerId).toBe("phone"); }); @@ -415,10 +413,10 @@ describe("confirmPhoneNumber", () => { expect(mockBehavior).toHaveBeenCalledWith(mockUI, credential); - expect(signInWithCredential).toHaveBeenCalledWith(mockUI.auth, credential); - expect(signInWithCredential).toHaveBeenCalledTimes(1); + expect(_signInWithCredential).toHaveBeenCalledWith(mockUI.auth, credential); + expect(_signInWithCredential).toHaveBeenCalledTimes(1); - // Calls pending pre-signInWithCredential call, then idle after. + // Calls pending pre-_signInWithCredential call, then idle after. expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]); }); @@ -431,7 +429,7 @@ describe("confirmPhoneNumber", () => { const error = new FirebaseError('auth/invalid-verification-code', 'Invalid verification code'); - vi.mocked(signInWithCredential).mockRejectedValue(error); + vi.mocked(_signInWithCredential).mockRejectedValue(error); await confirmPhoneNumber(mockUI, confirmationResult, verificationCode); @@ -573,7 +571,7 @@ describe("sendPasswordResetEmail", () => { vi.clearAllMocks(); }); - it("should update state and call signInWithCredential with no behavior", async () => { + it("should create credential and call signInWithCredential with no behavior", async () => { const mockUI = createMockUI(); const email = "test@example.com"; const link = "https://example.com/auth?oobCode=abc123"; @@ -581,17 +579,17 @@ describe("sendPasswordResetEmail", () => { const credential = EmailAuthProvider.credentialWithLink(email, link); vi.mocked(hasBehavior).mockReturnValue(false); vi.mocked(EmailAuthProvider.credentialWithLink).mockReturnValue(credential); - vi.mocked(signInWithCredential).mockResolvedValue({ providerId: "emailLink" } as UserCredential); + vi.mocked(_signInWithCredential).mockResolvedValue({ providerId: "emailLink" } as UserCredential); const result = await signInWithEmailLink(mockUI, email, link); + // Verify credential was created correctly + expect(EmailAuthProvider.credentialWithLink).toHaveBeenCalledWith(email, link); + + // Verify our signInWithCredential function was called (which internally calls Firebase) expect(hasBehavior).toHaveBeenCalledWith(mockUI, "autoUpgradeAnonymousCredential"); - - // Calls pending pre-signInWithCredential call, then idle after. - expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]); - - expect(signInWithCredential).toHaveBeenCalledWith(mockUI.auth, credential); - expect(signInWithCredential).toHaveBeenCalledTimes(1); + expect(_signInWithCredential).toHaveBeenCalledWith(mockUI.auth, credential); + expect(_signInWithCredential).toHaveBeenCalledTimes(1); // Assert that the result is a valid UserCredential. expect(result.providerId).toBe("emailLink"); @@ -610,6 +608,10 @@ describe("sendPasswordResetEmail", () => { const result = await signInWithEmailLink(mockUI, email, link); + // Verify credential was created correctly + expect(EmailAuthProvider.credentialWithLink).toHaveBeenCalledWith(email, link); + + // Verify our signInWithCredential function was called with behavior expect(hasBehavior).toHaveBeenCalledWith(mockUI, "autoUpgradeAnonymousCredential"); expect(getBehavior).toHaveBeenCalledWith(mockUI, "autoUpgradeAnonymousCredential"); @@ -633,15 +635,19 @@ describe("sendPasswordResetEmail", () => { await signInWithEmailLink(mockUI, email, link); + // Verify credential was created correctly + expect(EmailAuthProvider.credentialWithLink).toHaveBeenCalledWith(email, link); + + // Verify our signInWithCredential function was called with behavior expect(hasBehavior).toHaveBeenCalledWith(mockUI, "autoUpgradeAnonymousCredential"); expect(getBehavior).toHaveBeenCalledWith(mockUI, "autoUpgradeAnonymousCredential"); expect(mockBehavior).toHaveBeenCalledWith(mockUI, credential); - expect(signInWithCredential).toHaveBeenCalledWith(mockUI.auth, credential); - expect(signInWithCredential).toHaveBeenCalledTimes(1); + expect(_signInWithCredential).toHaveBeenCalledWith(mockUI.auth, credential); + expect(_signInWithCredential).toHaveBeenCalledTimes(1); - // Calls pending pre-signInWithCredential call, then idle after. + // Calls pending pre-_signInWithCredential call, then idle after. expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]); }); @@ -654,15 +660,123 @@ describe("sendPasswordResetEmail", () => { const error = new FirebaseError('auth/invalid-action-code', 'Invalid action code'); - vi.mocked(signInWithCredential).mockRejectedValue(error); + vi.mocked(_signInWithCredential).mockRejectedValue(error); await signInWithEmailLink(mockUI, email, link); + // Verify credential was created correctly + expect(EmailAuthProvider.credentialWithLink).toHaveBeenCalledWith(email, link); + + // Verify our signInWithCredential function was called and error was handled + expect(hasBehavior).toHaveBeenCalledWith(mockUI, "autoUpgradeAnonymousCredential"); expect(handleFirebaseError).toHaveBeenCalledWith(mockUI, error); expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]); }); }); + describe("signInWithCredential", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should update state and call _signInWithCredential with no behavior", async () => { + const mockUI = createMockUI(); + const credential = { providerId: "password" } as any; + + vi.mocked(hasBehavior).mockReturnValue(false); + vi.mocked(_signInWithCredential).mockResolvedValue({ providerId: "password" } as UserCredential); + + const result = await signInWithCredential(mockUI, credential); + + expect(hasBehavior).toHaveBeenCalledWith(mockUI, "autoUpgradeAnonymousCredential"); + + // Calls pending pre-_signInWithCredential call, then idle after. + expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]); + + expect(_signInWithCredential).toHaveBeenCalledWith(mockUI.auth, credential); + expect(_signInWithCredential).toHaveBeenCalledTimes(1); + + // Assert that the result is a valid UserCredential. + expect(result.providerId).toBe("password"); + }); + + it('should call the autoUpgradeAnonymousCredential behavior if enabled and return a value', async () => { + const mockUI = createMockUI(); + const credential = { providerId: "password" } as any; + + vi.mocked(hasBehavior).mockReturnValue(true); + const mockBehavior = vi.fn().mockResolvedValue({ providerId: "password" } as UserCredential); + vi.mocked(getBehavior).mockReturnValue(mockBehavior); + + const result = await signInWithCredential(mockUI, credential); + + expect(hasBehavior).toHaveBeenCalledWith(mockUI, "autoUpgradeAnonymousCredential"); + expect(getBehavior).toHaveBeenCalledWith(mockUI, "autoUpgradeAnonymousCredential"); + + expect(mockBehavior).toHaveBeenCalledWith(mockUI, credential); + expect(result.providerId).toBe("password"); + + // Only the `finally` block is called here. + expect(vi.mocked(mockUI.setState).mock.calls).toEqual([['idle']]); + }); + + it('should call the autoUpgradeAnonymousCredential behavior if enabled and handle no result from the behavior', async () => { + const mockUI = createMockUI(); + const credential = { providerId: "password" } as any; + + vi.mocked(hasBehavior).mockReturnValue(true); + const mockBehavior = vi.fn().mockResolvedValue(undefined); + vi.mocked(getBehavior).mockReturnValue(mockBehavior); + + await signInWithCredential(mockUI, credential); + + expect(hasBehavior).toHaveBeenCalledWith(mockUI, "autoUpgradeAnonymousCredential"); + expect(getBehavior).toHaveBeenCalledWith(mockUI, "autoUpgradeAnonymousCredential"); + + expect(mockBehavior).toHaveBeenCalledWith(mockUI, credential); + + expect(_signInWithCredential).toHaveBeenCalledWith(mockUI.auth, credential); + expect(_signInWithCredential).toHaveBeenCalledTimes(1); + + // Calls pending pre-_signInWithCredential call, then idle after. + expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]); + }); + + it("should call handleFirebaseError if an error is thrown", async () => { + const mockUI = createMockUI(); + const credential = { providerId: "password" } as any; + + vi.mocked(hasBehavior).mockReturnValue(false); + + const error = new FirebaseError('auth/invalid-credential', 'Invalid credential'); + + vi.mocked(_signInWithCredential).mockRejectedValue(error); + + await signInWithCredential(mockUI, credential); + + expect(handleFirebaseError).toHaveBeenCalledWith(mockUI, error); + expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"],["idle"]]); + }); + + it("should handle behavior errors", async () => { + const mockUI = createMockUI(); + const credential = { providerId: "password" } as any; + const error = new Error("Behavior error"); + + vi.mocked(hasBehavior).mockReturnValue(true); + const mockBehavior = vi.fn().mockRejectedValue(error); + vi.mocked(getBehavior).mockReturnValue(mockBehavior); + + await signInWithCredential(mockUI, credential); + + expect(handleFirebaseError).toHaveBeenCalledWith(mockUI, error); + + expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["idle"]]); + + expect(_signInWithCredential).not.toHaveBeenCalled(); + }); + }); + describe("signInAnonymously", () => { beforeEach(() => { vi.clearAllMocks(); diff --git a/packages/core/src/auth.ts b/packages/core/src/auth.ts index b8d38339..38de6518 100644 --- a/packages/core/src/auth.ts +++ b/packages/core/src/auth.ts @@ -21,6 +21,7 @@ import { sendSignInLinkToEmail as _sendSignInLinkToEmail, signInAnonymously as _signInAnonymously, signInWithPhoneNumber as _signInWithPhoneNumber, + signInWithCredential as _signInWithCredential, ActionCodeSettings, ApplicationVerifier, AuthProvider, @@ -28,9 +29,9 @@ import { EmailAuthProvider, linkWithCredential, PhoneAuthProvider, - signInWithCredential, signInWithRedirect, UserCredential, + AuthCredential, } from "firebase/auth"; import { FirebaseUIConfiguration } from "./config"; import { handleFirebaseError } from "./errors"; @@ -70,7 +71,7 @@ export async function signInWithEmailAndPassword( } ui.setState("pending"); - const result = await signInWithCredential(ui.auth, credential); + const result = await _signInWithCredential(ui.auth, credential); return handlePendingCredential(ui, result); } catch (error) { handleFirebaseError(ui, error); @@ -138,7 +139,7 @@ export async function confirmPhoneNumber( } ui.setState("pending"); - const result = await signInWithCredential(ui.auth, credential); + const result = await _signInWithCredential(ui.auth, credential); return handlePendingCredential(ui, result); } catch (error) { handleFirebaseError(ui, error); @@ -182,18 +183,27 @@ export async function signInWithEmailLink( email: string, link: string ): Promise { - try { - const credential = EmailAuthProvider.credentialWithLink(email, link); + const credential = EmailAuthProvider.credentialWithLink(email, link); + return signInWithCredential(ui, credential); +} +export async function signInWithCredential( + ui: FirebaseUIConfiguration, + credential: AuthCredential +): Promise { + try { if (hasBehavior(ui, "autoUpgradeAnonymousCredential")) { - const result = await getBehavior(ui, "autoUpgradeAnonymousCredential")(ui, credential); - if (result) { - return handlePendingCredential(ui, result); + const userCredential = await getBehavior(ui, "autoUpgradeAnonymousCredential")(ui, credential); + + // If they got here, they're either not anonymous or they've been linked. + // If the credential has been linked, we don't need to sign them in, so return early. + if (userCredential) { + return handlePendingCredential(ui, userCredential); } } ui.setState("pending"); - const result = await signInWithCredential(ui.auth, credential); + const result = await _signInWithCredential(ui.auth, credential); return handlePendingCredential(ui, result); } catch (error) { handleFirebaseError(ui, error); diff --git a/packages/core/src/behaviors/index.ts b/packages/core/src/behaviors/index.ts index a2b310c2..0ec2341c 100644 --- a/packages/core/src/behaviors/index.ts +++ b/packages/core/src/behaviors/index.ts @@ -3,6 +3,7 @@ import type { RecaptchaVerifier } from "firebase/auth"; import * as anonymousUpgradeHandlers from "./anonymous-upgrade"; import * as autoAnonymousLoginHandlers from "./auto-anonymous-login"; import * as recaptchaHandlers from "./recaptcha"; +import * as oneTapSignInHandlers from "./one-tap"; import { callableBehavior, initBehavior, @@ -22,6 +23,7 @@ type Registry = { typeof anonymousUpgradeHandlers.autoUpgradeAnonymousUserRedirectHandler >; recaptchaVerification: CallableBehavior<(ui: FirebaseUIConfiguration, element: HTMLElement) => RecaptchaVerifier>; + oneTapSignIn: InitBehavior<(ui: FirebaseUIConfiguration) => ReturnType>; }; export type Behavior = Pick; @@ -55,6 +57,14 @@ export function recaptchaVerification(options?: RecaptchaVerificationOptions): B }; } +export type OneTapSignInOptions = oneTapSignInHandlers.OneTapSignInOptions; + +export function oneTapSignIn(options: OneTapSignInOptions): Behavior<"oneTapSignIn"> { + return { + oneTapSignIn: initBehavior((ui) => oneTapSignInHandlers.oneTapSignInHandler(ui, options)), + }; +} + export function hasBehavior(ui: FirebaseUIConfiguration, key: T): boolean { return !!ui.behaviors[key]; } diff --git a/packages/core/src/behaviors/one-tap.test.ts b/packages/core/src/behaviors/one-tap.test.ts new file mode 100644 index 00000000..3e63250f --- /dev/null +++ b/packages/core/src/behaviors/one-tap.test.ts @@ -0,0 +1,324 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { Auth, User } from "firebase/auth"; +import { oneTapSignInHandler, type OneTapSignInOptions } from "./one-tap"; +import { createMockUI } from "~/tests/utils"; + +vi.mock("firebase/auth", () => ({ + GoogleAuthProvider: { + credential: vi.fn(), + }, +})); + +vi.mock("~/auth", () => ({ + signInWithCredential: vi.fn(), +})); + +const mockGoogleAccounts = { + id: { + initialize: vi.fn(), + prompt: vi.fn(), + }, +}; + +Object.defineProperty(window, 'google', { + value: { accounts: mockGoogleAccounts }, + writable: true, +}); + +Object.defineProperty(document, 'createElement', { + value: vi.fn(() => ({ + setAttribute: vi.fn(), + src: '', + async: false, + onload: null, + })), + writable: true, +}); + +Object.defineProperty(document, 'querySelector', { + value: vi.fn(), + writable: true, +}); + +Object.defineProperty(document.body, 'appendChild', { + value: vi.fn(), + writable: true, +}); + +import { GoogleAuthProvider } from "firebase/auth"; +import { signInWithCredential } from "~/auth"; + +describe("oneTapSignInHandler", () => { + let mockUI: ReturnType; + let mockScript: any; + let mockCreateElement: any; + + beforeEach(() => { + vi.clearAllMocks(); + + mockScript = { + setAttribute: vi.fn(), + src: '', + async: false, + onload: null, + }; + + mockCreateElement = vi.fn(() => mockScript); + Object.defineProperty(document, 'createElement', { + value: mockCreateElement, + writable: true, + }); + + vi.mocked(document.querySelector).mockReturnValue(null); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe("user authentication state checks", () => { + it("should not initialize one-tap when user is already signed in with real account", async () => { + const mockUser = { isAnonymous: false, uid: "real-user-123" } as User; + mockUI = createMockUI({ auth: { currentUser: mockUser } as Auth }); + + const options: OneTapSignInOptions = { clientId: "test-client-id" }; + + await oneTapSignInHandler(mockUI, options); + + expect(document.createElement).not.toHaveBeenCalled(); + expect(mockGoogleAccounts.id.initialize).not.toHaveBeenCalled(); + }); + + it("should initialize one-tap when user is anonymous", async () => { + const mockUser = { isAnonymous: true, uid: "anonymous-123" } as User; + mockUI = createMockUI({ auth: { currentUser: mockUser } as Auth }); + + const options: OneTapSignInOptions = { clientId: "test-client-id" }; + + await oneTapSignInHandler(mockUI, options); + + expect(document.createElement).toHaveBeenCalledWith('script'); + expect(mockScript.setAttribute).toHaveBeenCalledWith('data-one-tap-sign-in', 'true'); + expect(mockScript.src).toBe('https://accounts.google.com/gsi/client'); + expect(mockScript.async).toBe(true); + }); + + it("should initialize one-tap when no current user exists", async () => { + mockUI = createMockUI({ auth: { currentUser: null } as Auth }); + + const options: OneTapSignInOptions = { clientId: "test-client-id" }; + + await oneTapSignInHandler(mockUI, options); + + expect(document.createElement).toHaveBeenCalledWith('script'); + expect(mockScript.setAttribute).toHaveBeenCalledWith('data-one-tap-sign-in', 'true'); + expect(mockScript.src).toBe('https://accounts.google.com/gsi/client'); + expect(mockScript.async).toBe(true); + }); + }); + + describe("script loading prevention", () => { + it("should not load script if one-tap script already exists", async () => { + mockUI = createMockUI({ auth: { currentUser: null } as Auth }); + + const existingScript = { tagName: 'script' }; + vi.mocked(document.querySelector).mockReturnValue(existingScript as any); + + const options: OneTapSignInOptions = { clientId: "test-client-id" }; + + await oneTapSignInHandler(mockUI, options); + + expect(document.createElement).not.toHaveBeenCalled(); + expect(mockGoogleAccounts.id.initialize).not.toHaveBeenCalled(); + }); + + it("should check for existing script with correct selector", async () => { + mockUI = createMockUI({ auth: { currentUser: null } as Auth }); + + const options: OneTapSignInOptions = { clientId: "test-client-id" }; + + await oneTapSignInHandler(mockUI, options); + + expect(document.querySelector).toHaveBeenCalledWith('script[data-one-tap-sign-in]'); + }); + }); + + describe("script loading and initialization", () => { + beforeEach(() => { + mockUI = createMockUI({ auth: { currentUser: null } as Auth }); + }); + + it("should create and append script with correct attributes", async () => { + const options: OneTapSignInOptions = { clientId: "test-client-id" }; + + await oneTapSignInHandler(mockUI, options); + + expect(document.createElement).toHaveBeenCalledWith('script'); + expect(mockScript.setAttribute).toHaveBeenCalledWith('data-one-tap-sign-in', 'true'); + expect(mockScript.src).toBe('https://accounts.google.com/gsi/client'); + expect(mockScript.async).toBe(true); + expect(document.body.appendChild).toHaveBeenCalledWith(mockScript); + }); + + it("should initialize Google One Tap with basic options", async () => { + const options: OneTapSignInOptions = { clientId: "test-client-id" }; + + await oneTapSignInHandler(mockUI, options); + + if (mockScript.onload) { + mockScript.onload(); + } + + expect(mockGoogleAccounts.id.initialize).toHaveBeenCalledWith({ + client_id: "test-client-id", + auto_select: undefined, + cancel_on_tap_outside: undefined, + context: undefined, + ux_mode: undefined, + log_level: undefined, + callback: expect.any(Function), + }); + }); + + it("should initialize Google One Tap with all options", async () => { + const options: OneTapSignInOptions = { + clientId: "test-client-id", + autoSelect: true, + cancelOnTapOutside: false, + context: "signin", + uxMode: "popup", + logLevel: "debug", + }; + + await oneTapSignInHandler(mockUI, options); + + if (mockScript.onload) { + mockScript.onload(); + } + + expect(mockGoogleAccounts.id.initialize).toHaveBeenCalledWith({ + client_id: "test-client-id", + auto_select: true, + cancel_on_tap_outside: false, + context: "signin", + ux_mode: "popup", + log_level: "debug", + callback: expect.any(Function), + }); + }); + + it("should call prompt after initialization", async () => { + const options: OneTapSignInOptions = { clientId: "test-client-id" }; + + await oneTapSignInHandler(mockUI, options); + + if (mockScript.onload) { + mockScript.onload(); + } + + expect(mockGoogleAccounts.id.prompt).toHaveBeenCalled(); + }); + }); + + describe("callback integration", () => { + beforeEach(() => { + mockUI = createMockUI({ auth: { currentUser: null } as Auth }); + }); + + it("should handle Google One Tap callback with credential", async () => { + const options: OneTapSignInOptions = { clientId: "test-client-id" }; + const mockCredential = { providerId: "google.com" }; + const mockGoogleCredential = { credential: "google-credential-token" }; + + vi.mocked(GoogleAuthProvider.credential).mockReturnValue(mockCredential as any); + vi.mocked(signInWithCredential).mockResolvedValue({} as any); + + await oneTapSignInHandler(mockUI, options); + + if (mockScript.onload) { + mockScript.onload(); + } + + const initializeCall = vi.mocked(mockGoogleAccounts.id.initialize).mock.calls[0]; + const callback = initializeCall?.[0]?.callback; + + await callback(mockGoogleCredential); + + expect(GoogleAuthProvider.credential).toHaveBeenCalledWith("google-credential-token"); + expect(signInWithCredential).toHaveBeenCalledWith(mockUI, mockCredential); + }); + + it("should handle callback errors gracefully", async () => { + const options: OneTapSignInOptions = { clientId: "test-client-id" }; + const mockError = new Error("Google One Tap error"); + + vi.mocked(GoogleAuthProvider.credential).mockImplementation(() => { + throw mockError; + }); + + await oneTapSignInHandler(mockUI, options); + + if (mockScript.onload) { + mockScript.onload(); + } + + const initializeCall = vi.mocked(mockGoogleAccounts.id.initialize).mock.calls[0]; + const callback = initializeCall?.[0]?.callback; + + await expect(callback({ credential: "invalid-token" })).rejects.toThrow("Google One Tap error"); + }); + }); + + describe("options handling", () => { + beforeEach(() => { + mockUI = createMockUI({ auth: { currentUser: null } as Auth }); + }); + + it("should handle minimal options", async () => { + const options: OneTapSignInOptions = { clientId: "minimal-client-id" }; + + await oneTapSignInHandler(mockUI, options); + + if (mockScript.onload) { + mockScript.onload(); + } + + expect(mockGoogleAccounts.id.initialize).toHaveBeenCalledWith({ + client_id: "minimal-client-id", + auto_select: undefined, + cancel_on_tap_outside: undefined, + context: undefined, + ux_mode: undefined, + log_level: undefined, + callback: expect.any(Function), + }); + }); + + it("should handle all available options", async () => { + const options: OneTapSignInOptions = { + clientId: "full-options-client-id", + autoSelect: false, + cancelOnTapOutside: true, + context: "use", + uxMode: "redirect", + logLevel: "warn", + }; + + await oneTapSignInHandler(mockUI, options); + + if (mockScript.onload) { + mockScript.onload(); + } + + expect(mockGoogleAccounts.id.initialize).toHaveBeenCalledWith({ + client_id: "full-options-client-id", + auto_select: false, + cancel_on_tap_outside: true, + context: "use", + ux_mode: "redirect", + log_level: "warn", + callback: expect.any(Function), + }); + }); + }); +}); diff --git a/packages/core/src/behaviors/one-tap.ts b/packages/core/src/behaviors/one-tap.ts new file mode 100644 index 00000000..f678dd90 --- /dev/null +++ b/packages/core/src/behaviors/one-tap.ts @@ -0,0 +1,50 @@ +import { GoogleAuthProvider } from "firebase/auth"; +import type { IdConfiguration } from "google-one-tap"; +import type { FirebaseUIConfiguration } from "~/config"; +import { signInWithCredential } from "~/auth"; + +export type OneTapSignInOptions = { + clientId: IdConfiguration['client_id']; + autoSelect?: IdConfiguration['auto_select']; + cancelOnTapOutside?: IdConfiguration['cancel_on_tap_outside']; + context?: IdConfiguration['context']; + uxMode?: IdConfiguration['ux_mode']; + logLevel?: IdConfiguration['log_level']; +}; + +export const oneTapSignInHandler = async (ui: FirebaseUIConfiguration, options: OneTapSignInOptions) => { + // Only show one-tap if user is not signed in OR if they are anonymous. + // Don't show if user is already signed in with a real account. + if (ui.auth.currentUser && !ui.auth.currentUser.isAnonymous) { + return; + } + + // Prevent multiple instances of the script from being loaded, e.g. hot reload. + if (document.querySelector('script[data-one-tap-sign-in]')) { + return; + } + + const script = document.createElement('script'); + script.setAttribute('data-one-tap-sign-in', 'true'); + script.src = 'https://accounts.google.com/gsi/client'; + script.async = true; + + script.onload = () => { + window.google.accounts.id.initialize({ + client_id: options.clientId, + auto_select: options.autoSelect, + cancel_on_tap_outside: options.cancelOnTapOutside, + context: options.context, + ux_mode: options.uxMode, + log_level: options.logLevel, + callback: async (response) => { + const credential = GoogleAuthProvider.credential(response.credential); + await signInWithCredential(ui, credential); + }, + }); + + window.google.accounts.id.prompt(); + }; + + document.body.appendChild(script); +}; \ No newline at end of file diff --git a/packages/core/src/behaviors/utils.test.ts b/packages/core/src/behaviors/utils.test.ts index c574a712..289a1a1d 100644 --- a/packages/core/src/behaviors/utils.test.ts +++ b/packages/core/src/behaviors/utils.test.ts @@ -46,30 +46,6 @@ describe("Behaviors Utils", () => { expect(behavior1.handler).toBe(handler1); expect(behavior2.handler).toBe(handler2); }); - - it("should preserve specific function types and provide type inference", () => { - // Test that the handler function preserves its specific signature - const specificHandler = (ui: FirebaseUIConfiguration, element: HTMLElement, options?: { size?: string }) => { - return { ui, element, options }; - }; - - const behavior = callableBehavior(specificHandler); - - // The handler should maintain its specific signature - expect(behavior.handler).toBe(specificHandler); - expect(behavior.type).toBe("callable"); - - // Test that we can call the handler with the correct arguments - const mockUI = {} as FirebaseUIConfiguration; - const mockElement = document.createElement('div'); - const result = behavior.handler(mockUI, mockElement, { size: 'normal' }); - - expect(result).toEqual({ - ui: mockUI, - element: mockElement, - options: { size: 'normal' } - }); - }); }); describe("redirectBehavior", () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 75048db8..96a872eb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -172,25 +172,25 @@ importers: version: 20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1) '@angular/fire': specifier: ^20.0.1 - version: 20.0.1(4a96a039b009911f86ef7a0ebd7f5d89) + version: 20.0.1(c5e1eab461710a9d658b03f984199f2e) '@angular/forms': specifier: ^20.2.2 - version: 20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + version: 20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^20.2.2 version: 20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)) '@angular/platform-browser-dynamic': specifier: ^20.2.2 - version: 20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))) + version: 20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))) '@angular/platform-server': specifier: ^20.2.2 - version: 20.3.2(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + version: 20.3.2(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@angular/router': specifier: ^20.2.2 - version: 20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + version: 20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@angular/ssr': specifier: ^20.2.2 - version: 20.3.3(29f6c088f2c72629bcb82378a45b895e) + version: 20.3.3(e1b6fb7a76cc44225a2a22a5286f9942) '@firebase-ui/angular': specifier: workspace:* version: link:../../packages/angular @@ -287,7 +287,7 @@ importers: version: 5.9.2 vitest: specifier: ^3.2.0 - version: 3.2.4(@types/node@20.19.13)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@25.0.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) + version: 3.2.4(@types/node@20.19.13)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@25.0.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1) examples/nextjs: dependencies: @@ -461,19 +461,19 @@ importers: version: 20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1) '@angular/fire': specifier: 'catalog:' - version: 20.0.1(4a96a039b009911f86ef7a0ebd7f5d89) + version: 20.0.1(c5e1eab461710a9d658b03f984199f2e) '@angular/forms': specifier: 'catalog:' - version: 20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + version: 20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@angular/platform-browser': specifier: 'catalog:' version: 20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)) '@angular/platform-browser-dynamic': specifier: 'catalog:' - version: 20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))) + version: 20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))) '@angular/router': specifier: 'catalog:' - version: 20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + version: 20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@testing-library/jest-dom': specifier: ^6.6.0 version: 6.8.0 @@ -503,7 +503,7 @@ importers: version: 5.9.2 vitest: specifier: ^2.0.0 - version: 2.1.9(@types/node@24.3.1)(@vitest/ui@2.1.9)(jsdom@25.0.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1) + version: 2.1.9(@types/node@24.3.1)(@vitest/ui@2.1.9)(jsdom@25.0.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) zone.js: specifier: 'catalog:' version: 0.15.1 @@ -520,6 +520,9 @@ importers: specifier: 'catalog:' version: 4.1.11 devDependencies: + '@types/google-one-tap': + specifier: ^1.2.6 + version: 1.2.6 '@types/jsdom': specifier: 'catalog:' version: 21.1.7 @@ -3431,6 +3434,9 @@ packages: '@types/express@4.17.23': resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==} + '@types/google-one-tap@1.2.6': + resolution: {integrity: sha512-REmJsXVHvKb/sgI8DF+7IesMbDbcsEokHBqxU01ENZ8d98UPWdRLhUCtxEm9bhNFFg6PJGy7PNFdvovp0hK3jA==} + '@types/http-errors@2.0.5': resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} @@ -8178,7 +8184,7 @@ snapshots: dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2003.0(chokidar@4.0.3) - '@angular-devkit/build-webpack': 0.2003.0(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.101.2(esbuild@0.25.9)))(webpack@5.101.2(esbuild@0.25.9)) + '@angular-devkit/build-webpack': 0.2003.0(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.101.2))(webpack@5.101.2(esbuild@0.25.9)) '@angular-devkit/core': 20.3.0(chokidar@4.0.3) '@angular/build': 20.3.0(04b5738b93aadee3f8353f28e6721709) '@angular/compiler-cli': 20.3.0(@angular/compiler@20.3.0)(typescript@5.9.2) @@ -8228,15 +8234,15 @@ snapshots: tslib: 2.8.1 typescript: 5.9.2 webpack: 5.101.2(esbuild@0.25.9) - webpack-dev-middleware: 7.4.2(webpack@5.101.2(esbuild@0.25.9)) - webpack-dev-server: 5.2.2(webpack@5.101.2(esbuild@0.25.9)) + webpack-dev-middleware: 7.4.2(webpack@5.101.2) + webpack-dev-server: 5.2.2(webpack@5.101.2) webpack-merge: 6.0.1 webpack-subresource-integrity: 5.1.0(webpack@5.101.2(esbuild@0.25.9)) optionalDependencies: '@angular/core': 20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1) '@angular/platform-browser': 20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)) - '@angular/platform-server': 20.3.2(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) - '@angular/ssr': 20.3.3(29f6c088f2c72629bcb82378a45b895e) + '@angular/platform-server': 20.3.2(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + '@angular/ssr': 20.3.3(e1b6fb7a76cc44225a2a22a5286f9942) esbuild: 0.25.9 karma: 6.4.4 ng-packagr: 20.3.0(@angular/compiler-cli@20.3.0(@angular/compiler@20.3.0)(typescript@5.9.2))(tailwindcss@4.1.13)(tslib@2.8.1)(typescript@5.9.2) @@ -8268,7 +8274,7 @@ snapshots: dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2003.0(chokidar@4.0.3) - '@angular-devkit/build-webpack': 0.2003.0(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.101.2(esbuild@0.25.9)))(webpack@5.101.2(esbuild@0.25.9)) + '@angular-devkit/build-webpack': 0.2003.0(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.101.2))(webpack@5.101.2(esbuild@0.25.9)) '@angular-devkit/core': 20.3.0(chokidar@4.0.3) '@angular/build': 20.3.0(86ec70b3c2a2b389224b227d5e72fbc3) '@angular/compiler-cli': 20.3.0(@angular/compiler@20.3.0)(typescript@5.9.2) @@ -8318,15 +8324,15 @@ snapshots: tslib: 2.8.1 typescript: 5.9.2 webpack: 5.101.2(esbuild@0.25.9) - webpack-dev-middleware: 7.4.2(webpack@5.101.2(esbuild@0.25.9)) - webpack-dev-server: 5.2.2(webpack@5.101.2(esbuild@0.25.9)) + webpack-dev-middleware: 7.4.2(webpack@5.101.2) + webpack-dev-server: 5.2.2(webpack@5.101.2) webpack-merge: 6.0.1 webpack-subresource-integrity: 5.1.0(webpack@5.101.2(esbuild@0.25.9)) optionalDependencies: '@angular/core': 20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1) '@angular/platform-browser': 20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)) - '@angular/platform-server': 20.3.2(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) - '@angular/ssr': 20.3.3(29f6c088f2c72629bcb82378a45b895e) + '@angular/platform-server': 20.3.2(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + '@angular/ssr': 20.3.3(e1b6fb7a76cc44225a2a22a5286f9942) esbuild: 0.25.9 karma: 6.4.4 ng-packagr: 20.3.0(@angular/compiler-cli@20.3.0(@angular/compiler@20.3.0)(typescript@5.9.2))(tailwindcss@4.1.13)(tslib@2.8.1)(typescript@5.9.2) @@ -8354,12 +8360,12 @@ snapshots: - webpack-cli - yaml - '@angular-devkit/build-webpack@0.2003.0(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.101.2(esbuild@0.25.9)))(webpack@5.101.2(esbuild@0.25.9))': + '@angular-devkit/build-webpack@0.2003.0(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.101.2))(webpack@5.101.2(esbuild@0.25.9))': dependencies: '@angular-devkit/architect': 0.2003.0(chokidar@4.0.3) rxjs: 7.8.2 webpack: 5.101.2(esbuild@0.25.9) - webpack-dev-server: 5.2.2(webpack@5.101.2(esbuild@0.25.9)) + webpack-dev-server: 5.2.2(webpack@5.101.2) transitivePeerDependencies: - chokidar @@ -8424,15 +8430,15 @@ snapshots: optionalDependencies: '@angular/core': 20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1) '@angular/platform-browser': 20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)) - '@angular/platform-server': 20.3.2(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) - '@angular/ssr': 20.3.3(29f6c088f2c72629bcb82378a45b895e) + '@angular/platform-server': 20.3.2(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + '@angular/ssr': 20.3.3(e1b6fb7a76cc44225a2a22a5286f9942) karma: 6.4.4 less: 4.4.0 lmdb: 3.4.2 ng-packagr: 20.3.0(@angular/compiler-cli@20.3.0(@angular/compiler@20.3.0)(typescript@5.9.2))(tailwindcss@4.1.13)(tslib@2.8.1)(typescript@5.9.2) postcss: 8.5.6 tailwindcss: 4.1.13 - vitest: 2.1.9(@types/node@24.3.1)(@vitest/ui@2.1.9)(jsdom@25.0.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1) + vitest: 2.1.9(@types/node@24.3.1)(@vitest/ui@2.1.9)(jsdom@25.0.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) transitivePeerDependencies: - '@types/node' - chokidar @@ -8481,15 +8487,15 @@ snapshots: optionalDependencies: '@angular/core': 20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1) '@angular/platform-browser': 20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)) - '@angular/platform-server': 20.3.2(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) - '@angular/ssr': 20.3.3(29f6c088f2c72629bcb82378a45b895e) + '@angular/platform-server': 20.3.2(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + '@angular/ssr': 20.3.3(e1b6fb7a76cc44225a2a22a5286f9942) karma: 6.4.4 less: 4.4.0 lmdb: 3.4.2 ng-packagr: 20.3.0(@angular/compiler-cli@20.3.0(@angular/compiler@20.3.0)(typescript@5.9.2))(tailwindcss@4.1.13)(tslib@2.8.1)(typescript@5.9.2) postcss: 8.5.6 tailwindcss: 4.1.13 - vitest: 3.2.4(@types/node@20.19.13)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@25.0.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) + vitest: 3.2.4(@types/node@20.19.13)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@25.0.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1) transitivePeerDependencies: - '@types/node' - chokidar @@ -8587,25 +8593,25 @@ snapshots: '@angular/compiler': 20.3.0 zone.js: 0.15.1 - '@angular/fire@20.0.1(4a96a039b009911f86ef7a0ebd7f5d89)': + '@angular/fire@20.0.1(c5e1eab461710a9d658b03f984199f2e)': dependencies: '@angular-devkit/schematics': 20.3.0(chokidar@4.0.3) '@angular/common': 20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) '@angular/core': 20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1) '@angular/platform-browser': 20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)) - '@angular/platform-browser-dynamic': 20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))) + '@angular/platform-browser-dynamic': 20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))) '@schematics/angular': 20.3.0(chokidar@4.0.3) firebase: 11.10.0 rxfire: 6.1.0(firebase@11.10.0)(rxjs@7.8.2) rxjs: 7.8.2 tslib: 2.8.1 optionalDependencies: - '@angular/platform-server': 20.3.2(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + '@angular/platform-server': 20.3.2(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) transitivePeerDependencies: - '@react-native-async-storage/async-storage' - chokidar - '@angular/forms@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)': + '@angular/forms@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)': dependencies: '@angular/common': 20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) '@angular/core': 20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1) @@ -8613,7 +8619,7 @@ snapshots: rxjs: 7.8.2 tslib: 2.8.1 - '@angular/platform-browser-dynamic@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))': + '@angular/platform-browser-dynamic@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))': dependencies: '@angular/common': 20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) '@angular/compiler': 20.3.0 @@ -8629,7 +8635,7 @@ snapshots: optionalDependencies: '@angular/animations': 20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)) - '@angular/platform-server@20.3.2(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)': + '@angular/platform-server@20.3.2(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)': dependencies: '@angular/common': 20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) '@angular/compiler': 20.3.0 @@ -8639,7 +8645,7 @@ snapshots: tslib: 2.8.1 xhr2: 0.2.1 - '@angular/router@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)': + '@angular/router@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)': dependencies: '@angular/common': 20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) '@angular/core': 20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1) @@ -8647,14 +8653,14 @@ snapshots: rxjs: 7.8.2 tslib: 2.8.1 - '@angular/ssr@20.3.3(29f6c088f2c72629bcb82378a45b895e)': + '@angular/ssr@20.3.3(e1b6fb7a76cc44225a2a22a5286f9942)': dependencies: '@angular/common': 20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) '@angular/core': 20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1) - '@angular/router': 20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + '@angular/router': 20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) tslib: 2.8.1 optionalDependencies: - '@angular/platform-server': 20.3.2(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + '@angular/platform-server': 20.3.2(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.0)(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.0(@angular/animations@20.3.2(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.0(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.0(@angular/compiler@20.3.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@asamuzakjp/css-color@3.2.0': dependencies: @@ -11272,6 +11278,8 @@ snapshots: '@types/qs': 6.14.0 '@types/serve-static': 1.15.8 + '@types/google-one-tap@1.2.6': {} + '@types/http-errors@2.0.5': {} '@types/http-proxy@1.17.16': @@ -11539,7 +11547,7 @@ snapshots: std-env: 3.9.0 test-exclude: 7.0.1 tinyrainbow: 1.2.0 - vitest: 2.1.9(@types/node@24.3.1)(@vitest/ui@2.1.9)(jsdom@25.0.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1) + vitest: 2.1.9(@types/node@24.3.1)(@vitest/ui@2.1.9)(jsdom@25.0.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) transitivePeerDependencies: - supports-color @@ -11558,7 +11566,7 @@ snapshots: std-env: 3.9.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/node@20.19.13)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@25.0.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) + vitest: 3.2.4(@types/node@20.19.13)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@25.0.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1) transitivePeerDependencies: - supports-color @@ -11577,21 +11585,13 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@2.1.9(vite@5.4.20(@types/node@24.3.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1))': + '@vitest/mocker@2.1.9(vite@5.4.20(@types/node@24.3.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1))': dependencies: '@vitest/spy': 2.1.9 estree-walker: 3.0.3 magic-string: 0.30.19 optionalDependencies: - vite: 5.4.20(@types/node@24.3.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1) - - '@vitest/mocker@3.2.4(vite@6.3.6(@types/node@20.19.13)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1))': - dependencies: - '@vitest/spy': 3.2.4 - estree-walker: 3.0.3 - magic-string: 0.30.19 - optionalDependencies: - vite: 6.3.6(@types/node@20.19.13)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) + vite: 5.4.20(@types/node@24.3.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) '@vitest/mocker@3.2.4(vite@6.3.6(@types/node@24.3.1)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1))': dependencies: @@ -11649,7 +11649,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 1.2.0 - vitest: 2.1.9(@types/node@24.3.1)(@vitest/ui@2.1.9)(jsdom@25.0.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1) + vitest: 2.1.9(@types/node@24.3.1)(@vitest/ui@2.1.9)(jsdom@25.0.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) '@vitest/ui@3.2.4(vitest@3.2.4)': dependencies: @@ -11660,7 +11660,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/node@20.19.13)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@25.0.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) + vitest: 3.2.4(@types/node@24.3.1)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1) '@vitest/utils@2.1.9': dependencies: @@ -12862,7 +12862,7 @@ snapshots: eslint: 9.35.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-react: 7.37.5(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.35.0(jiti@2.5.1)) @@ -12896,7 +12896,7 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color @@ -12911,7 +12911,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -16177,13 +16177,13 @@ snapshots: vary@1.1.2: {} - vite-node@2.1.9(@types/node@24.3.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1): + vite-node@2.1.9(@types/node@24.3.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1): dependencies: cac: 6.7.14 debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 1.1.2 - vite: 5.4.20(@types/node@24.3.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1) + vite: 5.4.20(@types/node@24.3.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) transitivePeerDependencies: - '@types/node' - less @@ -16195,13 +16195,13 @@ snapshots: - supports-color - terser - vite-node@3.2.4(@types/node@20.19.13)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1): + vite-node@3.2.4(@types/node@20.19.13)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1): dependencies: cac: 6.7.14 debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 6.3.6(@types/node@20.19.13)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) + vite: 6.3.6(@types/node@20.19.13)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1) transitivePeerDependencies: - '@types/node' - jiti @@ -16267,7 +16267,7 @@ snapshots: - supports-color - typescript - vite@5.4.20(@types/node@24.3.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1): + vite@5.4.20(@types/node@24.3.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1): dependencies: esbuild: 0.21.5 postcss: 8.5.6 @@ -16275,12 +16275,12 @@ snapshots: optionalDependencies: '@types/node': 24.3.1 fsevents: 2.3.3 - less: 4.4.1 + less: 4.4.0 lightningcss: 1.30.1 - sass: 1.92.1 + sass: 1.90.0 terser: 5.43.1 - vite@6.3.6(@types/node@20.19.13)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1): + vite@6.3.6(@types/node@20.19.13)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1): dependencies: esbuild: 0.25.9 fdir: 6.5.0(picomatch@4.0.3) @@ -16292,9 +16292,9 @@ snapshots: '@types/node': 20.19.13 fsevents: 2.3.3 jiti: 2.5.1 - less: 4.4.0 + less: 4.4.1 lightningcss: 1.30.1 - sass: 1.90.0 + sass: 1.92.1 terser: 5.43.1 vite@6.3.6(@types/node@24.3.1)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1): @@ -16374,10 +16374,10 @@ snapshots: transitivePeerDependencies: - supports-color - vitest@2.1.9(@types/node@24.3.1)(@vitest/ui@2.1.9)(jsdom@25.0.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1): + vitest@2.1.9(@types/node@24.3.1)(@vitest/ui@2.1.9)(jsdom@25.0.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1): dependencies: '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(vite@5.4.20(@types/node@24.3.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1)) + '@vitest/mocker': 2.1.9(vite@5.4.20(@types/node@24.3.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)) '@vitest/pretty-format': 2.1.9 '@vitest/runner': 2.1.9 '@vitest/snapshot': 2.1.9 @@ -16393,8 +16393,8 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.1.1 tinyrainbow: 1.2.0 - vite: 5.4.20(@types/node@24.3.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1) - vite-node: 2.1.9(@types/node@24.3.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1) + vite: 5.4.20(@types/node@24.3.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) + vite-node: 2.1.9(@types/node@24.3.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.3.1 @@ -16411,11 +16411,11 @@ snapshots: - supports-color - terser - vitest@3.2.4(@types/node@20.19.13)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@25.0.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1): + vitest@3.2.4(@types/node@20.19.13)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@25.0.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@6.3.6(@types/node@20.19.13)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)) + '@vitest/mocker': 3.2.4(vite@6.3.6(@types/node@24.3.1)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -16433,8 +16433,8 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 6.3.6(@types/node@20.19.13)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) - vite-node: 3.2.4(@types/node@20.19.13)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) + vite: 6.3.6(@types/node@20.19.13)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1) + vite-node: 3.2.4(@types/node@20.19.13)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.19.13 @@ -16524,7 +16524,7 @@ snapshots: webidl-conversions@7.0.0: {} - webpack-dev-middleware@7.4.2(webpack@5.101.2(esbuild@0.25.9)): + webpack-dev-middleware@7.4.2(webpack@5.101.2): dependencies: colorette: 2.0.20 memfs: 4.39.0 @@ -16535,7 +16535,7 @@ snapshots: optionalDependencies: webpack: 5.101.2(esbuild@0.25.9) - webpack-dev-server@5.2.2(webpack@5.101.2(esbuild@0.25.9)): + webpack-dev-server@5.2.2(webpack@5.101.2): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 @@ -16563,7 +16563,7 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.2(webpack@5.101.2(esbuild@0.25.9)) + webpack-dev-middleware: 7.4.2(webpack@5.101.2) ws: 8.18.3 optionalDependencies: webpack: 5.101.2(esbuild@0.25.9)