cloudflare-next-intl 0.6.13 → 0.6.15
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.
- package/dist/src/config/init_config.d.ts +8 -3
- package/dist/src/config/init_config.js +40 -4
- package/dist/src/firebase_auth/client/auth_user_provider.js +57 -1
- package/dist/src/server/components/server_provider.d.ts +60 -1
- package/dist/src/server/components/server_provider.js +77 -4
- package/dist/src/types/types.d.ts +32 -3
- package/package.json +1 -1
|
@@ -2,9 +2,14 @@ import type { LocalePrefixMode, Locales, RoutingConfig } from '../types/types';
|
|
|
2
2
|
/**
|
|
3
3
|
* Defines and type-checks your app's i18n routing config.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* `AppLocales`/`AppLocalePrefixMode` from the literal
|
|
7
|
-
* in, giving you autocomplete/type errors on
|
|
5
|
+
* Mostly an identity function at runtime — it exists primarily so
|
|
6
|
+
* TypeScript infers `AppLocales`/`AppLocalePrefixMode` from the literal
|
|
7
|
+
* config object you pass in, giving you autocomplete/type errors on
|
|
8
|
+
* `locale` params elsewhere. The one runtime behavior: if `firebaseAuth` is
|
|
9
|
+
* set, `redirectAuthPath`/`homePath`/`verifyEmailPath` are auto-corrected to
|
|
10
|
+
* start with `/` (with a console warning) if you forgot it — see
|
|
11
|
+
* `normalizeFirebaseAuthPaths` above for why that specific typo is worth
|
|
12
|
+
* guarding against.
|
|
8
13
|
*
|
|
9
14
|
* Export the result from the file referenced by `@intl-config` (see your
|
|
10
15
|
* `next.config`), e.g.:
|
|
@@ -1,9 +1,45 @@
|
|
|
1
|
+
// Every path this package compares against `request.nextUrl.pathname`
|
|
2
|
+
// (always `/`-prefixed) must itself start with `/` — a missing leading
|
|
3
|
+
// slash means `path === fa.verifyEmailPath` (and the same check for
|
|
4
|
+
// `redirectAuthPath`/`homePath`) never matches, silently disabling that
|
|
5
|
+
// redirect/exemption entirely (e.g. an infinite redirect loop on
|
|
6
|
+
// `verifyEmailPath` because the page is never recognized as itself).
|
|
7
|
+
// Auto-prepending `/` here fixes the common typo (`'login'` instead of
|
|
8
|
+
// `'/login'`) at the source, for every consumer, instead of requiring each
|
|
9
|
+
// one to notice and fix it themselves.
|
|
10
|
+
const FIREBASE_AUTH_PATH_FIELDS = ['redirectAuthPath', 'homePath', 'verifyEmailPath'];
|
|
11
|
+
function normalizeFirebaseAuthPaths(config) {
|
|
12
|
+
const fa = config.firebaseAuth;
|
|
13
|
+
if (!fa)
|
|
14
|
+
return config;
|
|
15
|
+
let changed = false;
|
|
16
|
+
const normalizedFa = { ...fa };
|
|
17
|
+
for (const field of FIREBASE_AUTH_PATH_FIELDS) {
|
|
18
|
+
const value = normalizedFa[field];
|
|
19
|
+
if (typeof value === 'string' && value !== '' && !value.startsWith('/')) {
|
|
20
|
+
console.warn(`[cloudflare-next-intl] firebaseAuth.${field} ("${value}") is missing its leading "/" — ` +
|
|
21
|
+
`auto-corrected to "/${value}". Paths are compared against the URL pathname (always ` +
|
|
22
|
+
`"/"-prefixed), so without this fix the check would never match and silently disable the ` +
|
|
23
|
+
`redirect/exemption for this path. Fix your config to avoid this warning.`);
|
|
24
|
+
normalizedFa[field] = `/${value}`;
|
|
25
|
+
changed = true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (!changed)
|
|
29
|
+
return config;
|
|
30
|
+
return { ...config, firebaseAuth: normalizedFa };
|
|
31
|
+
}
|
|
1
32
|
/**
|
|
2
33
|
* Defines and type-checks your app's i18n routing config.
|
|
3
34
|
*
|
|
4
|
-
*
|
|
5
|
-
* `AppLocales`/`AppLocalePrefixMode` from the literal
|
|
6
|
-
* in, giving you autocomplete/type errors on
|
|
35
|
+
* Mostly an identity function at runtime — it exists primarily so
|
|
36
|
+
* TypeScript infers `AppLocales`/`AppLocalePrefixMode` from the literal
|
|
37
|
+
* config object you pass in, giving you autocomplete/type errors on
|
|
38
|
+
* `locale` params elsewhere. The one runtime behavior: if `firebaseAuth` is
|
|
39
|
+
* set, `redirectAuthPath`/`homePath`/`verifyEmailPath` are auto-corrected to
|
|
40
|
+
* start with `/` (with a console warning) if you forgot it — see
|
|
41
|
+
* `normalizeFirebaseAuthPaths` above for why that specific typo is worth
|
|
42
|
+
* guarding against.
|
|
7
43
|
*
|
|
8
44
|
* Export the result from the file referenced by `@intl-config` (see your
|
|
9
45
|
* `next.config`), e.g.:
|
|
@@ -15,5 +51,5 @@
|
|
|
15
51
|
* ```
|
|
16
52
|
*/
|
|
17
53
|
export function setIntlConfig(config) {
|
|
18
|
-
return config;
|
|
54
|
+
return normalizeFirebaseAuthPaths(config);
|
|
19
55
|
}
|
|
@@ -108,6 +108,19 @@ export default function AuthUserProvider({ initialUser = null, children }) {
|
|
|
108
108
|
// login-then-bounce-home flash whenever the two disagreed.
|
|
109
109
|
const consecutiveNulls = useRef(0);
|
|
110
110
|
const [confirmedSignedOut, setConfirmedSignedOut] = useState(initialUser === null);
|
|
111
|
+
// Whether `onSignIn`/`onSignOut` have already fired for the CURRENT
|
|
112
|
+
// signed-in/signed-out state — both seeded from `initialUser` so a
|
|
113
|
+
// callback observing the SAME state `initialUser` already established
|
|
114
|
+
// (server-resolved, not a fresh client-side transition) does not refire
|
|
115
|
+
// it. Reset on the opposite transition so the next real occurrence of
|
|
116
|
+
// this state fires again.
|
|
117
|
+
const signInCallbackFired = useRef(initialUser !== null);
|
|
118
|
+
const signOutCallbackFired = useRef(initialUser === null);
|
|
119
|
+
// Tracks the last-observed `emailVerified` value so `onEmailVerified`
|
|
120
|
+
// fires exactly once on the false→true edge, not on every later
|
|
121
|
+
// observation of an already-verified user (both `onIdTokenChanged` and
|
|
122
|
+
// `reloadUser` can be the first to observe the transition).
|
|
123
|
+
const emailVerifiedRef = useRef(initialUser?.emailVerified ?? false);
|
|
111
124
|
useEffect(() => {
|
|
112
125
|
const { user, loading } = state;
|
|
113
126
|
if (loading || isWhiteListed)
|
|
@@ -168,11 +181,42 @@ export default function AuthUserProvider({ initialUser = null, children }) {
|
|
|
168
181
|
if (user) {
|
|
169
182
|
consecutiveNulls.current = 0;
|
|
170
183
|
setConfirmedSignedOut(false);
|
|
184
|
+
signOutCallbackFired.current = false;
|
|
185
|
+
if (!signInCallbackFired.current) {
|
|
186
|
+
signInCallbackFired.current = true;
|
|
187
|
+
try {
|
|
188
|
+
await fa.onSignIn?.(user);
|
|
189
|
+
}
|
|
190
|
+
catch (e) {
|
|
191
|
+
console.error('AuthUserProvider: onSignIn callback failed', e);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (!emailVerifiedRef.current && user.emailVerified) {
|
|
195
|
+
emailVerifiedRef.current = true;
|
|
196
|
+
try {
|
|
197
|
+
await fa.onEmailVerified?.(user);
|
|
198
|
+
}
|
|
199
|
+
catch (e) {
|
|
200
|
+
console.error('AuthUserProvider: onEmailVerified callback failed', e);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
emailVerifiedRef.current = user.emailVerified;
|
|
205
|
+
}
|
|
171
206
|
}
|
|
172
207
|
else {
|
|
173
208
|
consecutiveNulls.current += 1;
|
|
174
|
-
|
|
209
|
+
signInCallbackFired.current = false;
|
|
210
|
+
if (consecutiveNulls.current >= 2 && !signOutCallbackFired.current) {
|
|
175
211
|
setConfirmedSignedOut(true);
|
|
212
|
+
signOutCallbackFired.current = true;
|
|
213
|
+
try {
|
|
214
|
+
await fa.onSignOut?.();
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
console.error('AuthUserProvider: onSignOut callback failed', e);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
176
220
|
}
|
|
177
221
|
const flipped = previous !== undefined && previous !== isSignedIn;
|
|
178
222
|
const contradictsPage = previous === undefined && isSignedIn === isAuthPage;
|
|
@@ -221,6 +265,18 @@ export default function AuthUserProvider({ initialUser = null, children }) {
|
|
|
221
265
|
}
|
|
222
266
|
}
|
|
223
267
|
await writeSession(user, sessionCookieName, maxAge, refreshTokenCookieName, refreshTokenMaxAge, emailVerifiedHintCookieName, confirmedToken);
|
|
268
|
+
if (!emailVerifiedRef.current && user.emailVerified) {
|
|
269
|
+
emailVerifiedRef.current = true;
|
|
270
|
+
try {
|
|
271
|
+
await fa.onEmailVerified?.(user);
|
|
272
|
+
}
|
|
273
|
+
catch (e) {
|
|
274
|
+
console.error('AuthUserProvider: onEmailVerified callback failed', e);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
emailVerifiedRef.current = user.emailVerified;
|
|
279
|
+
}
|
|
224
280
|
setAuthUserCache(user);
|
|
225
281
|
setState({ user, loading: false });
|
|
226
282
|
}
|
|
@@ -13,6 +13,64 @@ import type { TranslationObject } from "../../types/types";
|
|
|
13
13
|
* otherwise.
|
|
14
14
|
* @param messages Optional pre-loaded messages for `language`. If omitted,
|
|
15
15
|
* they're loaded via `getMessage(language)`.
|
|
16
|
+
* @param staticSafe Marks THIS RENDER of `IntlProvider` as one that's safe
|
|
17
|
+
* to serve from static rendering / ISR — i.e. the caller already knows
|
|
18
|
+
* the current route never needs a server-resolved auth user (a public
|
|
19
|
+
* page: marketing, privacy policy, docs, etc). Concretely, setting this
|
|
20
|
+
* to `true` skips the internal `resolveAuthUserAndRedirect()` call.
|
|
21
|
+
*
|
|
22
|
+
* ── Why this call is normally made, and why skipping it is safe ──
|
|
23
|
+
* When `firebaseAuth` is configured, `IntlProvider` by default calls
|
|
24
|
+
* `resolveAuthUserAndRedirect()`, which:
|
|
25
|
+
* 1. Reads the session cookie via `cookies()` and verifies it against
|
|
26
|
+
* Firebase (server-side, authoritative check for "who is this?").
|
|
27
|
+
* 2. Reads the current pathname via `headers()` (`x-pathname`, set by
|
|
28
|
+
* `intlMiddleware`) to redirect guest → `redirectAuthPath` or
|
|
29
|
+
* signed-in → `homePath` on an auth page.
|
|
30
|
+
* 3. Returns the resolved user so the client `AuthUserProvider` can
|
|
31
|
+
* render the correct signed-in/signed-out UI on the FIRST paint,
|
|
32
|
+
* with zero flash.
|
|
33
|
+
* Both `cookies()` and `headers()` are request-scoped APIs — calling
|
|
34
|
+
* either one forces Next.js to render the ENTIRE subtree dynamically on
|
|
35
|
+
* every request. No static HTML, no ISR, no caching — for that route
|
|
36
|
+
* AND every route nested under this same `IntlProvider` call, whether
|
|
37
|
+
* or not that specific route actually needs auth. A page in
|
|
38
|
+
* `firebaseAuth.whiteListPaths` (meant to be public) is NOT exempt from
|
|
39
|
+
* this cost today: the whitelist check happens only AFTER `cookies()`/
|
|
40
|
+
* `headers()` are already read, so it's just as dynamic as a protected
|
|
41
|
+
* page.
|
|
42
|
+
*
|
|
43
|
+
* The redirect part of step 2 is redundant on any project using the
|
|
44
|
+
* default middleware wiring (`firebaseAuth.middlewareEnabled !== false`,
|
|
45
|
+
* the default): `intlMiddleware`'s `update_session` step already
|
|
46
|
+
* validates the session JWT (refreshing it via Firebase's token API if
|
|
47
|
+
* expired) and performs the exact same guest/auth-page redirects —
|
|
48
|
+
* authoritatively, on every request, BEFORE this component ever runs.
|
|
49
|
+
* So `staticSafe: true` does not weaken auth enforcement — the
|
|
50
|
+
* middleware gate still applies unchanged. The only thing you give up
|
|
51
|
+
* is step 3: `initialAuthUser` is not seeded server-side, so the client
|
|
52
|
+
* `AuthUserProvider` resolves it itself after mount instead. In
|
|
53
|
+
* practice this means a signed-in user MAY see this route's
|
|
54
|
+
* logged-out-state UI (e.g. a nav avatar placeholder) for one client
|
|
55
|
+
* render before the real user data appears — never wrong/protected
|
|
56
|
+
* content, since middleware already gated that; just a delayed value.
|
|
57
|
+
*
|
|
58
|
+
* ── When to use it ──
|
|
59
|
+
* Set `staticSafe: true` only on `IntlProvider` calls that wrap routes
|
|
60
|
+
* you know are always public and don't render auth-dependent UI above
|
|
61
|
+
* the fold (or can tolerate that UI appearing a moment late). Leave the
|
|
62
|
+
* default (`false`) for any `IntlProvider` call that also wraps
|
|
63
|
+
* protected routes or routes where the auth-state flash would be
|
|
64
|
+
* visually jarring (dashboards, account pages, anything showing
|
|
65
|
+
* `initialAuthUser`-derived content immediately). If you need
|
|
66
|
+
* different behavior for public vs protected routes within the SAME
|
|
67
|
+
* app, render `IntlProvider` twice — once per layout/route-group, each
|
|
68
|
+
* with its own `staticSafe` value — rather than picking one value for
|
|
69
|
+
* the whole app. If `firebaseAuth.middlewareEnabled` is explicitly
|
|
70
|
+
* `false` (middleware auth disabled), do NOT set `staticSafe: true` —
|
|
71
|
+
* this component becomes the ONLY place performing the auth redirect,
|
|
72
|
+
* so skipping it there really does remove the security check, not just
|
|
73
|
+
* the flash.
|
|
16
74
|
*
|
|
17
75
|
* @example
|
|
18
76
|
* ```tsx
|
|
@@ -28,8 +86,9 @@ import type { TranslationObject } from "../../types/types";
|
|
|
28
86
|
* }
|
|
29
87
|
* ```
|
|
30
88
|
*/
|
|
31
|
-
export default function LocationzationProvider({ language, messages, children }: {
|
|
89
|
+
export default function LocationzationProvider({ language, messages, staticSafe, children }: {
|
|
32
90
|
language: string;
|
|
33
91
|
messages?: TranslationObject;
|
|
92
|
+
staticSafe?: boolean;
|
|
34
93
|
children: React.ReactNode;
|
|
35
94
|
}): Promise<Component>;
|
|
@@ -23,6 +23,64 @@ let authUserServerProviderModule;
|
|
|
23
23
|
* otherwise.
|
|
24
24
|
* @param messages Optional pre-loaded messages for `language`. If omitted,
|
|
25
25
|
* they're loaded via `getMessage(language)`.
|
|
26
|
+
* @param staticSafe Marks THIS RENDER of `IntlProvider` as one that's safe
|
|
27
|
+
* to serve from static rendering / ISR — i.e. the caller already knows
|
|
28
|
+
* the current route never needs a server-resolved auth user (a public
|
|
29
|
+
* page: marketing, privacy policy, docs, etc). Concretely, setting this
|
|
30
|
+
* to `true` skips the internal `resolveAuthUserAndRedirect()` call.
|
|
31
|
+
*
|
|
32
|
+
* ── Why this call is normally made, and why skipping it is safe ──
|
|
33
|
+
* When `firebaseAuth` is configured, `IntlProvider` by default calls
|
|
34
|
+
* `resolveAuthUserAndRedirect()`, which:
|
|
35
|
+
* 1. Reads the session cookie via `cookies()` and verifies it against
|
|
36
|
+
* Firebase (server-side, authoritative check for "who is this?").
|
|
37
|
+
* 2. Reads the current pathname via `headers()` (`x-pathname`, set by
|
|
38
|
+
* `intlMiddleware`) to redirect guest → `redirectAuthPath` or
|
|
39
|
+
* signed-in → `homePath` on an auth page.
|
|
40
|
+
* 3. Returns the resolved user so the client `AuthUserProvider` can
|
|
41
|
+
* render the correct signed-in/signed-out UI on the FIRST paint,
|
|
42
|
+
* with zero flash.
|
|
43
|
+
* Both `cookies()` and `headers()` are request-scoped APIs — calling
|
|
44
|
+
* either one forces Next.js to render the ENTIRE subtree dynamically on
|
|
45
|
+
* every request. No static HTML, no ISR, no caching — for that route
|
|
46
|
+
* AND every route nested under this same `IntlProvider` call, whether
|
|
47
|
+
* or not that specific route actually needs auth. A page in
|
|
48
|
+
* `firebaseAuth.whiteListPaths` (meant to be public) is NOT exempt from
|
|
49
|
+
* this cost today: the whitelist check happens only AFTER `cookies()`/
|
|
50
|
+
* `headers()` are already read, so it's just as dynamic as a protected
|
|
51
|
+
* page.
|
|
52
|
+
*
|
|
53
|
+
* The redirect part of step 2 is redundant on any project using the
|
|
54
|
+
* default middleware wiring (`firebaseAuth.middlewareEnabled !== false`,
|
|
55
|
+
* the default): `intlMiddleware`'s `update_session` step already
|
|
56
|
+
* validates the session JWT (refreshing it via Firebase's token API if
|
|
57
|
+
* expired) and performs the exact same guest/auth-page redirects —
|
|
58
|
+
* authoritatively, on every request, BEFORE this component ever runs.
|
|
59
|
+
* So `staticSafe: true` does not weaken auth enforcement — the
|
|
60
|
+
* middleware gate still applies unchanged. The only thing you give up
|
|
61
|
+
* is step 3: `initialAuthUser` is not seeded server-side, so the client
|
|
62
|
+
* `AuthUserProvider` resolves it itself after mount instead. In
|
|
63
|
+
* practice this means a signed-in user MAY see this route's
|
|
64
|
+
* logged-out-state UI (e.g. a nav avatar placeholder) for one client
|
|
65
|
+
* render before the real user data appears — never wrong/protected
|
|
66
|
+
* content, since middleware already gated that; just a delayed value.
|
|
67
|
+
*
|
|
68
|
+
* ── When to use it ──
|
|
69
|
+
* Set `staticSafe: true` only on `IntlProvider` calls that wrap routes
|
|
70
|
+
* you know are always public and don't render auth-dependent UI above
|
|
71
|
+
* the fold (or can tolerate that UI appearing a moment late). Leave the
|
|
72
|
+
* default (`false`) for any `IntlProvider` call that also wraps
|
|
73
|
+
* protected routes or routes where the auth-state flash would be
|
|
74
|
+
* visually jarring (dashboards, account pages, anything showing
|
|
75
|
+
* `initialAuthUser`-derived content immediately). If you need
|
|
76
|
+
* different behavior for public vs protected routes within the SAME
|
|
77
|
+
* app, render `IntlProvider` twice — once per layout/route-group, each
|
|
78
|
+
* with its own `staticSafe` value — rather than picking one value for
|
|
79
|
+
* the whole app. If `firebaseAuth.middlewareEnabled` is explicitly
|
|
80
|
+
* `false` (middleware auth disabled), do NOT set `staticSafe: true` —
|
|
81
|
+
* this component becomes the ONLY place performing the auth redirect,
|
|
82
|
+
* so skipping it there really does remove the security check, not just
|
|
83
|
+
* the flash.
|
|
26
84
|
*
|
|
27
85
|
* @example
|
|
28
86
|
* ```tsx
|
|
@@ -38,7 +96,7 @@ let authUserServerProviderModule;
|
|
|
38
96
|
* }
|
|
39
97
|
* ```
|
|
40
98
|
*/
|
|
41
|
-
export default async function LocationzationProvider({ language, messages, children }) {
|
|
99
|
+
export default async function LocationzationProvider({ language, messages, staticSafe = false, children }) {
|
|
42
100
|
if (!localesSet.has(language)) {
|
|
43
101
|
const { notFound } = await import("next/navigation");
|
|
44
102
|
notFound();
|
|
@@ -54,10 +112,25 @@ export default async function LocationzationProvider({ language, messages, child
|
|
|
54
112
|
let initialAuthUser = null;
|
|
55
113
|
const autoWireClientProvider = config.firebaseAuth?.autoWireClientProvider !== false;
|
|
56
114
|
if (config.firebaseAuth && autoWireClientProvider) {
|
|
57
|
-
|
|
58
|
-
|
|
115
|
+
// `staticSafe: true` with middleware auth disabled would silently
|
|
116
|
+
// drop the ONLY auth redirect this app has — not just the flash-
|
|
117
|
+
// prevention seed. Warn loudly rather than let that combination
|
|
118
|
+
// slip through unnoticed; still honor the caller's choice, since a
|
|
119
|
+
// hard throw here would be a worse failure mode than a console
|
|
120
|
+
// warning for what is, after all, a caller-controlled flag.
|
|
121
|
+
if (staticSafe && config.firebaseAuth.middlewareEnabled === false) {
|
|
122
|
+
console.warn('[cloudflare-next-intl] IntlProvider was called with `staticSafe: true` while ' +
|
|
123
|
+
'`firebaseAuth.middlewareEnabled` is `false`. With middleware auth disabled, ' +
|
|
124
|
+
'this component is the ONLY place performing the auth redirect — skipping it ' +
|
|
125
|
+
'here removes that protection entirely, it does not just remove a render flash. ' +
|
|
126
|
+
'Set `staticSafe: false` (or enable middleware auth) for this route.');
|
|
127
|
+
}
|
|
128
|
+
if (!staticSafe) {
|
|
129
|
+
if (!authUserServerProviderModule) {
|
|
130
|
+
authUserServerProviderModule = await import("../../firebase_auth/server/auth_user_server_provider");
|
|
131
|
+
}
|
|
132
|
+
initialAuthUser = await authUserServerProviderModule.resolveAuthUserAndRedirect();
|
|
59
133
|
}
|
|
60
|
-
initialAuthUser = await authUserServerProviderModule.resolveAuthUserAndRedirect();
|
|
61
134
|
}
|
|
62
135
|
let analyticsConfig;
|
|
63
136
|
let requiresConsent = true;
|
|
@@ -4,6 +4,7 @@ import type { Videos } from 'next/dist/lib/metadata/types/metadata-types';
|
|
|
4
4
|
import type { CookieConsentDialogProps } from '../cookie_consent/client/components/cookie_consent_dialog';
|
|
5
5
|
import type { PrivacyPolicyUpdateDialogProps } from '../cookie_consent/client/components/privacy_policy_update_dialog';
|
|
6
6
|
import type { ConsentValue } from '../cookie_consent/types';
|
|
7
|
+
import type { User } from 'firebase/auth';
|
|
7
8
|
/**
|
|
8
9
|
* Custom middleware hook, run by `intlMiddleware` for your own logic
|
|
9
10
|
* (e.g. auth, feature flags, A/B tests) — on top of the library's own
|
|
@@ -439,11 +440,11 @@ export interface FirebaseAuthRoutingConfig {
|
|
|
439
440
|
appId: string;
|
|
440
441
|
/** Firebase Analytics measurement ID. */
|
|
441
442
|
measurementId?: string;
|
|
442
|
-
/** Path to redirect signed-out users to, e.g. "/login". */
|
|
443
|
+
/** Path to redirect signed-out users to, e.g. "/login". Must start with "/" — `setIntlConfig` auto-corrects a missing leading slash with a warning. */
|
|
443
444
|
redirectAuthPath: string;
|
|
444
|
-
/** Path to redirect signed-in users away from auth pages to, e.g. "/". */
|
|
445
|
+
/** Path to redirect signed-in users away from auth pages to, e.g. "/". Must start with "/" — `setIntlConfig` auto-corrects a missing leading slash with a warning. */
|
|
445
446
|
homePath: string;
|
|
446
|
-
/** Path to redirect unverified-email users to. Omit to skip email-verification redirects. */
|
|
447
|
+
/** Path to redirect unverified-email users to. Omit to skip email-verification redirects. Must start with "/" — `setIntlConfig` auto-corrects a missing leading slash with a warning. */
|
|
447
448
|
verifyEmailPath?: string;
|
|
448
449
|
/** Returns true if the given (locale-stripped) path is an auth page (login/signup/etc). */
|
|
449
450
|
isAuthPath: (path: string) => boolean;
|
|
@@ -459,6 +460,34 @@ export interface FirebaseAuthRoutingConfig {
|
|
|
459
460
|
refreshTokenCookieName?: string;
|
|
460
461
|
/** Email-verified hint cookie name. Defaults to `'__fa_email_verified_hint__'`. Client-written, non-httpOnly; lets the middleware avoid an unnecessary token refresh when its view already matches the client's. */
|
|
461
462
|
emailVerifiedHintCookieName?: string;
|
|
463
|
+
/**
|
|
464
|
+
* Called once, the moment `AuthUserProvider` observes a real sign-in
|
|
465
|
+
* (a `null → user` transition) — never on a plain token refresh of an
|
|
466
|
+
* already-signed-in user. Runs after the session/refresh-token/
|
|
467
|
+
* email-verified-hint cookies have already been written for this
|
|
468
|
+
* user, so cookie state is in sync when this fires. A throw/rejection
|
|
469
|
+
* is caught and logged via `console.error`; it never blocks cookie
|
|
470
|
+
* sync or navigation.
|
|
471
|
+
*/
|
|
472
|
+
onSignIn?: (user: User) => void | Promise<void>;
|
|
473
|
+
/**
|
|
474
|
+
* Called once, on the `false → true` transition of `user.emailVerified`
|
|
475
|
+
* — never on a later observation of an already-verified user. Checked
|
|
476
|
+
* from both `AuthUserProvider`'s `onIdTokenChanged` listener and its
|
|
477
|
+
* `reloadUser()`, since either can be the first to observe the
|
|
478
|
+
* transition. A throw/rejection is caught and logged via
|
|
479
|
+
* `console.error`.
|
|
480
|
+
*/
|
|
481
|
+
onEmailVerified?: (user: User) => void | Promise<void>;
|
|
482
|
+
/**
|
|
483
|
+
* Called once, when sign-out is confirmed — after `AuthUserProvider`'s
|
|
484
|
+
* existing debounce for transient SDK null-callbacks (two consecutive
|
|
485
|
+
* `onIdTokenChanged(null)` calls), not on the first, possibly
|
|
486
|
+
* transient, null. Runs after the session/refresh-token cookies have
|
|
487
|
+
* already been cleared. A throw/rejection is caught and logged via
|
|
488
|
+
* `console.error`.
|
|
489
|
+
*/
|
|
490
|
+
onSignOut?: () => void | Promise<void>;
|
|
462
491
|
}
|
|
463
492
|
export interface CookieAttributes {
|
|
464
493
|
/**
|