import { DebugProtocol } from "@vscode/debugprotocol"; import { DebuggerWorker } from "./debugger-worker"; import { Breakpoint, Command, InitArguments, Stackframe, Variables, Location, Call } from "./types"; export class DebuggerClient { worker: Worker | DebuggerWorker seq: number; initialized: boolean; resolverMap: Map void>; finished: boolean; requestTimeout = 180000; createWorker: () => Worker | DebuggerWorker; private readonly commandsReturnLocation: string[] = [ Command.StepOver, Command.StepInto, Command.StepNext, Command.StepOut, Command.StepInstruction, Command.ContinueUntilBreakpoint, Command.Reset, Command.Step, ]; private readonly breakpointCommads: string[] = [ Command.AddBreakpoint, Command.RemoveAllBreakpoints, Command.RemoveBreakpoint ]; constructor(createWorker?: () => Worker) { this.seq = 0; this.createWorker = createWorker?? (() => new DebuggerWorker()); this.worker = this.createWorker(); this.initialized = false; this.resolverMap = new Map(); } public async initialize(init_arguments: InitArguments): Promise { this.finished = false; return await this.sendRequest(Command.Initialize, init_arguments); } public async reset(): Promise { this.finished = false; return await this.sendRequest(Command.Reset); } public async stepOver(): Promise { return await this.sendRequest(Command.StepOver) } public async stepInto(): Promise { return await this.sendRequest(Command.StepInto); } public async stepOut(): Promise { return await this.sendRequest(Command.StepOut); } public async stepNext(): Promise { return await this.sendRequest(Command.StepNext); } public async advance(steps?: number): Promise { return await this.sendRequest(Command.StepInstruction, steps); } public async continueUntilBreakpoint(): Promise { return await this.sendRequest(Command.ContinueUntilBreakpoint); } public async addBreakpoint(breakpoint: Breakpoint) { await this.sendRequest(Command.AddBreakpoint, breakpoint); } public async removeBreakpoint(breakpoint: Breakpoint) { await this.sendRequest(Command.RemoveAllBreakpoints, breakpoint); } public async removeAllBreakpoints() { await this.sendRequest(Command.RemoveAllBreakpoints) } public async requireVariables(): Promise { return await this.sendRequest(Command.Variables); } public async requireStacktrace(): Promise { return await this.sendRequest(Command.Stacktrace); } public async evaluateExpression(expression: string) { return await this.sendRequest(Command.Evaluate, expression); } public async runToEnd() { await this.sendRequest(Command.RunToEnd); } public async requireCurrentStep(): Promise { return await this.sendRequest(Command.Step); } public async getCallTrace(): Promise { return await this.sendRequest(Command.CallTrace); } private sendRequest(command: Command, args?: any): Promise { // wrap the timeout return this.timeoutPromise(this.requestTimeout, this.internalSendRequest(command, args)); } private internalSendRequest(command: Command, args?: any): Promise { return new Promise((res, rej) => { const channel = new MessageChannel(); channel.port1.onmessage = ({data}) => { channel.port1.close(); const resp = data as DebugProtocol.Response; if (resp.command === Command.Initialize) { this.initialized = true; } if (this.commandsReturnLocation.includes(resp.command) && resp.body == null) { this.finished = true; } if (resp.success) { res(resp.body); } else { rej(resp.message); } }; if (this.finished && !this.breakpointCommads.includes(command)) { rej("Trace already finishes."); } else { const request: DebugProtocol.Request = { command: command, seq: this.seq++, arguments: args, type: "request" } this.worker.postMessage(request, [channel.port2]); } }); } private timeoutPromise(ms: number, promise: Promise) { return new Promise((resolve, reject) => { const timeoutId = setTimeout(async () => { // Re-init the worker since the previous worker hangs this.worker.terminate(); this.worker = this.createWorker(); reject(new Error("promise timeout")) }, ms); promise.then( (res) => { clearTimeout(timeoutId); resolve(res); }, (err) => { clearTimeout(timeoutId); reject(err); } ); }) } }