import * as plugins from './plugins.js'; import * as interfaces from '../ts_interfaces/index.js'; /** The inspector has independent flow control from browser input and video signaling. */ export class BrowserDevToolsClient { private readonly frontend: plugins.DevToolsFrontend; private closed = false; private ready = false; private panel: plugins.TDevToolsPanel = 'elements'; private drain?: Promise; constructor(private readonly response: interfaces.IReq_ControllerBrowserDevToolsOpen['response'], iframe: HTMLIFrameElement, private readonly onClose: (reason: string) => void) { this.frontend = new plugins.DevToolsFrontend({ iframe, entrypointUrl: response.entrypointUrl, send: message => this.send(message), onClose: reason => this.fail(reason) }); for (const stream of [response.events, response.commands]) { void stream.opened.catch(error => this.fail(String(error))); void stream.completion.then(() => this.fail('The inspector disconnected.'), error => this.fail(String(error))); } } public async start(panel: plugins.TDevToolsPanel): Promise { this.panel = panel; const ready = this.frontend.start(); this.drain = this.receive(); void this.drain.catch(error => this.fail(String(error))); await ready; if (!this.closed) { this.ready = true; this.frontend.showPanel(this.panel); } } public showPanel(panel: plugins.TDevToolsPanel): void { this.panel = panel; if (this.ready && !this.closed) this.frontend.showPanel(panel); } private async send(message: string): Promise { if (this.closed) throw new Error('DevTools is closed.'); const packet = interfaces.encodeControllerDevToolsMessage(message, interfaces.controllerDevToolsCommandBytes); for (let offset = 0; offset < packet.length; offset += interfaces.controllerDevToolsWriteBytes) { if (this.closed) throw new Error('DevTools is closed.'); await this.response.commands.send(packet.subarray(offset, offset + interfaces.controllerDevToolsWriteBytes)); } } private async receive(): Promise { const decoder = new interfaces.ControllerDevToolsMessageDecoder(interfaces.controllerDevToolsEventBytes); while (!this.closed) { const chunk = await this.response.events.receive(); if (this.closed) return; if (chunk === undefined) { decoder.finish(); await this.response.events.accept(); return; } for (const message of decoder.push(chunk)) await this.frontend.dispatch(message); } } private fail(reason: string): void { if (this.closed) return; this.stop(); this.onClose(reason.slice(0, 512)); } public stop(): void { if (this.closed) return; this.closed = true; this.frontend.close(); for (const stream of [this.response.events, this.response.commands]) { void stream.abort(new Error('DevTools closed.')).catch(() => undefined); } } }