cloudflare-next-intl 0.10.20 → 0.10.22

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/README.md CHANGED
@@ -516,7 +516,7 @@ Features include:
516
516
  - `createForgotPasswordAction(locale, actionCodeSettings?)`: Accepts optional Firebase `AuthActionCodeSettings` (e.g. `url` redirect link).
517
517
  - `sendVerificationEmail(actionCodeSettings?)` on `useAuthUser()`: Custom action email settings when resending email verification.
518
518
  - `followSameOriginContinueUrl`: Automatically forwards emailed action links with `continueUrl` to the specified path (or external URL) directly from `intlMiddleware` (default `true`; set `false` on `firebaseAuth` config to disable). If `continueUrl` points to home root (`/`), it resolves to `actionLinkPath` (if set) or the mode target path (e.g. `/reset-password`).
519
- - `appCheck`: Firebase App Check integration supporting reCAPTCHA Enterprise (`recaptchaEnterpriseSiteKey`) and reCAPTCHA v3 (`recaptchaV3SiteKey`). For reCAPTCHA v3, defaults to a `CustomProvider` using an explicit `render=explicit` script tag to avoid iframe/webworker CDN integrity issues in private windows (`useExplicitRecaptchaScript: false` to opt out). When `appCheck` is configured, `getFirebaseAuthClient()` waits for App Check to initialize before returning, so every request made through its `auth` (sign-in included) carries an `X-Firebase-AppCheck` header and passes App Check enforcement. The trade-off: App Check (and, with token auto-refresh on, the reCAPTCHA script) now starts on the first `getFirebaseAuthClient()` call instead of the first token mint. If App Check initialization fails, the client is still returned, just without App Check. Supports server-side token minting via service accounts.
519
+ - `appCheck`: Firebase App Check integration supporting reCAPTCHA Enterprise (`recaptchaEnterpriseSiteKey`) and reCAPTCHA v3 (`recaptchaV3SiteKey`). For reCAPTCHA v3, defaults to a `CustomProvider` using an explicit `render=explicit` script tag to avoid iframe/webworker CDN integrity issues in private windows (`useExplicitRecaptchaScript: false` to opt out). When `appCheck` is configured, `getFirebaseAuthClient()` initializes App Check before it creates `auth`, so every request made through `auth` carries an `X-Firebase-AppCheck` header and passes App Check enforcement. That includes the `accounts:lookup` call Firebase makes on page load to restore a signed-in user, as well as sign-in. The trade-off: App Check (and, with token auto-refresh on, the reCAPTCHA script) now starts on the first `getFirebaseAuthClient()` call instead of the first token mint. If App Check initialization fails, the client is still returned, just without App Check. Supports server-side token minting via service accounts.
520
520
  - `resolveOptionalAuthUser()` (`cloudflare-next-intl/resolveOptionalAuthUser`): best-effort variant of `getAuthUser()` for callers that want to *attach* the current user when one happens to be known — error/telemetry reporting, analytics, logging — without failing or changing behavior when no request/session context is available. Swallows every failure and resolves `{ user: null }` instead of throwing. Use `getAuthUser()` (not this) whenever your own output actually depends on who's signed in — `checkDynamicPages` (below) treats `getAuthUser()`/`useAuthUser()`/`withUserDb()` as page-blocking dynamic-API signals but does not follow into this helper, so it's also the way to read "whoever's signed in, if anyone" from a page or a module it imports without forcing that page dynamic.
521
521
  - `resolveErrorReportingUser(useAuthUser?)` (same subpath): gated form of `resolveOptionalAuthUser()` for an `onError` sink — pass `ErrorHandlingParams.useAuthUser` straight through. Defaults to `false`: when not `true`, resolves `{ user: null }` immediately without calling `getAuthUser()` at all, so a page reached only through your `onError` sink's default path stays static-eligible. A specific `reportError({ ..., useAuthUser: true })` call site opts in per-call when it actually wants the user on that report.
522
522
 
@@ -3,11 +3,8 @@ import type { SerializedAuthUser } from "../../firebase_auth/types.js";
3
3
  import type { CookieConsentAnalyticsConfig, AutoAnalyticsEventsConfig } from "../../types/types.js";
