#!/usr/bin/env node import http from 'node:http'; import fs from 'node:fs/promises'; import path from 'node:path'; import assert from 'node:assert/strict'; import os from 'node:os'; import { randomUUID } from 'node:crypto'; import { parseArgs } from 'node:util'; const { values: options } = parseArgs({ options: { endpoint: { type: 'string', default: 'http://127.0.0.1:9223' }, 'dashboard-url': { type: 'string' }, output: { type: 'string' }, hold: { type: 'boolean', default: false }, help: { type: 'boolean', default: false }, } }); if (options.help) { console.log(`Usage: node apps/linux/scripts/test-inline-browser.mjs [options] Run against an already launched Windows Tauri candidate with a ready loopback dashboard and a loopback WebView2 CDP endpoint. All test pages are synthetic. --endpoint URL CDP endpoint (default http://127.0.0.1:9223) --dashboard-url URL Restrict dashboard discovery to this origin and path --output DIRECTORY Proof directory (default: unique OS temporary directory) --hold Keep fixtures open for native UI verification; create the printed continue file or press Ctrl+C to finish cleanup --help Print this help without connecting to the application`); process.exit(0); } const endpoint = new URL(options.endpoint); assert(['http:', 'https:'].includes(endpoint.protocol), 'CDP endpoint must be HTTP(S)'); assert(['127.0.0.1', 'localhost', '[::1]'].includes(endpoint.hostname), 'CDP must be loopback'); const dashboardUrl = options['dashboard-url'] ? new URL(options['dashboard-url']) : null; const run = `inline-live-${Date.now()}-${randomUUID().slice(0, 8)}`; const out = path.resolve(options.output ?? path.join(os.tmpdir(), run)); await fs.mkdir(out, { recursive: true }); const results = { run, started: new Date().toISOString(), tests: [], observations: {}, artifacts: [] }; const connections = new Set(); const ownedTabs = new Set(); const scopes = [`${run}-primary`, `${run}-secondary`]; const sessionA = `${run}-conversation-a`, sessionB = `${run}-conversation-b`; const requests = new Map(); let refusedUrl; let recoverInitialRedirect = false; let authenticatedRequests = 0; const fixtureAuthorization = `Basic ${Buffer.from('inline-fixture:synthetic-password').toString('base64')}`; const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); async function until(fn, description, timeout = 15000) { const end = Date.now() + timeout; let last; while (Date.now() < end) { try { const value = await fn(); if (value) return value; } catch (error) { last = error; } await delay(100); } throw new Error(`Timed out: ${description}${last ? ` (${last.message})` : ''}`); } class CDP { static async connect(url) { const parsed = new URL(url); assert(['127.0.0.1', 'localhost', '[::1]'].includes(parsed.hostname), 'Target CDP must be loopback'); const cdp = new CDP(); cdp.pending = new Map(); cdp.seq = 0; cdp.socket = new WebSocket(url); await new Promise((resolve, reject) => { const timer = setTimeout(() => { cdp.socket.close(); reject(new Error('CDP connection timed out')); }, 5000); cdp.socket.addEventListener('open', () => { clearTimeout(timer); resolve(); }, { once: true }); cdp.socket.addEventListener('error', () => { clearTimeout(timer); reject(new Error('CDP connection failed')); }, { once: true }); }); cdp.socket.addEventListener('message', event => { const message = JSON.parse(event.data); if (!message.id) return; const request = cdp.pending.get(message.id); if (!request) return; cdp.pending.delete(message.id); clearTimeout(request.timer); if (message.error) request.reject(new Error(JSON.stringify(message.error))); else request.resolve(message.result); }); cdp.socket.addEventListener('close', () => { for (const request of cdp.pending.values()) { clearTimeout(request.timer); request.reject(new Error('CDP target closed')); } cdp.pending.clear(); }); connections.add(cdp); return cdp; } send(method, params = {}, timeout = 20000) { const id = ++this.seq; return new Promise((resolve, reject) => { const timer = setTimeout(() => { this.pending.delete(id); reject(new Error(`CDP timeout: ${method}`)); }, timeout); this.pending.set(id, { resolve, reject, timer }); this.socket.send(JSON.stringify({ id, method, params })); }); } async evaluate(expression, userGesture = false) { const response = await this.send('Runtime.evaluate', { expression, returnByValue: true, awaitPromise: true, userGesture }); if (response.exceptionDetails) throw new Error(response.exceptionDetails.exception?.description ?? response.exceptionDetails.text); return response.result?.value; } close() { this.socket.close(); connections.delete(this); } } const server = http.createServer((req, res) => { const pathname = new URL(req.url, 'http://127.0.0.1').pathname; requests.set(pathname, (requests.get(pathname) ?? 0) + 1); if (pathname === '/cookie-set') res.setHeader('Set-Cookie', `${run}=shared-fixture-value; Path=/; SameSite=Lax`); if (pathname === '/cookie-clear') res.setHeader('Set-Cookie', `${run}=; Path=/; Max-Age=0; SameSite=Lax`); if (pathname === '/redirect') { res.writeHead(302, { Location: '/landing' }); res.end(); return; } if (pathname === '/failed-initial-redirect') { res.writeHead(302, { Location: recoverInitialRedirect ? '/recovered-initial-redirect' : refusedUrl }); res.end(); return; } if (pathname === '/basic-auth') { if (req.headers.authorization !== fixtureAuthorization) { res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="inline-browser-fixture"' }); res.end('Synthetic browser authentication required'); return; } authenticatedRequests += 1; } res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader('Cache-Control', 'no-store'); const name = pathname.slice(1) || 'home'; res.write(`