cloudflare-next-intl 0.8.59 → 0.8.61
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
|
@@ -593,7 +593,11 @@ third party without consent can itself be GDPR-relevant.
|
|
|
593
593
|
|
|
594
594
|
#### Stale Deploy & Chunk Load Error Recovery
|
|
595
595
|
|
|
596
|
-
When a new version of your application is deployed to Cloudflare Workers, users on older client sessions may encounter `ChunkLoadError` or failed dynamic imports when requesting outdated chunks.
|
|
596
|
+
When a new version of your application is deployed to Cloudflare Workers, users on older client sessions may encounter `ChunkLoadError` or failed dynamic imports when requesting outdated chunks.
|
|
597
|
+
|
|
598
|
+
`IntlHelperScript` renders an early-catch `<script>` (production only, id `stale-deploy-early-catch`) that runs before hydration and listens for `window.error`/`unhandledrejection` events matching the same patterns as `isStaleDeployError` (inlined as JSON, so it stays in sync with `staleDeployPatterns` config), then force-reloads once per build id. This covers the case a React-level recovery (`useStaleDeployRecovery` below) cannot: when the chunk that failed to load is part of your own error boundary/global-error bundle, React never gets a chance to render the recovery UI. Both layers share the same `sessionStorage['stale-deploy-recovery-reloaded']` marker keyed by build id, so they can't double-reload each other. No setup beyond rendering `<IntlHelperScript />` is required.
|
|
599
|
+
|
|
600
|
+
For errors that don't crash the module graph itself (a normal thrown error reaching an error boundary), use `isStaleDeployError` and `clearClientCache` in error boundaries or global error handlers to automatically recover:
|
|
597
601
|
|
|
598
602
|
```typescript
|
|
599
603
|
import { isStaleDeployError, clearClientCache } from "cloudflare-next-intl/errorHandling";
|
|
@@ -5,9 +5,9 @@ import requireFirebaseAuthConfig from '../require_config';
|
|
|
5
5
|
import { defaultAppCheckTokenCookieName, defaultRefreshTokenCookieName, defaultSessionCookieName, isIdTokenExpired, refreshIdToken, sessionCookieOptions } from '../middleware/update_session';
|
|
6
6
|
import reportError from '../../error_handling/report_error';
|
|
7
7
|
import mintServerAppCheckToken from './mint_server_app_check_token';
|
|
8
|
-
let
|
|
9
|
-
let
|
|
10
|
-
let
|
|
8
|
+
let baseAppReady;
|
|
9
|
+
let firebaseAppModuleReady;
|
|
10
|
+
let firebaseAuthModuleReady;
|
|
11
11
|
/**
|
|
12
12
|
* Writes a freshly-minted session/refresh pair back to the cookie jar so the
|
|
13
13
|
* NEXT request doesn't repeat the rejected-token round-trip. Next only allows
|
|
@@ -89,12 +89,10 @@ export const getAuthenticatedAppForUser = cache(async function getAuthenticatedA
|
|
|
89
89
|
// with `currentUser === null`. So a null user (not a throw) is the signal
|
|
90
90
|
// to drop the bad token and mint a replacement from the refresh cookie.
|
|
91
91
|
const attempt = async (idToken) => {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const { initializeApp, initializeServerApp } = firebaseAppModule;
|
|
97
|
-
const { getAuth } = firebaseAuthModule;
|
|
92
|
+
firebaseAppModuleReady ?? (firebaseAppModuleReady = import('firebase/app'));
|
|
93
|
+
firebaseAuthModuleReady ?? (firebaseAuthModuleReady = import('firebase/auth'));
|
|
94
|
+
const { initializeApp, initializeServerApp } = await firebaseAppModuleReady;
|
|
95
|
+
const { getAuth } = await firebaseAuthModuleReady;
|
|
98
96
|
const firebaseConfig = {
|
|
99
97
|
apiKey: fa.apiKey,
|
|
100
98
|
authDomain: fa.authDomain,
|
|
@@ -109,8 +107,12 @@ export const getAuthenticatedAppForUser = cache(async function getAuthenticatedA
|
|
|
109
107
|
// named app per token: `initializeServerApp` derives a distinct,
|
|
110
108
|
// token-scoped auth context from this same base app without
|
|
111
109
|
// registering a new named app in Firebase's global app registry.
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
// Cached as a promise (not the resolved app) so concurrent requests
|
|
111
|
+
// racing this on a cold start share one `initializeApp` call instead
|
|
112
|
+
// of each calling `initializeApp` with the same name and racing
|
|
113
|
+
// Firebase's internal app registry.
|
|
114
|
+
baseAppReady ?? (baseAppReady = (async () => initializeApp(firebaseConfig, 'firebase-auth-server-base'))());
|
|
115
|
+
const baseApp = await baseAppReady;
|
|
114
116
|
const firebaseServerApp = initializeServerApp(baseApp, { authIdToken: idToken, appCheckToken });
|
|
115
117
|
const auth = getAuth(firebaseServerApp);
|
|
116
118
|
await auth.authStateReady();
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
* - redirects to the locale-prefixed URL if the locale cookie disagrees
|
|
7
7
|
* with the current path (covers client-side navigation edge cases)
|
|
8
8
|
* - (prod only) checks `BUILD_ID` and force-reloads on stale deploys
|
|
9
|
+
* - (prod only) listens for `error`/`unhandledrejection` events matching
|
|
10
|
+
* `isStaleDeployError`'s patterns and force-reloads once per build id —
|
|
11
|
+
* catches a stale-chunk failure even when the failing chunk is your own
|
|
12
|
+
* error boundary, before React (and `useStaleDeployRecovery`) ever mounts
|
|
9
13
|
* - loads `recaptcha/api.js?render=explicit` when `firebaseAuth.appCheck`
|
|
10
14
|
* has a `recaptchaV3SiteKey` and `useExplicitRecaptchaScript` isn't
|
|
11
15
|
* `false`, so `window.grecaptcha` is ready before App Check's
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { isDarkCookieKey, localeCookieName } from "../../config/cookie_key";
|
|
3
3
|
import config from "../../config/intl_config";
|
|
4
4
|
import ClientHelperScript from "../../client/components/client_helper_script";
|
|
5
|
+
import { defaultStaleDeployPatterns } from "../../error_handling/is_stale_deploy_error";
|
|
5
6
|
const isDev = process.env.NODE_ENV === 'development';
|
|
6
7
|
const appCheck = config.firebaseAuth?.appCheck;
|
|
7
8
|
const shouldLoadExplicitRecaptchaScript = !!appCheck?.recaptchaV3SiteKey && appCheck.useExplicitRecaptchaScript !== false;
|
|
@@ -14,6 +15,10 @@ const secureCookieAttribute = isDev ? '+ " Secure;"' : '';
|
|
|
14
15
|
* - redirects to the locale-prefixed URL if the locale cookie disagrees
|
|
15
16
|
* with the current path (covers client-side navigation edge cases)
|
|
16
17
|
* - (prod only) checks `BUILD_ID` and force-reloads on stale deploys
|
|
18
|
+
* - (prod only) listens for `error`/`unhandledrejection` events matching
|
|
19
|
+
* `isStaleDeployError`'s patterns and force-reloads once per build id —
|
|
20
|
+
* catches a stale-chunk failure even when the failing chunk is your own
|
|
21
|
+
* error boundary, before React (and `useStaleDeployRecovery`) ever mounts
|
|
17
22
|
* - loads `recaptcha/api.js?render=explicit` when `firebaseAuth.appCheck`
|
|
18
23
|
* has a `recaptchaV3SiteKey` and `useExplicitRecaptchaScript` isn't
|
|
19
24
|
* `false`, so `window.grecaptcha` is ready before App Check's
|
|
@@ -32,6 +37,47 @@ const secureCookieAttribute = isDev ? '+ " Secure;"' : '';
|
|
|
32
37
|
export default function HelperScript() {
|
|
33
38
|
return _jsxs(_Fragment, { children: [shouldLoadExplicitRecaptchaScript &&
|
|
34
39
|
_jsx("script", { src: "https://www.google.com/recaptcha/api.js?render=explicit", async: true, defer: true }), !isDev &&
|
|
40
|
+
_jsx("script", { id: "stale-deploy-early-catch", dangerouslySetInnerHTML: {
|
|
41
|
+
__html: `(function() {
|
|
42
|
+
var patterns = ${JSON.stringify(defaultStaleDeployPatterns)};
|
|
43
|
+
var key = 'stale-deploy-recovery-reloaded';
|
|
44
|
+
var attemptedThisLoad = false;
|
|
45
|
+
function isStale(msg) {
|
|
46
|
+
if (msg === undefined || msg === null) return true;
|
|
47
|
+
msg = String(msg).toLowerCase();
|
|
48
|
+
for (var i = 0; i < patterns.length; i++) {
|
|
49
|
+
if (msg.indexOf(patterns[i]) > -1) return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
function recover(msg) {
|
|
54
|
+
// One reload attempt per page load, no matter how many matching
|
|
55
|
+
// errors fire in a row (a single stale deploy commonly throws
|
|
56
|
+
// several near-simultaneous chunk failures) — without this guard
|
|
57
|
+
// each one would race to read-then-write the same sessionStorage
|
|
58
|
+
// key and could re-trigger reload() multiple times before the
|
|
59
|
+
// first navigation lands.
|
|
60
|
+
if (attemptedThisLoad) return;
|
|
61
|
+
try {
|
|
62
|
+
if (!isStale(msg)) return;
|
|
63
|
+
var buildId = localStorage.getItem('buildId') || 'unknown';
|
|
64
|
+
// Same marker/key as useStaleDeployRecovery: one reload per
|
|
65
|
+
// build id, so a repeat failure on a build that already spent
|
|
66
|
+
// its reload falls through instead of reloading forever.
|
|
67
|
+
if (sessionStorage.getItem(key) === buildId) return;
|
|
68
|
+
attemptedThisLoad = true;
|
|
69
|
+
sessionStorage.setItem(key, buildId);
|
|
70
|
+
window.location.reload();
|
|
71
|
+
} catch (e) {
|
|
72
|
+
console.error('Stale Deploy Early Catch Script Error:', e);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
window.addEventListener('error', function(e) { recover(e.message); });
|
|
76
|
+
window.addEventListener('unhandledrejection', function(e) {
|
|
77
|
+
recover(e.reason && e.reason.message);
|
|
78
|
+
});
|
|
79
|
+
})();`
|
|
80
|
+
} }), !isDev &&
|
|
35
81
|
_jsx("script", { id: "build-id-script", children: `(async function() {
|
|
36
82
|
try {
|
|
37
83
|
const resp = await fetch('/BUILD_ID', { method: 'HEAD', cache: 'no-store' });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudflare-next-intl",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.61",
|
|
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",
|
|
@@ -209,6 +209,10 @@
|
|
|
209
209
|
"types": "./dist/src/error_handling/clear_client_cache.d.ts",
|
|
210
210
|
"import": "./dist/src/error_handling/clear_client_cache.js"
|
|
211
211
|
},
|
|
212
|
+
"./useStaleDeployRecovery": {
|
|
213
|
+
"types": "./dist/src/error_handling/use_stale_deploy_recovery.d.ts",
|
|
214
|
+
"import": "./dist/src/error_handling/use_stale_deploy_recovery.js"
|
|
215
|
+
},
|
|
212
216
|
"./db": {
|
|
213
217
|
"types": "./dist/src/db/index.d.ts",
|
|
214
218
|
"import": "./dist/src/db/index.js"
|