4
4
  import type { CookieConsentDialogProps } from "../../cookie_consent/client/components/cookie_consent_dialog.js";
5
5
  import type { PrivacyPolicyUpdateDialogProps } from "../../cookie_consent/client/components/privacy_policy_update_dialog.js";
6
- interface LocaleContextType {
7
- language: string;
8
- messages: TranslationObject;
9
- }
10
- export declare const LocaleContext: import("react").Context<LocaleContextType | undefined>;
6
+ import { LocaleContext } from "./locale_provider.js";
7
+ export { LocaleContext };
11
8
  export default function LocationzationClientProvider({ language, messages, initialAuthUser, skipAuthProvider, analyticsConfig, autoAnalyticsEventsConfig, requiresConsent, autoWireDialogs, dialogProps, updateDialogProps, children }: {
12
9
  language: string;
13
10
  messages: TranslationObject;
@@ -21,4 +18,3 @@ export default function LocationzationClientProvider({ language, messages, initi
21
18
  updateDialogProps?: PrivacyPolicyUpdateDialogProps;
22
19
  children: React.ReactNode;
23
20
  }): Component;
24
- export {};
@@ -1,14 +1,15 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { setLocaleCache, setMessageForLocaleCache } from "../../general/cache_variables.js";
4
- import { createContext, useEffect, useMemo, Suspense } from "react";
4
+ import { useEffect, useMemo, Suspense } from "react";
5
5
  import dynamic from "next/dynamic.js";
6
6
  import useLazyWrappingProvider from "./use_lazy_wrapping_provider.js";
7
7
  import config from "@intl-config";
8
8
  import installConsoleErrorOverride from "../../error_handling/install_console_error_override.js";
9
9
  import installGlobalErrorOverride from "../../error_handling/install_global_error_override.js";
10
10
  import AuthUserPendingProvider from "../../firebase_auth/client/auth_user_pending_provider.js";
11
- export const LocaleContext = createContext(undefined);
11
+ import { LocaleContext } from "./locale_provider.js";
12
+ export { LocaleContext };
12
13
  const loadAuthUserProvider = () => import("../../firebase_auth/client/auth_user_provider.js");
13
14
  const AutoFirebasePerformanceEvents = dynamic(() => import("../../firebase_auth/client/components/auto_firebase_performance_events.js"));
14
15
  const loadCookieConsentProvider = () => import("../../cookie_consent/client/cookie_consent_provider.js");
@@ -0,0 +1,13 @@
1
+ import { type ReactNode } from "react";
2
+ import type { TranslationObject } from "../../types/types.js";
3
+ interface LocaleContextType {
4
+ language: string;
5
+ messages: TranslationObject;
6
+ }
7
+ export declare const LocaleContext: import("react").Context<LocaleContextType | undefined>;
8
+ export default function LocaleProvider({ language, messages, children }: {
9
+ language: string;
10
+ messages: TranslationObject;
11
+ children: ReactNode;
12
+ }): React.JSX.Element;
13
+ export {};
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, useMemo } from "react";
4
+ export const LocaleContext = createContext(undefined);
5
+ export default function LocaleProvider({ language, messages, children }) {
6
+ const value = useMemo(() => ({ language, messages }), [language, messages]);
7
+ return _jsx(LocaleContext.Provider, { value: value, children: children });
8
+ }
@@ -49,11 +49,13 @@ async function writeAppCheckTokenCookie(appCheckTokenCookieName, maxAge) {
49
49
  console.error('AuthUserProvider: App Check token cookie sync failed', e);
50
50
  }
51
51
  }
