chore: dev-stack switches, shared e2e harness, and desktop shell rules

- `LIVE_RELOAD=false npm run dev` keeps the renderer alive across a machine
  suspend; the reload client otherwise destroys the session under test.
- `dev-peer.sh` plus a separate userdata dir runs a second local peer.
- `tools/voice-probe.js` samples peer state and RTP counters from a live
  window, persisting to localStorage so a renderer reload cannot erase it.
- e2e helpers for voice pairs, peer-role election, and a TURN relay.
- Electron single-instance and dev-client-load decisions move into rules
  files with colocated specs.
This commit is contained in:
2026-08-14 03:19:29 +02:00
parent d71e3a98da
commit e49b3ec112
41 changed files with 35947 additions and 50 deletions
+48 -1
View File
@@ -11,6 +11,7 @@ import * as fs from 'fs';
import * as path from 'path';
import { DESKTOP_APP_DISPLAY_NAME } from '../app/desktop-branding.rules';
import { readDesktopSettings } from '../desktop-settings';
import { DEV_CLIENT_LOAD_ATTEMPTS, loadDevelopmentClientWithRetry } from './dev-client-load.rules';
import { resolveDevelopmentClientUrl } from './dev-client-url.rules';
import { shouldRegisterDisplayMediaHandler } from './display-media-handler.rules';
@@ -261,6 +262,52 @@ function ensureDisplayMediaRequestHandler(): void {
);
}
function describeError(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
function buildDevClientFailurePage(url: string, reason: string): string {
const escapedReason = reason.replace(/&/g, '&amp;').replace(/</g, '&lt;');
return `<!doctype html>
<html><body style="background:#0a0a0f;color:#e5e7eb;font:14px system-ui;padding:48px">
<h1 style="font-size:18px">The dev client did not load</h1>
<p>Could not load <code>${url}</code> after ${DEV_CLIENT_LOAD_ATTEMPTS} attempts.</p>
<p style="color:#f87171"><code>${escapedReason}</code></p>
<p>Check that <code>npm run dev</code> is still running, then reload with Ctrl+R.</p>
</body></html>`;
}
async function loadDevelopmentClient(window: BrowserWindow, url: string): Promise<void> {
let lastError: unknown = null;
const outcome = await loadDevelopmentClientWithRetry({
isAborted: () => window.isDestroyed(),
load: () => window.loadURL(url),
onGiveUp: (attempts, error) => {
lastError = error;
console.error(`[Window] Dev client at ${url} failed after ${attempts} attempts: ${describeError(error)}`);
},
onRetry: (attempt, error) => {
lastError = error;
console.warn(`[Window] Dev client at ${url} not ready (attempt ${attempt}): ${describeError(error)}. Retrying.`);
},
wait: (delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs))
});
if (outcome !== 'failed' || window.isDestroyed()) {
return;
}
const failurePage = buildDevClientFailurePage(url, describeError(lastError));
try {
await window.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(failurePage)}`);
} catch (error) {
console.error(`[Window] Could not show the dev client failure page: ${describeError(error)}`);
}
}
export async function createWindow(): Promise<void> {
const windowIconPath = getWindowIconPath();
@@ -290,7 +337,7 @@ export async function createWindow(): Promise<void> {
ensureDisplayMediaRequestHandler();
if (process.env['NODE_ENV'] === 'development') {
await mainWindow.loadURL(resolveDevelopmentClientUrl(process.env['SSL'] === 'true'));
await loadDevelopmentClient(mainWindow, resolveDevelopmentClientUrl(process.env['SSL'] === 'true'));
if (process.env['DEBUG_DEVTOOLS'] === '1') {
mainWindow.webContents.openDevTools();