cloudflare-next-intl 0.9.54 → 0.9.56

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.
@@ -3,13 +3,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { setLocaleCache, setMessageForLocaleCache } from "../../general/cache_variables.js";
4
4
  import { createContext, useMemo } from "react";
5
5
  import dynamic from "next/dynamic.js";
6
+ import useLazyWrappingProvider from "./use_lazy_wrapping_provider.js";
6
7
  import config from "@intl-config";
7
8
  import installConsoleErrorOverride from "../../error_handling/install_console_error_override.js";
8
9
  import installGlobalErrorOverride from "../../error_handling/install_global_error_override.js";
9
10
  export const LocaleContext = createContext(undefined);
10
- const AuthUserProvider = dynamic(() => import("../../firebase_auth/client/auth_user_provider.js"));
11
+ const loadAuthUserProvider = () => import("../../firebase_auth/client/auth_user_provider.js");
11
12
  const AutoFirebasePerformanceEvents = dynamic(() => import("../../firebase_auth/client/components/auto_firebase_performance_events.js"));
12
- const CookieConsentProvider = dynamic(() => import("../../cookie_consent/client/cookie_consent_provider.js"));
13
+ const loadCookieConsentProvider = () => import("../../cookie_consent/client/cookie_consent_provider.js");
13
14
  const CookieConsentAnalytics = dynamic(() => import("../../cookie_consent/client/components/cookie_consent_analytics.js"));
14
15
  const AutoAnalyticsEvents = dynamic(() => import("../../cookie_consent/client/components/auto_analytics_events.js"));
15
16
  const CookieConsentDialog = dynamic(() => import("../../cookie_consent/client/components/cookie_consent_dialog.js"));