52
- async function clearSession(sessionCookieName, refreshTokenCookieName, emailVerifiedHintCookieName, appCheckTokenCookieName, refreshTokenMaxAge) {
52
+ async function clearSession(sessionCookieName, refreshTokenCookieName, emailVerifiedHintCookieName, appCheckTokenCookieName, refreshTokenMaxAge, clearServerCookies = true) {
53
53
  clearSessionCookie(sessionCookieName);
54
54
  clearRefreshTokenCookie(refreshTokenCookieName);
55
55
  clearAppCheckTokenCookie(appCheckTokenCookieName);
56
56
  writeEmailVerifiedHintCookie(emailVerifiedHintCookieName, false, refreshTokenMaxAge);
57
+ if (!clearServerCookies)
58
+ return;
57
59
  try {
58
60
  await clearSessionAction();
59
61
  }
@@ -98,6 +100,9 @@ export default function AuthUserProvider({ initialUser = null, children }) {
98
100
  const signInCallbackFired = useRef(initialUser !== null);
99
101
  const signOutCallbackFired = useRef(initialUser === null);
100
102
  const emailVerifiedRef = useRef(initialUser?.emailVerified ?? false);
103
+ const isAuthPageRef = useRef(isAuthPage);
104
+ isAuthPageRef.current = isAuthPage;
105
+ const initialSignedIn = initialUser !== null;
101
106
  const deferAuthSubscribe = initialUser === null && isWhiteListed && !isAuthPage && typeof window !== 'undefined';
102
107
  useEffect(() => {
103
108
  const { user, loading } = state;
@@ -131,7 +136,7 @@ export default function AuthUserProvider({ initialUser = null, children }) {
131
136
  await writeSession(user, sessionCookieName, maxAge, refreshTokenCookieName, refreshTokenMaxAge, emailVerifiedHintCookieName, appCheckTokenCookieName, appCheckTokenMaxAge);
132
137
  }
133
138
  else {
134
- await clearSession(sessionCookieName, refreshTokenCookieName, emailVerifiedHintCookieName, appCheckTokenCookieName, refreshTokenMaxAge);
139
+ await clearSession(sessionCookieName, refreshTokenCookieName, emailVerifiedHintCookieName, appCheckTokenCookieName, refreshTokenMaxAge, previous ?? (initialSignedIn || isAuthPageRef.current));
135
140
  }
136
141
  }
137
142
  catch (e) {
@@ -211,7 +216,7 @@ export default function AuthUserProvider({ initialUser = null, children }) {
211
216
  clearTimeout(timeoutHandle);
212
217
  unsubscribe?.();
213
218
  };
214
- }, [deferAuthSubscribe, router, isAuthPage, isWhiteListed, maxAge, sessionCookieName, refreshTokenMaxAge, refreshTokenCookieName, emailVerifiedHintCookieName, appCheckTokenCookieName, appCheckTokenMaxAge, fa]);
219
+ }, [initialSignedIn, deferAuthSubscribe, router, isAuthPage, isWhiteListed, maxAge, sessionCookieName, refreshTokenMaxAge, refreshTokenCookieName, emailVerifiedHintCookieName, appCheckTokenCookieName, appCheckTokenMaxAge, fa]);
215
220
  const reloadUser = useCallback(async () => {
216
221
  const { auth } = await getFirebaseAuthClient();
217
222
  const user = auth.currentUser;
@@ -122,15 +122,10 @@ async function initializeFirebaseAppCheck(app, appCheckConfig) {
122
122
  }
123
123
  const APP_CHECK_TOKEN_TIMEOUT_MS = 10000;
124
124
  let appCheckInitPromise;
125
- async function ensureAppCheck() {
126
- if (appCheckInitPromise)
127
- return appCheckInitPromise;
125
+ function initAppCheckFor(app) {
128
126
  const appCheckConfig = config.firebaseAuth?.appCheck;
129
127
  if (!appCheckConfig || typeof window === 'undefined')
130
- return;
131
- const { app } = await loadFirebaseAuthClient();
132
- if (appCheckInitPromise)
133
- return appCheckInitPromise;
128
+ return undefined;
134
129
  appCheckInitPromise = (async () => {
135
130
  try {
136
131
  cachedAppCheck = await initializeFirebaseAppCheck(app, appCheckConfig);
@@ -141,6 +136,9 @@ async function ensureAppCheck() {
141
136
  })();
142
137
  return appCheckInitPromise;
143
138
  }
139
+ async function ensureAppCheck() {
140
+ await getFirebaseAuthClient();
141
+ }
144
142
  export async function getAppCheckToken() {
145
143
  await ensureAppCheck().catch(() => undefined);
146
144
  if (!cachedAppCheck)
@@ -160,7 +158,7 @@ export async function getAppCheckToken() {
160
158
  }
161
159
  let cached;
162
160
  let cachedPromise;
163
- async function loadFirebaseAuthClient() {
161
+ export async function getFirebaseAuthClient() {
164
162
  requireFirebaseAuthConfig(config.firebaseAuth);
165
163
  if (cached)
166
164
  return cached;
@@ -183,6 +181,7 @@ async function loadFirebaseAuthClient() {
183
181
  measurementId: fa.measurementId,
184
182
  };
185
183
  const app = getApps().length ? getApp() : initializeApp(firebaseConfig);
184
+ await initAppCheckFor(app);
186
185
  if (perfModule) {
187
186
  cachedPerformance = perfModule.initializePerformance(app, { instrumentationEnabled: false });
188
187
  }
@@ -204,11 +203,6 @@ async function loadFirebaseAuthClient() {
204
203
  }
205
204
  return cachedPromise;
206
205
  }
207
- export async function getFirebaseAuthClient() {
208
- const client = await loadFirebaseAuthClient();
209
- await ensureAppCheck().catch(() => undefined);
210
- return client;
211
- }
212
206
  export function getFirebaseAuthClientSync() {
213
207
  return cached;
214
208
  }
@@ -283,7 +283,7 @@ export default async function updateSession(request, baseResponse, locale, rebui
283
283
  else if (unverifiedEmail) {
284
284
  response = buildRedirect(baseResponse, localeUrl(fa.verifyEmailPath), isPrefetch);
285
285
  }
286
- else if (isAuthPage || (isVerifyEmailPage && decodeTokenOnce(token)?.email_verified === true)) {
286
+ else if (isVerifyEmailPage && decodeTokenOnce(token)?.email_verified === true) {
287
287
  response = buildRedirect(baseResponse, localeUrl(fa.homePath), isPrefetch);
288
288
  }
289
289
  else {
@@ -8,6 +8,7 @@ import config from "../../config/intl_config.js";
8
8
  import installConsoleErrorOverride from "../../error_handling/install_console_error_override.js";
9
9
  import reportError from "../../error_handling/report_error.js";
10
10
  const LocationzationClientProvider = dynamic(() => import("../../client/components/client_provider.js"));
11
+ import LocaleProvider from "../../client/components/locale_provider.js";
11
12
  let authUserServerProviderModule;
12
13
  export default async function LocationzationProvider({ language, messages, staticSafe = true, children }) {
13
14
  if (!localesSet.has(language)) {
@@ -59,5 +60,5 @@ export default async function LocationzationProvider({ language, messages, stati
59
60
  }
60
61
  }
61
62
  }
62
- return (_jsx(Suspense, { fallback: children, children: _jsx(LocationzationClientProvider, { language: language, messages: messagesValue, initialAuthUser: initialAuthUser, skipAuthProvider: !autoWireClientProvider, analyticsConfig: analyticsConfig, autoAnalyticsEventsConfig: config.cookieConsent?.autoAnalyticsEvents, requiresConsent: requiresConsent, autoWireDialogs: config.cookieConsent?.autoWireDialogs !== false, dialogProps: config.cookieConsent?.dialogProps, updateDialogProps: config.cookieConsent?.updateDialogProps, children: children }) }));
63
+ return (_jsx(LocaleProvider, { language: language, messages: messagesValue, children: _jsx(Suspense, { fallback: children, children: _jsx(LocationzationClientProvider, { language: language, messages: messagesValue, initialAuthUser: initialAuthUser, skipAuthProvider: !autoWireClientProvider, analyticsConfig: analyticsConfig, autoAnalyticsEventsConfig: config.cookieConsent?.autoAnalyticsEvents, requiresConsent: requiresConsent, autoWireDialogs: config.cookieConsent?.autoWireDialogs !== false, dialogProps: config.cookieConsent?.dialogProps, updateDialogProps: config.cookieConsent?.updateDialogProps, children: children }) }) }));
63
64
  }
@@ -80,7 +80,7 @@ export function missingRequiredSymbols(code, patchName) {
80
80
  return REQUIRED_SYMBOLS[patchName].filter((symbol) => !code.includes(symbol));
81
81
  }
82
82
  function warnIncompatible(filePath, patchName, code) {
83
- console.warn(`[cfni:vinext-route-wiring-fix] ${filePath} no longer exposes ${missingRequiredSymbols(code, patchName).join(", ")} — this vinext version may have changed; the ${patchName} fix was NOT applied.`);
83
+ console.warn(`cfni:vinext-route-wiring-fix: ${filePath} no longer exposes ${missingRequiredSymbols(code, patchName).join(", ")} — this vinext version may have changed; the ${patchName} fix was NOT applied.`);
84
84
  }
85
85
  export function isAppPageRouteWiringAlreadyFixed(code, options = {}) {
86
86
  const hasBuggyPrefetch = code.includes("firstNestedEntry") &&
@@ -611,7 +611,7 @@ export function syncPatchVinextOnDisk(root = process.cwd(), options = {}) {
611
611
  changed = true;
612
612
  }
613
613
  else {
614
- console.warn(`[cfni:vinext-route-wiring-fix] ${wiringPath} does not match the expected shape for patchAppPageRouteWiring — this vinext version may have changed; the route-wiring fix was NOT applied.`);
614
+ console.warn(`cfni:vinext-route-wiring-fix: ${wiringPath} does not match the expected shape for patchAppPageRouteWiring — this vinext version may have changed; the route-wiring fix was NOT applied.`);
615
615
  }
616
616
  }
617
617
  }
@@ -632,7 +632,7 @@ export function syncPatchVinextOnDisk(root = process.cwd(), options = {}) {
632
632
  changed = true;
633
633
  }
634
634
  else {
635
- console.warn(`[cfni:vinext-route-wiring-fix] ${matchingPath} does not match the expected shape for patchRouteMatching — this vinext version may have changed; the route-matching fix was NOT applied.`);
635
+ console.warn(`cfni:vinext-route-wiring-fix: ${matchingPath} does not match the expected shape for patchRouteMatching — this vinext version may have changed; the route-matching fix was NOT applied.`);
636
636
  }
637
637
  }
638
638
  }
@@ -653,7 +653,7 @@ export function syncPatchVinextOnDisk(root = process.cwd(), options = {}) {
653
653
  changed = true;
654
654
  }
655
655
  else {
656
- console.warn(`[cfni:vinext-route-wiring-fix] ${optimisticPath} does not match the expected shape for patchOptimisticRouting — this vinext version may have changed; the optimistic-routing fix was NOT applied.`);
656
+ console.warn(`cfni:vinext-route-wiring-fix: ${optimisticPath} does not match the expected shape for patchOptimisticRouting — this vinext version may have changed; the optimistic-routing fix was NOT applied.`);
657
657
  }
658
658
  }
659
659
  }
@@ -674,7 +674,7 @@ export function syncPatchVinextOnDisk(root = process.cwd(), options = {}) {
674
674
  changed = true;
675
675
  }
676
676
  else {
677
- console.warn(`[cfni:vinext-route-wiring-fix] ${browserEntryPath} does not match the expected shape for patchPrefetchLearning — this vinext version may have changed; the prefetch-learning fix was NOT applied.`);
677
+ console.warn(`cfni:vinext-route-wiring-fix: ${browserEntryPath} does not match the expected shape for patchPrefetchLearning — this vinext version may have changed; the prefetch-learning fix was NOT applied.`);
678
678
  }
679
679
  }
680
680
  }
@@ -695,7 +695,7 @@ export function syncPatchVinextOnDisk(root = process.cwd(), options = {}) {
695
695
  changed = true;
696
696
  }
697
697
  else {
698
- console.warn(`[cfni:vinext-route-wiring-fix] ${probePath} does not match the expected shape for patchAppPageProbe — this vinext version may have changed; the suspense-probe fix was NOT applied.`);
698
+ console.warn(`cfni:vinext-route-wiring-fix: ${probePath} does not match the expected shape for patchAppPageProbe — this vinext version may have changed; the suspense-probe fix was NOT applied.`);
699
699
  }
700
700
  }
701
701
  }
@@ -713,7 +713,7 @@ export function syncPatchVinextOnDisk(root = process.cwd(), options = {}) {
713
713
  changed = true;
714
714
  }
715
715
  else {
716
- console.warn(`[cfni:vinext-route-wiring-fix] ${renderDependencyPath} does not match the expected shape for patchRenderDependency — this vinext version may have changed; the render-dependency fix was NOT applied.`);
716
+ console.warn(`cfni:vinext-route-wiring-fix: ${renderDependencyPath} does not match the expected shape for patchRenderDependency — this vinext version may have changed; the render-dependency fix was NOT applied.`);
717
717
  }
718
718
  }
719
719
  }
