Add an Electron desktop shell for the SPA

desktop/ serves the normal frontend build over a privileged app://j621
scheme, so localStorage, OPFS, Web Workers, WebCodecs and history routing
behave exactly like Chrome. Development points at the Vite dev server;
`npm run smoke` runs headless Electron and checks the bundled app.

External links open in the system browser, and download navigations
(?download=1 or media URLs) are rerouted through webContents.downloadURL,
since preventing them cancels the download. The setup screen names the
shell's origin when the connection test fails, and the deploy docs list
app://j621 for CORS_ALLOWED_ORIGINS.
This commit is contained in:
2026-09-20 17:07:22 -05:00
parent 3a07481dfc
commit cf129714be
14 changed files with 4227 additions and 1 deletions
+7 -1
View File
@@ -41,11 +41,17 @@ async function testBackend(base: string): Promise<TestResult> {
text: `The backend answered HTTP ${response.status}.`,
};
} catch {
// The desktop shell is a different origin from the backend, and that is
// the usual reason the first connection test fails there.
const desktopHint = window.j621Desktop
? ` The desktop app's origin is ${window.j621Desktop.origin} — allow it in CORS_ALLOWED_ORIGINS on the backend.`
: "";
return {
ok: false,
text:
"Could not reach the backend. Check the URL, and if it lives on " +
"another domain add this site to CORS_ALLOWED_ORIGINS there.",
"another domain add this site to CORS_ALLOWED_ORIGINS there." +
desktopHint,
};
} finally {
window.clearTimeout(timeout);
+16
View File
@@ -1,2 +1,18 @@
/** Baked at build time by Vite (see vite.config.ts). */
declare const __GIT_HASH__: string;
/**
* Injected by the Electron shell (desktop/src/preload.ts) when it hosts the
* SPA. Absent in browsers.
*/
interface J621DesktopBridge {
readonly isDesktop: true;
readonly platform: string;
/** The shell's origin (e.g. `app://j621`), for backend CORS hints. */
readonly origin: string;
getVersion(): Promise<string>;
}
interface Window {
j621Desktop?: J621DesktopBridge;
}