130 lines
5.4 KiB
TypeScript
130 lines
5.4 KiB
TypeScript
/**
|
|
* Classic inline script for the home loading screen.
|
|
*
|
|
* It has to run when the client bundle never runs, so the generated string is
|
|
* ES5: no modules, no React, no reload until the button is clicked.
|
|
* The timer is longer than the 20s in-bundle bootstrap slow threshold, so a
|
|
* normal hydrate does not flash this copy.
|
|
*/
|
|
|
|
import {
|
|
HOME_BOOTSTRAP_FAILURE,
|
|
sanitizeBootstrapBuildId,
|
|
} from "./home-bootstrap-failure.ts";
|
|
|
|
export const FIRST_PAINT_FALLBACK_TIMEOUT_MS = 25_000;
|
|
|
|
export const FIRST_PAINT_FALLBACK_TITLE = "这个页面没能加载完";
|
|
|
|
export const FIRST_PAINT_FALLBACK_BODY =
|
|
"网络中断或浏览器版本过旧都会这样。可以重新加载试一次;如果反复出现,请在 Safari 设置里清除本站数据,或把系统升级到 iOS 16.4 以上。";
|
|
|
|
export const FIRST_PAINT_FALLBACK_ACTION = "重新加载";
|
|
|
|
function jsDoubleQuoted(value: string): string {
|
|
let escaped = "";
|
|
for (let index = 0; index < value.length; index += 1) {
|
|
const char = value.charAt(index);
|
|
const code = value.charCodeAt(index);
|
|
if (char === "\\") escaped += "\\\\";
|
|
else if (char === "\"") escaped += "\\\"";
|
|
else if (char === "\n") escaped += "\\n";
|
|
else if (char === "\r") escaped += "\\r";
|
|
else if (char === "<") escaped += "\\u003c";
|
|
else if (code === 0x2028 || code === 0x2029) escaped += `\\u${code.toString(16)}`;
|
|
else escaped += char;
|
|
}
|
|
return `"${escaped}"`;
|
|
}
|
|
|
|
export function firstPaintFallbackBootScript(buildId?: string): string {
|
|
const safeBuild = sanitizeBootstrapBuildId(buildId ?? process.env.NEXT_PUBLIC_GIT_COMMIT);
|
|
const timeout = String(FIRST_PAINT_FALLBACK_TIMEOUT_MS);
|
|
const hydrateTimeout = jsDoubleQuoted(HOME_BOOTSTRAP_FAILURE.hydrateTimeout);
|
|
const chunk404 = jsDoubleQuoted(HOME_BOOTSTRAP_FAILURE.chunk404);
|
|
const chunkParse = jsDoubleQuoted(HOME_BOOTSTRAP_FAILURE.chunkParse);
|
|
const staleCache = jsDoubleQuoted(HOME_BOOTSTRAP_FAILURE.staleCache);
|
|
const weakNetwork = jsDoubleQuoted(HOME_BOOTSTRAP_FAILURE.weakNetwork);
|
|
return `(function () {
|
|
var started = Date.now();
|
|
var buildId = ${jsDoubleQuoted(safeBuild)};
|
|
var category = ${hydrateTimeout};
|
|
function fromCache(source) {
|
|
try {
|
|
if (!source || !window.performance || !window.performance.getEntriesByName) return false;
|
|
var entries = window.performance.getEntriesByName(source);
|
|
var i;
|
|
for (i = 0; i < entries.length; i++) {
|
|
var entry = entries[i];
|
|
if (entry && entry.decodedBodySize > 0 && entry.transferSize === 0) return true;
|
|
}
|
|
} catch (ignore) {}
|
|
return false;
|
|
}
|
|
function classify(message, source) {
|
|
var text = String(message || "");
|
|
var from = String(source || "");
|
|
var combined = text + " " + from;
|
|
if (/SyntaxError|Unexpected token|Invalid regular expression/i.test(text)) {
|
|
return fromCache(from) ? ${staleCache} : ${chunkParse};
|
|
}
|
|
if (/ChunkLoadError|Loading chunk|Failed to fetch dynamically imported module|Failed to load/i.test(combined) || /\\/_next\\/static\\//.test(from)) {
|
|
if (fromCache(from)) return ${staleCache};
|
|
if (navigator && navigator.onLine === false) return ${weakNetwork};
|
|
return ${chunk404};
|
|
}
|
|
return "";
|
|
}
|
|
window.addEventListener("error", function (event) {
|
|
var message = "";
|
|
var source = "";
|
|
if (event) {
|
|
message = event.message || "";
|
|
source = event.filename || "";
|
|
if (!source && event.target && event.target.src) source = event.target.src;
|
|
}
|
|
var next = classify(message, source);
|
|
if (next) category = next;
|
|
}, true);
|
|
window.setTimeout(function () {
|
|
var root = document.documentElement;
|
|
if (!root || root.getAttribute("data-hydrated") === "1") return;
|
|
var loading = document.querySelector(".app-loading");
|
|
if (!loading) return;
|
|
if ((category === ${hydrateTimeout} || category === ${chunk404}) && navigator && navigator.onLine === false) {
|
|
category = ${weakNetwork};
|
|
}
|
|
var slot = loading.querySelector(".app-loading-content");
|
|
if (!slot) return;
|
|
if (loading.getAttribute("data-bootstrap-fallback") === "1") return;
|
|
loading.setAttribute("data-bootstrap-fallback", "1");
|
|
loading.setAttribute("aria-busy", "false");
|
|
if (loading.className.indexOf("app-loading-error") === -1) {
|
|
loading.className += " app-loading-error";
|
|
}
|
|
slot.setAttribute("role", "alert");
|
|
slot.innerHTML = "";
|
|
var title = document.createElement("strong");
|
|
title.appendChild(document.createTextNode(${jsDoubleQuoted(FIRST_PAINT_FALLBACK_TITLE)}));
|
|
var body = document.createElement("span");
|
|
body.appendChild(document.createTextNode(${jsDoubleQuoted(FIRST_PAINT_FALLBACK_BODY)}));
|
|
var actions = document.createElement("div");
|
|
actions.className = "app-loading-actions";
|
|
var button = document.createElement("button");
|
|
button.className = "button-primary";
|
|
button.type = "button";
|
|
button.appendChild(document.createTextNode(${jsDoubleQuoted(FIRST_PAINT_FALLBACK_ACTION)}));
|
|
button.onclick = function () { window.location.reload(); };
|
|
actions.appendChild(button);
|
|
slot.appendChild(title);
|
|
slot.appendChild(body);
|
|
slot.appendChild(actions);
|
|
var duration = Date.now() - started;
|
|
root.setAttribute("data-bootstrap-failure", category);
|
|
root.setAttribute("data-bootstrap-build", buildId);
|
|
root.setAttribute("data-bootstrap-duration", String(duration));
|
|
window.__jyotishaBootstrapObservation = { category: category, buildId: buildId, durationMs: duration };
|
|
}, ${timeout});
|
|
})();`;
|
|
}
|