Files
lyuksovannyy 8872e73e76 dsh-remote 0.1.0: master/slave remote control for DSH
- authenticated WebSocket link (dependency-free RFC6455 + stream mux), pairing
  codes, per-device keys (hashed), revoke/remove
- /r/<device>/ proxy of the full DSH UI (HTTP, Remote WebSocket, SSE) with
  per-device cookie minting; slave->slave relay through the master
- Devices sidebar panel (all devices' sessions, new session on device),
  Remote devices settings page, finished-session toasts
- plugin + config sync master->slave: registry bundles via pluginManager,
  link: plugins as tarballs, cordis.patch.yml into a managed bundle layer
  (!!js preserved), allow/deny, opt-in per-secret credential sync
- deploy helpers + e2e probes, unit tests
2026-10-11 08:23:19 +00:00

44 lines
2.6 KiB
JavaScript

#!/usr/bin/env node
// Load one URL N times in headless Chromium and count uncaught exceptions per load.
// node deploy/e2e-exc.mjs <chrome> <url> <cookieAuthority> [loads] [settleMs]
import { spawn } from 'node:child_process'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { cookieName, mintCookie } from '../lib/auth-cookie.mjs'
const [chrome, url, authority, loads = '3', settle = '8000'] = process.argv.slice(2)
const home = process.env.DSH_HOME || path.join(os.homedir(), '.dsh')
const secret = fs.readFileSync(path.join(home, '.credentials.yaml'), 'utf8').match(/browser-session:[\s\S]*?secret:\s*([A-Za-z0-9_-]{43})/)[1]
const prof = fs.mkdtempSync(path.join(os.tmpdir(), 'dshr-exc-'))
const proc = spawn(chrome, ['--headless=new', '--no-sandbox', '--disable-gpu', `--user-data-dir=${prof}`, '--remote-debugging-port=0', 'about:blank'], { stdio: ['ignore', 'ignore', 'pipe'] })
const wsUrl = await new Promise((resolve, reject) => {
let buf = ''
proc.stderr.on('data', (d) => { buf += d; const m = buf.match(/DevTools listening on (ws:\/\/\S+)/); if (m) resolve(m[1]) })
setTimeout(() => reject(new Error('chrome did not start')), 15000)
})
const page = (await (await fetch(wsUrl.replace(/^ws/, 'http').replace(/\/devtools\/browser\/.*/, '/json/list'))).json()).find((t) => t.type === 'page')
const ws = new WebSocket(page.webSocketDebuggerUrl)
await new Promise((r) => ws.addEventListener('open', r))
let id = 0
const pending = new Map()
let exc = []
ws.addEventListener('message', (ev) => {
const m = JSON.parse(ev.data)
if (m.id && pending.has(m.id)) { pending.get(m.id)(m); pending.delete(m.id) }
if (m.method === 'Runtime.exceptionThrown') exc.push((m.params.exceptionDetails?.exception?.description || '').split('\n')[0].slice(0, 120))
})
const send = (method, params = {}) => new Promise((resolve) => { const i = ++id; pending.set(i, resolve); ws.send(JSON.stringify({ id: i, method, params })) })
await send('Page.enable'); await send('Runtime.enable'); await send('Network.enable')
const host = authority.split(':')[0]
await send('Network.setCookie', { name: cookieName(authority), value: mintCookie(authority, secret, 1).split('=').slice(1).join('='), domain: host, path: '/', httpOnly: true })
for (let i = 0; i < Number(loads); i++) {
exc = []
await send('Page.navigate', { url: `${url}${url.includes('?') ? '&' : '?'}n=${i}` })
await new Promise((r) => setTimeout(r, Number(settle)))
console.log(`load ${i}: ${exc.length} exceptions ${exc.join(' || ')}`)
}
ws.close(); proc.kill('SIGKILL')
try { fs.rmSync(prof, { recursive: true, force: true }) } catch { /* chrome may still hold files */ }
process.exit(0)