Files
upstream-plane/apps/web/core/lib/stale-asset-error.ts
upstream-mirror 23059a1de3
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
snapshot of https://github.com/makeplane/plane.git @ d868349a55de79979eb8b98701bf1702c67ad634 (2026-09-15)
2026-09-15 15:14:58 +05:00

67 lines
2.6 KiB
TypeScript

/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
// Error signatures produced when a deploy removes hashed assets that an open tab
// still references (stale-tab version skew).
const STALE_ASSET_ERROR_SIGNATURES = [
"Failed to fetch dynamically imported module", // Chromium
"error loading dynamically imported module", // Firefox
"Importing a module script failed", // Safari
"Unable to preload CSS", // Vite preload helper
// React Router's own wrapper when a route module's lazy import fails during
// navigation — the original browser error above never reaches here as a
// window event, only this synthesized message.
"No result returned from dataStrategy for route",
];
export const isStaleAssetErrorMessage = (message: string): boolean =>
STALE_ASSET_ERROR_SIGNATURES.some((signature) => message.includes(signature));
export const isStaleAssetError = (error: unknown): boolean => {
const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
return isStaleAssetErrorMessage(message);
};
// Reload-once-then-fall-through-to-boundary guard, shared by the window-level
// listeners (entry.client.tsx) and the route ErrorBoundary (root.tsx) so both
// paths to the same failure share one reload attempt instead of racing.
const STALE_ASSET_RELOAD_KEY = "__plane_chunk_reload";
const STALE_ASSET_RELOAD_WINDOW_MS = 30_000;
const hasRecentStaleAssetReload = (): boolean => {
try {
const lastReloadAt = Number(sessionStorage.getItem(STALE_ASSET_RELOAD_KEY));
return Number.isFinite(lastReloadAt) && Date.now() - lastReloadAt < STALE_ASSET_RELOAD_WINDOW_MS;
} catch {
// No storage means no loop guard — never auto-reload in that case.
return true;
}
};
let isRecoveringFromStaleAsset = false;
// Returns whether it actually triggered a reload, so callers that can otherwise
// fall back to default browser error handling (e.g. Vite's preload-error
// overlay) only suppress that fallback when a reload is really in flight.
export const recoverFromStaleAsset = (): boolean => {
if (isRecoveringFromStaleAsset) return false;
isRecoveringFromStaleAsset = true;
if (hasRecentStaleAssetReload()) {
// Second failure in a row — surface to the route error boundary instead of looping.
isRecoveringFromStaleAsset = false;
return false;
}
try {
sessionStorage.setItem(STALE_ASSET_RELOAD_KEY, String(Date.now()));
} catch {
isRecoveringFromStaleAsset = false;
return false;
}
window.location.reload();
return true;
};