@@ -732,7 +732,7 @@ export function syncPatchVinextOnDisk(root = process.cwd(), options = {}) {
732
732
  changed = true;
733
733
  }
734
734
  else {
735
- console.warn(`[cfni:vinext-route-wiring-fix] ${optimisticLearningTimeoutPath} does not match the expected shape for patchOptimisticLearningTimeout — this vinext version may have changed; the optimistic-learning-timeout fix was NOT applied.`);
735
+ console.warn(`cfni:vinext-route-wiring-fix: ${optimisticLearningTimeoutPath} does not match the expected shape for patchOptimisticLearningTimeout — this vinext version may have changed; the optimistic-learning-timeout fix was NOT applied.`);
736
736
  }
737
737
  }
738
738
  }
@@ -750,7 +750,7 @@ export function syncPatchVinextOnDisk(root = process.cwd(), options = {}) {
750
750
  changed = true;
751
751
  }
752
752
  else {
753
- console.warn(`[cfni:vinext-route-wiring-fix] ${pageInvokerSuspensionReleasePath} does not match the expected shape for patchPageInvokerSuspensionRelease — this vinext version may have changed; the page-invoker-suspension-release fix was NOT applied.`);
753
+ console.warn(`cfni:vinext-route-wiring-fix: ${pageInvokerSuspensionReleasePath} does not match the expected shape for patchPageInvokerSuspensionRelease — this vinext version may have changed; the page-invoker-suspension-release fix was NOT applied.`);
754
754
  }
