/** Real Chromium IndexedDB/Web Locks checks, in an isolated temporary browser profile. */ import { createServer } from 'node:http'; import { readFile, mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { resolve, join } from 'node:path'; import { spawn } from 'node:child_process'; import { stripTypeScriptTypes } from 'node:module'; import { setTimeout as pause } from 'node:timers/promises'; const root = resolve(import.meta.dirname, '../..'), profile = await mkdtemp(join(tmpdir(), 'endoleap-storage-browser-')); const server = createServer(async (request, response) => { try { const path = new URL(request.url, 'http://localhost').pathname; if (path === '/') { response.setHeader('Content-Type', 'text/html'); response.end('Headless storage checks'); return; } const filename = resolve(root, `.${path}`); if (!filename.startsWith(`${root}/src/lib/`) && !filename.startsWith(`${root}/tests/headless/`)) { response.writeHead(404).end(); return; } let source = await readFile(filename, 'utf8'); if (filename.endsWith('.ts')) source = stripTypeScriptTypes(source); response.setHeader('Content-Type', 'text/javascript'); response.end(source); } catch (error) { response.writeHead(500).end(String(error)); } }); await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)); const chrome = spawn(process.env.CHROME_BIN ?? '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', ['--headless=new', '--no-first-run', '--disable-background-networking', '--disable-component-update', '--remote-debugging-port=0', `--user-data-dir=${profile}`, `http://127.0.0.1:${server.address().port}/`], { stdio: 'ignore' }); let socket; try { let port; for (let attempt = 0; attempt < 100; attempt++) { try { port = Number((await readFile(join(profile, 'DevToolsActivePort'), 'utf8')).split('\n')[0]); break; } catch { await pause(100); } } if (!port) throw new Error('isolated Chromium did not start'); const tabs = await fetch(`http://127.0.0.1:${port}/json/list`).then(response => response.json()); socket = new WebSocket(tabs.find(tab => tab.type === 'page').webSocketDebuggerUrl); await new Promise((resolve, reject) => { socket.onopen = resolve; socket.onerror = reject; }); let next = 0; const pending = new Map(); socket.onmessage = event => { const message = JSON.parse(event.data); if (message.id) { const callbacks = pending.get(message.id); pending.delete(message.id); if (message.error) callbacks.reject(new Error(JSON.stringify(message.error))); else callbacks.resolve(message.result); } }; const call = (method, params) => new Promise((resolve, reject) => { const id = ++next; pending.set(id, { resolve, reject }); socket.send(JSON.stringify({ id, method, params })); }); const origin = `http://127.0.0.1:${server.address().port}`; await call('Page.navigate', { url: `${origin}/` }); let loaded = false; for (let attempt = 0; attempt < 100; attempt++) { const state = await call('Runtime.evaluate', { expression: `location.origin === ${JSON.stringify(origin)} && document.readyState === 'complete'`, returnByValue: true }); if (state.result?.value === true) { loaded = true; break; } await pause(50); } if (!loaded) throw new Error('browser test origin did not finish loading'); const result = await call('Runtime.evaluate', { expression: "import('/tests/headless/browser-storage-cases.mjs').then(module => module.run())", awaitPromise: true, returnByValue: true, timeout: 30000 }); if (result.exceptionDetails) throw new Error(result.exceptionDetails.exception?.description ?? result.exceptionDetails.text); for (const message of result.result.value) console.log(`PASS ${message}`); } finally { socket?.close(); const exited = new Promise(resolve => { if (chrome.exitCode !== null) resolve(); else chrome.once('exit', resolve); }); chrome.kill(); await exited; await new Promise(resolve => server.close(resolve)); await rm(profile, { recursive: true, force: true, maxRetries: 3 }); }