@@ -19,12 +20,14 @@ export default function LocationzationClientProvider({ language, messages, initi
19
20
  setMessageForLocaleCache(language, messages);
20
21
  installConsoleErrorOverride(config, true);
21
22
  installGlobalErrorOverride(config);
23
+ const { Provider: AuthUserProvider } = useLazyWrappingProvider(loadAuthUserProvider);
24
+ const { Provider: CookieConsentProvider, isReady: cookieConsentReady } = useLazyWrappingProvider(loadCookieConsentProvider);
22
25
  let providedChildren = children;
23
26
  if (config.firebaseAuth && !skipAuthProvider) {
24
27
  providedChildren = _jsxs(AuthUserProvider, { initialUser: initialAuthUser, children: [children, config.firebaseAuth.performance !== false && _jsx(AutoFirebasePerformanceEvents, {})] });
25
28
  }
26
29
  if (config.cookieConsent) {
27
- providedChildren = _jsxs(CookieConsentProvider, { requiresConsent: requiresConsent, children: [providedChildren, analyticsConfig && _jsx(CookieConsentAnalytics, { config: analyticsConfig }), analyticsConfig && (analyticsConfig.googleAnalyticsId || analyticsConfig.googleAdsId) && _jsx(AutoAnalyticsEvents, { config: autoAnalyticsEventsConfig }), autoWireDialogs && _jsx(CookieConsentDialog, { ...dialogProps }), autoWireDialogs && _jsx(PrivacyPolicyUpdateDialog, { ...updateDialogProps })] });
30
+ providedChildren = _jsxs(CookieConsentProvider, { requiresConsent: requiresConsent, children: [providedChildren, cookieConsentReady && analyticsConfig && _jsx(CookieConsentAnalytics, { config: analyticsConfig }), cookieConsentReady && analyticsConfig && (analyticsConfig.googleAnalyticsId || analyticsConfig.googleAdsId) && _jsx(AutoAnalyticsEvents, { config: autoAnalyticsEventsConfig }), cookieConsentReady && autoWireDialogs && _jsx(CookieConsentDialog, { ...dialogProps }), cookieConsentReady && autoWireDialogs && _jsx(PrivacyPolicyUpdateDialog, { ...updateDialogProps })] });
28
31
  }
29
32
  const contextValue = useMemo(() => ({ language, messages }), [language, messages]);
30
33
  return _jsx(LocaleContext.Provider, { value: contextValue, children: providedChildren });
@@ -3,11 +3,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { setLocaleCache, setMessageForLocaleCache } from "../../general/cache_variables.js";
4
4
  import { createContext, useMemo } from "react";
5
5
  import dynamic from "next/dynamic.js";
6
+ import useLazyWrappingProvider from "./use_lazy_wrapping_provider.js";
6
7
  import config from "@intl-config";
7
8
  import installConsoleErrorOverride from "../../error_handling/install_console_error_override.js";
8
9
  import installGlobalErrorOverride from "../../error_handling/install_global_error_override.js";
9
10
  export const LocaleContext = createContext(undefined);
10
- const CookieConsentProvider = dynamic(() => import("../../cookie_consent/client/cookie_consent_provider.js"));
11
+ const loadCookieConsentProvider = () => import("../../cookie_consent/client/cookie_consent_provider.js");
11
12
  const CookieConsentAnalytics = dynamic(() => import("../../cookie_consent/client/components/cookie_consent_analytics.js"));
12
13
  const AutoAnalyticsEvents = dynamic(() => import("../../cookie_consent/client/components/auto_analytics_events.js"));
13
14
  const CookieConsentDialog = dynamic(() => import("../../cookie_consent/client/components/cookie_consent_dialog.js"));
@@ -17,9 +18,10 @@ export default function LocationzationClientProvider({ language, messages, analy
17
18
  setMessageForLocaleCache(language, messages);
18
19
  installConsoleErrorOverride(config, true);
19
20
  installGlobalErrorOverride(config);
21
+ const { Provider: CookieConsentProvider, isReady: cookieConsentReady } = useLazyWrappingProvider(loadCookieConsentProvider);
20
22
  let providedChildren = children;
21
23
  if (config.cookieConsent) {
22
- providedChildren = _jsxs(CookieConsentProvider, { requiresConsent: requiresConsent, children: [providedChildren, analyticsConfig && _jsx(CookieConsentAnalytics, { config: analyticsConfig }), analyticsConfig && (analyticsConfig.googleAnalyticsId || analyticsConfig.googleAdsId) && _jsx(AutoAnalyticsEvents, { config: autoAnalyticsEventsConfig }), autoWireDialogs && _jsx(CookieConsentDialog, { ...dialogProps }), autoWireDialogs && _jsx(PrivacyPolicyUpdateDialog, { ...updateDialogProps })] });
24
+ providedChildren = _jsxs(CookieConsentProvider, { requiresConsent: requiresConsent, children: [providedChildren, cookieConsentReady && analyticsConfig && _jsx(CookieConsentAnalytics, { config: analyticsConfig }), cookieConsentReady && analyticsConfig && (analyticsConfig.googleAnalyticsId || analyticsConfig.googleAdsId) && _jsx(AutoAnalyticsEvents, { config: autoAnalyticsEventsConfig }), cookieConsentReady && autoWireDialogs && _jsx(CookieConsentDialog, { ...dialogProps }), cookieConsentReady && autoWireDialogs && _jsx(PrivacyPolicyUpdateDialog, { ...updateDialogProps })] });
23
25
  }
24
26
  const contextValue = useMemo(() => ({ language, messages }), [language, messages]);
25
27
  return _jsx(LocaleContext.Provider, { value: contextValue, children: providedChildren });
@@ -0,0 +1,12 @@
1
+ import { type ComponentType } from "react";
2
+ type ModuleLoader<P> = () => Promise<{
3
+ default: ComponentType<P>;
4
+ }>;
5
+ export interface LazyWrappingProviderResult<P extends Record<string, unknown>> {
6
+ Provider: ComponentType<P & {
7
+ children?: React.ReactNode;
8
+ }>;
9
+ isReady: boolean;
10
+ }
11
+ export default function useLazyWrappingProvider<P extends Record<string, unknown>>(loader: ModuleLoader<P>): LazyWrappingProviderResult<P>;
12
+ export {};
@@ -0,0 +1,34 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ const settledCache = new Map();
5
+ export default function useLazyWrappingProvider(loader) {
6
+ const opaqueLoader = loader;
7
+ const resolvedRef = useRef(settledCache.get(opaqueLoader) ?? null);
8
+ const [isReady, setIsReady] = useState(resolvedRef.current !== null);
9
+ useEffect(() => {
10
+ if (resolvedRef.current)
11
+ return;
12
+ let cancelled = false;
13
+ loader().then((mod) => {
14
+ if (cancelled)
15
+ return;
16
+ settledCache.set(opaqueLoader, mod.default);
17
+ resolvedRef.current = mod.default;
18
+ setIsReady(true);
19
+ });
20
+ return () => {
21
+ cancelled = true;
22
+ };
23
+ }, []);
24
+ const wrapperRef = useRef(null);
25
+ if (!wrapperRef.current) {
26
+ wrapperRef.current = function LazyWrappingProvider({ children, ...props }) {
27
+ const Resolved = resolvedRef.current;
28
+ if (!Resolved)
29
+ return children;
30
+ return _jsx(Resolved, { ...props, children: children });
31
+ };
32
+ }
33
+ return { Provider: wrapperRef.current, isReady };
34
+ }
@@ -1,2 +1,5 @@
1
1
  import type { FirebaseAppCheckConfig } from '../../types/types.js';
2
+ export declare const missingCredentialsReportState: {
3
+ reported: boolean;
4
+ };
2
5
  export default function mintServerAppCheckToken(projectId: string, apiKey: string, appCheck: FirebaseAppCheckConfig | undefined): Promise<string | undefined>;
@@ -3,12 +3,37 @@ import reportError from '../../error_handling/report_error.js';
3
3
  import signCustomTokenRemote from './sign_custom_token_remote.js';
4
4
  const APP_CHECK_CUSTOM_TOKEN_AUDIENCE = 'https://firebaseappcheck.googleapis.com/google.firebase.appcheck.v1.TokenExchangeService';
5
5
  const CUSTOM_TOKEN_LIFETIME = '5m';
6
+ export const missingCredentialsReportState = { reported: false };
7
+ async function reportMissingCredentials(appCheck, hasOauthTriple) {
8
+ if (appCheck.reportMissingServerCredentials === false)
9
+ return;
10
+ if (missingCredentialsReportState.reported)
11
+ return;
12
+ missingCredentialsReportState.reported = true;
13
+ const missing = [];
14
+ if (!appCheck.clientEmail)
15
+ missing.push('clientEmail');
16
+ if (!appCheck.appId)
17
+ missing.push('appId');
18
+ if (!appCheck.privateKey && !hasOauthTriple) {
19
+ missing.push('privateKey (or the full oauthClientId/oauthClientSecret/oauthRefreshToken triple)');
20
+ }
21
+ await reportError(config, {
22
+ error: new Error(`firebaseAuth.appCheck is configured but cannot mint an App Check token server-side — missing: ${missing.join(', ')}. `
23
+ + 'Signed-in users will render as signed-out on any cold navigation that arrives before the client writes the App Check cookie. '
24
+ + 'Set the missing service-account values, or pass `appCheck.reportMissingServerCredentials: false` to silence this.'),
25
+ classOrMethodName: 'mintServerAppCheckToken',
26
+ dedupKey: 'mintServerAppCheckToken:missing-server-credentials',
27
+ });
28
+ }
6
29
  export default async function mintServerAppCheckToken(projectId, apiKey, appCheck) {
7
- if (!appCheck?.clientEmail || !appCheck.appId)
30
+ if (!appCheck)
8
31
  return undefined;
9
- const hasOauthTriple = appCheck.oauthClientId && appCheck.oauthClientSecret && appCheck.oauthRefreshToken;
10
- if (!appCheck.privateKey && !hasOauthTriple)
32
+ const hasOauthTriple = Boolean(appCheck.oauthClientId && appCheck.oauthClientSecret && appCheck.oauthRefreshToken);
33
+ if (!appCheck.clientEmail || !appCheck.appId || (!appCheck.privateKey && !hasOauthTriple)) {
34
+ await reportMissingCredentials(appCheck, hasOauthTriple);
11
35
  return undefined;
36
+ }
12
37
  try {
13
38
  const claims = {
14
39
  iss: appCheck.clientEmail,
@@ -19,9 +19,16 @@ export default function HelperScript() {
19
19
  var key = 'stale-deploy-recovery-reloaded';
20
20
  var timeKey = 'stale-deploy-recovery-time';
21
21
  var countKey = 'stale-deploy-recovery-count';
22
- var maxAttempts = 2;
22
+ var maxAttempts = 3;
23
23
  var throttleMs = 15000;
24
24
  var attemptedThisLoad = false;
25
+ var retryScheduled = false;
26
+ // Set by the resource-error listener: the first same-origin
27
+ // chunk that failed. Reloading is pointless until that URL
28
+ // answers with real JavaScript again, so it doubles as the
29
+ // health probe below.
30
+ var probeUrl = null;
31
+ var maxProbes = 4;
25
32
  function isStale(msg) {
26
33
  if (msg === undefined || msg === null) return true;
27
34
  msg = String(msg).toLowerCase();
@@ -30,6 +37,49 @@ export default function HelperScript() {
30
37
  }
31
38
  return false;
32
39
  }
40
+ function showOverlay() {
41
+ try {
42
+ if (document.documentElement) {
43
+ document.documentElement.style.backgroundColor = '#ffffff';
44
+ }
45
+ if (document.body) {
46
+ document.body.style.backgroundColor = '#ffffff';
47
+ document.body.innerHTML = ${JSON.stringify(reloadHtml)};
48
+ }
49
+ } catch (e) {}
50
+ }
51
+ function doReload() {
52
+ try {
53
+ var u = new URL(window.location.href);
54
+ u.searchParams.set('_stale_reload', String(Date.now()));
55
+ window.location.replace(u.toString());
56
+ } catch (e) {
57
+ try { window.location.reload(); } catch (e2) {}
58
+ }
59
+ }
60
+ // A deploy swaps the Worker version colo by colo, so for a few
61
+ // seconds the document can come from the new version while a
62
+ // chunk request still lands on the old one, which answers with
63
+ // a plain-text 404 body instead of JavaScript. Reloading inside
64
+ // that window just reproduces the error, so poll the failed
65
+ // chunk with a cache-busted request until it is real
66
+ // JavaScript, then reload once. Give up after maxProbes and
67
+ // reload anyway rather than hanging on the overlay.
68
+ function probeThenReload(attempt) {
69
+ if (!probeUrl || typeof fetch !== 'function') return doReload();
70
+ var url = probeUrl + (probeUrl.indexOf('?') > -1 ? '&' : '?') + '_r=' + Date.now();
71
+ fetch(url, { cache: 'reload', credentials: 'omit' }).then(function(r) {
72
+ var ct = (r.headers.get('content-type') || '').toLowerCase();
73
+ if (!r.ok || ct.indexOf('javascript') === -1) throw new Error('unhealthy: ' + r.status + ' ' + ct);
74
+ doReload();
75
+ }).catch(function(err) {
76
+ if (attempt >= maxProbes) {
77
+ console.warn('[StaleDeploy early-catch] Asset still unhealthy after', attempt + 1, 'probes - reloading anyway:', String(err));
78
+ return doReload();
79
+ }
80
+ setTimeout(function() { probeThenReload(attempt + 1); }, 500 * Math.pow(2, attempt));
81
+ });
82
+ }
33
83
  function recover(msg, source) {
34
84
  if (attemptedThisLoad) return;
35
85
  try {
@@ -55,30 +105,28 @@ export default function HelperScript() {
55
105
  console.warn('[StaleDeploy early-catch] Skipping reload, attempts exhausted for buildId:', buildId, attempts);
56
106
  return;
57
107
  }
108
+ // A reload that lands inside the same throttle window
109
+ // means the previous recovery did not help: the assets
110
+ // are momentarily unreadable at the edge rather than
111
+ // stale. Waiting out the window and retrying lets the
112
+ // page heal itself instead of stranding the visitor on
113
+ // the error UI.
58
114
  if (sameBuild && throttled) {
59
- console.warn('[StaleDeploy early-catch] Skipping reload, throttled for buildId:', buildId);
115
+ if (retryScheduled) return;
116
+ retryScheduled = true;
117
+ var wait = throttleMs - (Date.now() - last);
118
+ if (!(wait > 0)) wait = 0;
119
+ console.warn('[StaleDeploy early-catch] Throttled for buildId:', buildId, '- retrying in', wait, 'ms');
120
+ showOverlay();
121
+ setTimeout(function() { retryScheduled = false; recover(msg, source); }, wait + 250);
60
122
  return;
61
123
  }
62
124
  attemptedThisLoad = true;
63
125
  sessionStorage.setItem(key, buildId);
64
126
  sessionStorage.setItem(countKey, String(attempts + 1));
65
127
  sessionStorage.setItem(timeKey, String(Date.now()));
66
- try {
67
- if (document.documentElement) {
68
- document.documentElement.style.backgroundColor = '#ffffff';
69
- }
70
- if (document.body) {
71
- document.body.style.backgroundColor = '#ffffff';
72
- document.body.innerHTML = ${JSON.stringify(reloadHtml)};
73
- }
74
- } catch (e) {}
75
- try {
76
- var u = new URL(window.location.href);
77
- u.searchParams.set('_stale_reload', String(Date.now()));
78
- window.location.replace(u.toString());
79
- } catch (e) {
80
- window.location.reload();
81
- }
128
+ showOverlay();
129
+ probeThenReload(0);
82
130
  } catch (e) {
83
131
  console.error('Stale Deploy Early Catch Script Error:', e);
84
132
  }
@@ -102,6 +150,7 @@ export default function HelperScript() {
102
150
  var sameOrigin = false;
103
151
  try { sameOrigin = new URL(src, window.location.href).origin === window.location.origin; } catch (err2) { return; }
104
152
  if (!sameOrigin) return;
153
+ if (!probeUrl) probeUrl = src;
105
154
  recover('chunk resource failed to load: ' + src, 'resource-error');
106
155
  } catch (err) {}
107
156
  }, true);
@@ -170,6 +170,7 @@ export interface FirebaseAppCheckConfig {
170
170
  oauthClientSecret?: string;
171
171
  oauthRefreshToken?: string;
172
172
  appId: string;
173
+ reportMissingServerCredentials?: boolean;
173
174
  }
174
175
  export interface CookieAttributes {
175
176
  domain?: string | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.9.54",
3
+ "version": "0.9.56",
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",