@ttt-productions/auth-core 1.6.15 → 1.6.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/acceptance.d.ts +0 -20
  2. package/dist/acceptance.d.ts.map +1 -1
  3. package/dist/acceptance.js +6 -26
  4. package/dist/acceptance.js.map +1 -1
  5. package/dist/{auth.d.ts → client/auth.d.ts} +1 -6
  6. package/dist/client/auth.d.ts.map +1 -0
  7. package/dist/client/auth.js +8 -0
  8. package/dist/client/auth.js.map +1 -0
  9. package/dist/client/claims.d.ts +11 -0
  10. package/dist/client/claims.d.ts.map +1 -0
  11. package/dist/client/claims.js +30 -0
  12. package/dist/client/claims.js.map +1 -0
  13. package/dist/client/index.d.ts +3 -0
  14. package/dist/client/index.d.ts.map +1 -0
  15. package/dist/client/index.js +3 -0
  16. package/dist/client/index.js.map +1 -0
  17. package/dist/email-verification.d.ts +0 -13
  18. package/dist/email-verification.d.ts.map +1 -1
  19. package/dist/email-verification.js +3 -19
  20. package/dist/email-verification.js.map +1 -1
  21. package/dist/env.d.ts +1 -1
  22. package/dist/env.js +5 -5
  23. package/dist/env.js.map +1 -1
  24. package/dist/errors.d.ts +1 -3
  25. package/dist/errors.d.ts.map +1 -1
  26. package/dist/errors.js +79 -86
  27. package/dist/errors.js.map +1 -1
  28. package/dist/index.d.ts +4 -5
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +4 -12
  31. package/dist/index.js.map +1 -1
  32. package/dist/react/AuthProvider.d.ts +3 -3
  33. package/dist/react/AuthProvider.d.ts.map +1 -1
  34. package/dist/react/AuthProvider.js +18 -36
  35. package/dist/react/AuthProvider.js.map +1 -1
  36. package/dist/react/index.d.ts +8 -8
  37. package/dist/react/index.js +6 -6
  38. package/dist/react/index.js.map +1 -1
  39. package/dist/react/redirect-storage.d.ts.map +1 -1
  40. package/dist/react/redirect-storage.js +5 -14
  41. package/dist/react/redirect-storage.js.map +1 -1
  42. package/dist/react/types.d.ts +1 -22
  43. package/dist/react/types.d.ts.map +1 -1
  44. package/dist/react/types.js.map +1 -1
  45. package/dist/react/useAuth.d.ts +1 -5
  46. package/dist/react/useAuth.d.ts.map +1 -1
  47. package/dist/react/useAuth.js +4 -8
  48. package/dist/react/useAuth.js.map +1 -1
  49. package/dist/react/useAuthGuard.d.ts +0 -33
  50. package/dist/react/useAuthGuard.d.ts.map +1 -1
  51. package/dist/react/useAuthGuard.js +7 -37
  52. package/dist/react/useAuthGuard.js.map +1 -1
  53. package/dist/react/useAuthState.d.ts +1 -1
  54. package/dist/react/useAuthState.d.ts.map +1 -1
  55. package/dist/react/useAuthState.js +2 -6
  56. package/dist/react/useAuthState.js.map +1 -1
  57. package/dist/refusal.d.ts +0 -6
  58. package/dist/refusal.d.ts.map +1 -1
  59. package/dist/refusal.js +6 -16
  60. package/dist/refusal.js.map +1 -1
  61. package/dist/server/assertAuth.d.ts +2 -2
  62. package/dist/server/assertAuth.d.ts.map +1 -1
  63. package/dist/server/assertAuth.js +13 -38
  64. package/dist/server/assertAuth.js.map +1 -1
  65. package/dist/server/authError.d.ts +3 -51
  66. package/dist/server/authError.d.ts.map +1 -1
  67. package/dist/server/authError.js +1 -1
  68. package/dist/server/authError.js.map +1 -1
  69. package/dist/server/index.d.ts +4 -4
  70. package/dist/server/index.d.ts.map +1 -1
  71. package/dist/server/index.js +2 -3
  72. package/dist/server/index.js.map +1 -1
  73. package/dist/server/types.d.ts +9 -109
  74. package/dist/server/types.d.ts.map +1 -1
  75. package/dist/server/types.js +0 -2
  76. package/dist/server/types.js.map +1 -1
  77. package/package.json +3 -3
  78. package/dist/auth.d.ts.map +0 -1
  79. package/dist/auth.js +0 -13
  80. package/dist/auth.js.map +0 -1
  81. package/dist/claims.d.ts +0 -38
  82. package/dist/claims.d.ts.map +0 -1
  83. package/dist/claims.js +0 -55
  84. package/dist/claims.js.map +0 -1
  85. package/dist/client.d.ts +0 -2
  86. package/dist/client.d.ts.map +0 -1
  87. package/dist/client.js +0 -5
  88. package/dist/client.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,13 +1,5 @@
1
- // Pure root: contracts, claims parsing, normalized errors, environment helpers.
2
- // Nothing here imports firebase/auth at runtime.
3
- //
4
- // Client auth runtime (onAuthStateChanged wrapper, getAuthUser) lives behind
5
- // "@ttt-productions/auth-core/client". Admin/Functions helpers live behind
6
- // "@ttt-productions/auth-core/server". React hooks live behind
7
- // "@ttt-productions/auth-core/react".
8
- export * from "./claims.js";
9
- export * from "./errors.js";
10
- export * from "./env.js";
11
- export * from "./acceptance.js";
12
- export * from "./email-verification.js";
1
+ export * from './errors.js';
2
+ export * from './env.js';
3
+ export * from './acceptance.js';
4
+ export * from './email-verification.js';
13
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,iDAAiD;AACjD,EAAE;AACF,6EAA6E;AAC7E,2EAA2E;AAC3E,+DAA+D;AAC/D,sCAAsC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC","sourcesContent":["// Pure root: contracts, claims parsing, normalized errors, environment helpers.\n// Nothing here imports firebase/auth at runtime.\n//\n// Client auth runtime (onAuthStateChanged wrapper, getAuthUser) lives behind\n// \"@ttt-productions/auth-core/client\". Admin/Functions helpers live behind\n// \"@ttt-productions/auth-core/server\". React hooks live behind\n// \"@ttt-productions/auth-core/react\".\nexport * from \"./claims.js\";\nexport * from \"./errors.js\";\nexport * from \"./env.js\";\nexport * from \"./acceptance.js\";\nexport * from \"./email-verification.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC","sourcesContent":["export * from './errors.js';\nexport * from './env.js';\nexport * from './acceptance.js';\nexport * from './email-verification.js';\n"]}
@@ -1,10 +1,10 @@
1
- import { type ReactNode } from "react";
2
- import type { AuthProviderConfig, AuthContextValue } from "./types.js";
1
+ import { type ReactNode } from 'react';
2
+ import type { AuthProviderConfig, AuthContextValue } from './types.js';
3
3
  export declare const AuthContext: import("react").Context<AuthContextValue<any> | null>;
4
4
  interface AuthProviderProps<TClaims> {
5
5
  config: AuthProviderConfig<TClaims>;
6
6
  children: ReactNode;
7
7
  }
8
- export declare function AuthProvider<TClaims = Record<string, unknown>>({ config, children }: AuthProviderProps<TClaims>): import("react").JSX.Element;
8
+ export declare function AuthProvider<TClaims = Record<string, unknown>>({ config, children, }: AuthProviderProps<TClaims>): import("react").JSX.Element;
9
9
  export {};
10
10
  //# sourceMappingURL=AuthProvider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AuthProvider.d.ts","sourceRoot":"","sources":["../../src/react/AuthProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAIf,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAavE,eAAO,MAAM,WAAW,uDAAoD,CAAC;AAE7E,UAAU,iBAAiB,CAAC,OAAO;IACjC,MAAM,EAAE,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACpC,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5D,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,iBAAiB,CAAC,OAAO,CAAC,+BA8HjD"}
