Files
iptvnator/libs/shared/interfaces/src/lib/renderer-reload-route.util.ts
T
4grayandClaude Fable 5.1 6f987d79d8 fix(shell): reload the packaged renderer back onto its in-app route (#1622)
The packaged renderer is index.html over file:// with path routing, so
after in-app navigation the document URL names a path with no file behind
it. A main-process reload (macOS View > Reload, DevTools) failed with
ERR_FILE_NOT_FOUND and stranded the window on Chromium's error page; a
renderer-initiated reload (the settings unsaved-changes guard's confirmed
location.reload()) was cancelled by the will-navigate trust check and
silently did nothing.

Both legs now re-load the packaged index with the route in a restoreRoute
query parameter, which main.ts restores with history.replaceState before
Angular bootstraps. The did-fail-load recovery is deferred to the error
page's dom-ready: a load issued from inside the failure event yields a
document that never receives animation frames and never paints.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 08:43:43 +02:00

73 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Route hand-off for a reloaded packaged renderer.
*
* The packaged renderer is loaded from `dist/apps/web/index.html` over
* `file://` and Angular uses PATH routing, so after in-app navigation the
* document URL is `file:///…/web/workspace/sources` — a path with no file
* behind it. A reload (the macOS View › Reload menu, DevTools, the settings
* unsaved-changes guard's confirmed reload) therefore fails with
* `ERR_FILE_NOT_FOUND` and strands the window on Chromium's error page.
*
* The Electron main process recovers such a failure by loading `index.html`
* again with the failed URL's route (its path relative to the renderer root,
* plus query and fragment) carried in this query parameter. The renderer
* consumes it before Angular bootstraps: `resolveRestoredRendererRoute`
* turns the current document URL into the in-app URL the router should
* start from, and `main.ts` installs it with `history.replaceState`, so the
* router's initial navigation lands on the route the user was on.
*/
/** Query parameter carrying the route to restore on the reloaded index. */
export const RENDERER_RESTORE_ROUTE_QUERY_PARAM = 'restoreRoute';
/**
* The URL to present instead of `currentHref` before the router's initial
* navigation, or `null` when the document carries no restore request.
*
* `baseUri` is `document.baseURI`: the packaged build's `<base href="./">`
* resolves to the renderer's directory, which is also what Angular strips
* from `location.pathname` to obtain the route. A route that would leave
* that directory — an absolute URL, another scheme, a `..` escape — is
* dropped and only the parameter is removed, so the app boots at its
* default route rather than following an arbitrary target.
*/
export function resolveRestoredRendererRoute(
currentHref: string,
baseUri: string
): string | null {
let current: URL;
let baseDirectory: URL;
try {
current = new URL(currentHref);
baseDirectory = new URL('./', baseUri);
} catch {
return null;
}
const route = current.searchParams.get(RENDERER_RESTORE_ROUTE_QUERY_PARAM);
if (route === null) {
return null;
}
current.searchParams.delete(RENDERER_RESTORE_ROUTE_QUERY_PARAM);
const stripped = current.href;
let target: URL;
try {
target = new URL(route, baseDirectory);
} catch {
return stripped;
}
const staysInsideRenderer =
target.protocol === baseDirectory.protocol &&
target.host === baseDirectory.host &&
target.pathname.startsWith(baseDirectory.pathname) &&
target.pathname !== baseDirectory.pathname;
return staysInsideRenderer ? target.href : stripped;
}