Handle stale frontend assets and harden worker startup

这个提交包含在:
cryptocommuniums-afk
2026-03-15 02:57:44 +08:00
父节点 e43b969d28
当前提交 585fd5773d
修改 3 个文件,包含 73 行新增5 行删除

查看文件

@@ -9,6 +9,7 @@ import { getLoginUrl } from "./const";
import "./index.css";
const queryClient = new QueryClient();
const ASSET_REFRESH_KEY = "asset-recovery-reloaded";
const redirectToLoginIfUnauthorized = (error: unknown) => {
if (!(error instanceof TRPCClientError)) return;
@@ -21,6 +22,60 @@ const redirectToLoginIfUnauthorized = (error: unknown) => {
window.location.href = getLoginUrl();
};
function reloadForStaleAsset(reason: string) {
if (typeof window === "undefined") return;
const alreadyReloaded = window.sessionStorage.getItem(ASSET_REFRESH_KEY) === "1";
if (alreadyReloaded) {
console.error("[Asset Recovery] stale asset still failing after reload", reason);
return;
}
window.sessionStorage.setItem(ASSET_REFRESH_KEY, "1");
console.warn("[Asset Recovery] reloading page due to stale asset failure:", reason);
window.location.reload();
}
function clearAssetRecoveryFlag() {
if (typeof window === "undefined") return;
window.sessionStorage.removeItem(ASSET_REFRESH_KEY);
}
if (typeof window !== "undefined") {
window.addEventListener("load", () => {
clearAssetRecoveryFlag();
}, { once: true });
window.addEventListener("vite:preloadError", (event) => {
const customEvent = event as Event & { payload?: unknown; preventDefault: () => void };
customEvent.preventDefault();
reloadForStaleAsset(String(customEvent.payload ?? "vite preload error"));
});
window.addEventListener("error", (event) => {
const target = event.target;
if (!(target instanceof HTMLLinkElement || target instanceof HTMLScriptElement)) {
return;
}
const assetUrl = target instanceof HTMLLinkElement ? target.href : target.src;
if (assetUrl.includes("/assets/")) {
reloadForStaleAsset(assetUrl);
}
}, true);
window.addEventListener("unhandledrejection", (event) => {
const reason = event.reason instanceof Error ? event.reason.message : String(event.reason ?? "");
if (
reason.includes("Failed to fetch dynamically imported module") ||
reason.includes("Importing a module script failed") ||
reason.includes("Unable to preload CSS")
) {
reloadForStaleAsset(reason);
}
});
}
queryClient.getQueryCache().subscribe(event => {
if (event.type === "updated" && event.action.type === "error") {
const error = event.query.state.error;

查看文件

@@ -13,9 +13,17 @@ export function serveStatic(app: Express) {
);
}
app.use(express.static(distPath));
app.use(express.static(distPath, { index: false }));
app.use("*", (req, res) => {
// Missing files under /assets or any path with an extension must return 404.
// Falling back to index.html causes browsers to report MIME errors on stale chunks.
const requestPath = req.originalUrl.split("?")[0];
if (path.extname(requestPath)) {
res.status(404).type("text/plain").send("Not found");
return;
}
app.use("*", (_req, res) => {
res.sendFile(path.resolve(distPath, "index.html"));
});
}

查看文件

@@ -35,10 +35,15 @@ async function workOnce() {
async function main() {
console.log(`[worker] ${workerId} started`);
for (;;) {
try {
const hasWorked = await workOnce();
if (!hasWorked) {
await sleep(ENV.backgroundTaskPollMs);
}
} catch (error) {
console.error("[worker] loop error", error);
await sleep(Math.max(ENV.backgroundTaskPollMs, 3_000));
}
}
}