cloudflare-next-intl 0.9.55 → 0.9.57

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.
@@ -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 = 3;
25
32
  function isStale(msg) {
26
33
  if (msg === undefined || msg === null) return true;
27
34
  msg = String(msg).toLowerCase();
@@ -30,6 +37,55 @@ export default function HelperScript() {
30
37
  }
31
38
  return false;
32
39
  }
40
+ // Cover the page, never replace it. Wiping document.body used
41
+ // to be safe because a reload followed immediately; now that a
42
+ // probe can run for a few seconds first, React keeps rendering
43
+ // against the tree and every removeChild/insertBefore throws,
44
+ // which crashes it into the very error UI this is hiding.
45
+ var overlayId = 'cfni-stale-deploy-overlay';
46
+ function showOverlay() {
47
+ try {
48
+ var root = document.documentElement;
49
+ if (!root || document.getElementById(overlayId)) return;
50
+ var el = document.createElement('div');
51
+ el.id = overlayId;
52
+ el.setAttribute('style', 'position:fixed;inset:0;z-index:2147483647;background:#ffffff;');
53
+ el.innerHTML = ${JSON.stringify(reloadHtml)};
54
+ (document.body || root).appendChild(el);
55
+ } catch (e) {}
56
+ }
57
+ function doReload() {
58
+ try {
59
+ var u = new URL(window.location.href);
60
+ u.searchParams.set('_stale_reload', String(Date.now()));
61
+ window.location.replace(u.toString());
62
+ } catch (e) {
63
+ try { window.location.reload(); } catch (e2) {}
64
+ }
65
+ }
66
+ // A deploy swaps the Worker version colo by colo, so for a few
67
+ // seconds the document can come from the new version while a
68
+ // chunk request still lands on the old one, which answers with
69
+ // a plain-text 404 body instead of JavaScript. Reloading inside
70
+ // that window just reproduces the error, so poll the failed
71
+ // chunk with a cache-busted request until it is real
72
+ // JavaScript, then reload once. Give up after maxProbes and
73
+ // reload anyway rather than hanging on the overlay.
74
+ function probeThenReload(attempt) {
75
+ if (!probeUrl || typeof fetch !== 'function') return doReload();
76
+ var url = probeUrl + (probeUrl.indexOf('?') > -1 ? '&' : '?') + '_r=' + Date.now();
77
+ fetch(url, { cache: 'reload', credentials: 'omit' }).then(function(r) {
78
+ var ct = (r.headers.get('content-type') || '').toLowerCase();
79
+ if (!r.ok || ct.indexOf('javascript') === -1) throw new Error('unhealthy: ' + r.status + ' ' + ct);
80
+ doReload();
81
+ }).catch(function(err) {
82
+ if (attempt >= maxProbes) {
83
+ console.warn('[StaleDeploy early-catch] Asset still unhealthy after', attempt + 1, 'probes - reloading anyway:', String(err));
84
+ return doReload();
85
+ }
86
+ setTimeout(function() { probeThenReload(attempt + 1); }, 300 * Math.pow(2, attempt));
87
+ });
88
+ }
33
89
  function recover(msg, source) {
34
90
  if (attemptedThisLoad) return;
35
91
  try {
@@ -55,30 +111,28 @@ export default function HelperScript() {
55
111
  console.warn('[StaleDeploy early-catch] Skipping reload, attempts exhausted for buildId:', buildId, attempts);
56
112
  return;
57
113
  }
114
+ // A reload that lands inside the same throttle window
115
+ // means the previous recovery did not help: the assets
116
+ // are momentarily unreadable at the edge rather than
117
+ // stale. Waiting out the window and retrying lets the
118
+ // page heal itself instead of stranding the visitor on
119
+ // the error UI.
58
120
  if (sameBuild && throttled) {
59
- console.warn('[StaleDeploy early-catch] Skipping reload, throttled for buildId:', buildId);
121
+ if (retryScheduled) return;
122
+ retryScheduled = true;
123
+ var wait = throttleMs - (Date.now() - last);
124
+ if (!(wait > 0)) wait = 0;
125
+ console.warn('[StaleDeploy early-catch] Throttled for buildId:', buildId, '- retrying in', wait, 'ms');
126
+ showOverlay();
127
+ setTimeout(function() { retryScheduled = false; recover(msg, source); }, wait + 250);
60
128
  return;
61
129
  }
62
130
  attemptedThisLoad = true;
63
131
  sessionStorage.setItem(key, buildId);
64
132
  sessionStorage.setItem(countKey, String(attempts + 1));
65
133
  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
- }
134
+ showOverlay();
135
+ probeThenReload(0);
82
136
  } catch (e) {
83
137
  console.error('Stale Deploy Early Catch Script Error:', e);
84
138
  }
@@ -102,6 +156,7 @@ export default function HelperScript() {
102
156
  var sameOrigin = false;
103
157
  try { sameOrigin = new URL(src, window.location.href).origin === window.location.origin; } catch (err2) { return; }
104
158
  if (!sameOrigin) return;
159
+ if (!probeUrl) probeUrl = src;
105
160
  recover('chunk resource failed to load: ' + src, 'resource-error');
106
161
  } catch (err) {}
107
162
  }, 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.55",
3
+ "version": "0.9.57",
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",