- sidebar: workspaces (+ sessions) of every other device appear inside the stock Workspaces list with a server icon and the device name; click folds sessions, hover + creates a session there, a session opens as the remote conversation view (/r/<device>/#/session/<id>); the viewed device's own rows stay native - Devices tab: every device with its state (online/offline, last seen, platform, workspace/session/running counts), offline and revoked devices included - deploy/e2e-dom.mjs, e2e-eval.mjs: DOM dump / evaluate-in-page helpers
51 lines
3.3 KiB
JavaScript
51 lines
3.3 KiB
JavaScript
#!/usr/bin/env node
|
|
// Evaluate a JS expression inside the DSH page (headless Chromium) and print the result.
|
|
// node deploy/e2e-eval.mjs <chrome> <port> '<expression>' [waitMs] [urlPath]
|
|
// Authenticates with the local browser-session secret (DSH_HOME). Handy for UI integration work.
|
|
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, port = '3081', expression, waitMs = '9000', urlPath = '/'] = 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 authority = `127.0.0.1:${port}`
|
|
const prof = fs.mkdtempSync(path.join(os.tmpdir(), 'dshr-eval-'))
|
|
const proc = spawn(chrome, ['--headless=new', '--no-sandbox', '--disable-gpu', `--user-data-dir=${prof}`, '--remote-debugging-port=0', '--window-size=1400,1000', '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()
|
|
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) } })
|
|
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')
|
|
await send('Network.setCookie', { name: cookieName(authority), value: mintCookie(authority, secret, 1).split('=').slice(1).join('='), domain: '127.0.0.1', path: '/', httpOnly: true })
|
|
await send('Page.navigate', { url: `http://${authority}${urlPath}` })
|
|
await new Promise((r) => setTimeout(r, Number(waitMs)))
|
|
const r = await send('Runtime.evaluate', { expression, awaitPromise: true, returnByValue: true })
|
|
const v = r.result?.result?.value
|
|
console.log(typeof v === 'string' ? v : JSON.stringify(v ?? r.result, null, 1))
|
|
// Optional second expression after the page settled (e.g. after the first one navigated).
|
|
if (process.env.THEN) {
|
|
await new Promise((res) => setTimeout(res, Number(process.env.THEN_WAIT || 12000)))
|
|
const r2 = await send('Runtime.evaluate', { expression: process.env.THEN, awaitPromise: true, returnByValue: true })
|
|
const v2 = r2.result?.result?.value
|
|
console.log(typeof v2 === 'string' ? v2 : JSON.stringify(v2 ?? r2.result, null, 1))
|
|
}
|
|
if (process.env.SHOT) {
|
|
const s = await send('Page.captureScreenshot', { format: 'png' })
|
|
fs.writeFileSync(process.env.SHOT, Buffer.from(s.result.data, 'base64'))
|
|
console.log('screenshot', process.env.SHOT)
|
|
}
|
|
ws.close(); proc.kill('SIGKILL')
|
|
try { fs.rmSync(prof, { recursive: true, force: true }) } catch { /* chrome may still hold files */ }
|
|
process.exit(0)
|