1
+ {"version":3,"file":"AuthProvider.d.ts","sourceRoot":"","sources":["../../src/react/AuthProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAIf,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAQvE,eAAO,MAAM,WAAW,uDAAoD,CAAC;AAE7E,UAAU,iBAAiB,CAAC,OAAO;IACjC,MAAM,EAAE,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACpC,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC9D,MAAM,EACN,QAAQ,GACT,EAAE,iBAAiB,CAAC,OAAO,CAAC,+BAwG5B"}
@@ -1,27 +1,18 @@
1
- "use client";
1
+ 'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { createContext, useEffect, useRef, useState, useCallback, useMemo, } from "react";
4
- import { useAuthState } from "./useAuthState.js";
5
- import { getIdTokenClaims } from "../claims.js";
3
+ import { createContext, useEffect, useRef, useState, useCallback, useMemo, } from 'react';
4
+ import { useAuthState } from './useAuthState.js';
5
+ import { getIdTokenClaims } from '../client/claims.js';
6
6
  export const AuthContext = createContext(null);
7
- export function AuthProvider({ config, children }) {
7
+ export function AuthProvider({ config, children, }) {
8
8
  const { user, loading: authLoading } = useAuthState(config.auth);
9
- // Claims are held with the session they were read for and derived at render (below), so a
10
- // render can never pair one account's `user` with another account's claims, and a read that
11
- // settles after the session changed (a late refresh, a slow initial read) is dropped.
12
9
  const [resolved, setResolved] = useState(null);
13
10
  const generationRef = useRef(0);
14
11
  const appliedGenerationRef = useRef(0);
15
12
  const currentUserRef = useRef(user);
16
13
  currentUserRef.current = user;
17
- // Stable config ref so effects don't re-fire on config object identity changes
18
14
  const configRef = useRef(config);
19
15
  configRef.current = config;
20
- /**
21
- * Read `forUser`'s claims under a new generation and apply them only if `forUser` is still
22
- * the signed-in session and no newer read has applied. A failed read leaves the session's
23
- * claims as they are, or settles an unread session on the default claims.
24
- */
25
16
  const readClaims = useCallback(async (forUser, read, context) => {
26
17
  const generation = ++generationRef.current;
27
18
  try {
@@ -29,7 +20,11 @@ export function AuthProvider({ config, children }) {
29
20
  if (currentUserRef.current !== forUser || generation <= appliedGenerationRef.current)
30
21
  return;
31
22
  appliedGenerationRef.current = generation;
32
- setResolved({ user: forUser, generation, claims: configRef.current.parseClaims(raw ?? {}) });
23
+ setResolved({
24
+ user: forUser,
25
+ generation,
26
+ claims: configRef.current.parseClaims(raw ?? {}),
27
+ });
33
28
  }
34
29
  catch (err) {
35
30
  if (currentUserRef.current !== forUser)
@@ -40,9 +35,6 @@ export function AuthProvider({ config, children }) {
40
35
  : { user: forUser, generation, claims: configRef.current.defaultClaims });
41
36
  }
42
37
  }, []);
43
- // --- Optional readiness gate (see AuthProviderConfig.readyGate) ---
44
- // Open immediately when no gate is configured; otherwise held closed until the
45
- // gate settles. Fail-open: a rejected gate is reported and then treated as open.
46
38
  const [gateOpen, setGateOpen] = useState(() => !config.readyGate);
47
39
  useEffect(() => {
48
40
  const gate = configRef.current.readyGate;
@@ -51,16 +43,15 @@ export function AuthProvider({ config, children }) {
51
43
  let cancelled = false;
52
44
  Promise.resolve()
53
45
  .then(() => gate())
54
- .catch((err) => configRef.current.onError?.(err, "readyGate"))
46
+ .catch((err) => configRef.current.onError?.(err, 'readyGate'))
55
47
  .finally(() => {
56
48
  if (!cancelled)
57
49
  setGateOpen(true);
58
50
  });
59
- return () => { cancelled = true; };
60
- // Mount-only by design (reads only the stable configRef): the gate is a
61
- // one-time client readiness signal.
51
+ return () => {
52
+ cancelled = true;
53
+ };
62
54
  }, []);
63
- // --- Claims fetching ---
64
55
  useEffect(() => {
65
56
  if (authLoading)
66
57
  return;
@@ -70,28 +61,19 @@ export function AuthProvider({ config, children }) {
70
61
  return;
71
62
  }
72
63
  configRef.current.onAuthStateChange?.(user);
73
- void readClaims(user, async () => ((await getIdTokenClaims(user)) ?? {}), "getIdTokenClaims");
64
+ void readClaims(user, async () => ((await getIdTokenClaims(user)) ?? {}), 'getIdTokenClaims');
74
65
  }, [user, authLoading, readClaims]);
75
- // --- refreshClaims ---
76
66
  const refreshClaims = useCallback(async () => {
77
- // The LIVE signed-in user, never the one this render saw: a caller can hold this callback from
78
- // before its own sign-in (a registration's submit handler creates the account and then asks for
79
- // the claims the server just set), and a refresh of that render's `null` user would do nothing —
80
- // leaving the new account on its pre-claim token until the hourly refresh.
67
+ // `auth.currentUser`, not this render's user: a caller can hold this callback from before its own sign-in.
81
68
  const liveUser = configRef.current.auth.currentUser;
82
69
  if (!liveUser)
83
70
  return;
84
- await readClaims(liveUser, async () => ((await liveUser.getIdTokenResult(true)).claims ?? {}), "refreshClaims");
71
+ await readClaims(liveUser, async () => ((await liveUser.getIdTokenResult(true)).claims ?? {}), 'refreshClaims');
85
72
  }, [readClaims]);
86
- // --- Derived at render: only the current session's claims are ever exposed ---
87
73
  const sessionClaims = user && resolved?.user === user ? resolved : null;
88
74
  const claimsLoading = authLoading || (user !== null && sessionClaims === null);
89
- // While the gate is closed a restored session is held back from consumers: a uid-keyed
90
- // subscription opened before the gate's readiness signal is rejected once and dies, so no
91
- // consumer may see a uid (or that session's claims) until the gate opens or fails open.
92
75
  const exposedUser = gateOpen ? user : null;
93
76
  const claims = exposedUser && sessionClaims ? sessionClaims.claims : config.defaultClaims;
94
- // --- Context value ---
95
77
  const loading = authLoading || claimsLoading || !gateOpen;
96
78
  const value = useMemo(() => ({
97
79
  user: exposedUser,
@@ -102,6 +84,6 @@ export function AuthProvider({ config, children }) {
102
84
  isAuthenticated: !!exposedUser,
103
85
  refreshClaims,
104
86
  }), [exposedUser, claims, loading, authLoading, claimsLoading, refreshClaims]);
105
- return (_jsx(AuthContext.Provider, { value: value, children: children }));
87
+ return _jsx(AuthContext.Provider, { value: value, children: children });
106
88
  }
107
89
  //# sourceMappingURL=AuthProvider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AuthProvider.js","sourceRoot":"","sources":["../../src/react/AuthProvider.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useEffect,\n useRef,\n useState,\n useCallback,\n useMemo,\n type ReactNode,\n} from \"react\";\nimport type { User } from \"firebase/auth\";\nimport { useAuthState } from \"./useAuthState.js\";\nimport { getIdTokenClaims } from \"../claims.js\";\nimport type { AuthProviderConfig, AuthContextValue } from \"./types.js\";\n\n/**\n * Claims resolved for one signed-in session. `user` is the Firebase `User` instance (it carries\n * the uid and is replaced on every sign-in), and `generation` orders the reads that produced it.\n */\ninterface ResolvedClaims<TClaims> {\n user: User;\n generation: number;\n claims: TClaims;\n}\n\n \nexport const AuthContext = createContext<AuthContextValue<any> | null>(null);\n\ninterface AuthProviderProps<TClaims> {\n config: AuthProviderConfig<TClaims>;\n children: ReactNode;\n}\n\nexport function AuthProvider<TClaims = Record<string, unknown>>(\n { config, children }: AuthProviderProps<TClaims>,\n) {\n const { user, loading: authLoading } = useAuthState(config.auth);\n\n // Claims are held with the session they were read for and derived at render (below), so a\n // render can never pair one account's `user` with another account's claims, and a read that\n // settles after the session changed (a late refresh, a slow initial read) is dropped.\n const [resolved, setResolved] = useState<ResolvedClaims<TClaims> | null>(null);\n const generationRef = useRef(0);\n const appliedGenerationRef = useRef(0);\n const currentUserRef = useRef<User | null>(user);\n currentUserRef.current = user;\n\n // Stable config ref so effects don't re-fire on config object identity changes\n const configRef = useRef(config);\n configRef.current = config;\n\n /**\n * Read `forUser`'s claims under a new generation and apply them only if `forUser` is still\n * the signed-in session and no newer read has applied. A failed read leaves the session's\n * claims as they are, or settles an unread session on the default claims.\n */\n const readClaims = useCallback(\n async (forUser: User, read: () => Promise<Record<string, unknown> | null>, context: string) => {\n const generation = ++generationRef.current;\n try {\n const raw = await read();\n if (currentUserRef.current !== forUser || generation <= appliedGenerationRef.current) return;\n appliedGenerationRef.current = generation;\n setResolved({ user: forUser, generation, claims: configRef.current.parseClaims(raw ?? {}) });\n } catch (err) {\n if (currentUserRef.current !== forUser) return;\n configRef.current.onError?.(err, context);\n setResolved((prev) =>\n prev && prev.user === forUser\n ? prev\n : { user: forUser, generation, claims: configRef.current.defaultClaims },\n );\n }\n },\n [],\n );\n\n // --- Optional readiness gate (see AuthProviderConfig.readyGate) ---\n // Open immediately when no gate is configured; otherwise held closed until the\n // gate settles. Fail-open: a rejected gate is reported and then treated as open.\n const [gateOpen, setGateOpen] = useState(() => !config.readyGate);\n useEffect(() => {\n const gate = configRef.current.readyGate;\n if (!gate) return;\n let cancelled = false;\n Promise.resolve()\n .then(() => gate())\n .catch((err) => configRef.current.onError?.(err, \"readyGate\"))\n .finally(() => {\n if (!cancelled) setGateOpen(true);\n });\n return () => { cancelled = true; };\n // Mount-only by design (reads only the stable configRef): the gate is a\n // one-time client readiness signal.\n }, []);\n\n // --- Claims fetching ---\n useEffect(() => {\n if (authLoading) return;\n\n if (!user) {\n setResolved(null);\n configRef.current.onAuthStateChange?.(null);\n return;\n }\n\n configRef.current.onAuthStateChange?.(user);\n void readClaims(\n user,\n async () => ((await getIdTokenClaims(user)) ?? {}) as Record<string, unknown>,\n \"getIdTokenClaims\",\n );\n }, [user, authLoading, readClaims]);\n\n // --- refreshClaims ---\n const refreshClaims = useCallback(async () => {\n // The LIVE signed-in user, never the one this render saw: a caller can hold this callback from\n // before its own sign-in (a registration's submit handler creates the account and then asks for\n // the claims the server just set), and a refresh of that render's `null` user would do nothing —\n // leaving the new account on its pre-claim token until the hourly refresh.\n const liveUser = configRef.current.auth.currentUser;\n if (!liveUser) return;\n await readClaims(\n liveUser,\n async () => ((await liveUser.getIdTokenResult(true)).claims ?? {}) as Record<string, unknown>,\n \"refreshClaims\",\n );\n }, [readClaims]);\n\n // --- Derived at render: only the current session's claims are ever exposed ---\n const sessionClaims = user && resolved?.user === user ? resolved : null;\n const claimsLoading = authLoading || (user !== null && sessionClaims === null);\n\n // While the gate is closed a restored session is held back from consumers: a uid-keyed\n // subscription opened before the gate's readiness signal is rejected once and dies, so no\n // consumer may see a uid (or that session's claims) until the gate opens or fails open.\n const exposedUser = gateOpen ? user : null;\n const claims = exposedUser && sessionClaims ? sessionClaims.claims : config.defaultClaims;\n\n // --- Context value ---\n const loading = authLoading || claimsLoading || !gateOpen;\n\n const value = useMemo<AuthContextValue<TClaims>>(\n () => ({\n user: exposedUser,\n claims,\n loading,\n authLoading,\n claimsLoading,\n isAuthenticated: !!exposedUser,\n refreshClaims,\n }),\n [exposedUser, claims, loading, authLoading, claimsLoading, refreshClaims],\n );\n\n return (\n <AuthContext.Provider value={value}>\n {children}\n </AuthContext.Provider>\n );\n}\n"]}
1
+ {"version":3,"file":"AuthProvider.js","sourceRoot":"","sources":["../../src/react/AuthProvider.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport {\n createContext,\n useEffect,\n useRef,\n useState,\n useCallback,\n useMemo,\n type ReactNode,\n} from 'react';\nimport type { User } from 'firebase/auth';\nimport { useAuthState } from './useAuthState.js';\nimport { getIdTokenClaims } from '../client/claims.js';\nimport type { AuthProviderConfig, AuthContextValue } from './types.js';\n\ninterface ResolvedClaims<TClaims> {\n user: User;\n generation: number;\n claims: TClaims;\n}\n\nexport const AuthContext = createContext<AuthContextValue<any> | null>(null);\n\ninterface AuthProviderProps<TClaims> {\n config: AuthProviderConfig<TClaims>;\n children: ReactNode;\n}\n\nexport function AuthProvider<TClaims = Record<string, unknown>>({\n config,\n children,\n}: AuthProviderProps<TClaims>) {\n const { user, loading: authLoading } = useAuthState(config.auth);\n\n const [resolved, setResolved] = useState<ResolvedClaims<TClaims> | null>(null);\n const generationRef = useRef(0);\n const appliedGenerationRef = useRef(0);\n const currentUserRef = useRef<User | null>(user);\n currentUserRef.current = user;\n\n const configRef = useRef(config);\n configRef.current = config;\n\n const readClaims = useCallback(\n async (forUser: User, read: () => Promise<Record<string, unknown> | null>, context: string) => {\n const generation = ++generationRef.current;\n try {\n const raw = await read();\n if (currentUserRef.current !== forUser || generation <= appliedGenerationRef.current)\n return;\n appliedGenerationRef.current = generation;\n setResolved({\n user: forUser,\n generation,\n claims: configRef.current.parseClaims(raw ?? {}),\n });\n } catch (err) {\n if (currentUserRef.current !== forUser) return;\n configRef.current.onError?.(err, context);\n setResolved((prev) =>\n prev && prev.user === forUser\n ? prev\n : { user: forUser, generation, claims: configRef.current.defaultClaims },\n );\n }\n },\n [],\n );\n\n const [gateOpen, setGateOpen] = useState(() => !config.readyGate);\n useEffect(() => {\n const gate = configRef.current.readyGate;\n if (!gate) return;\n let cancelled = false;\n Promise.resolve()\n .then(() => gate())\n .catch((err) => configRef.current.onError?.(err, 'readyGate'))\n .finally(() => {\n if (!cancelled) setGateOpen(true);\n });\n return () => {\n cancelled = true;\n };\n }, []);\n\n useEffect(() => {\n if (authLoading) return;\n\n if (!user) {\n setResolved(null);\n configRef.current.onAuthStateChange?.(null);\n return;\n }\n\n configRef.current.onAuthStateChange?.(user);\n void readClaims(\n user,\n async () => ((await getIdTokenClaims(user)) ?? {}) as Record<string, unknown>,\n 'getIdTokenClaims',\n );\n }, [user, authLoading, readClaims]);\n\n const refreshClaims = useCallback(async () => {\n // `auth.currentUser`, not this render's user: a caller can hold this callback from before its own sign-in.\n const liveUser = configRef.current.auth.currentUser;\n if (!liveUser) return;\n await readClaims(\n liveUser,\n async () => ((await liveUser.getIdTokenResult(true)).claims ?? {}) as Record<string, unknown>,\n 'refreshClaims',\n );\n }, [readClaims]);\n\n const sessionClaims = user && resolved?.user === user ? resolved : null;\n const claimsLoading = authLoading || (user !== null && sessionClaims === null);\n\n const exposedUser = gateOpen ? user : null;\n const claims = exposedUser && sessionClaims ? sessionClaims.claims : config.defaultClaims;\n\n const loading = authLoading || claimsLoading || !gateOpen;\n\n const value = useMemo<AuthContextValue<TClaims>>(\n () => ({\n user: exposedUser,\n claims,\n loading,\n authLoading,\n claimsLoading,\n isAuthenticated: !!exposedUser,\n refreshClaims,\n }),\n [exposedUser, claims, loading, authLoading, claimsLoading, refreshClaims],\n );\n\n return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;\n}\n"]}
@@ -1,9 +1,9 @@
1
- export type { AuthState } from "./useAuthState.js";
2
- export { useAuthState } from "./useAuthState.js";
3
- export type { AuthProviderConfig, AuthContextValue } from "./types.js";
4
- export { AuthProvider } from "./AuthProvider.js";
5
- export { AuthContext } from "./AuthProvider.js";
6
- export { useAuth } from "./useAuth.js";
7
- export { useAuthGuard } from "./useAuthGuard.js";
8
- export type { AuthGuardConfig } from "./useAuthGuard.js";
1
+ export type { AuthState } from './useAuthState.js';
2
+ export { useAuthState } from './useAuthState.js';
3
+ export type { AuthProviderConfig, AuthContextValue } from './types.js';
4
+ export { AuthProvider } from './AuthProvider.js';
5
+ export { AuthContext } from './AuthProvider.js';
6
+ export { useAuth } from './useAuth.js';
7
+ export { useAuthGuard } from './useAuthGuard.js';
8
+ export type { AuthGuardConfig } from './useAuthGuard.js';
9
9
  //# sourceMappingURL=index.d.ts.map
@@ -1,7 +1,7 @@
1
- "use client";
2
- export { useAuthState } from "./useAuthState.js";
3
- export { AuthProvider } from "./AuthProvider.js";
4
- export { AuthContext } from "./AuthProvider.js";
5
- export { useAuth } from "./useAuth.js";
6
- export { useAuthGuard } from "./useAuthGuard.js";
1
+ 'use client';
2
+ export { useAuthState } from './useAuthState.js';
3
+ export { AuthProvider } from './AuthProvider.js';
4
+ export { AuthContext } from './AuthProvider.js';
5
+ export { useAuth } from './useAuth.js';
6
+ export { useAuthGuard } from './useAuthGuard.js';
7
7
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAGb,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["\"use client\";\n\nexport type { AuthState } from \"./useAuthState.js\";\nexport { useAuthState } from \"./useAuthState.js\";\nexport type { AuthProviderConfig, AuthContextValue } from \"./types.js\";\nexport { AuthProvider } from \"./AuthProvider.js\";\nexport { AuthContext } from \"./AuthProvider.js\";\nexport { useAuth } from \"./useAuth.js\";\nexport { useAuthGuard } from \"./useAuthGuard.js\";\nexport type { AuthGuardConfig } from \"./useAuthGuard.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAGb,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["'use client';\n\nexport type { AuthState } from './useAuthState.js';\nexport { useAuthState } from './useAuthState.js';\nexport type { AuthProviderConfig, AuthContextValue } from './types.js';\nexport { AuthProvider } from './AuthProvider.js';\nexport { AuthContext } from './AuthProvider.js';\nexport { useAuth } from './useAuth.js';\nexport { useAuthGuard } from './useAuthGuard.js';\nexport type { AuthGuardConfig } from './useAuthGuard.js';\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"redirect-storage.d.ts","sourceRoot":"","sources":["../../src/react/redirect-storage.ts"],"names":[],"mappings":"AAmBA,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS3D;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAahE;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAQnD"}
1
+ {"version":3,"file":"redirect-storage.d.ts","sourceRoot":"","sources":["../../src/react/redirect-storage.ts"],"names":[],"mappings":"AAgBA,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAO3D;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAWhE;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAMnD"}
@@ -1,10 +1,7 @@
1
- // The post-login redirect path lives in localStorage. A browser that blocks site data throws
2
- // on the storage access itself (FRONTEND-010), so every access here is guarded: a blocked read
3
- // is "nothing saved", and a blocked write keeps the value in memory for the page's life so a
4
- // same-page sign-in still lands on it. Outside a browser every call is a no-op.
1
+ // A browser that blocks site data throws on the storage access itself (RULE-STD-FRONTEND-010).
5
2
  const memory = new Map();
6
3
  function inBrowser() {
7
- return typeof window !== "undefined";
4
+ return typeof window !== 'undefined';
8
5
  }
9
6
  function storage() {
10
7
  try {
@@ -22,9 +19,7 @@ export function readRedirectPath(key) {
22
19
  if (saved !== null)
23
20
  return saved;
24
21
  }
25
- catch {
26
- // Blocked store: fall through to the page-life copy.
27
- }
22
+ catch { }
28
23
  return memory.get(key) ?? null;
29
24
  }
30
25
  export function saveRedirectPath(key, path) {
@@ -37,9 +32,7 @@ export function saveRedirectPath(key, path) {
37
32
  memory.delete(key);
38
33
  return;
39
34
  }
40
- catch {
41
- // Blocked store: keep the page-life copy below.
42
- }
35
+ catch { }
43
36
  }
44
37
  memory.set(key, path);
45
38
  }
@@ -50,8 +43,6 @@ export function clearRedirectPath(key) {
50
43
  try {
51
44
  storage()?.removeItem(key);
52
45
  }
53
- catch {
54
- // Blocked store: nothing was persisted there.
55
- }
46
+ catch { }
56
47
  }
57
48
  //# sourceMappingURL=redirect-storage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"redirect-storage.js","sourceRoot":"","sources":["../../src/react/redirect-storage.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,+FAA+F;AAC/F,6FAA6F;AAC7F,gFAAgF;AAEhF,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;AAEzC,SAAS,SAAS;IAChB,OAAO,OAAO,MAAM,KAAK,WAAW,CAAC;AACvC,CAAC;AAED,SAAS,OAAO;IACd,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,YAAY,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,IAAI,CAAC,SAAS,EAAE;QAAE,OAAO,IAAI,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,OAAO,EAAE,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;QAC9C,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,qDAAqD;IACvD,CAAC;IACD,OAAO,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,GAAW,EAAE,IAAY;IACxD,IAAI,CAAC,SAAS,EAAE;QAAE,OAAO;IACzB,MAAM,KAAK,GAAG,OAAO,EAAE,CAAC;IACxB,IAAI,KAAK,EAAE,CAAC;QACV,IAAI,CAAC;YACH,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACzB,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACnB,OAAO;QACT,CAAC;QAAC,MAAM,CAAC;YACP,gDAAgD;QAClD,CAAC;IACH,CAAC;IACD,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,IAAI,CAAC,SAAS,EAAE;QAAE,OAAO;IACzB,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACnB,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,8CAA8C;IAChD,CAAC;AACH,CAAC","sourcesContent":["// The post-login redirect path lives in localStorage. A browser that blocks site data throws\n// on the storage access itself (FRONTEND-010), so every access here is guarded: a blocked read\n// is \"nothing saved\", and a blocked write keeps the value in memory for the page's life so a\n// same-page sign-in still lands on it. Outside a browser every call is a no-op.\n\nconst memory = new Map<string, string>();\n\nfunction inBrowser(): boolean {\n return typeof window !== \"undefined\";\n}\n\nfunction storage(): Storage | null {\n try {\n return window.localStorage;\n } catch {\n return null;\n }\n}\n\nexport function readRedirectPath(key: string): string | null {\n if (!inBrowser()) return null;\n try {\n const saved = storage()?.getItem(key) ?? null;\n if (saved !== null) return saved;\n } catch {\n // Blocked store: fall through to the page-life copy.\n }\n return memory.get(key) ?? null;\n}\n\nexport function saveRedirectPath(key: string, path: string): void {\n if (!inBrowser()) return;\n const store = storage();\n if (store) {\n try {\n store.setItem(key, path);\n memory.delete(key);\n return;\n } catch {\n // Blocked store: keep the page-life copy below.\n }\n }\n memory.set(key, path);\n}\n\nexport function clearRedirectPath(key: string): void {\n if (!inBrowser()) return;\n memory.delete(key);\n try {\n storage()?.removeItem(key);\n } catch {\n // Blocked store: nothing was persisted there.\n }\n}\n"]}
1
+ {"version":3,"file":"redirect-storage.js","sourceRoot":"","sources":["../../src/react/redirect-storage.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAE/F,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;AAEzC,SAAS,SAAS;IAChB,OAAO,OAAO,MAAM,KAAK,WAAW,CAAC;AACvC,CAAC;AAED,SAAS,OAAO;IACd,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,YAAY,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,IAAI,CAAC,SAAS,EAAE;QAAE,OAAO,IAAI,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,OAAO,EAAE,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;QAC9C,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IACV,OAAO,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,GAAW,EAAE,IAAY;IACxD,IAAI,CAAC,SAAS,EAAE;QAAE,OAAO;IACzB,MAAM,KAAK,GAAG,OAAO,EAAE,CAAC;IACxB,IAAI,KAAK,EAAE,CAAC;QACV,IAAI,CAAC;YACH,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACzB,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACnB,OAAO;QACT,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC;IACD,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,IAAI,CAAC,SAAS,EAAE;QAAE,OAAO;IACzB,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACnB,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;AACZ,CAAC","sourcesContent":["// A browser that blocks site data throws on the storage access itself (RULE-STD-FRONTEND-010).\n\nconst memory = new Map<string, string>();\n\nfunction inBrowser(): boolean {\n return typeof window !== 'undefined';\n}\n\nfunction storage(): Storage | null {\n try {\n return window.localStorage;\n } catch {\n return null;\n }\n}\n\nexport function readRedirectPath(key: string): string | null {\n if (!inBrowser()) return null;\n try {\n const saved = storage()?.getItem(key) ?? null;\n if (saved !== null) return saved;\n } catch {}\n return memory.get(key) ?? null;\n}\n\nexport function saveRedirectPath(key: string, path: string): void {\n if (!inBrowser()) return;\n const store = storage();\n if (store) {\n try {\n store.setItem(key, path);\n memory.delete(key);\n return;\n } catch {}\n }\n memory.set(key, path);\n}\n\nexport function clearRedirectPath(key: string): void {\n if (!inBrowser()) return;\n memory.delete(key);\n try {\n storage()?.removeItem(key);\n } catch {}\n}\n"]}
@@ -1,40 +1,19 @@
1
- import type { Auth, User } from "firebase/auth";
1
+ import type { Auth, User } from 'firebase/auth';
2
2
  export interface AuthProviderConfig<TClaims = Record<string, unknown>> {
3
3
  auth: Auth;
4
- /** Transform raw ID token claims into app claims shape */
5
4
  parseClaims: (raw: Record<string, unknown>) => TClaims;
6
- /** Default claims when no user or claims unavailable */
7
5
  defaultClaims: TClaims;
8
- /** Called on Firebase auth state change (for monitoring, analytics) */
9
6
  onAuthStateChange?: (user: User | null) => void;
10
- /** Called on errors (for Sentry) */
11
7
  onError?: (error: unknown, context: string) => void;
12
- /**
13
- * Optional readiness gate awaited before the provider reports `loading: false`.
14
- * Use when data consumers keyed on `user` must not mount until some async
15
- * client readiness completes first — e.g. an App Check / attestation token,
16
- * without which every data subscription opened at mount is rejected once and
17
- * dies. While it is closed the context exposes `user: null`,
18
- * `isAuthenticated: false` and the default claims, so no uid-keyed consumer can
19
- * mount early. The gate FAILS OPEN: a rejection is reported via `onError('readyGate')`
20
- * and the provider proceeds, so a broken gate degrades to today's behavior
21
- * rather than bricking the app. Called once on mount (client only).
22
- */
23
8
  readyGate?: () => Promise<unknown>;
24
9
  }
25
10
  export interface AuthContextValue<TClaims = Record<string, unknown>> {
26
- /** Firebase User */
27
11
  user: User | null;
28
- /** Parsed custom claims */
29
12
  claims: TClaims;
30
- /** True until auth + claims resolved */
31
13
  loading: boolean;
32
- /** Individual loading states */
33
14
  authLoading: boolean;
34
15
  claimsLoading: boolean;
35
- /** Convenience: !!user */
36
16
  isAuthenticated: boolean;
37
- /** Force refresh claims from Firebase */
38
17
  refreshClaims: () => Promise<void>;
39
18
  }
40
19
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/react/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAEhD,MAAM,WAAW,kBAAkB,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACnE,IAAI,EAAE,IAAI,CAAC;IAEX,0DAA0D;IAC1D,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;IAEvD,wDAAwD;IACxD,aAAa,EAAE,OAAO,CAAC;IAEvB,uEAAuE;IACvE,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAC;IAEhD,oCAAoC;IACpC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAEpD;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,gBAAgB,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACjE,oBAAoB;IACpB,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAElB,2BAA2B;IAC3B,MAAM,EAAE,OAAO,CAAC;IAEhB,wCAAwC;IACxC,OAAO,EAAE,OAAO,CAAC;IAEjB,gCAAgC;IAChC,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;IAEvB,0BAA0B;IAC1B,eAAe,EAAE,OAAO,CAAC;IAEzB,yCAAyC;IACzC,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/react/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAEhD,MAAM,WAAW,kBAAkB,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACnE,IAAI,EAAE,IAAI,CAAC;IAEX,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;IAEvD,aAAa,EAAE,OAAO,CAAC;IAEvB,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAC;IAEhD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAEpD,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,gBAAgB,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACjE,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAElB,MAAM,EAAE,OAAO,CAAC;IAEhB,OAAO,EAAE,OAAO,CAAC;IAEjB,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;IAEvB,eAAe,EAAE,OAAO,CAAC;IAEzB,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/react/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Auth, User } from \"firebase/auth\";\n\nexport interface AuthProviderConfig<TClaims = Record<string, unknown>> {\n auth: Auth;\n\n /** Transform raw ID token claims into app claims shape */\n parseClaims: (raw: Record<string, unknown>) => TClaims;\n\n /** Default claims when no user or claims unavailable */\n defaultClaims: TClaims;\n\n /** Called on Firebase auth state change (for monitoring, analytics) */\n onAuthStateChange?: (user: User | null) => void;\n\n /** Called on errors (for Sentry) */\n onError?: (error: unknown, context: string) => void;\n\n /**\n * Optional readiness gate awaited before the provider reports `loading: false`.\n * Use when data consumers keyed on `user` must not mount until some async\n * client readiness completes first — e.g. an App Check / attestation token,\n * without which every data subscription opened at mount is rejected once and\n * dies. While it is closed the context exposes `user: null`,\n * `isAuthenticated: false` and the default claims, so no uid-keyed consumer can\n * mount early. The gate FAILS OPEN: a rejection is reported via `onError('readyGate')`\n * and the provider proceeds, so a broken gate degrades to today's behavior\n * rather than bricking the app. Called once on mount (client only).\n */\n readyGate?: () => Promise<unknown>;\n}\n\nexport interface AuthContextValue<TClaims = Record<string, unknown>> {\n /** Firebase User */\n user: User | null;\n\n /** Parsed custom claims */\n claims: TClaims;\n\n /** True until auth + claims resolved */\n loading: boolean;\n\n /** Individual loading states */\n authLoading: boolean;\n claimsLoading: boolean;\n\n /** Convenience: !!user */\n isAuthenticated: boolean;\n\n /** Force refresh claims from Firebase */\n refreshClaims: () => Promise<void>;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/react/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Auth, User } from 'firebase/auth';\n\nexport interface AuthProviderConfig<TClaims = Record<string, unknown>> {\n auth: Auth;\n\n parseClaims: (raw: Record<string, unknown>) => TClaims;\n\n defaultClaims: TClaims;\n\n onAuthStateChange?: (user: User | null) => void;\n\n onError?: (error: unknown, context: string) => void;\n\n readyGate?: () => Promise<unknown>;\n}\n\nexport interface AuthContextValue<TClaims = Record<string, unknown>> {\n user: User | null;\n\n claims: TClaims;\n\n loading: boolean;\n\n authLoading: boolean;\n claimsLoading: boolean;\n\n isAuthenticated: boolean;\n\n refreshClaims: () => Promise<void>;\n}\n"]}
@@ -1,7 +1,3 @@
1
- import type { AuthContextValue } from "./types.js";
2
- /**
3
- * Access the auth context. Must be used within an AuthProvider.
4
- * Generic param lets each app specify its own claims type.
5
- */
1
+ import type { AuthContextValue } from './types.js';
6
2
  export declare function useAuth<TClaims = Record<string, unknown>>(): AuthContextValue<TClaims>;
7
3
  //# sourceMappingURL=useAuth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../src/react/useAuth.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD;;;GAGG;AACH,wBAAgB,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,gBAAgB,CAAC,OAAO,CAAC,CAMtF"}
1
+ {"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../src/react/useAuth.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,wBAAgB,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,gBAAgB,CAAC,OAAO,CAAC,CAMtF"}
@@ -1,14 +1,10 @@
1
- "use client";
2
- import { useContext } from "react";
3
- import { AuthContext } from "./AuthProvider.js";
4
- /**
5
- * Access the auth context. Must be used within an AuthProvider.
6
- * Generic param lets each app specify its own claims type.
7
- */
1
+ 'use client';
2
+ import { useContext } from 'react';
3
+ import { AuthContext } from './AuthProvider.js';
8
4
  export function useAuth() {
9
5
  const ctx = useContext(AuthContext);
10
6
  if (!ctx) {
11
- throw new Error("useAuth must be used within an AuthProvider");
7
+ throw new Error('useAuth must be used within an AuthProvider');
12
8
  }
13
9
  return ctx;
14
10
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useAuth.js","sourceRoot":"","sources":["../../src/react/useAuth.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhD;;;GAGG;AACH,MAAM,UAAU,OAAO;IACrB,MAAM,GAAG,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IACpC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,GAAgC,CAAC;AAC1C,CAAC","sourcesContent":["\"use client\";\n\nimport { useContext } from \"react\";\nimport { AuthContext } from \"./AuthProvider.js\";\nimport type { AuthContextValue } from \"./types.js\";\n\n/**\n * Access the auth context. Must be used within an AuthProvider.\n * Generic param lets each app specify its own claims type.\n */\nexport function useAuth<TClaims = Record<string, unknown>>(): AuthContextValue<TClaims> {\n const ctx = useContext(AuthContext);\n if (!ctx) {\n throw new Error(\"useAuth must be used within an AuthProvider\");\n }\n return ctx as AuthContextValue<TClaims>;\n}\n"]}
1
+ {"version":3,"file":"useAuth.js","sourceRoot":"","sources":["../../src/react/useAuth.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhD,MAAM,UAAU,OAAO;IACrB,MAAM,GAAG,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IACpC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,GAAgC,CAAC;AAC1C,CAAC","sourcesContent":["'use client';\n\nimport { useContext } from 'react';\nimport { AuthContext } from './AuthProvider.js';\nimport type { AuthContextValue } from './types.js';\n\nexport function useAuth<TClaims = Record<string, unknown>>(): AuthContextValue<TClaims> {\n const ctx = useContext(AuthContext);\n if (!ctx) {\n throw new Error('useAuth must be used within an AuthProvider');\n }\n return ctx as AuthContextValue<TClaims>;\n}\n"]}
@@ -1,50 +1,17 @@
1
1
  export interface AuthGuardConfig {
2
- /**
3
- * Routes accessible without authentication, matched by PREFIX
4
- * (`pathname.startsWith(entry)`), so `'/share/'` covers every `/share/:id`
5
- * beneath it and `'/terms'` covers `/terms/anything`. This is the SOLE source of public
6
- * routes — the hook has no built-in defaults and no app-specific literals, so
7
- * a consumer that needs a dynamic public section must list its prefix here.
8
- */
9
2
  publicRoutes: string[];
10
- /** Routes that redirect authenticated users away (login, register) */
11
3
  authRedirectRoutes: string[];
12
- /** Where unauthenticated users go */
13
4
  loginRoute: string;
14
- /** Where authenticated users go from authRedirectRoutes */
15
5
  defaultRoute: string;
16
- /** Special root path handling */
17
6
  rootRedirect?: {
18
7
  authenticated: string;
19
8
  unauthenticated: string;
20
9
  };
21
- /** localStorage key for post-login redirect */
22
10
  redirectKey?: string;
23
- /** Navigation function (e.g. router.replace) */
24
11
  replace: (path: string) => void;
25
- /** Current path */
26
12
  pathname: string;
27
- /** From useAuth() */
28
13
  loading: boolean;
29
- /** From useAuth() */
30
14
  isAuthenticated: boolean;
31
15
  }
32
- /**
33
- * Handles route protection. Call in app shell component.
34
- *
35
- * - Does nothing while loading.
36
- * - Redirects unauthenticated users from protected routes to loginRoute.
37
- * - Redirects authenticated users from authRedirectRoutes to defaultRoute.
38
- * - Saves attempted path for post-login redirect — a visitor's deep link only. When a
39
- * session ends (an observed signed-in → signed-out transition: a sign-out, a forced
40
- * sign-out, an expiry, another tab), the path it ended on is NOT saved and any saved
41
- * path is cleared, so the next account to sign in on this device never lands on the
42
- * previous account's page.
43
- *
44
- * "Public" is decided ENTIRELY by `config.publicRoutes` (prefix match). The hook
45
- * carries no built-in or implicit public route: every route a consumer wants
46
- * reachable while signed out — including a dynamic section like `/share/:id` — is
47
- * listed by that consumer as a prefix.
48
- */
49
16
  export declare function useAuthGuard(config: AuthGuardConfig): void;
50
17
  //# sourceMappingURL=useAuthGuard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useAuthGuard.d.ts","sourceRoot":"","sources":["../../src/react/useAuthGuard.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,sEAAsE;IACtE,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,YAAY,CAAC,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,mBAAmB;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,qBAAqB;IACrB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAsF1D"}
1
+ {"version":3,"file":"useAuthGuard.d.ts","sourceRoot":"","sources":["../../src/react/useAuthGuard.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAID,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAyE1D"}
@@ -1,39 +1,12 @@
1
- "use client";
2
- import { useEffect, useRef } from "react";
3
- import { clearRedirectPath, readRedirectPath, saveRedirectPath } from "./redirect-storage.js";
4
- const DEFAULT_REDIRECT_KEY = "auth_redirect_path";
5
- /**
6
- * Handles route protection. Call in app shell component.
7
- *
8
- * - Does nothing while loading.
9
- * - Redirects unauthenticated users from protected routes to loginRoute.
10
- * - Redirects authenticated users from authRedirectRoutes to defaultRoute.
11
- * - Saves attempted path for post-login redirect — a visitor's deep link only. When a
12
- * session ends (an observed signed-in → signed-out transition: a sign-out, a forced
13
- * sign-out, an expiry, another tab), the path it ended on is NOT saved and any saved
14
- * path is cleared, so the next account to sign in on this device never lands on the
15
- * previous account's page.
16
- *
17
- * "Public" is decided ENTIRELY by `config.publicRoutes` (prefix match). The hook
18
- * carries no built-in or implicit public route: every route a consumer wants
19
- * reachable while signed out — including a dynamic section like `/share/:id` — is
20
- * listed by that consumer as a prefix.
21
- */
1
+ 'use client';
2
+ import { useEffect, useRef } from 'react';
3
+ import { clearRedirectPath, readRedirectPath, saveRedirectPath } from './redirect-storage.js';
4
+ const DEFAULT_REDIRECT_KEY = 'auth_redirect_path';
22
5
  export function useAuthGuard(config) {
23
6
  const { publicRoutes, authRedirectRoutes, loginRoute, defaultRoute, rootRedirect, redirectKey = DEFAULT_REDIRECT_KEY, replace, pathname, loading, isAuthenticated, } = config;
24
- // The auth-route redirect below is SINGLE-SHOT per auth-route visit. That branch
25
- // CONSUMES redirectKey and issues a navigation, but the navigation commits
26
- // asynchronously (the router fetches the target route first), so this effect can
27
- // legitimately re-run while pathname still reads the auth route (an auth-state
28
- // re-render, a loading flap). A re-run then finds the key already gone and
29
- // re-replaces to defaultRoute, CLOBBERING the in-flight saved-path navigation —
30
- // a deep link that routed through login then landed on defaultRoute instead of
31
- // the saved path. The latch resets whenever the pathname is off the auth-route set.
7
+ // The router commits a navigation asynchronously, so this effect can re-run on the auth route
8
+ // after the key is consumed; the latch stops a second replace clobbering it.
32
9
  const authRouteRedirected = useRef(false);
33
- // The last settled authentication state this guard observed (null before the first), and
34
- // the pathname a session ended on. The effect can re-run on that same pathname before the
35
- // login navigation commits, so the "do not save" decision is held for that pathname rather
36
- // than for one run.
37
10
  const lastAuthenticated = useRef(null);
38
11
  const sessionEndedOn = useRef(null);
39
12
  useEffect(() => {
@@ -50,21 +23,18 @@ export function useAuthGuard(config) {
50
23
  if (!authRedirectRoutes.includes(pathname)) {
51
24
  authRouteRedirected.current = false;
52
25
  }
53
- // Root path special handling
54
- if (rootRedirect && pathname === "/") {
26
+ if (rootRedirect && pathname === '/') {
55
27
  replace(isAuthenticated ? rootRedirect.authenticated : rootRedirect.unauthenticated);
56
28
  return;
57
29
  }
58
30
  const isPublic = publicRoutes.some((r) => pathname.startsWith(r));
59
31
  const isAuthRedirect = authRedirectRoutes.includes(pathname);
60
- // Unauthenticated on protected route -> save a visitor's path, go to login
61
32
  if (!isAuthenticated && !isPublic) {
62
33
  if (sessionEndedOn.current === null)
63
34
  saveRedirectPath(redirectKey, pathname);
64
35
  replace(loginRoute);
65
36
  return;
66
37
  }
67
- // Authenticated user on auth-only route (login, register, etc.)
68
38
  if (isAuthenticated && isAuthRedirect) {
69
39
  if (authRouteRedirected.current)
70
40
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"useAuthGuard.js","sourceRoot":"","sources":["../../src/react/useAuthGuard.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AA+B9F,MAAM,oBAAoB,GAAG,oBAAoB,CAAC;AAElD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,YAAY,CAAC,MAAuB;IAClD,MAAM,EACJ,YAAY,EACZ,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,WAAW,GAAG,oBAAoB,EAClC,OAAO,EACP,QAAQ,EACR,OAAO,EACP,eAAe,GAChB,GAAG,MAAM,CAAC;IAEX,iFAAiF;IACjF,2EAA2E;IAC3E,iFAAiF;IACjF,+EAA+E;IAC/E,2EAA2E;IAC3E,gFAAgF;IAChF,+EAA+E;IAC/E,oFAAoF;IACpF,MAAM,mBAAmB,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAE1C,yFAAyF;IACzF,0FAA0F;IAC1F,2FAA2F;IAC3F,oBAAoB;IACpB,MAAM,iBAAiB,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IACvD,MAAM,cAAc,GAAG,MAAM,CAAgB,IAAI,CAAC,CAAC;IAEnD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO;YAAE,OAAO;QAEpB,IAAI,iBAAiB,CAAC,OAAO,KAAK,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YAC3D,cAAc,CAAC,OAAO,GAAG,QAAQ,CAAC;YAClC,iBAAiB,CAAC,WAAW,CAAC,CAAC;QACjC,CAAC;aAAM,IAAI,eAAe,IAAI,cAAc,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YAClE,cAAc,CAAC,OAAO,GAAG,IAAI,CAAC;QAChC,CAAC;QACD,iBAAiB,CAAC,OAAO,GAAG,eAAe,CAAC;QAE5C,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3C,mBAAmB,CAAC,OAAO,GAAG,KAAK,CAAC;QACtC,CAAC;QAED,6BAA6B;QAC7B,IAAI,YAAY,IAAI,QAAQ,KAAK,GAAG,EAAE,CAAC;YACrC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;YACrF,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QAClE,MAAM,cAAc,GAAG,kBAAkB,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAE7D,2EAA2E;QAC3E,IAAI,CAAC,eAAe,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClC,IAAI,cAAc,CAAC,OAAO,KAAK,IAAI;gBAAE,gBAAgB,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;YAC7E,OAAO,CAAC,UAAU,CAAC,CAAC;YACpB,OAAO;QACT,CAAC;QAED,gEAAgE;QAChE,IAAI,eAAe,IAAI,cAAc,EAAE,CAAC;YACtC,IAAI,mBAAmB,CAAC,OAAO;gBAAE,OAAO;YACxC,mBAAmB,CAAC,OAAO,GAAG,IAAI,CAAC;YACnC,IAAI,MAAM,GAAG,YAAY,CAAC;YAC1B,MAAM,KAAK,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;YAC5C,IAAI,KAAK,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACjD,MAAM,GAAG,KAAK,CAAC;YACjB,CAAC;YACD,iBAAiB,CAAC,WAAW,CAAC,CAAC;YAC/B,OAAO,CAAC,MAAM,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,EAAE;QACD,OAAO;QACP,eAAe;QACf,QAAQ;QACR,YAAY;QACZ,kBAAkB;QAClB,UAAU;QACV,YAAY;QACZ,YAAY;QACZ,WAAW;QACX,OAAO;KACR,CAAC,CAAC;AACL,CAAC","sourcesContent":["\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport { clearRedirectPath, readRedirectPath, saveRedirectPath } from \"./redirect-storage.js\";\n\nexport interface AuthGuardConfig {\n /**\n * Routes accessible without authentication, matched by PREFIX\n * (`pathname.startsWith(entry)`), so `'/share/'` covers every `/share/:id`\n * beneath it and `'/terms'` covers `/terms/anything`. This is the SOLE source of public\n * routes — the hook has no built-in defaults and no app-specific literals, so\n * a consumer that needs a dynamic public section must list its prefix here.\n */\n publicRoutes: string[];\n /** Routes that redirect authenticated users away (login, register) */\n authRedirectRoutes: string[];\n /** Where unauthenticated users go */\n loginRoute: string;\n /** Where authenticated users go from authRedirectRoutes */\n defaultRoute: string;\n /** Special root path handling */\n rootRedirect?: { authenticated: string; unauthenticated: string };\n /** localStorage key for post-login redirect */\n redirectKey?: string;\n /** Navigation function (e.g. router.replace) */\n replace: (path: string) => void;\n /** Current path */\n pathname: string;\n /** From useAuth() */\n loading: boolean;\n /** From useAuth() */\n isAuthenticated: boolean;\n}\n\nconst DEFAULT_REDIRECT_KEY = \"auth_redirect_path\";\n\n/**\n * Handles route protection. Call in app shell component.\n *\n * - Does nothing while loading.\n * - Redirects unauthenticated users from protected routes to loginRoute.\n * - Redirects authenticated users from authRedirectRoutes to defaultRoute.\n * - Saves attempted path for post-login redirect — a visitor's deep link only. When a\n * session ends (an observed signed-in → signed-out transition: a sign-out, a forced\n * sign-out, an expiry, another tab), the path it ended on is NOT saved and any saved\n * path is cleared, so the next account to sign in on this device never lands on the\n * previous account's page.\n *\n * \"Public\" is decided ENTIRELY by `config.publicRoutes` (prefix match). The hook\n * carries no built-in or implicit public route: every route a consumer wants\n * reachable while signed out — including a dynamic section like `/share/:id` — is\n * listed by that consumer as a prefix.\n */\nexport function useAuthGuard(config: AuthGuardConfig): void {\n const {\n publicRoutes,\n authRedirectRoutes,\n loginRoute,\n defaultRoute,\n rootRedirect,\n redirectKey = DEFAULT_REDIRECT_KEY,\n replace,\n pathname,\n loading,\n isAuthenticated,\n } = config;\n\n // The auth-route redirect below is SINGLE-SHOT per auth-route visit. That branch\n // CONSUMES redirectKey and issues a navigation, but the navigation commits\n // asynchronously (the router fetches the target route first), so this effect can\n // legitimately re-run while pathname still reads the auth route (an auth-state\n // re-render, a loading flap). A re-run then finds the key already gone and\n // re-replaces to defaultRoute, CLOBBERING the in-flight saved-path navigation —\n // a deep link that routed through login then landed on defaultRoute instead of\n // the saved path. The latch resets whenever the pathname is off the auth-route set.\n const authRouteRedirected = useRef(false);\n\n // The last settled authentication state this guard observed (null before the first), and\n // the pathname a session ended on. The effect can re-run on that same pathname before the\n // login navigation commits, so the \"do not save\" decision is held for that pathname rather\n // than for one run.\n const lastAuthenticated = useRef<boolean | null>(null);\n const sessionEndedOn = useRef<string | null>(null);\n\n useEffect(() => {\n if (loading) return;\n\n if (lastAuthenticated.current === true && !isAuthenticated) {\n sessionEndedOn.current = pathname;\n clearRedirectPath(redirectKey);\n } else if (isAuthenticated || sessionEndedOn.current !== pathname) {\n sessionEndedOn.current = null;\n }\n lastAuthenticated.current = isAuthenticated;\n\n if (!authRedirectRoutes.includes(pathname)) {\n authRouteRedirected.current = false;\n }\n\n // Root path special handling\n if (rootRedirect && pathname === \"/\") {\n replace(isAuthenticated ? rootRedirect.authenticated : rootRedirect.unauthenticated);\n return;\n }\n\n const isPublic = publicRoutes.some((r) => pathname.startsWith(r));\n const isAuthRedirect = authRedirectRoutes.includes(pathname);\n\n // Unauthenticated on protected route -> save a visitor's path, go to login\n if (!isAuthenticated && !isPublic) {\n if (sessionEndedOn.current === null) saveRedirectPath(redirectKey, pathname);\n replace(loginRoute);\n return;\n }\n\n // Authenticated user on auth-only route (login, register, etc.)\n if (isAuthenticated && isAuthRedirect) {\n if (authRouteRedirected.current) return;\n authRouteRedirected.current = true;\n let target = defaultRoute;\n const saved = readRedirectPath(redirectKey);\n if (saved && !authRedirectRoutes.includes(saved)) {\n target = saved;\n }\n clearRedirectPath(redirectKey);\n replace(target);\n }\n }, [\n loading,\n isAuthenticated,\n pathname,\n publicRoutes,\n authRedirectRoutes,\n loginRoute,\n defaultRoute,\n rootRedirect,\n redirectKey,\n replace,\n ]);\n}\n"]}
1
+ {"version":3,"file":"useAuthGuard.js","sourceRoot":"","sources":["../../src/react/useAuthGuard.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAe9F,MAAM,oBAAoB,GAAG,oBAAoB,CAAC;AAElD,MAAM,UAAU,YAAY,CAAC,MAAuB;IAClD,MAAM,EACJ,YAAY,EACZ,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,WAAW,GAAG,oBAAoB,EAClC,OAAO,EACP,QAAQ,EACR,OAAO,EACP,eAAe,GAChB,GAAG,MAAM,CAAC;IAEX,8FAA8F;IAC9F,6EAA6E;IAC7E,MAAM,mBAAmB,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAE1C,MAAM,iBAAiB,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IACvD,MAAM,cAAc,GAAG,MAAM,CAAgB,IAAI,CAAC,CAAC;IAEnD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO;YAAE,OAAO;QAEpB,IAAI,iBAAiB,CAAC,OAAO,KAAK,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YAC3D,cAAc,CAAC,OAAO,GAAG,QAAQ,CAAC;YAClC,iBAAiB,CAAC,WAAW,CAAC,CAAC;QACjC,CAAC;aAAM,IAAI,eAAe,IAAI,cAAc,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YAClE,cAAc,CAAC,OAAO,GAAG,IAAI,CAAC;QAChC,CAAC;QACD,iBAAiB,CAAC,OAAO,GAAG,eAAe,CAAC;QAE5C,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3C,mBAAmB,CAAC,OAAO,GAAG,KAAK,CAAC;QACtC,CAAC;QAED,IAAI,YAAY,IAAI,QAAQ,KAAK,GAAG,EAAE,CAAC;YACrC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;YACrF,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QAClE,MAAM,cAAc,GAAG,kBAAkB,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAE7D,IAAI,CAAC,eAAe,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClC,IAAI,cAAc,CAAC,OAAO,KAAK,IAAI;gBAAE,gBAAgB,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;YAC7E,OAAO,CAAC,UAAU,CAAC,CAAC;YACpB,OAAO;QACT,CAAC;QAED,IAAI,eAAe,IAAI,cAAc,EAAE,CAAC;YACtC,IAAI,mBAAmB,CAAC,OAAO;gBAAE,OAAO;YACxC,mBAAmB,CAAC,OAAO,GAAG,IAAI,CAAC;YACnC,IAAI,MAAM,GAAG,YAAY,CAAC;YAC1B,MAAM,KAAK,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;YAC5C,IAAI,KAAK,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACjD,MAAM,GAAG,KAAK,CAAC;YACjB,CAAC;YACD,iBAAiB,CAAC,WAAW,CAAC,CAAC;YAC/B,OAAO,CAAC,MAAM,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,EAAE;QACD,OAAO;QACP,eAAe;QACf,QAAQ;QACR,YAAY;QACZ,kBAAkB;QAClB,UAAU;QACV,YAAY;QACZ,YAAY;QACZ,WAAW;QACX,OAAO;KACR,CAAC,CAAC;AACL,CAAC","sourcesContent":["'use client';\n\nimport { useEffect, useRef } from 'react';\nimport { clearRedirectPath, readRedirectPath, saveRedirectPath } from './redirect-storage.js';\n\nexport interface AuthGuardConfig {\n publicRoutes: string[];\n authRedirectRoutes: string[];\n loginRoute: string;\n defaultRoute: string;\n rootRedirect?: { authenticated: string; unauthenticated: string };\n redirectKey?: string;\n replace: (path: string) => void;\n pathname: string;\n loading: boolean;\n isAuthenticated: boolean;\n}\n\nconst DEFAULT_REDIRECT_KEY = 'auth_redirect_path';\n\nexport function useAuthGuard(config: AuthGuardConfig): void {\n const {\n publicRoutes,\n authRedirectRoutes,\n loginRoute,\n defaultRoute,\n rootRedirect,\n redirectKey = DEFAULT_REDIRECT_KEY,\n replace,\n pathname,\n loading,\n isAuthenticated,\n } = config;\n\n // The router commits a navigation asynchronously, so this effect can re-run on the auth route\n // after the key is consumed; the latch stops a second replace clobbering it.\n const authRouteRedirected = useRef(false);\n\n const lastAuthenticated = useRef<boolean | null>(null);\n const sessionEndedOn = useRef<string | null>(null);\n\n useEffect(() => {\n if (loading) return;\n\n if (lastAuthenticated.current === true && !isAuthenticated) {\n sessionEndedOn.current = pathname;\n clearRedirectPath(redirectKey);\n } else if (isAuthenticated || sessionEndedOn.current !== pathname) {\n sessionEndedOn.current = null;\n }\n lastAuthenticated.current = isAuthenticated;\n\n if (!authRedirectRoutes.includes(pathname)) {\n authRouteRedirected.current = false;\n }\n\n if (rootRedirect && pathname === '/') {\n replace(isAuthenticated ? rootRedirect.authenticated : rootRedirect.unauthenticated);\n return;\n }\n\n const isPublic = publicRoutes.some((r) => pathname.startsWith(r));\n const isAuthRedirect = authRedirectRoutes.includes(pathname);\n\n if (!isAuthenticated && !isPublic) {\n if (sessionEndedOn.current === null) saveRedirectPath(redirectKey, pathname);\n replace(loginRoute);\n return;\n }\n\n if (isAuthenticated && isAuthRedirect) {\n if (authRouteRedirected.current) return;\n authRouteRedirected.current = true;\n let target = defaultRoute;\n const saved = readRedirectPath(redirectKey);\n if (saved && !authRedirectRoutes.includes(saved)) {\n target = saved;\n }\n clearRedirectPath(redirectKey);\n replace(target);\n }\n }, [\n loading,\n isAuthenticated,\n pathname,\n publicRoutes,\n authRedirectRoutes,\n loginRoute,\n defaultRoute,\n rootRedirect,\n redirectKey,\n replace,\n ]);\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { Auth, User } from "firebase/auth";
1
+ import type { Auth, User } from 'firebase/auth';
2
2
  export type AuthState = {
3
3
  user: User | null;
4
4
  loading: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"useAuthState.d.ts","sourceRoot":"","sources":["../../src/react/useAuthState.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAGhD,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,CA8BlD"}
1
+ {"version":3,"file":"useAuthState.d.ts","sourceRoot":"","sources":["../../src/react/useAuthState.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAGhD,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,CA0BlD"}