Files
orca/config/scripts/pty-transcript-recording.mjs
T

99 lines
2.2 KiB
JavaScript

const STOP_KEY = 0x1d
export async function recordPtyTranscript({ spawn, sink, stdin, stdout, options }) {
let recording = true
let term
let stopping = false
const timers = []
const wasRaw = stdin.isTTY === true && stdin.isRaw === true
const stop = () => {
if (stopping) {
return
}
stopping = true
recording = false
try {
term.kill()
} catch {
// A concurrent physical exit can make the native handle unavailable.
}
}
const onInput = (chunk) => {
if (chunk.includes(STOP_KEY)) {
stop()
} else if (!stopping) {
term.write(chunk)
}
}
let sinkFailure
const onSinkError = (error) => {
sinkFailure = error
if (term) {
stop()
}
}
sink.on('error', onSinkError)
let exitCode
let captureFailure
try {
term = await spawn((bytes) => {
if (recording) {
sink.write(bytes)
}
stdout.write(bytes)
})
const exit = new Promise((resolve) => term.onExit(({ exitCode }) => resolve(exitCode ?? 0)))
await term.waitForSpawn?.()
if (sinkFailure) {
throw sinkFailure
}
if (stdin.isTTY) {
stdin.setRawMode(true)
}
stdin.on('data', onInput)
stdin.resume()
for (const send of options.sends) {
timers.push(
setTimeout(() => {
if (!stopping) {
term.write(send.text)
}
}, send.atMs)
)
}
if (options.duration !== null) {
timers.push(setTimeout(stop, options.duration * 1000))
}
exitCode = await exit
} catch (error) {
captureFailure = error
} finally {
recording = false
for (const timer of timers) {
clearTimeout(timer)
}
stdin.off('data', onInput)
if (stdin.isTTY) {
stdin.setRawMode(wasRaw)
}
stdin.pause()
try {
term?.destroy()
} catch (error) {
captureFailure ??= error
}
await new Promise((resolve) => {
if (sink.destroyed) {
return resolve()
}
sink.once('error', resolve)
sink.end(resolve)
})
sink.off('error', onSinkError)
}
if (captureFailure || sinkFailure) {
throw captureFailure ?? sinkFailure
}
return exitCode
}