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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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);
|