755
755
  }
756
756
  }
@@ -768,7 +768,7 @@ export function syncPatchVinextOnDisk(root = process.cwd(), options = {}) {
768
768
  changed = true;
769
769
  }
770
770
  else {
771
- console.warn(`[cfni:vinext-route-wiring-fix] ${navControllerPath} does not match the expected shape for patchRefreshDeferralNavController — this vinext version may have changed; the refresh-deferral fix was NOT applied.`);
771
+ console.warn(`cfni:vinext-route-wiring-fix: ${navControllerPath} does not match the expected shape for patchRefreshDeferralNavController — this vinext version may have changed; the refresh-deferral fix was NOT applied.`);
772
772
  }
773
773
  }
774
774
  const entryPath = resolveVinextBrowserEntryPath(root);
@@ -860,7 +860,7 @@ export function vinextRouteWiringFixPlugin(options = {}) {
860
860
  if (changed || isVinextOptimizeDepsCacheStale(root, cacheDir)) {
861
861
  const busted = bustVinextOptimizeDepsCache(cacheDir);
862
862
  if (busted) {
863
- console.log("[cfni:vinext-route-wiring-fix] patched vinext on disk and cleared its stale Vite optimizeDeps cache — dependencies will re-bundle on next request.");
863
+ console.log("cfni:vinext-route-wiring-fix: patched vinext on disk and cleared its stale Vite optimizeDeps cache — dependencies will re-bundle on next request.");
864
864
  }
865
865
  }
866
866
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.10.20",
3
+ "version": "0.10.22",
4
4
  "description": "Optimized Next Intl Package Special for App Router and Cloudflare",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",