From 09949dc1c64695d15d78f70eaf0abda014d132a4 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Thu, 6 Oct 2022 15:04:10 -0400 Subject: [PATCH 01/74] Add initial DebugProtocolServer and handshake test --- .vscode/launch.json | 4 +- package-lock.json | 97 ++++++++ package.json | 1 + src/debugProtocol/Debugger.spec.ts | 35 +++ .../requests/HandshakeRequestV3.ts | 35 +++ src/debugProtocol/requests/ProtocolRequest.ts | 17 ++ .../responses/HandshakeResponseV3.ts | 128 +++++++--- .../responses/ProtocolResponse.ts | 47 ++++ .../server/DebugProtocolServer.ts | 227 ++++++++++++++++++ src/debugProtocol/server/PluginInterface.ts | 56 +++++ src/debugProtocol/server/ProtocolPlugin.ts | 45 ++++ src/managers/ActionQueue.ts | 6 +- 12 files changed, 653 insertions(+), 45 deletions(-) create mode 100644 src/debugProtocol/requests/HandshakeRequestV3.ts create mode 100644 src/debugProtocol/requests/ProtocolRequest.ts create mode 100644 src/debugProtocol/responses/ProtocolResponse.ts create mode 100644 src/debugProtocol/server/DebugProtocolServer.ts create mode 100644 src/debugProtocol/server/PluginInterface.ts create mode 100644 src/debugProtocol/server/ProtocolPlugin.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 981537e0..fff84015 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,7 +2,7 @@ "version": "0.2.0", "configurations": [ { - "type": "pwa-node", + "type": "node", "request": "launch", "name": "Run Roku Sample Project", "skipFiles": [ @@ -17,7 +17,7 @@ }, { "name": "Debug Tests", - "type": "pwa-node", + "type": "node", "request": "launch", "smartStep": false, "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", diff --git a/package-lock.json b/package-lock.json index 29388b20..cdb76144 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,6 +52,7 @@ "mocha": "^9.1.3", "nyc": "^15.1.0", "p-defer": "^4.0.0", + "portfinder": "^1.0.32", "rimraf": "^3.0.2", "rmfr": "^2.0.0", "rxjs": "^7.4.0", @@ -1116,6 +1117,15 @@ "node": "*" } }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, "node_modules/asynckit": { "version": "0.4.0", "license": "MIT" @@ -2950,6 +2960,12 @@ "node": ">=8" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, "node_modules/lodash.flattendeep": { "version": "4.4.0", "dev": true, @@ -3664,6 +3680,41 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/portfinder": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", + "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", + "dev": true, + "dependencies": { + "async": "^2.6.4", + "debug": "^3.2.7", + "mkdirp": "^0.5.6" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/portfinder/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/portfinder/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "dev": true, @@ -5521,6 +5572,15 @@ "version": "1.1.0", "dev": true }, + "async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, "asynckit": { "version": "0.4.0" }, @@ -6687,6 +6747,12 @@ "p-locate": "^4.1.0" } }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, "lodash.flattendeep": { "version": "4.4.0", "dev": true @@ -7133,6 +7199,37 @@ "picomatch": { "version": "2.3.0" }, + "portfinder": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", + "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", + "dev": true, + "requires": { + "async": "^2.6.4", + "debug": "^3.2.7", + "mkdirp": "^0.5.6" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + } + } + }, "prelude-ls": { "version": "1.2.1", "dev": true diff --git a/package.json b/package.json index 88de68f6..a685bb05 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "mocha": "^9.1.3", "nyc": "^15.1.0", "p-defer": "^4.0.0", + "portfinder": "^1.0.32", "rimraf": "^3.0.2", "rmfr": "^2.0.0", "rxjs": "^7.4.0", diff --git a/src/debugProtocol/Debugger.spec.ts b/src/debugProtocol/Debugger.spec.ts index 7869de75..aa445da4 100644 --- a/src/debugProtocol/Debugger.spec.ts +++ b/src/debugProtocol/Debugger.spec.ts @@ -6,6 +6,10 @@ import { createSandbox } from 'sinon'; import { createHandShakeResponse, createHandShakeResponseV3, createProtocolEventV3 } from './responses/responseCreationHelpers.spec'; import { HandshakeResponseV3, ProtocolEventV3 } from './responses'; import { ERROR_CODES, UPDATE_TYPES, VARIABLE_REQUEST_FLAGS } from './Constants'; +import { DebugProtocolServer, DebugProtocolServerOptions } from './server/DebugProtocolServer'; +import * as portfinder from 'portfinder'; +import { util } from '../util'; + const sinon = createSandbox(); describe('debugProtocol Debugger', () => { @@ -215,3 +219,34 @@ describe('debugProtocol Debugger', () => { }); }); }); + + +describe.only('Debugger new tests', () => { + let server: DebugProtocolServer; + let client: Debugger; + const options = { + controllerPort: undefined as number, + host: '127.0.0.1' + }; + + beforeEach(async () => { + if (!options.controllerPort) { + options.controllerPort = await portfinder.getPortPromise(); + } + server = new DebugProtocolServer(options); + await server.start(); + + client = new Debugger(options); + }); + + afterEach(async () => { + client?.destroy(); + //shut down and destroy the server after each test + await server?.stop(); + await util.sleep(10); + }); + + it('receives magic and sends response', async () => { + await client.connect(); + }); +}); diff --git a/src/debugProtocol/requests/HandshakeRequestV3.ts b/src/debugProtocol/requests/HandshakeRequestV3.ts new file mode 100644 index 00000000..7f5e0c7f --- /dev/null +++ b/src/debugProtocol/requests/HandshakeRequestV3.ts @@ -0,0 +1,35 @@ +import { SmartBuffer } from 'smart-buffer'; +import { util } from '../../util'; +import { ProtocolRequest } from './ProtocolRequest'; + +export class HandshakeRequestV3 extends ProtocolRequest { + + public constructor(arg: Buffer | HandshakeRequestV3) { + super(); + if (Buffer.isBuffer(arg)) { + this.loadBuffer(arg); + } else { + this.loadJson(arg.data); + } + } + + loadJson(data: HandshakeRequestV3['data']) { + this.data.magic = data.magic; + this.success = true; + } + + loadBuffer(buffer: Buffer) { + this.data.magic = util.readStringNT(SmartBuffer.fromBuffer(buffer)); + this.success = true; + } + + toBuffer() { + return new SmartBuffer({ + size: Buffer.byteLength(this.data.magic) + 1 + }).writeStringNT(this.data.magic).toBuffer(); + } + + public data = { + magic: undefined as string + }; +} diff --git a/src/debugProtocol/requests/ProtocolRequest.ts b/src/debugProtocol/requests/ProtocolRequest.ts new file mode 100644 index 00000000..7687f08c --- /dev/null +++ b/src/debugProtocol/requests/ProtocolRequest.ts @@ -0,0 +1,17 @@ +export abstract class ProtocolRequest { + /** + * Was this class successful in parsing/ingesting the data in its constructor + */ + public success = false; + + /** + * Convert the current object into the debug protocol binary format, + * stored in a `Buffer` + */ + public abstract toBuffer(): Buffer; + + /** + * Contains the actual request data + */ + public abstract data: any; +} diff --git a/src/debugProtocol/responses/HandshakeResponseV3.ts b/src/debugProtocol/responses/HandshakeResponseV3.ts index 4c51cc88..ec10f2b8 100644 --- a/src/debugProtocol/responses/HandshakeResponseV3.ts +++ b/src/debugProtocol/responses/HandshakeResponseV3.ts @@ -1,53 +1,101 @@ import { SmartBuffer } from 'smart-buffer'; import * as semver from 'semver'; import { util } from '../../util'; +import { ProtocolResponse } from './ProtocolResponse'; -export class HandshakeResponseV3 { - - constructor(buffer: Buffer) { - // Required size of the handshake - if (buffer.byteLength >= 20) { - try { - let bufferReader = SmartBuffer.fromBuffer(buffer); - this.magic = util.readStringNT(bufferReader); // magic_number - this.majorVersion = bufferReader.readInt32LE(); // protocol_major_version - this.minorVersion = bufferReader.readInt32LE(); // protocol_minor_version - this.patchVersion = bufferReader.readInt32LE(); // protocol_patch_version - - const legacyReadSize = bufferReader.readOffset; - this.remainingPacketLength = bufferReader.readInt32LE(); // remaining_packet_length - - const requiredBufferSize = this.remainingPacketLength + legacyReadSize; - this.revisionTimeStamp = new Date(Number(bufferReader.readBigUInt64LE())); // platform_revision_timestamp - - if (bufferReader.length < requiredBufferSize) { - throw new Error(`Missing buffer data according to the remaining packet length: ${bufferReader.length}/${requiredBufferSize}`); - } - this.readOffset = requiredBufferSize; - - const versionString = [this.majorVersion, this.minorVersion, this.patchVersion].join('.'); - - // We only support v3 or above with this handshake - if (!semver.satisfies(versionString, '>=3.0.0')) { - throw new Error(`unsupported version ${versionString}`); - } - this.success = true; - } catch (error) { - // Could not parse - } +export class HandshakeResponseV3 extends ProtocolResponse { + + public constructor(json: HandshakeResponseV3['data']); + public constructor(buffer: Buffer); + public constructor(arg: Buffer | HandshakeResponseV3['data']) { + super(); + if (Buffer.isBuffer(arg)) { + this.loadFromBuffer(arg); + } else { + this.loadFromJson(arg); } } + private loadFromBuffer(buffer: Buffer) { + this.bufferLoaderHelper(buffer, 20, (smartBuffer: SmartBuffer) => { + this.data.magic = util.readStringNT(smartBuffer); // debugger_magic + this.data.majorVersion = smartBuffer.readInt32LE(); // protocol_major_version + this.data.minorVersion = smartBuffer.readInt32LE(); // protocol_minor_version + this.data.patchVersion = smartBuffer.readInt32LE(); // protocol_patch_version + + const legacyReadSize = smartBuffer.readOffset; + const remainingPacketLength = smartBuffer.readInt32LE(); // remaining_packet_length + + const requiredBufferSize = remainingPacketLength + legacyReadSize; + this.data.revisionTimeStamp = new Date(Number(smartBuffer.readBigUInt64LE())); // platform_revision_timestamp + + if (smartBuffer.length < requiredBufferSize) { + throw new Error(`Missing buffer data according to the remaining packet length: ${smartBuffer.length}/${requiredBufferSize}`); + } + //set the buffer offset + smartBuffer.readOffset = requiredBufferSize; + + const versionString = `${this.data.majorVersion}.${this.data.minorVersion}.${this.data.patchVersion}`; + + // We only support v3 or above with this handshake + if (!semver.satisfies(versionString, '>=3.0.0')) { + throw new Error(`unsupported version ${versionString}`); + } + return true; + }); + } + + private loadFromJson(data: HandshakeResponseV3['data']) { + this.data = data; + } + + /** + * Convert the data into a buffer + */ + public toBuffer() { + let buffer = new SmartBuffer(); + buffer.writeStringNT(this.data.magic); // magic_number + buffer.writeUInt32LE(this.data.majorVersion); // protocol_major_version + buffer.writeUInt32LE(this.data.minorVersion); // protocol_minor_version + buffer.writeUInt32LE(this.data.patchVersion); // protocol_patch_version + + //As of BrightScript debug protocol 3.0.0 (Roku OS 11.0), all packets from the debugging target include a packet_length. + //The length is always in bytes, and includes the packet_length field, itself. + //This field avoids the need for changes to the major version of the protocol because it allows a debugger client to + //read past data it does not understand and is not critical to debugger operations. + const remainingDataBuffer = new SmartBuffer(); + remainingDataBuffer.writeBigInt64LE(BigInt( + this.data.revisionTimeStamp.getTime() + )); // platform_revision_timestamp + + buffer.writeUInt32LE(remainingDataBuffer.writeOffset + 4); // remaining_packet_length + buffer.writeBuffer(remainingDataBuffer.toBuffer()); + + return buffer.toBuffer(); + } + public watchPacketLength = true; // this will always be false for the new protocol versions public success = false; public readOffset = 0; public requestId = 0; - // response fields - public magic: string; - public majorVersion = -1; - public minorVersion = -1; - public patchVersion = -1; - public remainingPacketLength = -1; - public revisionTimeStamp: Date; + public data = { + /** + * The Roku Brightscript debug protocol identifier, which is the following 64-bit value :0x0067756265647362LU. + * + * This is equal to 29120988069524322LU or the following little-endian value: b'bsdebug\0. + */ + magic: undefined as string, + majorVersion: -1, + minorVersion: -1, + patchVersion: -1, + /** + * A platform-specific implementation timestamp (in milliseconds since epoch [1970-01-01T00:00:00.000Z]). + * + * As of BrightScript debug protocol 3.0.0 (Roku OS 11.0), a timestamp is sent to the debugger client in the initial handshake. This timestamp is platform-specific data that is included in the system software of the platform being debugged. It is changed by the platform's vendor when there is any change that affects the behavior of the debugger. + * + * The value can be used in manners similar to a build number, and is primarily used to differentiate between pre-release builds of the platform being debugged. + */ + revisionTimeStamp: undefined as Date + }; } diff --git a/src/debugProtocol/responses/ProtocolResponse.ts b/src/debugProtocol/responses/ProtocolResponse.ts new file mode 100644 index 00000000..cc235dbb --- /dev/null +++ b/src/debugProtocol/responses/ProtocolResponse.ts @@ -0,0 +1,47 @@ +import { SmartBuffer } from 'smart-buffer'; + +export abstract class ProtocolResponse { + /** + * Was this class successful in parsing/ingesting the data in its constructor + */ + public success = false; + + /** + * The number of bytes that were read from a buffer if this was a success + */ + public readOffset: number; + + /** + * Convert the current object into the debug protocol binary format, + * stored in a `Buffer` + */ + public abstract toBuffer(): Buffer; + + /** + * Contains the actual response data + */ + public data: TData; + + /** + * Helper function for buffer loading. + * Handles things like try/catch, setting buffer read offset, etc + */ + protected bufferLoaderHelper(buffer: Buffer, minByteLength: number, processor: (buffer: SmartBuffer) => boolean) { + // Required size of this processor + if (buffer.byteLength >= minByteLength) { + try { + let smartBuffer = SmartBuffer.fromBuffer(buffer); + + //have the processor consume the requred bytes + this.success = processor(smartBuffer); + + this.readOffset = smartBuffer.readOffset; + this.success = true; + } catch (error) { + // Could not parse + this.readOffset = 0; + this.success = true; + } + } + } +} diff --git a/src/debugProtocol/server/DebugProtocolServer.ts b/src/debugProtocol/server/DebugProtocolServer.ts new file mode 100644 index 00000000..f761c3b0 --- /dev/null +++ b/src/debugProtocol/server/DebugProtocolServer.ts @@ -0,0 +1,227 @@ +import { EventEmitter } from 'eventemitter3'; +import * as Net from 'net'; +import { SmartBuffer } from 'smart-buffer'; +import { ActionQueue } from '../../managers/ActionQueue'; +import { HandshakeRequestV3 } from '../requests/HandshakeRequestV3'; +import type { ProtocolRequest } from '../requests/ProtocolRequest'; +import { HandshakeResponseV3 } from '../responses'; +import type { ProtocolResponse } from '../responses/ProtocolResponse'; +import PluginInterface from './PluginInterface'; +import type { ProtocolPlugin } from './ProtocolPlugin'; + +export const DEBUGGER_MAGIC = 'bsdebug'; + +/** + * A class that emulates the way a Roku's DebugProtocol debug session/server works. This is mostly useful for unit testing, + * but might eventually be helpful for an off-device emulator as well + */ +export class DebugProtocolServer { + constructor( + public options: DebugProtocolServerOptions + ) { + + } + + /** + * Indicates whether the client has sent the magic string to kick off the debug session. + */ + private isHandshakeComplete = false; + + private buffer = Buffer.alloc(0); + + /** + * The server + */ + private server: Net.Server; + /** + * Once a client connects, this is a reference to that client + */ + private client: Net.Socket; + + /** + * A collection of plugins that can interact with the server at lifecycle points + */ + public plugins = new PluginInterface(); + + /** + * A queue for processing the incoming buffer, every transmission at a time + */ + private bufferQueue = new ActionQueue(); + + + /** + * Run the server. This opens a socket and listens for a connection. + * The promise resolves when the server has started listening. It does NOT wait for a client to connect + */ + public start() { + return new Promise((resolve) => { + this.server = new Net.Server({}); + //Roku only allows 1 connection, so we should too. + this.server.maxConnections = 1; + + //whenever a client makes a connection + // eslint-disable-next-line @typescript-eslint/no-misused-promises + this.server.on('connection', async (socket: Net.Socket) => { + const event = await this.plugins.emit('onClientConnected', { + server: this, + client: socket + }); + this.client = event.client; + + //anytime we receive incoming data from the client + this.client.on('data', (data) => { + //queue up processing the new data, chunk by chunk + void this.bufferQueue.run(() => { + this.buffer = Buffer.concat([this.buffer, data]); + void this.process(); + return true; + }); + }); + }); + + this.server.listen({ + port: this.options.controllerPort ?? 8081, + hostName: this.options.host ?? '0.0.0.0' + }, resolve); + }); + } + + public async stop() { + //close the client socket + await new Promise((resolve) => { + this.client.end(resolve); + }).catch(() => { }); + + //now close the server + return new Promise((resolve, reject) => { + this.server.close((err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } + + private async process() { + let request: ProtocolRequest; + + //handle the handshake flow + if (!this.isHandshakeComplete) { + //the client should send a magic string to kick off the debugger + const request = new HandshakeRequestV3(this.buffer); + if (request.success && request.data.magic === this.magic) { + //send the handshake response + + //the handshake has been completed. + this.isHandshakeComplete = true; + + await this.sendResponse(new HandshakeResponseV3({ + magic: this.magic, + majorVersion: 3, + minorVersion: 1, + patchVersion: 0, + //TODO update this to an actual date from the device + revisionTimeStamp: new Date(2022, 1, 1) + })); + } + } else { + //at this point, there is an active debug session. The plugin must provide us all the real-world data + const requestEvent = await this.plugins.emit('provideRequest', { + server: this, + buffer: this.buffer, + request: undefined + }); + + //assume the plugin gave back a new buffer with the processed data removed + this.buffer = requestEvent.buffer; + + //now ask the plugin to provide a response for the given request + const responseEvent = await this.plugins.emit('provideResponse', { + server: this, + request: request, + response: undefined + }); + + await this.sendResponse(responseEvent.response); + } + } + + /** + * Send a response from the server to the client. This involves writing the response buffer to the client socket + */ + private async sendResponse(response: ProtocolResponse) { + await this.plugins.emit('beforeSendResponse', { + server: this, + response: response + }); + + this.client.write(response.toBuffer()); + + await this.plugins.emit('afterSendResponse', { + server: this, + response: response + }); + } + + /** + * An event emitter used for all of the events this server emitts + */ + private emitter = new EventEmitter(); + + public on(eventName: 'before-send-response', callback: (event: T) => void); + public on(eventName: 'after-send-response', callback: (event: T) => void); + public on(eventName: 'client-connected', callback: (event: T) => void); + public on(eventName: string, callback: (data: T) => void) + public on(eventName: string, callback: (data: T) => void) { + this.emitter.on(eventName, callback); + return () => { + this.emitter?.removeListener(eventName, callback); + }; + } + + /** + * Subscribe to an event exactly one time. This will fire the very next time an event happens, + * and then immediately unsubscribe + */ + public once(eventName: string): Promise { + return new Promise((resolve) => { + const off = this.on(eventName, (event) => { + off(); + resolve(event); + }); + }); + } + + public emit(eventName: 'before-send-response', event: T): T; + public emit(eventName: 'after-send-response', event: T): T; + public emit(eventName: 'client-connected', event: T): T; + public emit(eventName: string, event: any): T { + this.emitter?.emit(eventName, event); + return event; + } + + /** + * The magic string used to kick off the debug session. + * @default "bsdebug" + */ + private get magic() { + return this.options.magic ?? DEBUGGER_MAGIC; + } +} + +export interface DebugProtocolServerOptions { + /** + * The magic that is sent as part of the handshake + */ + magic?: string; + /** + * The port to use for the primary communication between this server and a client + */ + controllerPort?: number; + /** + * A specific host to listen on. If not specified, all hosts are used + */ + host?: string; +} diff --git a/src/debugProtocol/server/PluginInterface.ts b/src/debugProtocol/server/PluginInterface.ts new file mode 100644 index 00000000..7a94e305 --- /dev/null +++ b/src/debugProtocol/server/PluginInterface.ts @@ -0,0 +1,56 @@ +// inspiration: https://github.com/andywer/typed-emitter/blob/master/index.d.ts +export type Arguments = [T] extends [(...args: infer U) => any] + ? U + : [T] extends [void] ? [] : [T]; + +export default class PluginInterface { + constructor( + private plugins = [] as TPlugin[] + ) { } + + /** + * Call `event` on plugins + */ + public async emit(eventName: K, event: Arguments[0]) { + for (let plugin of this.plugins) { + if ((plugin as any)[eventName]) { + await Promise.resolve((plugin as any)[eventName](event)); + } + } + return event; + } + + /** + * Add a plugin to the end of the list of plugins + */ + public add(plugin: T) { + if (!this.has(plugin)) { + this.plugins.push(plugin); + } + return plugin; + } + + /** + * Is the specified plugin present in the list + */ + public has(plugin: TPlugin) { + return this.plugins.includes(plugin); + } + + /** + * Remove the specified plugin + */ + public remove(plugin: T) { + if (this.has(plugin)) { + this.plugins.splice(this.plugins.indexOf(plugin)); + } + return plugin; + } + + /** + * Remove all plugins + */ + public clear() { + this.plugins = []; + } +} diff --git a/src/debugProtocol/server/ProtocolPlugin.ts b/src/debugProtocol/server/ProtocolPlugin.ts new file mode 100644 index 00000000..4c6b8446 --- /dev/null +++ b/src/debugProtocol/server/ProtocolPlugin.ts @@ -0,0 +1,45 @@ +import type { DebugProtocolServer } from './DebugProtocolServer'; +import type { ProtocolResponse } from '../responses/ProtocolResponse'; +import type { Socket } from 'net'; +import type { ProtocolRequest } from '../requests/ProtocolRequest'; + +export interface ProtocolPlugin { + onClientConnected: Handler; + + provideRequest: Handler; + provideResponse: Handler; + + beforeSendResponse: Handler; + afterSendResponse: Handler; +} + +export interface OnClientConnectedEvent { + server: DebugProtocolServer; + client: Socket; +} + +export interface ProvideRequestEvent { + server: DebugProtocolServer; + buffer: Buffer; + /** + * The plugin should provide this property + */ + request?: ProtocolRequest; +} +export interface ProvideResponseEvent { + server: DebugProtocolServer; + request: ProtocolRequest; + /** + * The plugin should provide this property + */ + response?: ProtocolResponse; +} + +export interface BeforeSendResponseEvent { + server: DebugProtocolServer; + response: ProtocolResponse; +} +export type AfterSendResponseEvent = BeforeSendResponseEvent; + +export type Handler = (event: T) => R; + diff --git a/src/managers/ActionQueue.ts b/src/managers/ActionQueue.ts index e716c298..9952a7a7 100644 --- a/src/managers/ActionQueue.ts +++ b/src/managers/ActionQueue.ts @@ -8,11 +8,11 @@ import { defer } from '../util'; export class ActionQueue { private queueItems: Array<{ - action: () => Promise; + action: () => boolean | Promise; deferred: Deferred; }> = []; - public async run(action: () => Promise) { + public async run(action: () => boolean | Promise) { this.queueItems.push({ action: action, deferred: defer() @@ -24,7 +24,7 @@ export class ActionQueue { while (this.queueItems.length > 0) { const queueItem = this.queueItems[0]; try { - const isFinished = await queueItem.action(); + const isFinished = await Promise.resolve(queueItem.action()); if (isFinished) { this.queueItems.shift(); queueItem.deferred.resolve(); From 7b996809a52374b5a192a5c5261861f35c6a3111 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Thu, 6 Oct 2022 15:22:21 -0400 Subject: [PATCH 02/74] Add test plugin to intercept requests. --- src/debugProtocol/Debugger.spec.ts | 51 ++++++++++++++++++++++ src/debugProtocol/server/ProtocolPlugin.ts | 8 ++-- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/debugProtocol/Debugger.spec.ts b/src/debugProtocol/Debugger.spec.ts index aa445da4..f87f5243 100644 --- a/src/debugProtocol/Debugger.spec.ts +++ b/src/debugProtocol/Debugger.spec.ts @@ -9,6 +9,9 @@ import { ERROR_CODES, UPDATE_TYPES, VARIABLE_REQUEST_FLAGS } from './Constants'; import { DebugProtocolServer, DebugProtocolServerOptions } from './server/DebugProtocolServer'; import * as portfinder from 'portfinder'; import { util } from '../util'; +import { BeforeSendResponseEvent, Handler, OnClientConnectedEvent, ProtocolPlugin, ProvideRequestEvent, ProvideResponseEvent } from './server/ProtocolPlugin'; +import { ProtocolResponse } from './responses/ProtocolResponse'; +import { ProtocolRequest } from './requests/ProtocolRequest'; const sinon = createSandbox(); @@ -220,10 +223,50 @@ describe('debugProtocol Debugger', () => { }); }); +/** + * A class that intercepts all debug server events and provides test data for them + */ +class TestPlugin implements ProtocolPlugin { + /** + * A list of responses to be sent by the server in this exact order. + * One of these will be sent for every `provideResponse` event received. + */ + public responseQueue: ProtocolResponse[] = []; + + /** + * A running list of requests received by the server during this test + */ + public readonly requests: ReadonlyArray = []; + + /** + * A running list of responses sent by the server during this test + */ + public readonly responses: ReadonlyArray = []; + + /** + * Whenever the server receives a request, this event allows us to send back a response + */ + provideResponse(event: ProvideResponseEvent) { + //store the request for testing purposes + (this.requests as Array).push(event.request); + + const response = this.responseQueue.shift(); + if (!response) { + throw new Error('There was no response available to send back'); + } + event.response = response; + } + + beforeSendResponse(event: BeforeSendResponseEvent) { + //store the response for testing purposes + (this.responses as Array).push(event.response); + } +} describe.only('Debugger new tests', () => { let server: DebugProtocolServer; let client: Debugger; + let plugin: TestPlugin; const options = { controllerPort: undefined as number, host: '127.0.0.1' @@ -234,6 +277,7 @@ describe.only('Debugger new tests', () => { options.controllerPort = await portfinder.getPortPromise(); } server = new DebugProtocolServer(options); + plugin = server.plugins.add(new TestPlugin()); await server.start(); client = new Debugger(options); @@ -248,5 +292,12 @@ describe.only('Debugger new tests', () => { it('receives magic and sends response', async () => { await client.connect(); + expect(plugin.responses[0].data).to.eql({ + magic: 'bsdebug', + majorVersion: 3, + minorVersion: 1, + patchVersion: 0, + revisionTimeStamp: new Date(2022, 1, 1) + } as HandshakeResponseV3['data']); }); }); diff --git a/src/debugProtocol/server/ProtocolPlugin.ts b/src/debugProtocol/server/ProtocolPlugin.ts index 4c6b8446..5fe130f3 100644 --- a/src/debugProtocol/server/ProtocolPlugin.ts +++ b/src/debugProtocol/server/ProtocolPlugin.ts @@ -4,13 +4,13 @@ import type { Socket } from 'net'; import type { ProtocolRequest } from '../requests/ProtocolRequest'; export interface ProtocolPlugin { - onClientConnected: Handler; + onClientConnected?: Handler; - provideRequest: Handler; + provideRequest?: Handler; provideResponse: Handler; - beforeSendResponse: Handler; - afterSendResponse: Handler; + beforeSendResponse?: Handler; + afterSendResponse?: Handler; } export interface OnClientConnectedEvent { From 205070e989b52b1f4cb6a3a5b63d11e67b413fb1 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Thu, 6 Oct 2022 15:52:02 -0400 Subject: [PATCH 03/74] Port "not correct magic" test to new flow --- src/debugProtocol/Debugger.spec.ts | 37 +++++++- src/debugProtocol/requests/ProtocolRequest.ts | 5 + .../server/DebugProtocolServer.ts | 93 ++++++++++++------- 3 files changed, 95 insertions(+), 40 deletions(-) diff --git a/src/debugProtocol/Debugger.spec.ts b/src/debugProtocol/Debugger.spec.ts index f87f5243..43c26357 100644 --- a/src/debugProtocol/Debugger.spec.ts +++ b/src/debugProtocol/Debugger.spec.ts @@ -9,9 +9,11 @@ import { ERROR_CODES, UPDATE_TYPES, VARIABLE_REQUEST_FLAGS } from './Constants'; import { DebugProtocolServer, DebugProtocolServerOptions } from './server/DebugProtocolServer'; import * as portfinder from 'portfinder'; import { util } from '../util'; -import { BeforeSendResponseEvent, Handler, OnClientConnectedEvent, ProtocolPlugin, ProvideRequestEvent, ProvideResponseEvent } from './server/ProtocolPlugin'; -import { ProtocolResponse } from './responses/ProtocolResponse'; -import { ProtocolRequest } from './requests/ProtocolRequest'; +import type { BeforeSendResponseEvent, ProtocolPlugin, ProvideResponseEvent } from './server/ProtocolPlugin'; +import { Handler, OnClientConnectedEvent, ProvideRequestEvent } from './server/ProtocolPlugin'; +import type { ProtocolResponse } from './responses/ProtocolResponse'; +import type { ProtocolRequest } from './requests/ProtocolRequest'; +import { HandshakeRequestV3 } from './requests/HandshakeRequestV3'; const sinon = createSandbox(); @@ -231,7 +233,14 @@ class TestPlugin implements ProtocolPlugin { * A list of responses to be sent by the server in this exact order. * One of these will be sent for every `provideResponse` event received. */ - public responseQueue: ProtocolResponse[] = []; + private responseQueue: ProtocolResponse[] = []; + + /** + * Adds a response to the queue, which should be returned from the server in first-in-first-out order, one for each request received by the server + */ + public pushResponse(response: ProtocolResponse) { + this.responseQueue.push(response); + } /** * A running list of requests received by the server during this test @@ -251,7 +260,8 @@ class TestPlugin implements ProtocolPlugin { (this.requests as Array).push(event.request); const response = this.responseQueue.shift(); - if (!response) { + //if there's no response, AND this isn't the handshake, fail. (we want the protocol to handle the handshake most of the time) + if (!response && !(event.request instanceof HandshakeRequestV3)) { throw new Error('There was no response available to send back'); } event.response = response; @@ -300,4 +310,21 @@ describe.only('Debugger new tests', () => { revisionTimeStamp: new Date(2022, 1, 1) } as HandshakeResponseV3['data']); }); + + it('throws on magic mismatch', async () => { + plugin.pushResponse(new HandshakeResponseV3({ + magic: 'not correct magic', + majorVersion: 3, + minorVersion: 1, + patchVersion: 0, + revisionTimeStamp: new Date(2022, 1, 1) + })); + + const verifyHandshakePromise = client.once('handshake-verified'); + + await client.connect(); + + //wait for the debugger to finish verifying the handshake + expect(await verifyHandshakePromise).to.be.false; + }); }); diff --git a/src/debugProtocol/requests/ProtocolRequest.ts b/src/debugProtocol/requests/ProtocolRequest.ts index 7687f08c..73ee64ad 100644 --- a/src/debugProtocol/requests/ProtocolRequest.ts +++ b/src/debugProtocol/requests/ProtocolRequest.ts @@ -4,6 +4,11 @@ export abstract class ProtocolRequest { */ public success = false; + /** + * The number of bytes that were read from a buffer if this was a success + */ + public readOffset: number; + /** * Convert the current object into the debug protocol binary format, * stored in a `Buffer` diff --git a/src/debugProtocol/server/DebugProtocolServer.ts b/src/debugProtocol/server/DebugProtocolServer.ts index f761c3b0..aa8fbfae 100644 --- a/src/debugProtocol/server/DebugProtocolServer.ts +++ b/src/debugProtocol/server/DebugProtocolServer.ts @@ -4,7 +4,7 @@ import { SmartBuffer } from 'smart-buffer'; import { ActionQueue } from '../../managers/ActionQueue'; import { HandshakeRequestV3 } from '../requests/HandshakeRequestV3'; import type { ProtocolRequest } from '../requests/ProtocolRequest'; -import { HandshakeResponseV3 } from '../responses'; +import { HandshakeResponse, HandshakeResponseV3 } from '../responses'; import type { ProtocolResponse } from '../responses/ProtocolResponse'; import PluginInterface from './PluginInterface'; import type { ProtocolPlugin } from './ProtocolPlugin'; @@ -104,48 +104,71 @@ export class DebugProtocolServer { }); } - private async process() { + /** + * Given a buffer, find the request that matches it + */ + private getRequest(buffer: Buffer) { let request: ProtocolRequest; - - //handle the handshake flow + //if we haven't seen the handshake yet, look for the handshake first if (!this.isHandshakeComplete) { - //the client should send a magic string to kick off the debugger - const request = new HandshakeRequestV3(this.buffer); - if (request.success && request.data.magic === this.magic) { - //send the handshake response - - //the handshake has been completed. - this.isHandshakeComplete = true; - - await this.sendResponse(new HandshakeResponseV3({ - magic: this.magic, - majorVersion: 3, - minorVersion: 1, - patchVersion: 0, - //TODO update this to an actual date from the device - revisionTimeStamp: new Date(2022, 1, 1) - })); + request = new HandshakeRequestV3(buffer); + if (request.success) { + return request; } - } else { - //at this point, there is an active debug session. The plugin must provide us all the real-world data - const requestEvent = await this.plugins.emit('provideRequest', { - server: this, - buffer: this.buffer, - request: undefined - }); + } - //assume the plugin gave back a new buffer with the processed data removed - this.buffer = requestEvent.buffer; + //TODO handle all the other request types (variables, step, etc...) + } - //now ask the plugin to provide a response for the given request - const responseEvent = await this.plugins.emit('provideResponse', { - server: this, - request: request, - response: undefined + private getResponse(request: ProtocolRequest) { + if (request instanceof HandshakeRequestV3) { + return new HandshakeResponseV3({ + magic: this.magic, + majorVersion: 3, + minorVersion: 1, + patchVersion: 0, + //TODO update this to an actual date from the device + revisionTimeStamp: new Date(2022, 1, 1) }); + } + } - await this.sendResponse(responseEvent.response); + private async process() { + //at this point, there is an active debug session. The plugin must provide us all the real-world data + let { buffer, request } = await this.plugins.emit('provideRequest', { + server: this, + buffer: this.buffer, + request: undefined + }); + + //we must build the request if the plugin didn't supply one (most plugins won't provide a request...) + if (!request) { + request = this.getRequest(buffer); } + + //trim the buffer now that the request has been processed + this.buffer = buffer.slice(request.readOffset); + + //now ask the plugin to provide a response for the given request + let { response } = await this.plugins.emit('provideResponse', { + server: this, + request: request, + response: undefined + }); + + + //if the plugin didn't provide a response, we need to try our best to make one (we only support a few...plugins should provide most of them) + if (request instanceof HandshakeRequestV3 && !response) { + response = this.getResponse(request); + } + + //the client should send a magic string to kick off the debugger + if (response instanceof HandshakeResponseV3 && request.data.magic === this.magic) { + this.isHandshakeComplete = true; + } + + //send the response to the client. (TODO handle when the response is missing) + await this.sendResponse(response); } /** From 1284e15a15de1e92fd37b62a327db85d40202a1d Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Fri, 7 Oct 2022 06:24:09 -0400 Subject: [PATCH 04/74] Fix handshake version handling --- src/debugProtocol/Debugger.spec.ts | 45 ++-------- src/debugProtocol/Debugger.ts | 8 +- .../responses/HandshakeResponse.ts | 83 ++++++++++++------- .../responses/HandshakeResponseV3.ts | 12 +-- .../responses/ProtocolResponse.ts | 5 +- 5 files changed, 77 insertions(+), 76 deletions(-) diff --git a/src/debugProtocol/Debugger.spec.ts b/src/debugProtocol/Debugger.spec.ts index 43c26357..003efba1 100644 --- a/src/debugProtocol/Debugger.spec.ts +++ b/src/debugProtocol/Debugger.spec.ts @@ -61,20 +61,6 @@ describe('debugProtocol Debugger', () => { await bsDebugger.once('handshake-verified') ).to.be.true; }); - - it('throws on magic mismatch', async () => { - roku.waitForMagic(); - roku.sendHandshakeResponse('not correct magic'); - - void bsDebugger.connect(); - - void roku.processActions(); - - //wait for the debugger to finish verifying the handshake - expect( - await bsDebugger.once('handshake-verified') - ).to.be.false; - }); }); describe('parseUnhandledData', () => { @@ -98,27 +84,6 @@ describe('debugProtocol Debugger', () => { expect(bsDebugger['unhandledData'].byteLength).to.be.equal(0); }); - it('handles v3 handshake', () => { - let mockResponse = createHandShakeResponseV3({ - magic: Debugger.DEBUGGER_MAGIC, - major: 3, - minor: 0, - patch: 0, - revisionTimeStamp: Date.now() - }); - - bsDebugger['unhandledData'] = mockResponse.toBuffer(); - - expect(bsDebugger.watchPacketLength).to.be.equal(false); - expect(bsDebugger.handshakeComplete).to.be.equal(false); - - expect(bsDebugger['parseUnhandledData'](bsDebugger['unhandledData'])).to.be.equal(true); - - expect(bsDebugger.watchPacketLength).to.be.equal(true); - expect(bsDebugger.handshakeComplete).to.be.equal(true); - expect(bsDebugger['unhandledData'].byteLength).to.be.equal(0); - }); - it('handles events after handshake', () => { let handshake = createHandShakeResponseV3({ magic: Debugger.DEBUGGER_MAGIC, @@ -300,7 +265,11 @@ describe.only('Debugger new tests', () => { await util.sleep(10); }); - it('receives magic and sends response', async () => { + it('handles v3 handshake', async () => { + //these are false by default + expect(client.watchPacketLength).to.be.equal(false); + expect(client.handshakeComplete).to.be.equal(false); + await client.connect(); expect(plugin.responses[0].data).to.eql({ magic: 'bsdebug', @@ -309,6 +278,10 @@ describe.only('Debugger new tests', () => { patchVersion: 0, revisionTimeStamp: new Date(2022, 1, 1) } as HandshakeResponseV3['data']); + + //version 3.0 includes packet length, so these should be true now + expect(client.watchPacketLength).to.be.equal(true); + expect(client.handshakeComplete).to.be.equal(true); }); it('throws on magic mismatch', async () => { diff --git a/src/debugProtocol/Debugger.ts b/src/debugProtocol/Debugger.ts index cee3f9df..3346234c 100644 --- a/src/debugProtocol/Debugger.ts +++ b/src/debugProtocol/Debugger.ts @@ -563,11 +563,11 @@ export class Debugger { this.parseUnhandledData(this.unhandledData); } - private verifyHandshake(debuggerHandshake: HandshakeResponse): boolean { - const magicIsValid = (Debugger.DEBUGGER_MAGIC === debuggerHandshake.magic); + private verifyHandshake(debuggerHandshake: HandshakeResponse | HandshakeResponseV3): boolean { + const magicIsValid = (Debugger.DEBUGGER_MAGIC === debuggerHandshake.data.magic); if (magicIsValid) { this.logger.log('Magic is valid.'); - this.protocolVersion = [debuggerHandshake.majorVersion, debuggerHandshake.minorVersion, debuggerHandshake.patchVersion].join('.'); + this.protocolVersion = debuggerHandshake.getVersion(); this.logger.log('Protocol Version:', this.protocolVersion); this.watchPacketLength = debuggerHandshake.watchPacketLength; @@ -599,7 +599,7 @@ export class Debugger { this.emit('handshake-verified', handshakeVerified); return handshakeVerified; } else { - this.logger.log('Closing connection due to bad debugger magic', debuggerHandshake.magic); + this.logger.log('Closing connection due to bad debugger magic', debuggerHandshake.data.magic); this.emit('handshake-verified', false); this.shutdown('close'); return false; diff --git a/src/debugProtocol/responses/HandshakeResponse.ts b/src/debugProtocol/responses/HandshakeResponse.ts index b0fd39bf..13fbbe87 100644 --- a/src/debugProtocol/responses/HandshakeResponse.ts +++ b/src/debugProtocol/responses/HandshakeResponse.ts @@ -1,41 +1,68 @@ import { SmartBuffer } from 'smart-buffer'; import * as semver from 'semver'; import { util } from '../../util'; +import { ProtocolResponse } from './ProtocolResponse'; -export class HandshakeResponse { - - constructor(buffer: Buffer) { - // Required size of the handshake - if (buffer.byteLength >= 20) { - try { - let bufferReader = SmartBuffer.fromBuffer(buffer); - this.magic = util.readStringNT(bufferReader); // magic_number - this.majorVersion = bufferReader.readInt32LE(); // protocol_major_version - this.minorVersion = bufferReader.readInt32LE(); // protocol_minor_version - this.patchVersion = bufferReader.readInt32LE(); // protocol_patch_version - this.readOffset = bufferReader.readOffset; - - const versionString = [this.majorVersion, this.minorVersion, this.patchVersion].join('.'); - - // We only support version prior to v3 with this handshake - if (!semver.satisfies(versionString, '<3.0.0')) { - throw new Error(`unsupported version ${versionString}`); - } - this.success = true; - } catch (error) { - // Could not parse - } +export class HandshakeResponse extends ProtocolResponse { + + public constructor(json: HandshakeResponse['data']); + public constructor(buffer: Buffer); + public constructor(arg: Buffer | HandshakeResponse['data']) { + super(); + if (Buffer.isBuffer(arg)) { + this.loadFromBuffer(arg); + } else { + this.loadFromJson(arg); } } + loadFromBuffer(buffer: Buffer) { + this.bufferLoaderHelper(buffer, 20, (smartBuffer: SmartBuffer) => { + this.data.magic = util.readStringNT(smartBuffer); // magic_number + this.data.majorVersion = smartBuffer.readInt32LE(); // protocol_major_version + this.data.minorVersion = smartBuffer.readInt32LE(); // protocol_minor_version + this.data.patchVersion = smartBuffer.readInt32LE(); // protocol_patch_version + + // We only support version prior to v3 with this handshake + if (!semver.satisfies(this.getVersion(), '<3.0.0')) { + throw new Error(`unsupported version ${this.getVersion()}`); + } + return true; + }); + } + + private loadFromJson(data: HandshakeResponse['data']) { + this.data = data; + } + + public toBuffer() { + let buffer = new SmartBuffer(); + buffer.writeStringNT(this.data.magic); // magic_number + buffer.writeUInt32LE(this.data.majorVersion); // protocol_major_version + buffer.writeUInt32LE(this.data.minorVersion); // protocol_minor_version + buffer.writeUInt32LE(this.data.patchVersion); // protocol_patch_version + + return buffer.toBuffer(); + } + public watchPacketLength = false; // this will always be false for older protocol versions public success = false; public readOffset = 0; public requestId = 0; - // response fields - public magic: string; - public majorVersion = -1; - public minorVersion = -1; - public patchVersion = -1; + getVersion() { + return [this.data.majorVersion, this.data.minorVersion, this.data.patchVersion].join('.'); + } + + public data = { + /** + * The Roku Brightscript debug protocol identifier, which is the following 64-bit value :0x0067756265647362LU. + * + * This is equal to 29120988069524322LU or the following little-endian value: b'bsdebug\0. + */ + magic: undefined as string, + majorVersion: -1, + minorVersion: -1, + patchVersion: -1 + }; } diff --git a/src/debugProtocol/responses/HandshakeResponseV3.ts b/src/debugProtocol/responses/HandshakeResponseV3.ts index ec10f2b8..1c597fad 100644 --- a/src/debugProtocol/responses/HandshakeResponseV3.ts +++ b/src/debugProtocol/responses/HandshakeResponseV3.ts @@ -35,11 +35,9 @@ export class HandshakeResponseV3 extends ProtocolResponse { //set the buffer offset smartBuffer.readOffset = requiredBufferSize; - const versionString = `${this.data.majorVersion}.${this.data.minorVersion}.${this.data.patchVersion}`; - // We only support v3 or above with this handshake - if (!semver.satisfies(versionString, '>=3.0.0')) { - throw new Error(`unsupported version ${versionString}`); + if (!semver.satisfies(this.getVersion(), '>=3.0.0')) { + throw new Error(`unsupported version ${this.getVersion()}`); } return true; }); @@ -74,11 +72,15 @@ export class HandshakeResponseV3 extends ProtocolResponse { return buffer.toBuffer(); } - public watchPacketLength = true; // this will always be false for the new protocol versions + public watchPacketLength = true; // this will always be true for the new protocol versions public success = false; public readOffset = 0; public requestId = 0; + public getVersion() { + return [this.data.majorVersion, this.data.minorVersion, this.data.patchVersion].join('.'); + } + public data = { /** * The Roku Brightscript debug protocol identifier, which is the following 64-bit value :0x0067756265647362LU. diff --git a/src/debugProtocol/responses/ProtocolResponse.ts b/src/debugProtocol/responses/ProtocolResponse.ts index cc235dbb..31c83c28 100644 --- a/src/debugProtocol/responses/ProtocolResponse.ts +++ b/src/debugProtocol/responses/ProtocolResponse.ts @@ -32,11 +32,10 @@ export abstract class ProtocolResponse { try { let smartBuffer = SmartBuffer.fromBuffer(buffer); - //have the processor consume the requred bytes - this.success = processor(smartBuffer); + //have the processor consume the requred bytes. + this.success = processor(smartBuffer) ?? true; this.readOffset = smartBuffer.readOffset; - this.success = true; } catch (error) { // Could not parse this.readOffset = 0; From f47c84dccab14a5f265783c5187140f406a961cd Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Fri, 7 Oct 2022 15:26:40 -0400 Subject: [PATCH 05/74] Adds all request classes --- src/debugProtocol/Constants.ts | 9 + src/debugProtocol/Debugger.spec.ts | 137 +++++------ src/debugProtocol/Debugger.ts | 215 +++++++++--------- src/debugProtocol/ProtocolUtil.ts | 65 ++++++ .../requests/AddBreakpointsRequest.spec.ts | 84 +++++++ .../requests/AddBreakpointsRequest.ts | 76 +++++++ .../AddConditionalBreakpointsRequest.spec.ts | 89 ++++++++ .../AddConditionalBreakpointsRequest.ts | 99 ++++++++ .../requests/ContinueRequest.spec.ts | 25 ++ src/debugProtocol/requests/ContinueRequest.ts | 39 ++++ .../requests/ExecuteRequest.spec.ts | 36 +++ src/debugProtocol/requests/ExecuteRequest.ts | 57 +++++ .../requests/ExitChannelRequest.spec.ts | 25 ++ .../requests/ExitChannelRequest.ts | 39 ++++ .../requests/HandshakeRequest.ts | 39 ++++ .../requests/HandshakeRequestV3.ts | 35 --- .../requests/ListBreakpointsRequest.spec.ts | 25 ++ .../requests/ListBreakpointsRequest.ts | 39 ++++ src/debugProtocol/requests/ProtocolRequest.ts | 22 +- .../requests/RemoveBreakpointsCommand.spec.ts | 32 +++ .../requests/RemoveBreakpointsRequest.ts | 61 +++++ .../requests/StackTraceRequest.spec.ts | 30 +++ .../requests/StackTraceRequest.ts | 46 ++++ .../requests/StepRequest.spec.ts | 33 +++ src/debugProtocol/requests/StepRequest.ts | 50 ++++ .../requests/StopRequest.spec.ts | 25 ++ src/debugProtocol/requests/StopRequest.ts | 38 ++++ .../requests/ThreadsRequest.spec.ts | 25 ++ src/debugProtocol/requests/ThreadsRequest.ts | 39 ++++ .../requests/VariablesRequest.spec.ts | 155 +++++++++++++ .../requests/VariablesRequest.ts | 135 +++++++++++ .../responses/HandshakeResponse.ts | 7 +- .../responses/HandshakeResponseV3.ts | 13 +- .../responses/ProtocolResponse.ts | 20 +- .../AllThreadsStoppedUpdateResponse.ts | 114 ++++++++++ .../responses/updates/UpdateResponse.ts | 43 ++++ .../server/DebugProtocolServer.ts | 24 +- 37 files changed, 1816 insertions(+), 229 deletions(-) create mode 100644 src/debugProtocol/ProtocolUtil.ts create mode 100644 src/debugProtocol/requests/AddBreakpointsRequest.spec.ts create mode 100644 src/debugProtocol/requests/AddBreakpointsRequest.ts create mode 100644 src/debugProtocol/requests/AddConditionalBreakpointsRequest.spec.ts create mode 100644 src/debugProtocol/requests/AddConditionalBreakpointsRequest.ts create mode 100644 src/debugProtocol/requests/ContinueRequest.spec.ts create mode 100644 src/debugProtocol/requests/ContinueRequest.ts create mode 100644 src/debugProtocol/requests/ExecuteRequest.spec.ts create mode 100644 src/debugProtocol/requests/ExecuteRequest.ts create mode 100644 src/debugProtocol/requests/ExitChannelRequest.spec.ts create mode 100644 src/debugProtocol/requests/ExitChannelRequest.ts create mode 100644 src/debugProtocol/requests/HandshakeRequest.ts delete mode 100644 src/debugProtocol/requests/HandshakeRequestV3.ts create mode 100644 src/debugProtocol/requests/ListBreakpointsRequest.spec.ts create mode 100644 src/debugProtocol/requests/ListBreakpointsRequest.ts create mode 100644 src/debugProtocol/requests/RemoveBreakpointsCommand.spec.ts create mode 100644 src/debugProtocol/requests/RemoveBreakpointsRequest.ts create mode 100644 src/debugProtocol/requests/StackTraceRequest.spec.ts create mode 100644 src/debugProtocol/requests/StackTraceRequest.ts create mode 100644 src/debugProtocol/requests/StepRequest.spec.ts create mode 100644 src/debugProtocol/requests/StepRequest.ts create mode 100644 src/debugProtocol/requests/StopRequest.spec.ts create mode 100644 src/debugProtocol/requests/StopRequest.ts create mode 100644 src/debugProtocol/requests/ThreadsRequest.spec.ts create mode 100644 src/debugProtocol/requests/ThreadsRequest.ts create mode 100644 src/debugProtocol/requests/VariablesRequest.spec.ts create mode 100644 src/debugProtocol/requests/VariablesRequest.ts create mode 100644 src/debugProtocol/responses/updates/AllThreadsStoppedUpdateResponse.ts create mode 100644 src/debugProtocol/responses/updates/UpdateResponse.ts diff --git a/src/debugProtocol/Constants.ts b/src/debugProtocol/Constants.ts index aa04e9fd..e1949b4d 100644 --- a/src/debugProtocol/Constants.ts +++ b/src/debugProtocol/Constants.ts @@ -135,3 +135,12 @@ export function getUpdateType(value: number): UPDATE_TYPES { return UPDATE_TYPES.UNDEF; } } + +/** + * Common properties found on all Command `.data` objects + */ +export interface CommandData { + packetLength: number; + requestId: number; + commandCode: number; +} diff --git a/src/debugProtocol/Debugger.spec.ts b/src/debugProtocol/Debugger.spec.ts index 003efba1..47cbea7c 100644 --- a/src/debugProtocol/Debugger.spec.ts +++ b/src/debugProtocol/Debugger.spec.ts @@ -1,11 +1,11 @@ import { Debugger } from './Debugger'; import { expect } from 'chai'; -import { SmartBuffer } from 'smart-buffer'; +import type { SmartBuffer } from 'smart-buffer'; import { MockDebugProtocolServer } from './MockDebugProtocolServer.spec'; import { createSandbox } from 'sinon'; import { createHandShakeResponse, createHandShakeResponseV3, createProtocolEventV3 } from './responses/responseCreationHelpers.spec'; -import { HandshakeResponseV3, ProtocolEventV3 } from './responses'; -import { ERROR_CODES, UPDATE_TYPES, VARIABLE_REQUEST_FLAGS } from './Constants'; +import { HandshakeResponse, HandshakeResponseV3, ProtocolEventV3 } from './responses'; +import { ERROR_CODES, STOP_REASONS, UPDATE_TYPES, VARIABLE_REQUEST_FLAGS } from './Constants'; import { DebugProtocolServer, DebugProtocolServerOptions } from './server/DebugProtocolServer'; import * as portfinder from 'portfinder'; import { util } from '../util'; @@ -13,7 +13,8 @@ import type { BeforeSendResponseEvent, ProtocolPlugin, ProvideResponseEvent } fr import { Handler, OnClientConnectedEvent, ProvideRequestEvent } from './server/ProtocolPlugin'; import type { ProtocolResponse } from './responses/ProtocolResponse'; import type { ProtocolRequest } from './requests/ProtocolRequest'; -import { HandshakeRequestV3 } from './requests/HandshakeRequestV3'; +import { HandshakeRequest } from './requests/HandshakeRequest'; +import { AllThreadsStoppedUpdateResponse } from './responses/updates/AllThreadsStoppedUpdateResponse'; const sinon = createSandbox(); @@ -63,65 +64,6 @@ describe('debugProtocol Debugger', () => { }); }); - describe('parseUnhandledData', () => { - it('handles legacy handshake', () => { - let mockResponse = createHandShakeResponse({ - magic: Debugger.DEBUGGER_MAGIC, - major: 1, - minor: 0, - patch: 0 - }); - - bsDebugger['unhandledData'] = mockResponse.toBuffer(); - - expect(bsDebugger.watchPacketLength).to.be.equal(false); - expect(bsDebugger.handshakeComplete).to.be.equal(false); - - expect(bsDebugger['parseUnhandledData'](bsDebugger['unhandledData'])).to.be.equal(true); - - expect(bsDebugger.watchPacketLength).to.be.equal(false); - expect(bsDebugger.handshakeComplete).to.be.equal(true); - expect(bsDebugger['unhandledData'].byteLength).to.be.equal(0); - }); - - it('handles events after handshake', () => { - let handshake = createHandShakeResponseV3({ - magic: Debugger.DEBUGGER_MAGIC, - major: 3, - minor: 0, - patch: 0, - revisionTimeStamp: Date.now() - }); - - let protocolEvent = createProtocolEventV3({ - requestId: 0, - errorCode: ERROR_CODES.CANT_CONTINUE, - updateType: UPDATE_TYPES.ALL_THREADS_STOPPED - }); - - let mockResponse = new SmartBuffer(); - mockResponse.writeBuffer(handshake.toBuffer()); - mockResponse.writeBuffer(protocolEvent.toBuffer()); - - bsDebugger['unhandledData'] = mockResponse.toBuffer(); - - const stub = sinon.stub(bsDebugger as any, 'removedProcessedBytes').callThrough(); - - expect(bsDebugger.watchPacketLength).to.be.equal(false); - expect(bsDebugger.handshakeComplete).to.be.equal(false); - - expect(bsDebugger['parseUnhandledData'](bsDebugger['unhandledData'])).to.be.equal(true); - - expect(bsDebugger.watchPacketLength).to.be.equal(true); - expect(bsDebugger.handshakeComplete).to.be.equal(true); - expect(bsDebugger['unhandledData'].byteLength).to.be.equal(0); - - let calls = stub.getCalls(); - expect(calls[0].args[0]).instanceOf(HandshakeResponseV3); - expect(calls[1].args[0]).instanceOf(ProtocolEventV3); - }); - }); - describe('getVariables', () => { function getVariablesRequestBufferToJson(buffer: SmartBuffer) { const result = { @@ -226,7 +168,7 @@ class TestPlugin implements ProtocolPlugin { const response = this.responseQueue.shift(); //if there's no response, AND this isn't the handshake, fail. (we want the protocol to handle the handshake most of the time) - if (!response && !(event.request instanceof HandshakeRequestV3)) { + if (!response && !(event.request instanceof HandshakeRequest)) { throw new Error('There was no response available to send back'); } event.response = response; @@ -238,7 +180,7 @@ class TestPlugin implements ProtocolPlugin { } } -describe.only('Debugger new tests', () => { +describe.skip('Debugger new tests', () => { let server: DebugProtocolServer; let client: Debugger; let plugin: TestPlugin; @@ -256,6 +198,8 @@ describe.only('Debugger new tests', () => { await server.start(); client = new Debugger(options); + //disable logging for tests because they clutter the test output + client['logger'].logLevel = 'off'; }); afterEach(async () => { @@ -300,4 +244,67 @@ describe.only('Debugger new tests', () => { //wait for the debugger to finish verifying the handshake expect(await verifyHandshakePromise).to.be.false; }); + + it('handles legacy handshake', async () => { + + expect(client.watchPacketLength).to.be.equal(false); + expect(client.handshakeComplete).to.be.equal(false); + + plugin.pushResponse(new HandshakeResponse({ + magic: Debugger.DEBUGGER_MAGIC, + majorVersion: 1, + minorVersion: 0, + patchVersion: 0 + })); + + await client.connect(); + + expect(client.watchPacketLength).to.be.equal(false); + expect(client.handshakeComplete).to.be.equal(true); + }); + + it('handles events after handshake', async () => { + await client.connect(); + + await server.sendUpdate( + new AllThreadsStoppedUpdateResponse({ + primaryThreadIndex: 1, + stopReason: STOP_REASONS.BREAK, + stopReasonDetail: 'test' + }) + ); + const event = await client.once('suspend'); + expect(event.data).include({ + primaryThreadIndex: 1, + stopReason: STOP_REASONS.BREAK, + stopReasonDetail: 'test' + }); + // let protocolEvent = createProtocolEventV3({ + // requestId: 0, + // errorCode: ERROR_CODES.CANT_CONTINUE, + // updateType: UPDATE_TYPES.ALL_THREADS_STOPPED + // }); + + // let mockResponse = new SmartBuffer(); + // mockResponse.writeBuffer(handshake.toBuffer()); + // mockResponse.writeBuffer(protocolEvent.toBuffer()); + + // bsDebugger['unhandledData'] = mockResponse.toBuffer(); + + // const stub = sinon.stub(bsDebugger as any, 'removedProcessedBytes').callThrough(); + + // expect(bsDebugger.watchPacketLength).to.be.equal(false); + // expect(bsDebugger.handshakeComplete).to.be.equal(false); + + // expect(bsDebugger['parseUnhandledData'](bsDebugger['unhandledData'])).to.be.equal(true); + + // expect(bsDebugger.watchPacketLength).to.be.equal(true); + // expect(bsDebugger.handshakeComplete).to.be.equal(true); + // expect(bsDebugger['unhandledData'].byteLength).to.be.equal(0); + + // let calls = stub.getCalls(); + // expect(calls[0].args[0]).instanceOf(HandshakeResponseV3); + // expect(calls[1].args[0]).instanceOf(ProtocolEventV3); + }); + }); diff --git a/src/debugProtocol/Debugger.ts b/src/debugProtocol/Debugger.ts index 3346234c..a5e72ea5 100644 --- a/src/debugProtocol/Debugger.ts +++ b/src/debugProtocol/Debugger.ts @@ -27,6 +27,20 @@ import { AddBreakpointsResponse } from './responses/AddBreakpointsResponse'; import { RemoveBreakpointsResponse } from './responses/RemoveBreakpointsResponse'; import { util } from '../util'; import { BreakpointErrorUpdateResponse } from './responses/BreakpointErrorUpdateResponse'; +import type { ProtocolRequest } from './requests/ProtocolRequest'; +import { ContinueRequest } from './requests/ContinueRequest'; +import { StopRequest } from './requests/StopRequest'; +import { ExitChannelRequest } from './requests/ExitChannelRequest'; +import { ProtocolResponse } from './responses/ProtocolResponse'; +import { StepRequest } from './requests/StepRequest'; +import { RemoveBreakpointsRequest } from './requests/RemoveBreakpointsRequest'; +import { ListBreakpointsRequest } from './requests/ListBreakpointsRequest'; +import { VariablesRequest } from './requests/VariablesRequest'; +import { StackTraceRequest } from './requests/StackTraceRequest'; +import { ThreadsRequest } from './requests/ThreadsRequest'; +import { ExecuteRequest } from './requests/ExecuteRequest'; +import { AddBreakpointsRequest } from './requests/AddBreakpointsRequest'; +import { AddConditionalBreakpointsRequest } from './requests/AddConditionalBreakpointsRequest'; export class Debugger { @@ -65,7 +79,7 @@ export class Debugger { private unhandledData: Buffer; private stopped = false; private totalRequests = 0; - private activeRequests = {}; + private activeRequests1 = new Map(); private options: ConstructorOptions; /** @@ -220,40 +234,59 @@ export class Debugger { public async continue() { if (this.stopped) { this.stopped = false; - return this.makeRequest(new SmartBuffer({ size: 12 }), COMMANDS.CONTINUE); + return this.makeRequest( + ContinueRequest.fromJson({ + requestId: this.totalRequests++ + }) + ); } } public async pause(force = false) { if (!this.stopped || force) { - return this.makeRequest(new SmartBuffer({ size: 12 }), COMMANDS.STOP); + return this.makeRequest( + StopRequest.fromJson({ + requestId: this.totalRequests++ + }) + ); } } public async exitChannel() { - return this.makeRequest(new SmartBuffer({ size: 12 }), COMMANDS.EXIT_CHANNEL); + return this.makeRequest( + ExitChannelRequest.fromJson({ + requestId: this.totalRequests++ + }) + ); } - public async stepIn(threadId: number = this.primaryThread) { - return this.step(STEP_TYPE.STEP_TYPE_LINE, threadId); + public async stepIn(threadIndex: number = this.primaryThread) { + return this.step(STEP_TYPE.STEP_TYPE_LINE, threadIndex); } - public async stepOver(threadId: number = this.primaryThread) { - return this.step(STEP_TYPE.STEP_TYPE_OVER, threadId); + public async stepOver(threadIndex: number = this.primaryThread) { + return this.step(STEP_TYPE.STEP_TYPE_OVER, threadIndex); } - public async stepOut(threadId: number = this.primaryThread) { - return this.step(STEP_TYPE.STEP_TYPE_OUT, threadId); + public async stepOut(threadIndex: number = this.primaryThread) { + return this.step(STEP_TYPE.STEP_TYPE_OUT, threadIndex); } - private async step(stepType: STEP_TYPE, threadId: number): Promise { - this.logger.log('[step]', { stepType: STEP_TYPE[stepType], threadId, stopped: this.stopped }); + private async step(stepType: STEP_TYPE, threadIndex: number): Promise { + this.logger.log('[step]', { stepType: STEP_TYPE[stepType], threadId: threadIndex, stopped: this.stopped }); + let buffer = new SmartBuffer({ size: 17 }); - buffer.writeUInt32LE(threadId); // thread_index + buffer.writeUInt32LE(threadIndex); // thread_index buffer.writeUInt8(stepType); // step_type if (this.stopped) { this.stopped = false; - let stepResult = await this.makeRequest(buffer, COMMANDS.STEP); + let stepResult = await this.makeRequest( + StepRequest.fromJson({ + requestId: this.totalRequests++, + stepType: stepType, + threadIndex: threadIndex + }) + ); if (stepResult.errorCode === ERROR_CODES.OK) { // this.stopped = true; // this.emit('suspend'); @@ -267,7 +300,10 @@ export class Debugger { public async threads() { if (this.stopped) { - let result = await this.makeRequest(new SmartBuffer({ size: 12 }), COMMANDS.THREADS); + let result = await this.makeRequest( + ThreadsRequest.fromJson({ + requestId: this.totalRequests++ + })); if (result.errorCode === ERROR_CODES.OK) { //older versions of the debug protocol had issues with maintaining the active thread, so our workaround is to keep track of it elsewhere @@ -293,7 +329,12 @@ export class Debugger { let buffer = new SmartBuffer({ size: 16 }); buffer.writeUInt32LE(threadIndex); // thread_index if (this.stopped && threadIndex > -1) { - return this.makeRequest(buffer, COMMANDS.STACKTRACE); + return this.makeRequest( + StackTraceRequest.fromJson({ + requestId: this.totalRequests++, + threadIndex: threadIndex + }) + ); } } @@ -310,115 +351,84 @@ export class Debugger { */ public async getVariables(variablePathEntries: Array = [], getChildKeys = true, stackFrameIndex: number = this.stackFrameIndex, threadIndex: number = this.primaryThread) { if (this.stopped && threadIndex > -1) { - //starting in protocol v3.1.0, it supports marking certain path items as case-insensitive (i.e. parts of DottedGet expressions) - const sendCaseInsensitiveData = semver.satisfies(this.protocolVersion, '>=3.1.0') && variablePathEntries.length > 0; - let buffer = new SmartBuffer({ size: 17 }); - let flags = 0; - if (getChildKeys) { - // eslint-disable-next-line no-bitwise - flags |= VARIABLE_REQUEST_FLAGS.GET_CHILD_KEYS; - } - if (sendCaseInsensitiveData) { - // eslint-disable-next-line no-bitwise - flags |= VARIABLE_REQUEST_FLAGS.CASE_SENSITIVITY_OPTIONS; - } - buffer.writeUInt8(flags); // variable_request_flags - buffer.writeUInt32LE(threadIndex); // thread_index - buffer.writeUInt32LE(stackFrameIndex); // stack_frame_index - buffer.writeUInt32LE(variablePathEntries.length); // variable_path_len - variablePathEntries.forEach(entry => { - if (entry.startsWith('"') && entry.endsWith('"')) { + const request = VariablesRequest.fromJson({ + requestId: this.totalRequests++, + threadIndex: threadIndex, + stackFrameIndex: stackFrameIndex, + getChildKeys: getChildKeys, + variablePathEntries: variablePathEntries.map(x => ({ //remove leading and trailing quotes - entry = entry.substring(1, entry.length - 1); - } - buffer.writeStringNT(entry); // variable_path_entries - optional + name: x.replace(/^"/, '').replace(/"$/, ''), + isCaseSensitive: x.startsWith('"') && x.endsWith('"') + })), + //starting in protocol v3.1.0, it supports marking certain path items as case-insensitive (i.e. parts of DottedGet expressions) + enableCaseInsensitivityFlag: semver.satisfies(this.protocolVersion, '>=3.1.0') && variablePathEntries.length > 0 }); - if (sendCaseInsensitiveData) { - variablePathEntries.forEach(entry => { - buffer.writeUInt8( - //0 means case SENSITIVE lookup, 1 means case INsensitive lookup - entry.startsWith('"') ? 0 : 1 - ); - }); - } - return this.makeRequest(buffer, COMMANDS.VARIABLES, variablePathEntries); + return this.makeRequest(request); } } public async executeCommand(sourceCode: string, stackFrameIndex: number = this.stackFrameIndex, threadIndex: number = this.primaryThread) { if (this.stopped && threadIndex > -1) { - console.log(sourceCode); - let buffer = new SmartBuffer({ size: 8 }); - buffer.writeUInt32LE(threadIndex); // thread_index - buffer.writeUInt32LE(stackFrameIndex); // stack_frame_index - buffer.writeStringNT(sourceCode); // source_code - return this.makeRequest(buffer, COMMANDS.EXECUTE, sourceCode); + return this.makeRequest( + ExecuteRequest.fromJson({ + requestId: this.totalRequests++, + threadIndex: threadIndex, + stackFrameIndex: stackFrameIndex, + sourceCode: sourceCode + }) + ); } } public async addBreakpoints(breakpoints: Array): Promise { const { enableComponentLibrarySpecificBreakpoints } = this; if (breakpoints?.length > 0) { - let buffer = new SmartBuffer(); - //set the `FLAGS` value if supported + const json = { + requestId: this.totalRequests++, + breakpoints: breakpoints.map(x => ({ + ...x, + ignoreCount: x.hitCount + })) + }; + if (this.supportsConditionalBreakpoints) { - buffer.writeUInt32LE(0); // flags - Should always be passed as 0. Unused, reserved for future use. + return this.makeRequest( + AddBreakpointsRequest.fromJson(json) + ); + } else { + return this.makeRequest( + AddConditionalBreakpointsRequest.fromJson(json) + ); } - buffer.writeUInt32LE(breakpoints.length); // num_breakpoints - The number of breakpoints in the breakpoints array. - breakpoints.forEach((breakpoint) => { - let { filePath } = breakpoint; - //protocol >= v3.1.0 requires complib breakpoints have a special prefix - if (enableComponentLibrarySpecificBreakpoints) { - if (breakpoint.componentLibraryName) { - filePath = filePath.replace(/^pkg:\//i, `lib:/${breakpoint.componentLibraryName}/`); - } - } - - buffer.writeStringNT(filePath); // file_path - The path of the source file where the breakpoint is to be inserted. - buffer.writeUInt32LE(breakpoint.lineNumber); // line_number - The line number in the channel application code where the breakpoint is to be executed. - buffer.writeUInt32LE(breakpoint.hitCount ?? 0); // ignore_count - The number of times to ignore the breakpoint condition before executing the breakpoint. This number is decremented each time the channel application reaches the breakpoint. - //if the protocol supports conditional breakpoints, add any present condition - if (this.supportsConditionalBreakpoints) { - //There's a bug in 3.1 where empty conditional expressions would crash the breakpoints, so just default to `true` which always succeeds - buffer.writeStringNT(breakpoint.conditionalExpression ?? 'true'); // cond_expr - the condition that must evaluate to `true` in order to hit the breakpoint - } - }); - return this.makeRequest(buffer, - this.supportsConditionalBreakpoints ? COMMANDS.ADD_CONDITIONAL_BREAKPOINTS : COMMANDS.ADD_BREAKPOINTS - //COMMANDS.ADD_BREAKPOINTS - ); } return new AddBreakpointsResponse(null); } public async listBreakpoints(): Promise { - return this.makeRequest(new SmartBuffer({ size: 12 }), COMMANDS.LIST_BREAKPOINTS); + return this.makeRequest( + ListBreakpointsRequest.fromJson({ + requestId: this.totalRequests++ + }) + ); } public async removeBreakpoints(breakpointIds: number[]): Promise { if (breakpointIds?.length > 0) { - let buffer = new SmartBuffer(); - buffer.writeUInt32LE(breakpointIds.length); // num_breakpoints - The number of breakpoints in the breakpoints array. - breakpointIds.forEach((breakpointId) => { - buffer.writeUInt32LE(breakpointId); // breakpoint_ids - An array of breakpoint IDs representing the breakpoints to be removed. + const command = RemoveBreakpointsRequest.fromJson({ + requestId: this.totalRequests++, + breakpointIds: breakpointIds }); - return this.makeRequest(buffer, COMMANDS.REMOVE_BREAKPOINTS); + return this.makeRequest(command); } return new RemoveBreakpointsResponse(null); } - private async makeRequest(buffer: SmartBuffer, command: COMMANDS, extraData?) { + private async makeRequest(request: ProtocolRequest) { this.totalRequests++; let requestId = this.totalRequests; - buffer.insertUInt32LE(command, 0); // command_code - An enum representing the debugging command being sent. See the COMMANDS enum - buffer.insertUInt32LE(requestId, 0); // request_id - The ID of the debugger request (must be >=1). This ID is included in the debugger response. - buffer.insertUInt32LE(buffer.writeOffset + 4, 0); // packet_length - The size of the packet to be sent. - - this.activeRequests[requestId] = { - commandType: command, - commandTypeText: COMMANDS[command], - extraData: extraData - }; + + this.activeRequests1.set(requestId, request); return new Promise((resolve, reject) => { let unsubscribe = this.on('data', (data) => { @@ -428,11 +438,11 @@ export class Debugger { } }); - this.logger.debug('makeRequest', `requestId=${requestId}`, this.activeRequests[requestId]); + this.logger.debug('makeRequest', `requestId=${requestId}`, this.activeRequests1.get(requestId)); if (this.controllerClient) { - this.controllerClient.write(buffer.toBuffer()); + this.controllerClient.write(request.toBuffer()); } else { - throw new Error(`Controller connection was closed - Command: ${COMMANDS[command]}`); + throw new Error(`Controller connection was closed - Command: ${COMMANDS[request.data.commandCode]}`); } }); } @@ -487,8 +497,9 @@ export class Debugger { return this.checkResponse(new UndefinedResponse(slicedBuffer), buffer, packetLength); } } else { - this.logger.log('Command Type:', COMMANDS[this.activeRequests[debuggerRequestResponse.requestId].commandType]); - switch (this.activeRequests[debuggerRequestResponse.requestId].commandType) { + const request = this.activeRequests1.get(debuggerRequestResponse.requestId); + this.logger.log('Command Type:', COMMANDS[request.data.commandCode]); + switch (request.data.commandCode) { case COMMANDS.STOP: case COMMANDS.CONTINUE: case COMMANDS.STEP: @@ -551,9 +562,9 @@ export class Debugger { } private removedProcessedBytes(responseHandler: { requestId: number; readOffset: number }, unhandledData: Buffer, packetLength = 0) { - const activeRequest = this.activeRequests[responseHandler.requestId]; - if (responseHandler.requestId > 0 && this.activeRequests[responseHandler.requestId]) { - delete this.activeRequests[responseHandler.requestId]; + const activeRequest = this.activeRequests1.get(responseHandler.requestId); + if (responseHandler.requestId > 0 && activeRequest) { + this.activeRequests1.delete(responseHandler.requestId); } this.emit('data', responseHandler); diff --git a/src/debugProtocol/ProtocolUtil.ts b/src/debugProtocol/ProtocolUtil.ts new file mode 100644 index 00000000..799f3a69 --- /dev/null +++ b/src/debugProtocol/ProtocolUtil.ts @@ -0,0 +1,65 @@ +import { SmartBuffer } from 'smart-buffer'; +import type { CommandData as RequestData } from './Constants'; +import type { ProtocolRequest } from './requests/ProtocolRequest'; +import type { ProtocolResponse } from './responses/ProtocolResponse'; + +export class ProtocolUtils { + + /** + * Load json data onto an event, and mark it as successful + */ + public loadJson(event: { data: any; success: boolean }, data: any) { + event.data = { + ...event.data, + ...(data ?? {}) + }; + event.success = true; + } + + /** + * Helper function for buffer loading. + * Handles things like try/catch, setting buffer read offset, etc + */ + public bufferLoaderHelper(event: { success: boolean; readOffset: number }, buffer: Buffer, minByteLength: number, processor: (buffer: SmartBuffer) => boolean | void) { + // Required size of this processor + if (buffer.byteLength >= minByteLength) { + try { + let smartBuffer = SmartBuffer.fromBuffer(buffer); + + //have the processor consume the requred bytes. + event.success = (processor(smartBuffer) ?? true) as boolean; + + event.readOffset = smartBuffer.readOffset; + } catch (error) { + // Could not parse + event.readOffset = 0; + event.success = false; + } + } + return event; + } + + /** + * Load the common `Command` (i.e. `DebuggerRequest`) fields + */ + public loadCommonRequestFields(command: ProtocolRequest | ProtocolResponse, smartBuffer: SmartBuffer) { + command.data.packetLength = smartBuffer.readUInt32LE(); // packet_length + command.data.requestId = smartBuffer.readUInt32LE(); // request_id + command.data.commandCode = smartBuffer.readUInt32LE(); // command_code + } + + /** + * Inserts the common command fields to the beginning of the buffer, and computes + * the correct `packet_length` value. + */ + public insertCommonRequestFields(command: ProtocolRequest | ProtocolResponse, smartBuffer: SmartBuffer) { + smartBuffer.insertUInt32LE(command.data.commandCode, 0); // command_code - An enum representing the debugging command being sent. See the COMMANDS enum + smartBuffer.insertUInt32LE(command.data.requestId, 0); // request_id - The ID of the debugger request (must be >=1). This ID is included in the debugger response. + smartBuffer.insertUInt32LE(smartBuffer.writeOffset + 4, 0); // packet_length - The size of the packet to be sent. + command.data.packetLength = smartBuffer.writeOffset; + return smartBuffer; + } +} + +export const protocolUtils = new ProtocolUtils(); + diff --git a/src/debugProtocol/requests/AddBreakpointsRequest.spec.ts b/src/debugProtocol/requests/AddBreakpointsRequest.spec.ts new file mode 100644 index 00000000..a2b5d9b6 --- /dev/null +++ b/src/debugProtocol/requests/AddBreakpointsRequest.spec.ts @@ -0,0 +1,84 @@ +import { expect } from 'chai'; +import { COMMANDS } from '../Constants'; +import { AddBreakpointsRequest } from './AddBreakpointsRequest'; + +describe('AddBreakpointsRequest', () => { + it('serializes and deserializes properly with zero breakpoints', () => { + const command = AddBreakpointsRequest.fromJson({ + requestId: 3, + breakpoints: [] + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 3, + commandCode: COMMANDS.ADD_BREAKPOINTS, + + breakpoints: [] + }); + + expect( + AddBreakpointsRequest.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 16, // 4 bytes + requestId: 3, // 4 bytes + commandCode: COMMANDS.ADD_BREAKPOINTS, // 4 bytes + + // num_breakpoints // 4 bytes + breakpoints: [] + }); + }); + + it('serializes and deserializes properly with breakpoints', () => { + const command = AddBreakpointsRequest.fromJson({ + requestId: 3, + breakpoints: [{ + filePath: 'source/main.brs', + ignoreCount: 3, + lineNumber: 1 + }, + { + filePath: 'source/main.brs', + ignoreCount: undefined, //we default to 0 + lineNumber: 2 + }] + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 3, + commandCode: COMMANDS.CONTINUE, + + breakpoints: [{ + filePath: 'source/main.brs', + ignoreCount: 3, + lineNumber: 1 + }, + { + filePath: 'source/main.brs', + ignoreCount: 0, + lineNumber: 2 + }] + }); + + expect( + AddBreakpointsRequest.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 64, // 4 bytes + requestId: 3, // 4 bytes + commandCode: COMMANDS.CONTINUE, // 4 bytes + + // num_breakpoints // 4 bytes + breakpoints: [{ + filePath: 'source/main.brs', // 16 bytes + ignoreCount: 3, // 4 bytes + lineNumber: 1 // 4 bytes + }, + { + filePath: 'source/main.brs', // 16 bytes + ignoreCount: 0, // 4 bytes + lineNumber: 2 // 4 bytes + }] + }); + }); +}); diff --git a/src/debugProtocol/requests/AddBreakpointsRequest.ts b/src/debugProtocol/requests/AddBreakpointsRequest.ts new file mode 100644 index 00000000..13b5bcd1 --- /dev/null +++ b/src/debugProtocol/requests/AddBreakpointsRequest.ts @@ -0,0 +1,76 @@ +import { SmartBuffer } from 'smart-buffer'; +import { util } from '../../util'; +import type { CommandData } from '../Constants'; +import { COMMANDS } from '../Constants'; +import { protocolUtils } from '../ProtocolUtil'; +import type { ProtocolRequest } from './ProtocolRequest'; + +export class AddBreakpointsRequest implements ProtocolRequest { + + public static fromJson(data: { + requestId: number; + breakpoints: Array<{ + filePath: string; + lineNumber: number; + ignoreCount: number; + }>; + }) { + const request = new AddBreakpointsRequest(); + protocolUtils.loadJson(request, data); + request.data.breakpoints ??= []; + //default ignoreCount to 0 for consistency purposes + for (const breakpoint of request.data.breakpoints) { + breakpoint.ignoreCount ??= 0; + } + return request; + } + + public static fromBuffer(buffer: Buffer) { + const request = new AddBreakpointsRequest(); + protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtils.loadCommonRequestFields(request, smartBuffer); + + const numBreakpoints = smartBuffer.readUInt32LE(); // num_breakpoints + request.data.breakpoints = []; + for (let i = 0; i < numBreakpoints; i++) { + request.data.breakpoints.push({ + filePath: util.readStringNT(smartBuffer), // file_path + lineNumber: smartBuffer.readUInt32LE(), // line_number + ignoreCount: smartBuffer.readUInt32LE() // ignore_count + }); + } + }); + return request; + } + + public toBuffer(): Buffer { + const smartBuffer = new SmartBuffer(); + + smartBuffer.writeUInt32LE(this.data.breakpoints.length); // num_breakpoints + for (const breakpoint of this.data.breakpoints) { + smartBuffer.writeStringNT(breakpoint.filePath); // file_path + smartBuffer.writeUInt32LE(breakpoint.lineNumber); // line_number + smartBuffer.writeUInt32LE(breakpoint.ignoreCount); // ignore_count + } + + protocolUtils.insertCommonRequestFields(this, smartBuffer); + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = -1; + + public data = { + breakpoints: undefined as Array<{ + filePath: string; + lineNumber: number; + ignoreCount: number; + }>, + + //common props + packetLength: undefined as number, + requestId: undefined as number, + commandCode: COMMANDS.ADD_BREAKPOINTS + }; +} diff --git a/src/debugProtocol/requests/AddConditionalBreakpointsRequest.spec.ts b/src/debugProtocol/requests/AddConditionalBreakpointsRequest.spec.ts new file mode 100644 index 00000000..99620b73 --- /dev/null +++ b/src/debugProtocol/requests/AddConditionalBreakpointsRequest.spec.ts @@ -0,0 +1,89 @@ +import { expect } from 'chai'; +import { COMMANDS } from '../Constants'; +import { AddConditionalBreakpointsRequest } from './AddConditionalBreakpointsRequest'; + +describe('AddConditionalBreakpointsRequest', () => { + it('serializes and deserializes properly with zero breakpoints', () => { + const command = AddConditionalBreakpointsRequest.fromJson({ + requestId: 3, + breakpoints: [] + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 3, + commandCode: COMMANDS.ADD_CONDITIONAL_BREAKPOINTS, + + breakpoints: [] + }); + + expect( + AddConditionalBreakpointsRequest.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 16, // 4 bytes + requestId: 3, // 4 bytes + commandCode: COMMANDS.ADD_CONDITIONAL_BREAKPOINTS, // 4 bytes + + // num_breakpoints // 4 bytes + breakpoints: [] + }); + }); + + it('serializes and deserializes properly with breakpoints', () => { + const command = AddConditionalBreakpointsRequest.fromJson({ + requestId: 3, + breakpoints: [{ + filePath: 'source/main.brs', + ignoreCount: 3, + lineNumber: 1, + conditionalExpression: '1=1' + }, + { + filePath: 'source/main.brs', + ignoreCount: undefined, //we default to 0 + lineNumber: 2 + }] + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 3, + commandCode: COMMANDS.ADD_CONDITIONAL_BREAKPOINTS, + + breakpoints: [{ + filePath: 'source/main.brs', + ignoreCount: 3, + lineNumber: 1, + conditionalExpression: '1=1' + }, + { + filePath: 'source/main.brs', + ignoreCount: 0, + lineNumber: 2, + conditionalExpression: 'true' + }] + }); + + expect( + AddConditionalBreakpointsRequest.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 73, // 4 bytes + requestId: 3, // 4 bytes + commandCode: COMMANDS.ADD_CONDITIONAL_BREAKPOINTS, // 4 bytes + + // num_breakpoints // 4 bytes + breakpoints: [{ + filePath: 'source/main.brs', // 16 bytes + ignoreCount: 3, // 4 bytes + lineNumber: 1, // 4 bytes + conditionalExpression: '1=1' // 4 bytes + }, + { + filePath: 'source/main.brs', // 16 bytes + ignoreCount: 0, // 4 bytes + lineNumber: 2, // 4 bytes + conditionalExpression: 'true' // 5 bytes + }] + }); + }); +}); diff --git a/src/debugProtocol/requests/AddConditionalBreakpointsRequest.ts b/src/debugProtocol/requests/AddConditionalBreakpointsRequest.ts new file mode 100644 index 00000000..f989887f --- /dev/null +++ b/src/debugProtocol/requests/AddConditionalBreakpointsRequest.ts @@ -0,0 +1,99 @@ +import { SmartBuffer } from 'smart-buffer'; +import { util } from '../../util'; +import { COMMANDS } from '../Constants'; +import { protocolUtils } from '../ProtocolUtil'; +import type { ProtocolRequest } from './ProtocolRequest'; + +export class AddConditionalBreakpointsRequest implements ProtocolRequest { + + public static fromJson(data: { + requestId: number; + breakpoints: Array<{ + filePath: string; + lineNumber: number; + ignoreCount: number; + conditionalExpression?: string; + }>; + }) { + const request = new AddConditionalBreakpointsRequest(); + protocolUtils.loadJson(request, data); + request.data.breakpoints ??= []; + //default ignoreCount to 0 for consistency purposes + for (const breakpoint of request.data.breakpoints) { + breakpoint.ignoreCount ??= 0; + //There's a bug in 3.1 where empty conditional expressions would crash the breakpoints, so just default to `true` which always succeeds + breakpoint.conditionalExpression = breakpoint.conditionalExpression?.trim() ? breakpoint.conditionalExpression : 'true'; + } + return request; + } + + public static fromBuffer(buffer: Buffer) { + const request = new AddConditionalBreakpointsRequest(); + protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtils.loadCommonRequestFields(request, smartBuffer); + + const numBreakpoints = smartBuffer.readUInt32LE(); // num_breakpoints + request.data.breakpoints = []; + for (let i = 0; i < numBreakpoints; i++) { + request.data.breakpoints.push({ + filePath: util.readStringNT(smartBuffer), // file_path + lineNumber: smartBuffer.readUInt32LE(), // line_number + ignoreCount: smartBuffer.readUInt32LE(), // ignore_count + conditionalExpression: util.readStringNT(smartBuffer) // cond_expr + }); + } + }); + return request; + } + + public toBuffer(): Buffer { + const smartBuffer = new SmartBuffer(); + + smartBuffer.writeUInt32LE(0); // flags - Should always be passed as 0. Unused, reserved for future use. + + smartBuffer.writeUInt32LE(this.data.breakpoints.length); // num_breakpoints + for (const breakpoint of this.data.breakpoints) { + smartBuffer.writeStringNT(breakpoint.filePath); // file_path + smartBuffer.writeUInt32LE(breakpoint.lineNumber); // line_number + smartBuffer.writeUInt32LE(breakpoint.ignoreCount); // ignore_count + smartBuffer.writeStringNT(breakpoint.conditionalExpression); // cond_expr + } + + protocolUtils.insertCommonRequestFields(this, smartBuffer); + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = -1; + + public data = { + breakpoints: undefined as Array<{ + /** + * The path of the source file where the conditional breakpoint is to be inserted. + * + * "pkg:/" specifies a file in the channel + * + * "lib://" specifies a file in a library. + */ + filePath: string; + /** + * The line number in the channel application code where the breakpoint is to be executed. + */ + lineNumber: number; + /** + * The number of times to ignore the breakpoint condition before executing the breakpoint. This number is decremented each time the channel application reaches the breakpoint. If cond_expr is specified, the ignore_count is only updated if it evaluates to true. + */ + ignoreCount: number; + /** + * BrightScript code that evaluates to a boolean value. The cond_expr is compiled and executed in the context where the breakpoint is located. If cond_expr is specified, the ignore_count is only be updated if this evaluates to true. + */ + conditionalExpression?: string; + }>, + + //common props + packetLength: undefined as number, + requestId: undefined as number, + commandCode: COMMANDS.ADD_CONDITIONAL_BREAKPOINTS + }; +} diff --git a/src/debugProtocol/requests/ContinueRequest.spec.ts b/src/debugProtocol/requests/ContinueRequest.spec.ts new file mode 100644 index 00000000..683facab --- /dev/null +++ b/src/debugProtocol/requests/ContinueRequest.spec.ts @@ -0,0 +1,25 @@ +import { expect } from 'chai'; +import { COMMANDS } from '../Constants'; +import { ContinueRequest } from './ContinueRequest'; + +describe('ContinueRequest', () => { + it('serializes and deserializes properly', () => { + const command = ContinueRequest.fromJson({ + requestId: 3 + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 3, + commandCode: COMMANDS.CONTINUE + }); + + expect( + ContinueRequest.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 12, // 4 bytes + requestId: 3, // 4 bytes + commandCode: COMMANDS.CONTINUE // 4 bytes + }); + }); +}); diff --git a/src/debugProtocol/requests/ContinueRequest.ts b/src/debugProtocol/requests/ContinueRequest.ts new file mode 100644 index 00000000..00d16c86 --- /dev/null +++ b/src/debugProtocol/requests/ContinueRequest.ts @@ -0,0 +1,39 @@ +import { SmartBuffer } from 'smart-buffer'; +import type { CommandData } from '../Constants'; +import { COMMANDS } from '../Constants'; +import { protocolUtils } from '../ProtocolUtil'; +import type { ProtocolRequest } from './ProtocolRequest'; + +export class ContinueRequest implements ProtocolRequest { + + public static fromJson(data: { requestId: number }) { + const request = new ContinueRequest(); + protocolUtils.loadJson(request, data); + return request; + } + + public static fromBuffer(buffer: Buffer) { + const request = new ContinueRequest(); + protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtils.loadCommonRequestFields(request, smartBuffer); + }); + return request; + } + + public toBuffer(): Buffer { + const smartBuffer = new SmartBuffer(); + protocolUtils.insertCommonRequestFields(this, smartBuffer); + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = -1; + + public data = { + //common props + packetLength: undefined as number, + requestId: undefined as number, + commandCode: COMMANDS.CONTINUE + }; +} diff --git a/src/debugProtocol/requests/ExecuteRequest.spec.ts b/src/debugProtocol/requests/ExecuteRequest.spec.ts new file mode 100644 index 00000000..43f94384 --- /dev/null +++ b/src/debugProtocol/requests/ExecuteRequest.spec.ts @@ -0,0 +1,36 @@ +import { expect } from 'chai'; +import { COMMANDS } from '../Constants'; +import { ExecuteRequest } from './ExecuteRequest'; + +describe('ExecuteRequest', () => { + it('serializes and deserializes properly', () => { + const command = ExecuteRequest.fromJson({ + requestId: 3, + sourceCode: 'print "text"', + stackFrameIndex: 2, + threadIndex: 1 + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 3, + commandCode: COMMANDS.EXECUTE, + + sourceCode: 'print "text"', + stackFrameIndex: 2, + threadIndex: 1 + }); + + expect( + ExecuteRequest.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 33, // 4 bytes + requestId: 3, // 4 bytes + commandCode: COMMANDS.EXECUTE, // 4 bytes + + sourceCode: 'print "text"', // 13 bytes + stackFrameIndex: 2, // 4 bytes + threadIndex: 1 // 4 bytes + }); + }); +}); diff --git a/src/debugProtocol/requests/ExecuteRequest.ts b/src/debugProtocol/requests/ExecuteRequest.ts new file mode 100644 index 00000000..31a09807 --- /dev/null +++ b/src/debugProtocol/requests/ExecuteRequest.ts @@ -0,0 +1,57 @@ +import { SmartBuffer } from 'smart-buffer'; +import type { CommandData } from '../Constants'; +import { COMMANDS } from '../Constants'; +import { protocolUtils } from '../ProtocolUtil'; +import type { ProtocolRequest } from './ProtocolRequest'; + +export class ExecuteRequest implements ProtocolRequest { + + public static fromJson(data: { + requestId: number; + threadIndex: number; + stackFrameIndex: number; + sourceCode: string; + }) { + const request = new ExecuteRequest(); + protocolUtils.loadJson(request, data); + return request; + } + + public static fromBuffer(buffer: Buffer) { + const request = new ExecuteRequest(); + protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtils.loadCommonRequestFields(request, smartBuffer); + + request.data.threadIndex = smartBuffer.readUInt32LE(); // thread_index + request.data.stackFrameIndex = smartBuffer.readUInt32LE(); // stack_frame_index + request.data.sourceCode = smartBuffer.readStringNT(); // source_code + }); + return request; + } + + public toBuffer(): Buffer { + const smartBuffer = new SmartBuffer(); + + smartBuffer.writeUInt32LE(this.data.threadIndex); // thread_index + smartBuffer.writeUInt32LE(this.data.stackFrameIndex); // stack_frame_index + smartBuffer.writeStringNT(this.data.sourceCode); // source_code + + protocolUtils.insertCommonRequestFields(this, smartBuffer); + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = -1; + + public data = { + threadIndex: undefined as number, + stackFrameIndex: undefined as number, + sourceCode: undefined as string, + + //common props + packetLength: undefined as number, + requestId: undefined as number, + commandCode: COMMANDS.EXECUTE + }; +} diff --git a/src/debugProtocol/requests/ExitChannelRequest.spec.ts b/src/debugProtocol/requests/ExitChannelRequest.spec.ts new file mode 100644 index 00000000..399cd618 --- /dev/null +++ b/src/debugProtocol/requests/ExitChannelRequest.spec.ts @@ -0,0 +1,25 @@ +import { expect } from 'chai'; +import { COMMANDS } from '../Constants'; +import { ExitChannelRequest } from './ExitChannelRequest'; + +describe('ExitChannelRequest', () => { + it('serializes and deserializes properly', () => { + const command = ExitChannelRequest.fromJson({ + requestId: 3 + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 3, + commandCode: COMMANDS.EXIT_CHANNEL + }); + + expect( + ExitChannelRequest.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 12, // 4 bytes + requestId: 3, // 4 bytes + commandCode: COMMANDS.EXIT_CHANNEL // 4 bytes + }); + }); +}); diff --git a/src/debugProtocol/requests/ExitChannelRequest.ts b/src/debugProtocol/requests/ExitChannelRequest.ts new file mode 100644 index 00000000..1f464463 --- /dev/null +++ b/src/debugProtocol/requests/ExitChannelRequest.ts @@ -0,0 +1,39 @@ +import { SmartBuffer } from 'smart-buffer'; +import type { CommandData } from '../Constants'; +import { COMMANDS } from '../Constants'; +import { protocolUtils } from '../ProtocolUtil'; +import type { ProtocolRequest } from './ProtocolRequest'; + +export class ExitChannelRequest implements ProtocolRequest { + + public static fromJson(data: { requestId: number }) { + const request = new ExitChannelRequest(); + protocolUtils.loadJson(request, data); + return request; + } + + public static fromBuffer(buffer: Buffer) { + const request = new ExitChannelRequest(); + protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtils.loadCommonRequestFields(request, smartBuffer); + }); + return request; + } + + public toBuffer(): Buffer { + const smartBuffer = new SmartBuffer(); + protocolUtils.insertCommonRequestFields(this, smartBuffer); + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = -1; + + public data = { + //common props + packetLength: undefined as number, + requestId: undefined as number, + commandCode: COMMANDS.EXIT_CHANNEL + }; +} diff --git a/src/debugProtocol/requests/HandshakeRequest.ts b/src/debugProtocol/requests/HandshakeRequest.ts new file mode 100644 index 00000000..76ee1a47 --- /dev/null +++ b/src/debugProtocol/requests/HandshakeRequest.ts @@ -0,0 +1,39 @@ +import { SmartBuffer } from 'smart-buffer'; +import { util } from '../../util'; +import { protocolUtils } from '../ProtocolUtil'; +import type { ProtocolRequest } from './ProtocolRequest'; + +/** + * The initial handshake sent by the client. This is just the `magic` to initiate the debug protocol session + * @since protocol v1.0.0 + */ +export class HandshakeRequest implements ProtocolRequest { + + public static fromJson(data: { magic: string }) { + const request = new HandshakeRequest(); + protocolUtils.loadJson(request, data); + return request; + } + + public static loadBuffer(buffer: Buffer) { + const request = new HandshakeRequest(); + protocolUtils.bufferLoaderHelper(request, buffer, 0, (smartBuffer) => { + request.data.magic = util.readStringNT(smartBuffer); + }); + return request; + } + + public toBuffer() { + return new SmartBuffer({ + size: Buffer.byteLength(this.data.magic) + 1 + }).writeStringNT(this.data.magic).toBuffer(); + } + + public success = false; + + public readOffset = -1; + + public data = { + magic: undefined as string + }; +} diff --git a/src/debugProtocol/requests/HandshakeRequestV3.ts b/src/debugProtocol/requests/HandshakeRequestV3.ts deleted file mode 100644 index 7f5e0c7f..00000000 --- a/src/debugProtocol/requests/HandshakeRequestV3.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { SmartBuffer } from 'smart-buffer'; -import { util } from '../../util'; -import { ProtocolRequest } from './ProtocolRequest'; - -export class HandshakeRequestV3 extends ProtocolRequest { - - public constructor(arg: Buffer | HandshakeRequestV3) { - super(); - if (Buffer.isBuffer(arg)) { - this.loadBuffer(arg); - } else { - this.loadJson(arg.data); - } - } - - loadJson(data: HandshakeRequestV3['data']) { - this.data.magic = data.magic; - this.success = true; - } - - loadBuffer(buffer: Buffer) { - this.data.magic = util.readStringNT(SmartBuffer.fromBuffer(buffer)); - this.success = true; - } - - toBuffer() { - return new SmartBuffer({ - size: Buffer.byteLength(this.data.magic) + 1 - }).writeStringNT(this.data.magic).toBuffer(); - } - - public data = { - magic: undefined as string - }; -} diff --git a/src/debugProtocol/requests/ListBreakpointsRequest.spec.ts b/src/debugProtocol/requests/ListBreakpointsRequest.spec.ts new file mode 100644 index 00000000..0b401594 --- /dev/null +++ b/src/debugProtocol/requests/ListBreakpointsRequest.spec.ts @@ -0,0 +1,25 @@ +import { expect } from 'chai'; +import { COMMANDS } from '../Constants'; +import { ListBreakpointsRequest } from './ListBreakpointsRequest'; + +describe('ListBreakpointsRequest', () => { + it('serializes and deserializes properly', () => { + const command = ListBreakpointsRequest.fromJson({ + requestId: 3 + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 3, + commandCode: COMMANDS.LIST_BREAKPOINTS + }); + + expect( + ListBreakpointsRequest.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 12, // 4 bytes + requestId: 3, // 4 bytes + commandCode: COMMANDS.LIST_BREAKPOINTS // 4 bytes + }); + }); +}); diff --git a/src/debugProtocol/requests/ListBreakpointsRequest.ts b/src/debugProtocol/requests/ListBreakpointsRequest.ts new file mode 100644 index 00000000..6fa0b598 --- /dev/null +++ b/src/debugProtocol/requests/ListBreakpointsRequest.ts @@ -0,0 +1,39 @@ +import { SmartBuffer } from 'smart-buffer'; +import type { CommandData } from '../Constants'; +import { COMMANDS } from '../Constants'; +import { protocolUtils } from '../ProtocolUtil'; +import type { ProtocolRequest } from './ProtocolRequest'; + +export class ListBreakpointsRequest implements ProtocolRequest { + + public static fromJson(data: { requestId: number }) { + const request = new ListBreakpointsRequest(); + protocolUtils.loadJson(request, data); + return request; + } + + public static fromBuffer(buffer: Buffer) { + const request = new ListBreakpointsRequest(); + protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtils.loadCommonRequestFields(request, smartBuffer); + }); + return request; + } + + public toBuffer(): Buffer { + const smartBuffer = new SmartBuffer(); + protocolUtils.insertCommonRequestFields(this, smartBuffer); + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = -1; + + public data = { + //common props + packetLength: undefined as number, + requestId: undefined as number, + commandCode: COMMANDS.LIST_BREAKPOINTS + }; +} diff --git a/src/debugProtocol/requests/ProtocolRequest.ts b/src/debugProtocol/requests/ProtocolRequest.ts index 73ee64ad..d17b185e 100644 --- a/src/debugProtocol/requests/ProtocolRequest.ts +++ b/src/debugProtocol/requests/ProtocolRequest.ts @@ -1,22 +1,28 @@ -export abstract class ProtocolRequest { +import type { COMMANDS } from '../Constants'; + +export interface ProtocolRequest { /** - * Was this class successful in parsing/ingesting the data in its constructor + * Was this event successful in parsing/ingesting the data in its constructor */ - public success = false; + success: boolean; /** * The number of bytes that were read from a buffer if this was a success */ - public readOffset: number; + readOffset: number; /** - * Convert the current object into the debug protocol binary format, + * Serialize this event into Convert the current object into the debug protocol binary format, * stored in a `Buffer` */ - public abstract toBuffer(): Buffer; + toBuffer(): Buffer; /** - * Contains the actual request data + * Contains the actual event data */ - public abstract data: any; + data: TData; +} + +export interface HasCommandCode { + commandCode: COMMANDS; } diff --git a/src/debugProtocol/requests/RemoveBreakpointsCommand.spec.ts b/src/debugProtocol/requests/RemoveBreakpointsCommand.spec.ts new file mode 100644 index 00000000..b159c08e --- /dev/null +++ b/src/debugProtocol/requests/RemoveBreakpointsCommand.spec.ts @@ -0,0 +1,32 @@ +import { expect } from 'chai'; +import { COMMANDS } from '../Constants'; +import { RemoveBreakpointsRequest } from './RemoveBreakpointsRequest'; + +describe('RemoveBreakpointsRequest', () => { + it('serializes and deserializes properly', () => { + const command = RemoveBreakpointsRequest.fromJson({ + requestId: 3, + breakpointIds: [1, 2, 100] + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 3, + commandCode: COMMANDS.REMOVE_BREAKPOINTS, + + breakpointIds: [1, 2, 100], + numBreakpoints: 3 + }); + + expect( + RemoveBreakpointsRequest.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 28, // 4 bytes + requestId: 3, // 4 bytes + commandCode: COMMANDS.REMOVE_BREAKPOINTS, // 4 bytes + + breakpointIds: [1, 2, 100], // 12 bytes + numBreakpoints: 3 // 4 bytes + }); + }); +}); diff --git a/src/debugProtocol/requests/RemoveBreakpointsRequest.ts b/src/debugProtocol/requests/RemoveBreakpointsRequest.ts new file mode 100644 index 00000000..34efbab6 --- /dev/null +++ b/src/debugProtocol/requests/RemoveBreakpointsRequest.ts @@ -0,0 +1,61 @@ +import { SmartBuffer } from 'smart-buffer'; +import type { CommandData } from '../Constants'; +import { COMMANDS } from '../Constants'; +import { protocolUtils } from '../ProtocolUtil'; +import type { ProtocolRequest } from './ProtocolRequest'; + +export class RemoveBreakpointsRequest implements ProtocolRequest { + + public static fromJson(data: { requestId: number; breakpointIds: number[] }) { + const request = new RemoveBreakpointsRequest(); + protocolUtils.loadJson(request, data); + request.data.numBreakpoints = request.data.breakpointIds.length; + return request; + } + + public static fromBuffer(buffer: Buffer) { + const command = new RemoveBreakpointsRequest(); + protocolUtils.bufferLoaderHelper(command, buffer, 16, (smartBuffer) => { + protocolUtils.loadCommonRequestFields(command, smartBuffer); + command.data.numBreakpoints = smartBuffer.readUInt32LE(); + command.data.breakpointIds = []; + for (let i = 0; i < command.data.numBreakpoints; i++) { + command.data.breakpointIds.push( + smartBuffer.readUInt32LE() + ); + } + }); + return command; + } + + public toBuffer() { + const smartBuffer = new SmartBuffer(); + smartBuffer.writeUInt32LE(this.data.breakpointIds?.length ?? 0); // num_breakpoints + for (const breakpointId of this.data.breakpointIds ?? []) { + smartBuffer.writeUInt32LE(breakpointId as number); // breakpoint_ids + } + protocolUtils.insertCommonRequestFields(this, smartBuffer); + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = -1; + + public data = { + /** + * The number of breakpoints in the breakpoints array. + */ + numBreakpoints: undefined as number, + /** + * An array of breakpoint IDs representing the breakpoints to be removed. + */ + breakpointIds: [], + + + //common props + packetLength: undefined as number, + requestId: undefined as number, + commandCode: COMMANDS.REMOVE_BREAKPOINTS + }; +} diff --git a/src/debugProtocol/requests/StackTraceRequest.spec.ts b/src/debugProtocol/requests/StackTraceRequest.spec.ts new file mode 100644 index 00000000..fddc0c2a --- /dev/null +++ b/src/debugProtocol/requests/StackTraceRequest.spec.ts @@ -0,0 +1,30 @@ +import { expect } from 'chai'; +import { COMMANDS, STEP_TYPE } from '../Constants'; +import { StackTraceRequest } from './StackTraceRequest'; + +describe('StackTraceRequest', () => { + it('serializes and deserializes properly', () => { + const command = StackTraceRequest.fromJson({ + requestId: 3, + threadIndex: 2 + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 3, + commandCode: COMMANDS.STACKTRACE, + + threadIndex: 2 + }); + + expect( + StackTraceRequest.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 16, // 4 bytes + requestId: 3, // 4 bytes + commandCode: COMMANDS.STACKTRACE, // 4 bytes + + threadIndex: 2 //4 bytes + }); + }); +}); diff --git a/src/debugProtocol/requests/StackTraceRequest.ts b/src/debugProtocol/requests/StackTraceRequest.ts new file mode 100644 index 00000000..c6fb69c9 --- /dev/null +++ b/src/debugProtocol/requests/StackTraceRequest.ts @@ -0,0 +1,46 @@ +import { SmartBuffer } from 'smart-buffer'; +import type { CommandData, STEP_TYPE } from '../Constants'; +import { COMMANDS } from '../Constants'; +import { protocolUtils } from '../ProtocolUtil'; +import type { ProtocolRequest } from './ProtocolRequest'; + +export class StackTraceRequest implements ProtocolRequest { + + public static fromJson(data: { requestId: number; threadIndex: number }) { + const request = new StackTraceRequest(); + protocolUtils.loadJson(request, data); + return request; + } + + public static fromBuffer(buffer: Buffer) { + const request = new StackTraceRequest(); + protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtils.loadCommonRequestFields(request, smartBuffer); + request.data.threadIndex = smartBuffer.readUInt32LE(); //thread_index + }); + return request; + } + + public toBuffer(): Buffer { + const smartBuffer = new SmartBuffer(); + + smartBuffer.writeUInt32LE(this.data.threadIndex); //thread_index + + protocolUtils.insertCommonRequestFields(this, smartBuffer); + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = -1; + + public data = { + threadIndex: undefined as number, + + //common props + packetLength: undefined as number, + requestId: undefined as number, + commandCode: COMMANDS.STACKTRACE + }; +} + diff --git a/src/debugProtocol/requests/StepRequest.spec.ts b/src/debugProtocol/requests/StepRequest.spec.ts new file mode 100644 index 00000000..cef83aeb --- /dev/null +++ b/src/debugProtocol/requests/StepRequest.spec.ts @@ -0,0 +1,33 @@ +import { expect } from 'chai'; +import { COMMANDS, STEP_TYPE } from '../Constants'; +import { StepRequest } from './StepRequest'; + +describe('StepRequest', () => { + it('serializes and deserializes properly', () => { + const command = StepRequest.fromJson({ + requestId: 3, + threadIndex: 2, + stepType: STEP_TYPE.STEP_TYPE_LINE + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 3, + commandCode: COMMANDS.STEP, + + threadIndex: 2, + stepType: STEP_TYPE.STEP_TYPE_LINE + }); + + expect( + StepRequest.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 17, // 4 bytes + requestId: 3, // 4 bytes + commandCode: COMMANDS.STEP, // 4 bytes + + stepType: STEP_TYPE.STEP_TYPE_LINE, // 1 byte + threadIndex: 2 // 4 bytes + }); + }); +}); diff --git a/src/debugProtocol/requests/StepRequest.ts b/src/debugProtocol/requests/StepRequest.ts new file mode 100644 index 00000000..f59bdbff --- /dev/null +++ b/src/debugProtocol/requests/StepRequest.ts @@ -0,0 +1,50 @@ +import { SmartBuffer } from 'smart-buffer'; +import type { CommandData, STEP_TYPE } from '../Constants'; +import { COMMANDS } from '../Constants'; +import { protocolUtils } from '../ProtocolUtil'; +import type { ProtocolRequest } from './ProtocolRequest'; + +export class StepRequest implements ProtocolRequest { + + public static fromJson(data: { requestId: number; threadIndex: number; stepType: STEP_TYPE }) { + const request = new StepRequest(); + protocolUtils.loadJson(request, data); + return request; + } + + public static fromBuffer(buffer: Buffer) { + const request = new StepRequest(); + protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtils.loadCommonRequestFields(request, smartBuffer); + request.data.threadIndex = smartBuffer.readUInt32LE(); // thread_index + request.data.stepType = smartBuffer.readUInt8(); // step_type + }); + return request; + } + + public toBuffer(): Buffer { + const smartBuffer = new SmartBuffer(); + + smartBuffer.writeUInt32LE(this.data.threadIndex); //thread_index + smartBuffer.writeUInt8(this.data.stepType); //step_type + + protocolUtils.insertCommonRequestFields(this, smartBuffer); + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = -1; + + public data = { + threadIndex: undefined as number, + stepType: undefined as STEP_TYPE, + + //common props + commandCode: COMMANDS.STEP, + packetLength: undefined as number, + requestId: undefined as number + + }; +} + diff --git a/src/debugProtocol/requests/StopRequest.spec.ts b/src/debugProtocol/requests/StopRequest.spec.ts new file mode 100644 index 00000000..2928a548 --- /dev/null +++ b/src/debugProtocol/requests/StopRequest.spec.ts @@ -0,0 +1,25 @@ +import { expect } from 'chai'; +import { COMMANDS, STEP_TYPE } from '../Constants'; +import { StopRequest } from './StopRequest'; + +describe('StopRequest', () => { + it('serializes and deserializes properly', () => { + const command = StopRequest.fromJson({ + requestId: 3 + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 3, + commandCode: COMMANDS.STOP + }); + + expect( + StopRequest.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 12, // 4 bytes + requestId: 3, // 4 bytes + commandCode: COMMANDS.STOP // 4 bytes + }); + }); +}); diff --git a/src/debugProtocol/requests/StopRequest.ts b/src/debugProtocol/requests/StopRequest.ts new file mode 100644 index 00000000..6441cad2 --- /dev/null +++ b/src/debugProtocol/requests/StopRequest.ts @@ -0,0 +1,38 @@ +import { SmartBuffer } from 'smart-buffer'; +import type { CommandData } from '../Constants'; +import { COMMANDS } from '../Constants'; +import { protocolUtils } from '../ProtocolUtil'; +import type { ProtocolRequest } from './ProtocolRequest'; + +export class StopRequest implements ProtocolRequest { + + public static fromJson(data: { requestId: number }) { + const request = new StopRequest(); + protocolUtils.loadJson(request, data); + return request; + } + + public static fromBuffer(buffer: Buffer) { + const request = new StopRequest(); + protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtils.loadCommonRequestFields(request, smartBuffer); + }); + return request; + } + + public toBuffer(): Buffer { + const smartBuffer = new SmartBuffer(); + protocolUtils.insertCommonRequestFields(this, smartBuffer); + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = -1; + + public data = { + packetLength: undefined as number, + requestId: undefined as number, + commandCode: COMMANDS.STOP + }; +} diff --git a/src/debugProtocol/requests/ThreadsRequest.spec.ts b/src/debugProtocol/requests/ThreadsRequest.spec.ts new file mode 100644 index 00000000..c9b755a1 --- /dev/null +++ b/src/debugProtocol/requests/ThreadsRequest.spec.ts @@ -0,0 +1,25 @@ +import { expect } from 'chai'; +import { COMMANDS } from '../Constants'; +import { ThreadsRequest } from './ThreadsRequest'; + +describe('ThreadsRequest', () => { + it('serializes and deserializes properly', () => { + const command = ThreadsRequest.fromJson({ + requestId: 3 + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 3, + commandCode: COMMANDS.THREADS + }); + + expect( + ThreadsRequest.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 12, // 4 bytes + requestId: 3, // 4 bytes + commandCode: COMMANDS.THREADS // 4 bytes + }); + }); +}); diff --git a/src/debugProtocol/requests/ThreadsRequest.ts b/src/debugProtocol/requests/ThreadsRequest.ts new file mode 100644 index 00000000..08aff476 --- /dev/null +++ b/src/debugProtocol/requests/ThreadsRequest.ts @@ -0,0 +1,39 @@ +import { SmartBuffer } from 'smart-buffer'; +import type { CommandData } from '../Constants'; +import { COMMANDS } from '../Constants'; +import { protocolUtils } from '../ProtocolUtil'; +import type { ProtocolRequest } from './ProtocolRequest'; + +export class ThreadsRequest implements ProtocolRequest { + + public static fromJson(data: { requestId: number }) { + const request = new ThreadsRequest(); + protocolUtils.loadJson(request, data); + return request; + } + + public static fromBuffer(buffer: Buffer) { + const request = new ThreadsRequest(); + protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtils.loadCommonRequestFields(request, smartBuffer); + }); + return request; + } + + public toBuffer(): Buffer { + const smartBuffer = new SmartBuffer(); + protocolUtils.insertCommonRequestFields(this, smartBuffer); + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = -1; + + public data = { + //common props + packetLength: undefined as number, + requestId: undefined as number, + commandCode: COMMANDS.THREADS + }; +} diff --git a/src/debugProtocol/requests/VariablesRequest.spec.ts b/src/debugProtocol/requests/VariablesRequest.spec.ts new file mode 100644 index 00000000..c60fb81d --- /dev/null +++ b/src/debugProtocol/requests/VariablesRequest.spec.ts @@ -0,0 +1,155 @@ +import { expect } from 'chai'; +import { COMMANDS } from '../Constants'; +import { VariablesRequest } from './VariablesRequest'; + +describe('VariablesRequest', () => { + it('serializes and deserializes properly for case sensitive lookups', () => { + const command = VariablesRequest.fromJson({ + requestId: 3, + getChildKeys: true, + enableCaseInsensitivityFlag: false, + stackFrameIndex: 1, + threadIndex: 2, + variablePathEntries: [ + { name: 'a', isCaseSensitive: false }, + { name: 'b', isCaseSensitive: false }, + { name: 'c', isCaseSensitive: false } + ] + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 3, + commandCode: COMMANDS.VARIABLES, + + getChildKeys: true, + enableCaseInsensitivityFlag: false, + stackFrameIndex: 1, + threadIndex: 2, + variablePathEntries: [ + { name: 'a', isCaseSensitive: true }, + { name: 'b', isCaseSensitive: true }, + { name: 'c', isCaseSensitive: true } + ] + }); + + expect( + VariablesRequest.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 31, // 4 bytes + requestId: 3, // 4 bytes + commandCode: COMMANDS.VARIABLES, // 4 bytes, + + //variable_request_flags // 1 byte + getChildKeys: true, // 0 bytes + enableCaseInsensitivityFlag: false, // 0 bytes + stackFrameIndex: 1, // 4 bytes + threadIndex: 2, // 4 bytes + // variable_path_len // 4 bytes + variablePathEntries: [ + { name: 'a', isCaseSensitive: true }, // 2 bytes + { name: 'b', isCaseSensitive: true }, // 2 bytes + { name: 'c', isCaseSensitive: true } // 2 bytes + ] + }); + }); + + it('serializes and deserializes properly for case insensitivesensitive lookups', () => { + const command = VariablesRequest.fromJson({ + requestId: 3, + getChildKeys: false, + enableCaseInsensitivityFlag: true, + stackFrameIndex: 1, + threadIndex: 2, + variablePathEntries: [ + { name: 'a', isCaseSensitive: false }, + { name: 'b', isCaseSensitive: true }, + { name: 'c', isCaseSensitive: false } + ] + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 3, + commandCode: COMMANDS.VARIABLES, + + getChildKeys: false, + enableCaseInsensitivityFlag: true, + stackFrameIndex: 1, + threadIndex: 2, + variablePathEntries: [ + { name: 'a', isCaseSensitive: false }, + { name: 'b', isCaseSensitive: true }, + { name: 'c', isCaseSensitive: false } + ] + }); + + expect( + VariablesRequest.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 34, // 4 bytes + requestId: 3, // 4 bytes + commandCode: COMMANDS.VARIABLES, // 4 bytes, + + //variable_request_flags // 1 byte + getChildKeys: false, // 0 bytes + enableCaseInsensitivityFlag: true, // 0 bytes + stackFrameIndex: 1, // 4 bytes + threadIndex: 2, // 4 bytes + // variable_path_len // 4 bytes + variablePathEntries: [ + { + name: 'a', // 2 bytes + isCaseSensitive: false // 1 byte + }, // ? + { + name: 'b', // 2 bytes + isCaseSensitive: true // 1 byte + }, // ? + { + name: 'c', // 2 bytes + isCaseSensitive: false // 1 byte + } // ? + ] + }); + }); + + it('supports empty variables list', () => { + const command = VariablesRequest.fromJson({ + requestId: 3, + getChildKeys: false, + enableCaseInsensitivityFlag: true, + stackFrameIndex: 1, + threadIndex: 2, + variablePathEntries: [] + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 3, + commandCode: COMMANDS.VARIABLES, + + getChildKeys: false, + enableCaseInsensitivityFlag: true, + stackFrameIndex: 1, + threadIndex: 2, + variablePathEntries: [] + }); + + expect( + VariablesRequest.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 25, // 4 bytes + requestId: 3, // 4 bytes + commandCode: COMMANDS.VARIABLES, // 4 bytes, + + //variable_request_flags // 1 byte + getChildKeys: false, // 0 bytes + enableCaseInsensitivityFlag: true, // 0 bytes + stackFrameIndex: 1, // 4 bytes + threadIndex: 2, // 4 bytes + // variable_path_len // 4 bytes + variablePathEntries: [] + }); + }); +}); diff --git a/src/debugProtocol/requests/VariablesRequest.ts b/src/debugProtocol/requests/VariablesRequest.ts new file mode 100644 index 00000000..8e2c8a59 --- /dev/null +++ b/src/debugProtocol/requests/VariablesRequest.ts @@ -0,0 +1,135 @@ +/* eslint-disable no-bitwise */ +import { SmartBuffer } from 'smart-buffer'; +import { COMMANDS, VARIABLE_REQUEST_FLAGS } from '../Constants'; +import { protocolUtils } from '../ProtocolUtil'; +import type { ProtocolRequest } from './ProtocolRequest'; +import * as semver from 'semver'; +import { util } from '../../util'; + +export class VariablesRequest implements ProtocolRequest { + + public static fromJson(data: { + requestId: number; + getChildKeys: boolean; + enableCaseInsensitivityFlag: boolean; + threadIndex: number; + stackFrameIndex: number; + variablePathEntries: Array<{ + name: string; + isCaseSensitive: boolean; + }>; + }) { + const request = new VariablesRequest(); + protocolUtils.loadJson(request, data); + request.data.variablePathEntries ??= []; + // force all variables to case SENSITIVE if using the flag is disabled (just for consistency purposes), + // as it won't actually be sent + if (!request.data.enableCaseInsensitivityFlag) { + for (const entry of request.data.variablePathEntries) { + entry.isCaseSensitive = true; + } + } + return request; + } + + public static fromBuffer(buffer: Buffer) { + const request = new VariablesRequest(); + protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtils.loadCommonRequestFields(request, smartBuffer); + + const variableRequestFlags = smartBuffer.readUInt8(); // variable_request_flags + + request.data.getChildKeys = !!(variableRequestFlags & VARIABLE_REQUEST_FLAGS.GET_CHILD_KEYS); + request.data.enableCaseInsensitivityFlag = !!(variableRequestFlags & VARIABLE_REQUEST_FLAGS.CASE_SENSITIVITY_OPTIONS); + request.data.threadIndex = smartBuffer.readUInt32LE(); // thread_index + request.data.stackFrameIndex = smartBuffer.readUInt32LE(); // stack_frame_index + const variablePathLength = smartBuffer.readUInt32LE(); // variable_path_len + request.data.variablePathEntries = []; + if (variablePathLength > 0) { + for (let i = 0; i < variablePathLength; i++) { + request.data.variablePathEntries.push({ + name: util.readStringNT(smartBuffer), // variable_path_entries - optional + isCaseSensitive: true + }); + } + + //get the case sensitive settings for each part of the path + if (request.data.enableCaseInsensitivityFlag) { + for (let i = 0; i < variablePathLength; i++) { + //0 means case SENSITIVE lookup, 1 means case INsensitive lookup + request.data.variablePathEntries[i].isCaseSensitive = smartBuffer.readUInt8() === 0 ? true : false; + } + } + } + }); + return request; + } + + public toBuffer(): Buffer { + const smartBuffer = new SmartBuffer(); + + //build the flags var + let variableRequestFlags = 0; + variableRequestFlags |= this.data.getChildKeys ? VARIABLE_REQUEST_FLAGS.GET_CHILD_KEYS : 0; + variableRequestFlags |= this.data.enableCaseInsensitivityFlag ? VARIABLE_REQUEST_FLAGS.CASE_SENSITIVITY_OPTIONS : 0; + + smartBuffer.writeUInt8(variableRequestFlags); // variable_request_flags + smartBuffer.writeUInt32LE(this.data.threadIndex); // thread_index + smartBuffer.writeUInt32LE(this.data.stackFrameIndex); // stack_frame_index + smartBuffer.writeUInt32LE(this.data.variablePathEntries.length); // variable_path_len + for (const entry of this.data.variablePathEntries) { + smartBuffer.writeStringNT(entry.name); // variable_path_entries - optional + } + if (this.data.enableCaseInsensitivityFlag) { + for (const entry of this.data.variablePathEntries) { + //0 means case SENSITIVE lookup, 1 means case INsensitive lookup + smartBuffer.writeUInt8(entry.isCaseSensitive ? 0 : 1); + } + } + + protocolUtils.insertCommonRequestFields(this, smartBuffer); + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = -1; + + public data = { + /** + * Indicates whether the VARIABLES response includes the child keys for container types like lists and associative arrays. If this is set to true (0x01), the VARIABLES response include the child keys. + */ + getChildKeys: undefined as boolean, + + /** + * Enables the client application to send path_force_case_insensitive data for each variable + */ + enableCaseInsensitivityFlag: undefined as boolean, + + /** + * The index of the thread containing the variable. + */ + threadIndex: undefined as number, + /** + * The index of the frame returned from the STACKTRACE command. + * The 0 index contains the first function called; nframes-1 contains the last. + * This indexing does not match the order of the frames returned from the STACKTRACE command + */ + stackFrameIndex: undefined as number, + + /** + * A set of one or more path entries to the variable to be inspected. For example, `m.top.myarray[6]` can be accessed with `["m","top","myarray","6"]`. + * + * If no path is specified, the variables accessible from the specified stack frame are returned. + */ + variablePathEntries: undefined as Array<{ + name: string; + isCaseSensitive: boolean; + }>, + + //common props + packetLength: undefined as number, + requestId: undefined as number, + commandCode: COMMANDS.VARIABLES + }; +} diff --git a/src/debugProtocol/responses/HandshakeResponse.ts b/src/debugProtocol/responses/HandshakeResponse.ts index 13fbbe87..f459e34f 100644 --- a/src/debugProtocol/responses/HandshakeResponse.ts +++ b/src/debugProtocol/responses/HandshakeResponse.ts @@ -12,12 +12,12 @@ export class HandshakeResponse extends ProtocolResponse { if (Buffer.isBuffer(arg)) { this.loadFromBuffer(arg); } else { - this.loadFromJson(arg); + this.loadJson(arg); } } loadFromBuffer(buffer: Buffer) { - this.bufferLoaderHelper(buffer, 20, (smartBuffer: SmartBuffer) => { + this.bufferLoaderHelper(buffer, 20, null, (smartBuffer: SmartBuffer) => { this.data.magic = util.readStringNT(smartBuffer); // magic_number this.data.majorVersion = smartBuffer.readInt32LE(); // protocol_major_version this.data.minorVersion = smartBuffer.readInt32LE(); // protocol_minor_version @@ -31,8 +31,9 @@ export class HandshakeResponse extends ProtocolResponse { }); } - private loadFromJson(data: HandshakeResponse['data']) { + protected loadJson(data: HandshakeResponse['data']) { this.data = data; + this.success = true; } public toBuffer() { diff --git a/src/debugProtocol/responses/HandshakeResponseV3.ts b/src/debugProtocol/responses/HandshakeResponseV3.ts index 1c597fad..722688fb 100644 --- a/src/debugProtocol/responses/HandshakeResponseV3.ts +++ b/src/debugProtocol/responses/HandshakeResponseV3.ts @@ -12,12 +12,12 @@ export class HandshakeResponseV3 extends ProtocolResponse { if (Buffer.isBuffer(arg)) { this.loadFromBuffer(arg); } else { - this.loadFromJson(arg); + this.loadJson(arg); } } private loadFromBuffer(buffer: Buffer) { - this.bufferLoaderHelper(buffer, 20, (smartBuffer: SmartBuffer) => { + this.bufferLoaderHelper(buffer, 20, null, (smartBuffer: SmartBuffer) => { this.data.magic = util.readStringNT(smartBuffer); // debugger_magic this.data.majorVersion = smartBuffer.readInt32LE(); // protocol_major_version this.data.minorVersion = smartBuffer.readInt32LE(); // protocol_minor_version @@ -39,12 +39,13 @@ export class HandshakeResponseV3 extends ProtocolResponse { if (!semver.satisfies(this.getVersion(), '>=3.0.0')) { throw new Error(`unsupported version ${this.getVersion()}`); } - return true; + this.watchPacketLength = true; }); } - private loadFromJson(data: HandshakeResponseV3['data']) { - this.data = data; + protected loadJson(data: HandshakeResponseV3['data']) { + super.loadJson(data); + this.watchPacketLength = true; } /** @@ -72,7 +73,7 @@ export class HandshakeResponseV3 extends ProtocolResponse { return buffer.toBuffer(); } - public watchPacketLength = true; // this will always be true for the new protocol versions + public watchPacketLength = false; // this will always be true for the new protocol versions public success = false; public readOffset = 0; public requestId = 0; diff --git a/src/debugProtocol/responses/ProtocolResponse.ts b/src/debugProtocol/responses/ProtocolResponse.ts index 31c83c28..c9985da9 100644 --- a/src/debugProtocol/responses/ProtocolResponse.ts +++ b/src/debugProtocol/responses/ProtocolResponse.ts @@ -17,6 +17,14 @@ export abstract class ProtocolResponse { */ public abstract toBuffer(): Buffer; + /** + * Load the payload in json form + */ + protected loadJson(data: any) { + this.data = data; + this.success = true; + } + /** * Contains the actual response data */ @@ -26,14 +34,14 @@ export abstract class ProtocolResponse { * Helper function for buffer loading. * Handles things like try/catch, setting buffer read offset, etc */ - protected bufferLoaderHelper(buffer: Buffer, minByteLength: number, processor: (buffer: SmartBuffer) => boolean) { + protected bufferLoaderHelper(buffer: Buffer, minByteLength: number, data: Record | string | number | null, processor: (buffer: SmartBuffer) => boolean | void) { // Required size of this processor if (buffer.byteLength >= minByteLength) { try { let smartBuffer = SmartBuffer.fromBuffer(buffer); //have the processor consume the requred bytes. - this.success = processor(smartBuffer) ?? true; + this.success = (processor(smartBuffer) ?? true) as boolean; this.readOffset = smartBuffer.readOffset; } catch (error) { @@ -43,4 +51,12 @@ export abstract class ProtocolResponse { } } } + + /** + * Given a buffer, prepend the packet_length based on the size of the buffer and return the new buffer + */ + protected getBufferWithPacketLength(smartBuffer: SmartBuffer) { + smartBuffer.insertUInt32LE(smartBuffer.length + 4, 0); + return smartBuffer.toBuffer(); + } } diff --git a/src/debugProtocol/responses/updates/AllThreadsStoppedUpdateResponse.ts b/src/debugProtocol/responses/updates/AllThreadsStoppedUpdateResponse.ts new file mode 100644 index 00000000..0fa3b955 --- /dev/null +++ b/src/debugProtocol/responses/updates/AllThreadsStoppedUpdateResponse.ts @@ -0,0 +1,114 @@ +import { SmartBuffer } from 'smart-buffer'; +import { STOP_REASONS, UPDATE_TYPES } from '../../Constants'; +import { util } from '../../../util'; +import { ProtocolResponse } from '../ProtocolResponse'; +import { UpdateResponse } from './UpdateResponse'; + +/** + * All threads are stopped and an ALL_THREADS_STOPPED message is sent to the debugging client. + * + * The data field includes information on why the threads were stopped. + */ +export class AllThreadsStoppedUpdateResponse extends UpdateResponse { + public constructor(arg: Buffer | Pick) { + super(); + if (Buffer.isBuffer(arg)) { + this.loadBuffer(arg); + } else { + this.loadJson(arg); + } + } + + private loadBuffer(buffer: Buffer) { + this.bufferLoaderHelper(buffer, 12, UPDATE_TYPES.ALL_THREADS_STOPPED, (smartBuffer) => { + //bail if it's not the update type we wanted + if (this.data.updateType !== UPDATE_TYPES.ALL_THREADS_STOPPED) { + return false; + } + this.data.primaryThreadIndex = smartBuffer.readInt32LE(); + this.data.stopReason = getStopReason(smartBuffer.readUInt8()); + this.data.stopReasonDetail = util.readStringNT(smartBuffer); + }); + } + + public toBuffer() { + let buffer = new SmartBuffer(); + buffer.writeUInt32LE(this.data.requestId); // request_id + buffer.writeUInt32LE(this.data.errorCode); // error_code + buffer.writeUInt32LE(this.data.updateType); // update_type + + buffer.writeUInt32LE(this.data.primaryThreadIndex); // update_type + buffer.writeUInt8(this.data.stopReason); // stop_reason + buffer.writeStringNT(this.data.stopReasonDetail); //stop_reason_detail + + return this.getBufferWithPacketLength(buffer); + } + + public data = { + // DebuggerUpdate fields + packetLength: undefined as number, + requestId: undefined as number, + errorCode: undefined as number, + updateType: UPDATE_TYPES.ALL_THREADS_STOPPED, + + //ALL_THREADS_STOPPED fields + primaryThreadIndex: -1, + stopReason: -1, + stopReasonDetail: undefined as string + }; +} + +export class ThreadsStopped { + + constructor(bufferReader: SmartBuffer) { + if (bufferReader.length >= bufferReader.readOffset + 6) { + this.primaryThreadIndex = bufferReader.readInt32LE(); + this.stopReason = getStopReason(bufferReader.readUInt8()); + this.stopReasonDetail = util.readStringNT(bufferReader); + this.success = true; + } + } + public success = false; + + // response fields + public primaryThreadIndex = -1; + public stopReason = -1; + public stopReasonDetail: string; +} + +export class ThreadAttached { + + constructor(bufferReader: SmartBuffer) { + if (bufferReader.length >= bufferReader.readOffset + 6) { + this.threadIndex = bufferReader.readInt32LE(); + this.stopReason = getStopReason(bufferReader.readUInt8()); + this.stopReasonDetail = util.readStringNT(bufferReader); + this.success = true; + } + } + public success = false; + + // response fields + public threadIndex = -1; + public stopReason = -1; + public stopReasonDetail: string; +} + +function getStopReason(value: number): STOP_REASONS { + switch (value) { + case STOP_REASONS.BREAK: + return STOP_REASONS.BREAK; + case STOP_REASONS.NORMAL_EXIT: + return STOP_REASONS.NORMAL_EXIT; + case STOP_REASONS.NOT_STOPPED: + return STOP_REASONS.NOT_STOPPED; + case STOP_REASONS.RUNTIME_ERROR: + return STOP_REASONS.RUNTIME_ERROR; + case STOP_REASONS.STOP_STATEMENT: + return STOP_REASONS.STOP_STATEMENT; + case STOP_REASONS.UNDEFINED: + return STOP_REASONS.UNDEFINED; + default: + return STOP_REASONS.UNDEFINED; + } +} diff --git a/src/debugProtocol/responses/updates/UpdateResponse.ts b/src/debugProtocol/responses/updates/UpdateResponse.ts new file mode 100644 index 00000000..9f49898b --- /dev/null +++ b/src/debugProtocol/responses/updates/UpdateResponse.ts @@ -0,0 +1,43 @@ +import type { SmartBuffer } from 'smart-buffer'; +import type { UPDATE_TYPES } from '../../Constants'; +import { ProtocolResponse } from '../ProtocolResponse'; + +export abstract class UpdateResponse extends ProtocolResponse { + + protected bufferLoaderHelper(buffer: Buffer, minByteLength: number, updateType: UPDATE_TYPES, processor: (buffer: SmartBuffer) => boolean | void) { + //extract the common update information + super.bufferLoaderHelper(buffer, minByteLength + 12, null, (smartBuffer) => { + this.data.packetLength = smartBuffer.readUInt32LE(); // packet_length + this.data.requestId = smartBuffer.readUInt32LE(); // request_id + this.data.errorCode = smartBuffer.readUInt32LE(); // error_code + + if (smartBuffer.length < this.data.packetLength) { + throw new Error(`Incomplete packet. Bytes received: ${smartBuffer.length}/${this.data.packetLength}`); + } + + // requestId 0 means this is an update. + if (this.data.requestId === 0) { + this.data.updateType = smartBuffer.readUInt32LE(); + + //if this is not the update type we want, return false + if (this.data.updateType !== updateType) { + return false; + } + + } else { + //not an update. We should not proceed any further. + throw new Error('This is not an update'); + } + //call the specific update handler + return processor(smartBuffer); + }); + } + + public abstract data: { + packetLength: number; + requestId: number; + errorCode: number; + updateType: number; + }; +} + diff --git a/src/debugProtocol/server/DebugProtocolServer.ts b/src/debugProtocol/server/DebugProtocolServer.ts index aa8fbfae..a724750f 100644 --- a/src/debugProtocol/server/DebugProtocolServer.ts +++ b/src/debugProtocol/server/DebugProtocolServer.ts @@ -2,7 +2,7 @@ import { EventEmitter } from 'eventemitter3'; import * as Net from 'net'; import { SmartBuffer } from 'smart-buffer'; import { ActionQueue } from '../../managers/ActionQueue'; -import { HandshakeRequestV3 } from '../requests/HandshakeRequestV3'; +import { HandshakeRequest } from '../requests/HandshakeRequest'; import type { ProtocolRequest } from '../requests/ProtocolRequest'; import { HandshakeResponse, HandshakeResponseV3 } from '../responses'; import type { ProtocolResponse } from '../responses/ProtocolResponse'; @@ -111,7 +111,7 @@ export class DebugProtocolServer { let request: ProtocolRequest; //if we haven't seen the handshake yet, look for the handshake first if (!this.isHandshakeComplete) { - request = new HandshakeRequestV3(buffer); + request = new HandshakeRequest(buffer); if (request.success) { return request; } @@ -121,7 +121,7 @@ export class DebugProtocolServer { } private getResponse(request: ProtocolRequest) { - if (request instanceof HandshakeRequestV3) { + if (request instanceof HandshakeRequest) { return new HandshakeResponseV3({ magic: this.magic, majorVersion: 3, @@ -158,12 +158,12 @@ export class DebugProtocolServer { //if the plugin didn't provide a response, we need to try our best to make one (we only support a few...plugins should provide most of them) - if (request instanceof HandshakeRequestV3 && !response) { + if (request instanceof HandshakeRequest && !response) { response = this.getResponse(request); } //the client should send a magic string to kick off the debugger - if (response instanceof HandshakeResponseV3 && request.data.magic === this.magic) { + if ((response instanceof HandshakeResponse || response instanceof HandshakeResponseV3) && request.data.magic === this.magic) { this.isHandshakeComplete = true; } @@ -175,17 +175,25 @@ export class DebugProtocolServer { * Send a response from the server to the client. This involves writing the response buffer to the client socket */ private async sendResponse(response: ProtocolResponse) { - await this.plugins.emit('beforeSendResponse', { + const event = await this.plugins.emit('beforeSendResponse', { server: this, response: response }); - this.client.write(response.toBuffer()); + this.client.write(event.response.toBuffer()); await this.plugins.emit('afterSendResponse', { server: this, - response: response + response: event.response }); + return event.response; + } + + /** + * Send an update from the server to the client. This can be things like ALL_THREADS_STOPPED + */ + public sendUpdate(update: ProtocolResponse) { + return this.sendResponse(update); } /** From 9cb3d0976bca03b1cf576594f9d1769a409176b8 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Sat, 8 Oct 2022 22:30:29 -0400 Subject: [PATCH 06/74] Refactored response classes --- src/adapters/DebugProtocolAdapter.spec.ts | 4 +- src/debugProtocol/Constants.ts | 3 +- src/debugProtocol/Debugger.spec.ts | 20 +- src/debugProtocol/Debugger.ts | 328 +++++++++--------- src/debugProtocol/ProtocolUtil.ts | 94 +++-- src/debugProtocol/events/ProtocolEvent.ts | 57 +++ .../requests/AddBreakpointsRequest.spec.ts | 2 +- .../requests/AddBreakpointsRequest.ts | 10 +- .../AddConditionalBreakpointsRequest.spec.ts | 2 +- .../AddConditionalBreakpointsRequest.ts | 8 +- .../requests/ContinueRequest.spec.ts | 2 +- .../{ => events}/requests/ContinueRequest.ts | 8 +- .../requests/ExecuteRequest.spec.ts | 2 +- .../{ => events}/requests/ExecuteRequest.ts | 8 +- .../requests/ExitChannelRequest.spec.ts | 2 +- .../requests/ExitChannelRequest.ts | 8 +- .../{ => events}/requests/HandshakeRequest.ts | 18 +- .../requests/ListBreakpointsRequest.spec.ts | 2 +- .../requests/ListBreakpointsRequest.ts | 7 +- .../requests/RemoveBreakpointsCommand.spec.ts | 2 +- .../requests/RemoveBreakpointsRequest.ts | 8 +- .../requests/StackTraceRequest.spec.ts | 2 +- .../requests/StackTraceRequest.ts | 7 +- .../{ => events}/requests/StepRequest.spec.ts | 2 +- .../{ => events}/requests/StepRequest.ts | 8 +- .../{ => events}/requests/StopRequest.spec.ts | 2 +- .../{ => events}/requests/StopRequest.ts | 8 +- .../requests/ThreadsRequest.spec.ts | 2 +- .../{ => events}/requests/ThreadsRequest.ts | 8 +- .../requests/VariablesRequest.spec.ts | 2 +- .../{ => events}/requests/VariablesRequest.ts | 9 +- .../events/responses/GenericResponse.spec.ts | 37 ++ .../events/responses/GenericResponse.ts | 43 +++ .../responses/GenericResponseV3.spec.ts | 60 ++++ .../events/responses/GenericResponseV3.ts | 44 +++ .../responses/HandshakeResponse.spec.ts | 49 +++ .../events/responses/HandshakeResponse.ts | 83 +++++ .../responses/HandshakeResponseV3.spec.ts | 83 +++++ .../events/responses/HandshakeResponseV3.ts | 117 +++++++ .../responses/ListBreakpointsResponse.spec.ts | 168 +++++++++ .../responses/ListBreakpointsResponse.ts | 120 +++++++ .../updates/AllThreadsStoppedUpdate.spec.ts | 38 ++ .../updates/AllThreadsStoppedUpdate.ts} | 77 ++-- .../events/updates/UpdateResponse.ts | 13 + .../zzresponsesOld}/AddBreakpointsResponse.ts | 2 +- .../BreakpointErrorResponse.spec.ts | 2 +- .../BreakpointErrorUpdateResponse.ts | 4 +- .../zzresponsesOld}/ConnectIOPortResponse.ts | 2 +- .../zzresponsesOld}/ExecuteResponseV3.ts | 2 +- .../RemoveBreakpointsResponse.ts | 2 +- .../zzresponsesOld}/StackTraceResponse.ts | 2 +- .../zzresponsesOld}/StackTraceResponseV3.ts | 2 +- .../zzresponsesOld}/ThreadsResponse.ts | 4 +- .../zzresponsesOld}/UndefinedResponse.ts | 2 +- .../zzresponsesOld}/UpdateThreadsResponse.ts | 4 +- .../zzresponsesOld}/VariableResponse.spec.ts | 2 +- .../zzresponsesOld}/VariableResponse.ts | 4 +- .../zzresponsesOld}/index.ts | 6 +- .../responseCreationHelpers.spec.ts | 6 +- src/debugProtocol/requests/ProtocolRequest.ts | 28 -- .../responses/HandshakeResponse.spec.ts | 57 --- .../responses/HandshakeResponse.ts | 69 ---- .../responses/HandshakeResponseV3.spec.ts | 89 ----- .../responses/HandshakeResponseV3.ts | 104 ------ .../responses/ListBreakpointsResponse.spec.ts | 133 ------- .../responses/ListBreakpointsResponse.ts | 72 ---- .../responses/ProtocolEvent.spec.ts | 48 --- src/debugProtocol/responses/ProtocolEvent.ts | 35 -- .../responses/ProtocolEventV3.ts | 39 --- .../responses/ProtocolResponse.ts | 62 ---- .../responses/updates/UpdateResponse.ts | 43 --- .../server/DebugProtocolServer.ts | 21 +- src/debugProtocol/server/ProtocolPlugin.ts | 4 +- src/index.ts | 2 +- 74 files changed, 1320 insertions(+), 1109 deletions(-) create mode 100644 src/debugProtocol/events/ProtocolEvent.ts rename src/debugProtocol/{ => events}/requests/AddBreakpointsRequest.spec.ts (98%) rename src/debugProtocol/{ => events}/requests/AddBreakpointsRequest.ts (90%) rename src/debugProtocol/{ => events}/requests/AddConditionalBreakpointsRequest.spec.ts (98%) rename src/debugProtocol/{ => events}/requests/AddConditionalBreakpointsRequest.ts (95%) rename src/debugProtocol/{ => events}/requests/ContinueRequest.spec.ts (93%) rename src/debugProtocol/{ => events}/requests/ContinueRequest.ts (83%) rename src/debugProtocol/{ => events}/requests/ExecuteRequest.spec.ts (95%) rename src/debugProtocol/{ => events}/requests/ExecuteRequest.ts (89%) rename src/debugProtocol/{ => events}/requests/ExitChannelRequest.spec.ts (94%) rename src/debugProtocol/{ => events}/requests/ExitChannelRequest.ts (83%) rename src/debugProtocol/{ => events}/requests/HandshakeRequest.ts (57%) rename src/debugProtocol/{ => events}/requests/ListBreakpointsRequest.spec.ts (94%) rename src/debugProtocol/{ => events}/requests/ListBreakpointsRequest.ts (83%) rename src/debugProtocol/{ => events}/requests/RemoveBreakpointsCommand.spec.ts (95%) rename src/debugProtocol/{ => events}/requests/RemoveBreakpointsRequest.ts (90%) rename src/debugProtocol/{ => events}/requests/StackTraceRequest.spec.ts (93%) rename src/debugProtocol/{ => events}/requests/StackTraceRequest.ts (85%) rename src/debugProtocol/{ => events}/requests/StepRequest.spec.ts (94%) rename src/debugProtocol/{ => events}/requests/StepRequest.ts (86%) rename src/debugProtocol/{ => events}/requests/StopRequest.spec.ts (92%) rename src/debugProtocol/{ => events}/requests/StopRequest.ts (82%) rename src/debugProtocol/{ => events}/requests/ThreadsRequest.spec.ts (93%) rename src/debugProtocol/{ => events}/requests/ThreadsRequest.ts (82%) rename src/debugProtocol/{ => events}/requests/VariablesRequest.spec.ts (99%) rename src/debugProtocol/{ => events}/requests/VariablesRequest.ts (95%) create mode 100644 src/debugProtocol/events/responses/GenericResponse.spec.ts create mode 100644 src/debugProtocol/events/responses/GenericResponse.ts create mode 100644 src/debugProtocol/events/responses/GenericResponseV3.spec.ts create mode 100644 src/debugProtocol/events/responses/GenericResponseV3.ts create mode 100644 src/debugProtocol/events/responses/HandshakeResponse.spec.ts create mode 100644 src/debugProtocol/events/responses/HandshakeResponse.ts create mode 100644 src/debugProtocol/events/responses/HandshakeResponseV3.spec.ts create mode 100644 src/debugProtocol/events/responses/HandshakeResponseV3.ts create mode 100644 src/debugProtocol/events/responses/ListBreakpointsResponse.spec.ts create mode 100644 src/debugProtocol/events/responses/ListBreakpointsResponse.ts create mode 100644 src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts rename src/debugProtocol/{responses/updates/AllThreadsStoppedUpdateResponse.ts => events/updates/AllThreadsStoppedUpdate.ts} (54%) create mode 100644 src/debugProtocol/events/updates/UpdateResponse.ts rename src/debugProtocol/{responses => events/zzresponsesOld}/AddBreakpointsResponse.ts (66%) rename src/debugProtocol/{responses => events/zzresponsesOld}/BreakpointErrorResponse.spec.ts (97%) rename src/debugProtocol/{responses => events/zzresponsesOld}/BreakpointErrorUpdateResponse.ts (97%) rename src/debugProtocol/{responses => events/zzresponsesOld}/ConnectIOPortResponse.ts (96%) rename src/debugProtocol/{responses => events/zzresponsesOld}/ExecuteResponseV3.ts (98%) rename src/debugProtocol/{responses => events/zzresponsesOld}/RemoveBreakpointsResponse.ts (67%) rename src/debugProtocol/{responses => events/zzresponsesOld}/StackTraceResponse.ts (98%) rename src/debugProtocol/{responses => events/zzresponsesOld}/StackTraceResponseV3.ts (98%) rename src/debugProtocol/{responses => events/zzresponsesOld}/ThreadsResponse.ts (97%) rename src/debugProtocol/{responses => events/zzresponsesOld}/UndefinedResponse.ts (96%) rename src/debugProtocol/{responses => events/zzresponsesOld}/UpdateThreadsResponse.ts (96%) rename src/debugProtocol/{responses => events/zzresponsesOld}/VariableResponse.spec.ts (99%) rename src/debugProtocol/{responses => events/zzresponsesOld}/VariableResponse.ts (98%) rename src/debugProtocol/{responses => events/zzresponsesOld}/index.ts (65%) rename src/debugProtocol/{responses => events/zzresponsesOld}/responseCreationHelpers.spec.ts (98%) delete mode 100644 src/debugProtocol/requests/ProtocolRequest.ts delete mode 100644 src/debugProtocol/responses/HandshakeResponse.spec.ts delete mode 100644 src/debugProtocol/responses/HandshakeResponse.ts delete mode 100644 src/debugProtocol/responses/HandshakeResponseV3.spec.ts delete mode 100644 src/debugProtocol/responses/HandshakeResponseV3.ts delete mode 100644 src/debugProtocol/responses/ListBreakpointsResponse.spec.ts delete mode 100644 src/debugProtocol/responses/ListBreakpointsResponse.ts delete mode 100644 src/debugProtocol/responses/ProtocolEvent.spec.ts delete mode 100644 src/debugProtocol/responses/ProtocolEvent.ts delete mode 100644 src/debugProtocol/responses/ProtocolEventV3.ts delete mode 100644 src/debugProtocol/responses/ProtocolResponse.ts delete mode 100644 src/debugProtocol/responses/updates/UpdateResponse.ts diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index d723565c..5287f83b 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -3,8 +3,8 @@ import { expect } from 'chai'; import { Debugger } from '../debugProtocol/Debugger'; import { DebugProtocolAdapter } from './DebugProtocolAdapter'; import { createSandbox } from 'sinon'; -import type { VariableInfo } from '../debugProtocol/responses'; -import { VariableResponse } from '../debugProtocol/responses'; +import type { VariableInfo } from '../debugProtocol/events/zzresponsesOld'; +import { VariableResponse } from '../debugProtocol/events/zzresponsesOld'; import { ERROR_CODES } from './../debugProtocol/Constants'; const sinon = createSandbox(); diff --git a/src/debugProtocol/Constants.ts b/src/debugProtocol/Constants.ts index e1949b4d..17169659 100644 --- a/src/debugProtocol/Constants.ts +++ b/src/debugProtocol/Constants.ts @@ -139,8 +139,9 @@ export function getUpdateType(value: number): UPDATE_TYPES { /** * Common properties found on all Command `.data` objects */ -export interface CommandData { +export interface RequestData { packetLength: number; requestId: number; commandCode: number; } + \ No newline at end of file diff --git a/src/debugProtocol/Debugger.spec.ts b/src/debugProtocol/Debugger.spec.ts index 47cbea7c..428024bc 100644 --- a/src/debugProtocol/Debugger.spec.ts +++ b/src/debugProtocol/Debugger.spec.ts @@ -3,18 +3,18 @@ import { expect } from 'chai'; import type { SmartBuffer } from 'smart-buffer'; import { MockDebugProtocolServer } from './MockDebugProtocolServer.spec'; import { createSandbox } from 'sinon'; -import { createHandShakeResponse, createHandShakeResponseV3, createProtocolEventV3 } from './responses/responseCreationHelpers.spec'; -import { HandshakeResponse, HandshakeResponseV3, ProtocolEventV3 } from './responses'; +import { createHandShakeResponse, createHandShakeResponseV3, createProtocolEventV3 } from './events/zzresponsesOld/responseCreationHelpers.spec'; +import { HandshakeResponse, HandshakeResponseV3, ProtocolEventV3 } from './events/zzresponsesOld'; import { ERROR_CODES, STOP_REASONS, UPDATE_TYPES, VARIABLE_REQUEST_FLAGS } from './Constants'; import { DebugProtocolServer, DebugProtocolServerOptions } from './server/DebugProtocolServer'; import * as portfinder from 'portfinder'; import { util } from '../util'; import type { BeforeSendResponseEvent, ProtocolPlugin, ProvideResponseEvent } from './server/ProtocolPlugin'; import { Handler, OnClientConnectedEvent, ProvideRequestEvent } from './server/ProtocolPlugin'; -import type { ProtocolResponse } from './responses/ProtocolResponse'; -import type { ProtocolRequest } from './requests/ProtocolRequest'; -import { HandshakeRequest } from './requests/HandshakeRequest'; -import { AllThreadsStoppedUpdateResponse } from './responses/updates/AllThreadsStoppedUpdateResponse'; +import type { ProtocolResponse } from './events/zzresponsesOld/ProtocolResponse'; +import type { ProtocolRequest } from './events/requests/ProtocolRequest'; +import { HandshakeRequest } from './events/requests/HandshakeRequest'; +import { AllThreadsStoppedUpdateResponse } from './events/updates/AllThreadsStoppedUpdate'; const sinon = createSandbox(); @@ -212,7 +212,7 @@ describe.skip('Debugger new tests', () => { it('handles v3 handshake', async () => { //these are false by default expect(client.watchPacketLength).to.be.equal(false); - expect(client.handshakeComplete).to.be.equal(false); + expect(client.isHandshakeComplete).to.be.equal(false); await client.connect(); expect(plugin.responses[0].data).to.eql({ @@ -225,7 +225,7 @@ describe.skip('Debugger new tests', () => { //version 3.0 includes packet length, so these should be true now expect(client.watchPacketLength).to.be.equal(true); - expect(client.handshakeComplete).to.be.equal(true); + expect(client.isHandshakeComplete).to.be.equal(true); }); it('throws on magic mismatch', async () => { @@ -248,7 +248,7 @@ describe.skip('Debugger new tests', () => { it('handles legacy handshake', async () => { expect(client.watchPacketLength).to.be.equal(false); - expect(client.handshakeComplete).to.be.equal(false); + expect(client.isHandshakeComplete).to.be.equal(false); plugin.pushResponse(new HandshakeResponse({ magic: Debugger.DEBUGGER_MAGIC, @@ -260,7 +260,7 @@ describe.skip('Debugger new tests', () => { await client.connect(); expect(client.watchPacketLength).to.be.equal(false); - expect(client.handshakeComplete).to.be.equal(true); + expect(client.isHandshakeComplete).to.be.equal(true); }); it('handles events after handshake', async () => { diff --git a/src/debugProtocol/Debugger.ts b/src/debugProtocol/Debugger.ts index a5e72ea5..0619d0db 100644 --- a/src/debugProtocol/Debugger.ts +++ b/src/debugProtocol/Debugger.ts @@ -4,11 +4,9 @@ import * as semver from 'semver'; import type { ThreadAttached, ThreadsStopped -} from './responses'; +} from './events/zzresponsesOld'; import { ConnectIOPortResponse, - HandshakeResponse, - HandshakeResponseV3, ProtocolEvent, ProtocolEventV3, StackTraceResponse, @@ -17,30 +15,32 @@ import { UndefinedResponse, UpdateThreadsResponse, VariableResponse -} from './responses'; +} from './events/zzresponsesOld'; import { PROTOCOL_ERROR_CODES, COMMANDS, STEP_TYPE, STOP_REASONS, VARIABLE_REQUEST_FLAGS, ERROR_CODES, UPDATE_TYPES } from './Constants'; import { SmartBuffer } from 'smart-buffer'; import { logger } from '../logging'; -import { ExecuteResponseV3 } from './responses/ExecuteResponseV3'; -import { ListBreakpointsResponse } from './responses/ListBreakpointsResponse'; -import { AddBreakpointsResponse } from './responses/AddBreakpointsResponse'; -import { RemoveBreakpointsResponse } from './responses/RemoveBreakpointsResponse'; +import { ExecuteResponseV3 } from './events/zzresponsesOld/ExecuteResponseV3'; +import { ListBreakpointsResponse } from './events/responses/ListBreakpointsResponse'; +import { AddBreakpointsResponse } from './events/zzresponsesOld/AddBreakpointsResponse'; +import { RemoveBreakpointsResponse } from './events/zzresponsesOld/RemoveBreakpointsResponse'; import { util } from '../util'; -import { BreakpointErrorUpdateResponse } from './responses/BreakpointErrorUpdateResponse'; -import type { ProtocolRequest } from './requests/ProtocolRequest'; -import { ContinueRequest } from './requests/ContinueRequest'; -import { StopRequest } from './requests/StopRequest'; -import { ExitChannelRequest } from './requests/ExitChannelRequest'; -import { ProtocolResponse } from './responses/ProtocolResponse'; -import { StepRequest } from './requests/StepRequest'; -import { RemoveBreakpointsRequest } from './requests/RemoveBreakpointsRequest'; -import { ListBreakpointsRequest } from './requests/ListBreakpointsRequest'; -import { VariablesRequest } from './requests/VariablesRequest'; -import { StackTraceRequest } from './requests/StackTraceRequest'; -import { ThreadsRequest } from './requests/ThreadsRequest'; -import { ExecuteRequest } from './requests/ExecuteRequest'; -import { AddBreakpointsRequest } from './requests/AddBreakpointsRequest'; -import { AddConditionalBreakpointsRequest } from './requests/AddConditionalBreakpointsRequest'; +import { BreakpointErrorUpdateResponse } from './events/zzresponsesOld/BreakpointErrorUpdateResponse'; +import { ContinueRequest } from './events/requests/ContinueRequest'; +import { StopRequest } from './events/requests/StopRequest'; +import { ExitChannelRequest } from './events/requests/ExitChannelRequest'; +import { StepRequest } from './events/requests/StepRequest'; +import { RemoveBreakpointsRequest } from './events/requests/RemoveBreakpointsRequest'; +import { ListBreakpointsRequest } from './events/requests/ListBreakpointsRequest'; +import { VariablesRequest } from './events/requests/VariablesRequest'; +import { StackTraceRequest } from './events/requests/StackTraceRequest'; +import { ThreadsRequest } from './events/requests/ThreadsRequest'; +import { ExecuteRequest } from './events/requests/ExecuteRequest'; +import { AddBreakpointsRequest } from './events/requests/AddBreakpointsRequest'; +import { AddConditionalBreakpointsRequest } from './events/requests/AddConditionalBreakpointsRequest'; +import type { ProtocolRequest, ProtocolResponse, ProtocolUpdate } from './events/ProtocolEvent'; +import { HandshakeResponse } from './events/responses/HandshakeResponse'; +import { HandshakeResponseV3 } from './events/responses/HandshakeResponseV3'; +import { HandshakeRequest } from './events/requests/HandshakeRequest'; export class Debugger { @@ -66,7 +66,7 @@ export class Debugger { public static DEBUGGER_MAGIC = 'bsdebug'; // 64-bit = [b'bsdebug\0' little-endian] public scriptTitle: string; - public handshakeComplete = false; + public isHandshakeComplete = false; public connectedToIoPort = false; public watchPacketLength = false; public protocolVersion: string; @@ -76,7 +76,7 @@ export class Debugger { private emitter = new EventEmitter(); private controllerClient: Net.Socket; private ioClient: Net.Socket; - private unhandledData: Buffer; + private buffer = Buffer.alloc(0); private stopped = false; private totalRequests = 0; private activeRequests1 = new Map(); @@ -111,7 +111,6 @@ export class Debugger { public once(eventName: 'app-exit' | 'cannot-continue' | 'close' | 'start'): Promise; public once(eventName: 'data'): Promise; public once(eventName: 'runtime-error' | 'suspend'): Promise; - public once(eventName: 'connected'): Promise; public once(eventName: 'io-output'): Promise; public once(eventName: 'protocol-version'): Promise; public once(eventName: 'handshake-verified'): Promise; @@ -124,13 +123,9 @@ export class Debugger { }); } - /** - * Subscribe to various events - */ public on(eventName: 'app-exit' | 'cannot-continue' | 'close' | 'start', handler: () => void); - public on(eventName: 'data', handler: (data: any) => void); + public on(eventName: 'data', handler: (data: ProtocolResponse | ProtocolUpdate) => void); public on(eventName: 'runtime-error' | 'suspend', handler: (data: UpdateThreadsResponse) => void); - public on(eventName: 'connected', handler: (connected: boolean) => void); public on(eventName: 'io-output', handler: (output: string) => void); public on(eventName: 'protocol-version', handler: (data: ProtocolVersionDetails) => void); public on(eventName: 'handshake-verified', handler: (data: HandshakeResponse) => void); @@ -143,8 +138,9 @@ export class Debugger { }; } + private emit(eventName: 'response', data: { request: ProtocolRequest; response: ProtocolResponse }); private emit(eventName: 'suspend' | 'runtime-error', data: UpdateThreadsResponse); - private emit(eventName: 'app-exit' | 'cannot-continue' | 'close' | 'connected' | 'data' | 'handshake-verified' | 'io-output' | 'protocol-version' | 'start', data?); + private emit(eventName: 'app-exit' | 'cannot-continue' | 'close' | 'data' | 'handshake-verified' | 'io-output' | 'protocol-version' | 'start', data?); private emit(eventName: string, data?) { //emit these events on next tick, otherwise they will be processed immediately which could cause issues setTimeout(() => { @@ -189,19 +185,15 @@ export class Debugger { // If there is no error, the server has accepted the request and created a new dedicated control socket this.controllerClient = await this.establishControllerConnection(); - this.controllerClient.on('data', (buffer) => { - if (this.unhandledData) { - this.unhandledData = Buffer.concat([this.unhandledData, buffer]); - } else { - this.unhandledData = buffer; - } + this.controllerClient.on('data', (data) => { + this.buffer = Buffer.concat([this.buffer, data]); - this.logger.debug(`on('data'): incoming bytes`, buffer.length); - const startBufferSize = this.unhandledData.length; + this.logger.debug(`on('data'): incoming bytes`, data.length); + const startBufferSize = this.buffer.length; - this.parseUnhandledData(this.unhandledData); + this.process(); - const endBufferSize = this.unhandledData?.length ?? 0; + const endBufferSize = this.buffer?.length ?? 0; this.logger.debug(`buffer size before:`, startBufferSize, ', buffer size after:', endBufferSize, ', bytes consumed:', startBufferSize - endBufferSize); }); @@ -218,17 +210,17 @@ export class Debugger { }); //send the magic, which triggers the debug session - this.sendMagic(); - - //wait for the handshake response from the device - const isConnected = await this.once('connected'); - return isConnected; - } - - private sendMagic() { - let buffer = new SmartBuffer({ size: Buffer.byteLength(Debugger.DEBUGGER_MAGIC) + 1 }).writeStringNT(Debugger.DEBUGGER_MAGIC).toBuffer(); this.logger.log('Sending magic to server'); - this.controllerClient.write(buffer); + //send the handshake request, and wait for the handshake response from the device + const response = await this.makeRequest( + HandshakeRequest.fromJson({ + magic: Debugger.DEBUGGER_MAGIC + }) + ); + + this.verifyHandshake(response); + this.isHandshakeComplete = true; + return response.success; } public async continue() { @@ -431,10 +423,10 @@ export class Debugger { this.activeRequests1.set(requestId, request); return new Promise((resolve, reject) => { - let unsubscribe = this.on('data', (data) => { - if (data.requestId === requestId) { + let unsubscribe = this.on('data', (event) => { + if (event.data.requestId === requestId) { unsubscribe(); - resolve(data as T); + resolve(event as T); } }); @@ -447,105 +439,128 @@ export class Debugger { }); } - private parseUnhandledData(buffer: Buffer): boolean { - if (buffer.length < 1) { + private process(): boolean { + if (this.buffer.length < 1) { // short circuit if the buffer is empty return false; } - if (this.handshakeComplete) { - let debuggerRequestResponse = this.watchPacketLength ? new ProtocolEventV3(buffer) : new ProtocolEvent(buffer); - let packetLength = debuggerRequestResponse.packetLength; - let slicedBuffer = packetLength ? buffer.slice(4) : buffer; + const event = this.getResponseOrUpdate(this.buffer); - this.logger.log(`incoming bytes: ${buffer.length}`, debuggerRequestResponse); - if (debuggerRequestResponse.success) { - if (debuggerRequestResponse.requestId > this.totalRequests) { - this.removedProcessedBytes(debuggerRequestResponse, slicedBuffer, packetLength); - return true; - } + //we got a response + if (event) { + //find any matching request for this response/update + const request = this.activeRequests1.get(event.data.requestId); - if (debuggerRequestResponse.errorCode !== ERROR_CODES.OK) { - this.logger.error(debuggerRequestResponse.errorCode, debuggerRequestResponse); - this.removedProcessedBytes(debuggerRequestResponse, buffer, packetLength); - return true; - } + if (request) { + // we received a response for this request, so remove the request from the list + this.activeRequests1.delete(event.data.requestId); + } - if (debuggerRequestResponse.updateType > 0) { - this.logger.log('Update Type:', UPDATE_TYPES[debuggerRequestResponse.updateType]); - switch (debuggerRequestResponse.updateType) { - case UPDATE_TYPES.IO_PORT_OPENED: - return this.connectToIoPort(new ConnectIOPortResponse(slicedBuffer), buffer, packetLength); - case UPDATE_TYPES.ALL_THREADS_STOPPED: - case UPDATE_TYPES.THREAD_ATTACHED: - let debuggerUpdateThreads = new UpdateThreadsResponse(slicedBuffer); - if (debuggerUpdateThreads.success) { - this.handleThreadsUpdate(debuggerUpdateThreads); - this.removedProcessedBytes(debuggerUpdateThreads, slicedBuffer, packetLength); - return true; - } - return false; - case UPDATE_TYPES.UNDEF: - return this.checkResponse(new UndefinedResponse(slicedBuffer), buffer, packetLength); - case UPDATE_TYPES.BREAKPOINT_ERROR: - const response = new BreakpointErrorUpdateResponse(slicedBuffer); - //we do nothing with breakpoint errors at this time. - return this.checkResponse(response, buffer, packetLength); - case UPDATE_TYPES.COMPILE_ERROR: - return this.checkResponse(new UndefinedResponse(slicedBuffer), buffer, packetLength); - default: - return this.checkResponse(new UndefinedResponse(slicedBuffer), buffer, packetLength); - } - } else { - const request = this.activeRequests1.get(debuggerRequestResponse.requestId); - this.logger.log('Command Type:', COMMANDS[request.data.commandCode]); - switch (request.data.commandCode) { - case COMMANDS.STOP: - case COMMANDS.CONTINUE: - case COMMANDS.STEP: - case COMMANDS.EXIT_CHANNEL: - this.removedProcessedBytes(debuggerRequestResponse, buffer, packetLength); - return true; - case COMMANDS.EXECUTE: - return this.checkResponse(new ExecuteResponseV3(slicedBuffer), buffer, packetLength); - case COMMANDS.ADD_BREAKPOINTS: - case COMMANDS.ADD_CONDITIONAL_BREAKPOINTS: - return this.checkResponse(new AddBreakpointsResponse(slicedBuffer), buffer, packetLength); - case COMMANDS.LIST_BREAKPOINTS: - return this.checkResponse(new ListBreakpointsResponse(slicedBuffer), buffer, packetLength); - case COMMANDS.REMOVE_BREAKPOINTS: - return this.checkResponse(new RemoveBreakpointsResponse(slicedBuffer), buffer, packetLength); - case COMMANDS.VARIABLES: - return this.checkResponse(new VariableResponse(slicedBuffer), buffer, packetLength); - case COMMANDS.STACKTRACE: - return this.checkResponse( - packetLength ? new StackTraceResponseV3(slicedBuffer) : new StackTraceResponse(slicedBuffer), - buffer, - packetLength); - case COMMANDS.THREADS: - return this.checkResponse(new ThreadsResponse(slicedBuffer), buffer, packetLength); - default: - return this.checkResponse(debuggerRequestResponse, buffer, packetLength); - } - } + this.emit('data', event); + + //remove the processed data from the buffer + this.buffer = this.buffer.slice(event.readOffset); + this.logger.debug('[raw]', `requestId=${event.data.requestId}`, request, event.constructor?.name ?? '', event); + + } + + // process again (will run recursively until the buffer is empty) + this.process(); + } + + /** + * Given a buffer, try to parse into a specific ProtocolResponse or ProtocolUpdate + */ + private getResponseOrUpdate(buffer: Buffer): ProtocolResponse { + //if we haven't seen a handshake yet, try to convert the buffer into a handshake + if (!this.isHandshakeComplete) { + //try building the v3 handshake response first + let handshakev3 = HandshakeResponseV3.fromBuffer(buffer); + if (handshakev3.success) { + return handshakev3; } - } else { - let debuggerHandshake: HandshakeResponse | HandshakeResponseV3; - debuggerHandshake = new HandshakeResponseV3(buffer); - this.logger.log(`incoming bytes: ${buffer.length}`, debuggerHandshake); + //we didn't get a v3 handshake. try building an older handshake response + let handshake = HandshakeResponse.fromBuffer(buffer); + if (handshake.success) { + return handshake; + } + } - if (!debuggerHandshake.success) { - debuggerHandshake = new HandshakeResponse(buffer); + let debuggerRequestResponse = this.watchPacketLength ? new ProtocolEventV3(buffer) : new ProtocolEvent(buffer); + let packetLength = debuggerRequestResponse.packetLength; + let slicedBuffer = packetLength ? buffer.slice(4) : buffer; + + this.logger.log(`incoming bytes: ${buffer.length}`, debuggerRequestResponse); + if (debuggerRequestResponse.success) { + if (debuggerRequestResponse.requestId > this.totalRequests) { + this.removedProcessedBytes(debuggerRequestResponse, slicedBuffer, packetLength); + return true; } - if (debuggerHandshake.success) { - this.handshakeComplete = true; - this.verifyHandshake(debuggerHandshake); - this.removedProcessedBytes(debuggerHandshake, buffer); - //once the handshake is complete, we have successfully "connected" - this.emit('connected', true); + if (debuggerRequestResponse.errorCode !== ERROR_CODES.OK) { + this.logger.error(debuggerRequestResponse.errorCode, debuggerRequestResponse); + this.removedProcessedBytes(debuggerRequestResponse, buffer, packetLength); return true; } + + if (debuggerRequestResponse.updateType > 0) { + this.logger.log('Update Type:', UPDATE_TYPES[debuggerRequestResponse.updateType]); + switch (debuggerRequestResponse.updateType) { + case UPDATE_TYPES.IO_PORT_OPENED: + return this.connectToIoPort(new ConnectIOPortResponse(slicedBuffer), buffer, packetLength); + case UPDATE_TYPES.ALL_THREADS_STOPPED: + case UPDATE_TYPES.THREAD_ATTACHED: + let debuggerUpdateThreads = new UpdateThreadsResponse(slicedBuffer); + if (debuggerUpdateThreads.success) { + this.handleThreadsUpdate(debuggerUpdateThreads); + this.removedProcessedBytes(debuggerUpdateThreads, slicedBuffer, packetLength); + return true; + } + return false; + case UPDATE_TYPES.UNDEF: + return this.checkResponse(new UndefinedResponse(slicedBuffer), buffer, packetLength); + case UPDATE_TYPES.BREAKPOINT_ERROR: + const response = new BreakpointErrorUpdateResponse(slicedBuffer); + //we do nothing with breakpoint errors at this time. + return this.checkResponse(response, buffer, packetLength); + case UPDATE_TYPES.COMPILE_ERROR: + return this.checkResponse(new UndefinedResponse(slicedBuffer), buffer, packetLength); + default: + return this.checkResponse(new UndefinedResponse(slicedBuffer), buffer, packetLength); + } + } else { + const request = this.activeRequests1.get(debuggerRequestResponse.requestId); + this.logger.log('Command Type:', COMMANDS[request.data.commandCode]); + switch (request.data.commandCode) { + case COMMANDS.STOP: + case COMMANDS.CONTINUE: + case COMMANDS.STEP: + case COMMANDS.EXIT_CHANNEL: + this.removedProcessedBytes(debuggerRequestResponse, buffer, packetLength); + return true; + case COMMANDS.EXECUTE: + return this.checkResponse(new ExecuteResponseV3(slicedBuffer), buffer, packetLength); + case COMMANDS.ADD_BREAKPOINTS: + case COMMANDS.ADD_CONDITIONAL_BREAKPOINTS: + return this.checkResponse(new AddBreakpointsResponse(slicedBuffer), buffer, packetLength); + case COMMANDS.LIST_BREAKPOINTS: + return this.checkResponse(new ListBreakpointsResponse(slicedBuffer), buffer, packetLength); + case COMMANDS.REMOVE_BREAKPOINTS: + return this.checkResponse(new RemoveBreakpointsResponse(slicedBuffer), buffer, packetLength); + case COMMANDS.VARIABLES: + return this.checkResponse(new VariableResponse(slicedBuffer), buffer, packetLength); + case COMMANDS.STACKTRACE: + return this.checkResponse( + packetLength ? new StackTraceResponseV3(slicedBuffer) : new StackTraceResponse(slicedBuffer), + buffer, + packetLength); + case COMMANDS.THREADS: + return this.checkResponse(new ThreadsResponse(slicedBuffer), buffer, packetLength); + default: + return this.checkResponse(debuggerRequestResponse, buffer, packetLength); + } + } } return false; @@ -561,27 +576,30 @@ export class Debugger { return false; } - private removedProcessedBytes(responseHandler: { requestId: number; readOffset: number }, unhandledData: Buffer, packetLength = 0) { - const activeRequest = this.activeRequests1.get(responseHandler.requestId); - if (responseHandler.requestId > 0 && activeRequest) { - this.activeRequests1.delete(responseHandler.requestId); + private removedProcessedBytes(response: { requestId?: number; readOffset: number }, unhandledData: Buffer, packetLength = 0) { + const request = this.activeRequests1.get(response.requestId); + if (response?.requestId > 0 && request) { + this.activeRequests1.delete(response.requestId); } - this.emit('data', responseHandler); + this.emit('data', response); - this.unhandledData = unhandledData.slice(packetLength ? packetLength : responseHandler.readOffset); - this.logger.debug('[raw]', `requestId=${responseHandler?.requestId}`, activeRequest, (responseHandler as any)?.constructor?.name ?? '', responseHandler); - this.parseUnhandledData(this.unhandledData); + this.buffer = unhandledData.slice(packetLength ? packetLength : response.readOffset); + this.logger.debug('[raw]', `requestId=${response?.requestId}`, request, (response as any)?.constructor?.name ?? '', response); + this.process(this.buffer); } - private verifyHandshake(debuggerHandshake: HandshakeResponse | HandshakeResponseV3): boolean { - const magicIsValid = (Debugger.DEBUGGER_MAGIC === debuggerHandshake.data.magic); - if (magicIsValid) { + /** + * Verify all the handshake data + */ + private verifyHandshake(response: HandshakeResponse | HandshakeResponseV3): boolean { + if (Debugger.DEBUGGER_MAGIC === response.data.magic) { this.logger.log('Magic is valid.'); - this.protocolVersion = debuggerHandshake.getVersion(); + + this.protocolVersion = response.data.protocolVersion; this.logger.log('Protocol Version:', this.protocolVersion); - this.watchPacketLength = debuggerHandshake.watchPacketLength; + this.watchPacketLength = response.watchPacketLength; let handshakeVerified = true; @@ -610,7 +628,7 @@ export class Debugger { this.emit('handshake-verified', handshakeVerified); return handshakeVerified; } else { - this.logger.log('Closing connection due to bad debugger magic', debuggerHandshake.data.magic); + this.logger.log('Closing connection due to bad debugger magic', response.data.magic); this.emit('handshake-verified', false); this.shutdown('close'); return false; diff --git a/src/debugProtocol/ProtocolUtil.ts b/src/debugProtocol/ProtocolUtil.ts index 799f3a69..b4eaad5d 100644 --- a/src/debugProtocol/ProtocolUtil.ts +++ b/src/debugProtocol/ProtocolUtil.ts @@ -1,7 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; -import type { CommandData as RequestData } from './Constants'; -import type { ProtocolRequest } from './requests/ProtocolRequest'; -import type { ProtocolResponse } from './responses/ProtocolResponse'; +import type { UPDATE_TYPES } from './Constants'; +import type { ProtocolRequest, ProtocolResponse, ProtocolUpdate } from './events/ProtocolEvent'; export class ProtocolUtils { @@ -20,43 +19,98 @@ export class ProtocolUtils { * Helper function for buffer loading. * Handles things like try/catch, setting buffer read offset, etc */ - public bufferLoaderHelper(event: { success: boolean; readOffset: number }, buffer: Buffer, minByteLength: number, processor: (buffer: SmartBuffer) => boolean | void) { + public bufferLoaderHelper(event: { success: boolean; readOffset: number; data?: { packetLength?: number } }, buffer: Buffer, minByteLength: number, processor: (buffer: SmartBuffer) => boolean | void) { // Required size of this processor - if (buffer.byteLength >= minByteLength) { - try { + try { + if (buffer.byteLength >= minByteLength) { let smartBuffer = SmartBuffer.fromBuffer(buffer); //have the processor consume the requred bytes. event.success = (processor(smartBuffer) ?? true) as boolean; - event.readOffset = smartBuffer.readOffset; - } catch (error) { - // Could not parse - event.readOffset = 0; - event.success = false; + //if the event has a packet length, use THAT as the read offset. Otherwise, set the offset to the end of the read position of the buffer + if (event.success) { + if (!event.readOffset) { + event.readOffset = event.data.packetLength ?? smartBuffer.readOffset; + } + } } + } catch (error) { + // Could not parse + event.readOffset = 0; + event.success = false; } return event; } /** - * Load the common `Command` (i.e. `DebuggerRequest`) fields + * Load the common DebuggerRequest fields */ - public loadCommonRequestFields(command: ProtocolRequest | ProtocolResponse, smartBuffer: SmartBuffer) { - command.data.packetLength = smartBuffer.readUInt32LE(); // packet_length - command.data.requestId = smartBuffer.readUInt32LE(); // request_id - command.data.commandCode = smartBuffer.readUInt32LE(); // command_code + public loadCommonRequestFields(request: ProtocolRequest, smartBuffer: SmartBuffer) { + request.data.packetLength = smartBuffer.readUInt32LE(); // packet_length + request.data.requestId = smartBuffer.readUInt32LE(); // request_id + request.data.commandCode = smartBuffer.readUInt32LE(); // command_code + } + + /** + * Load the common DebuggerResponse + */ + public loadCommonResponseFields(request: ProtocolResponse, smartBuffer: SmartBuffer) { + request.data.packetLength = smartBuffer.readUInt32LE(); // packet_length + request.data.requestId = smartBuffer.readUInt32LE(); // request_id + request.data.errorCode = smartBuffer.readUInt32LE(); // error_code + } + + public loadCommonUpdateFields(update: ProtocolUpdate, smartBuffer: SmartBuffer, updateType: UPDATE_TYPES) { + update.data.packetLength = smartBuffer.readUInt32LE(); // packet_length + update.data.requestId = smartBuffer.readUInt32LE(); // request_id + update.data.errorCode = smartBuffer.readUInt32LE(); // error_code + // requestId 0 means this is an update. + if (update.data.requestId === 0) { + update.data.updateType = smartBuffer.readUInt32LE(); + + //if this is not the update type we want, return false + if (update.data.updateType !== updateType) { + return false; + } + + } else { + //not an update. We should not proceed any further. + throw new Error('This is not an update'); + } } /** * Inserts the common command fields to the beginning of the buffer, and computes * the correct `packet_length` value. */ - public insertCommonRequestFields(command: ProtocolRequest | ProtocolResponse, smartBuffer: SmartBuffer) { - smartBuffer.insertUInt32LE(command.data.commandCode, 0); // command_code - An enum representing the debugging command being sent. See the COMMANDS enum - smartBuffer.insertUInt32LE(command.data.requestId, 0); // request_id - The ID of the debugger request (must be >=1). This ID is included in the debugger response. + public insertCommonRequestFields(request: ProtocolRequest, smartBuffer: SmartBuffer) { + smartBuffer.insertUInt32LE(request.data.commandCode, 0); // command_code - An enum representing the debugging command being sent. See the COMMANDS enum + smartBuffer.insertUInt32LE(request.data.requestId, 0); // request_id - The ID of the debugger request (must be >=1). This ID is included in the debugger response. smartBuffer.insertUInt32LE(smartBuffer.writeOffset + 4, 0); // packet_length - The size of the packet to be sent. - command.data.packetLength = smartBuffer.writeOffset; + request.data.packetLength = smartBuffer.writeOffset; + return smartBuffer; + } + + public insertCommonResponseFields(response: ProtocolResponse, smartBuffer: SmartBuffer) { + smartBuffer.insertUInt32LE(response.data.errorCode, 0); // error_code + smartBuffer.insertUInt32LE(response.data.requestId, 0); // request_id + smartBuffer.insertUInt32LE(smartBuffer.writeOffset + 4, 0); // packet_length + response.data.packetLength = smartBuffer.writeOffset; + return smartBuffer; + } + + + /** + * Inserts the common response fields to the beginning of the buffer, and computes + * the correct `packet_length` value. + */ + public insertCommonUpdateFields(update: ProtocolUpdate, smartBuffer: SmartBuffer) { + smartBuffer.insertUInt32LE(update.data.updateType, 0); // update_type + smartBuffer.insertUInt32LE(update.data.errorCode, 0); // error_code + smartBuffer.insertUInt32LE(update.data.requestId, 0); // request_id + smartBuffer.insertUInt32LE(smartBuffer.writeOffset + 4, 0); // packet_length + update.data.packetLength = smartBuffer.writeOffset; return smartBuffer; } } diff --git a/src/debugProtocol/events/ProtocolEvent.ts b/src/debugProtocol/events/ProtocolEvent.ts new file mode 100644 index 00000000..23f382b9 --- /dev/null +++ b/src/debugProtocol/events/ProtocolEvent.ts @@ -0,0 +1,57 @@ +import type { COMMANDS, UPDATE_TYPES } from '../Constants'; + +export interface ProtocolEvent { + /** + * Was this event successful in parsing/ingesting the data in its constructor + */ + success: boolean; + + /** + * The number of bytes that were read from a buffer if this was a success + */ + readOffset: number; + + /** + * Serialize this event into Convert the current object into the debug protocol binary format, + * stored in a `Buffer` + */ + toBuffer(): Buffer; + + /** + * Contains the actual event data + */ + data: TData; +} + +/** + * The fields that every ProtocolRequest must have + */ +export interface ProtocolRequestData { + //common props + packetLength: number; + requestId: number; + commandCode: COMMANDS; +} +export type ProtocolRequest = ProtocolEvent; + +/** + * The fields that every ProtocolUpdateResponse must have + */ +export interface ProtocolUpdateData { + packetLength: number; + requestId: number; + errorCode: number; + updateType: UPDATE_TYPES; +} +export type ProtocolUpdate = ProtocolEvent; + +/** + * The fields that every ProtocolResponse must have + */ +export interface ProtocolResponseData { + packetLength: number; + requestId: number; + errorCode: number; +} +export type ProtocolResponse = ProtocolEvent; + diff --git a/src/debugProtocol/requests/AddBreakpointsRequest.spec.ts b/src/debugProtocol/events/requests/AddBreakpointsRequest.spec.ts similarity index 98% rename from src/debugProtocol/requests/AddBreakpointsRequest.spec.ts rename to src/debugProtocol/events/requests/AddBreakpointsRequest.spec.ts index a2b5d9b6..fe218564 100644 --- a/src/debugProtocol/requests/AddBreakpointsRequest.spec.ts +++ b/src/debugProtocol/events/requests/AddBreakpointsRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS } from '../Constants'; +import { COMMANDS } from '../../Constants'; import { AddBreakpointsRequest } from './AddBreakpointsRequest'; describe('AddBreakpointsRequest', () => { diff --git a/src/debugProtocol/requests/AddBreakpointsRequest.ts b/src/debugProtocol/events/requests/AddBreakpointsRequest.ts similarity index 90% rename from src/debugProtocol/requests/AddBreakpointsRequest.ts rename to src/debugProtocol/events/requests/AddBreakpointsRequest.ts index 13b5bcd1..7c69e4e9 100644 --- a/src/debugProtocol/requests/AddBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/AddBreakpointsRequest.ts @@ -1,9 +1,9 @@ import { SmartBuffer } from 'smart-buffer'; -import { util } from '../../util'; -import type { CommandData } from '../Constants'; -import { COMMANDS } from '../Constants'; -import { protocolUtils } from '../ProtocolUtil'; -import type { ProtocolRequest } from './ProtocolRequest'; +import { util } from '../../../util'; +import type { RequestData } from '../../Constants'; +import { COMMANDS } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; +import type { ProtocolRequest } from '../ProtocolEvent'; export class AddBreakpointsRequest implements ProtocolRequest { diff --git a/src/debugProtocol/requests/AddConditionalBreakpointsRequest.spec.ts b/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.spec.ts similarity index 98% rename from src/debugProtocol/requests/AddConditionalBreakpointsRequest.spec.ts rename to src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.spec.ts index 99620b73..c8440d11 100644 --- a/src/debugProtocol/requests/AddConditionalBreakpointsRequest.spec.ts +++ b/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS } from '../Constants'; +import { COMMANDS } from '../../Constants'; import { AddConditionalBreakpointsRequest } from './AddConditionalBreakpointsRequest'; describe('AddConditionalBreakpointsRequest', () => { diff --git a/src/debugProtocol/requests/AddConditionalBreakpointsRequest.ts b/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts similarity index 95% rename from src/debugProtocol/requests/AddConditionalBreakpointsRequest.ts rename to src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts index f989887f..bea4247e 100644 --- a/src/debugProtocol/requests/AddConditionalBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts @@ -1,8 +1,8 @@ import { SmartBuffer } from 'smart-buffer'; -import { util } from '../../util'; -import { COMMANDS } from '../Constants'; -import { protocolUtils } from '../ProtocolUtil'; -import type { ProtocolRequest } from './ProtocolRequest'; +import { util } from '../../../util'; +import { COMMANDS } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; +import type { ProtocolRequest } from '../ProtocolEvent'; export class AddConditionalBreakpointsRequest implements ProtocolRequest { diff --git a/src/debugProtocol/requests/ContinueRequest.spec.ts b/src/debugProtocol/events/requests/ContinueRequest.spec.ts similarity index 93% rename from src/debugProtocol/requests/ContinueRequest.spec.ts rename to src/debugProtocol/events/requests/ContinueRequest.spec.ts index 683facab..ea8fdd24 100644 --- a/src/debugProtocol/requests/ContinueRequest.spec.ts +++ b/src/debugProtocol/events/requests/ContinueRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS } from '../Constants'; +import { COMMANDS } from '../../Constants'; import { ContinueRequest } from './ContinueRequest'; describe('ContinueRequest', () => { diff --git a/src/debugProtocol/requests/ContinueRequest.ts b/src/debugProtocol/events/requests/ContinueRequest.ts similarity index 83% rename from src/debugProtocol/requests/ContinueRequest.ts rename to src/debugProtocol/events/requests/ContinueRequest.ts index 00d16c86..55513e1c 100644 --- a/src/debugProtocol/requests/ContinueRequest.ts +++ b/src/debugProtocol/events/requests/ContinueRequest.ts @@ -1,8 +1,8 @@ import { SmartBuffer } from 'smart-buffer'; -import type { CommandData } from '../Constants'; -import { COMMANDS } from '../Constants'; -import { protocolUtils } from '../ProtocolUtil'; -import type { ProtocolRequest } from './ProtocolRequest'; +import type { RequestData } from '../../Constants'; +import { COMMANDS } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; +import type { ProtocolRequest } from '../ProtocolEvent'; export class ContinueRequest implements ProtocolRequest { diff --git a/src/debugProtocol/requests/ExecuteRequest.spec.ts b/src/debugProtocol/events/requests/ExecuteRequest.spec.ts similarity index 95% rename from src/debugProtocol/requests/ExecuteRequest.spec.ts rename to src/debugProtocol/events/requests/ExecuteRequest.spec.ts index 43f94384..dcc423e7 100644 --- a/src/debugProtocol/requests/ExecuteRequest.spec.ts +++ b/src/debugProtocol/events/requests/ExecuteRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS } from '../Constants'; +import { COMMANDS } from '../../Constants'; import { ExecuteRequest } from './ExecuteRequest'; describe('ExecuteRequest', () => { diff --git a/src/debugProtocol/requests/ExecuteRequest.ts b/src/debugProtocol/events/requests/ExecuteRequest.ts similarity index 89% rename from src/debugProtocol/requests/ExecuteRequest.ts rename to src/debugProtocol/events/requests/ExecuteRequest.ts index 31a09807..da9be107 100644 --- a/src/debugProtocol/requests/ExecuteRequest.ts +++ b/src/debugProtocol/events/requests/ExecuteRequest.ts @@ -1,8 +1,8 @@ import { SmartBuffer } from 'smart-buffer'; -import type { CommandData } from '../Constants'; -import { COMMANDS } from '../Constants'; -import { protocolUtils } from '../ProtocolUtil'; -import type { ProtocolRequest } from './ProtocolRequest'; +import type { RequestData } from '../../Constants'; +import { COMMANDS } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; +import type { ProtocolRequest } from '../ProtocolEvent'; export class ExecuteRequest implements ProtocolRequest { diff --git a/src/debugProtocol/requests/ExitChannelRequest.spec.ts b/src/debugProtocol/events/requests/ExitChannelRequest.spec.ts similarity index 94% rename from src/debugProtocol/requests/ExitChannelRequest.spec.ts rename to src/debugProtocol/events/requests/ExitChannelRequest.spec.ts index 399cd618..34452130 100644 --- a/src/debugProtocol/requests/ExitChannelRequest.spec.ts +++ b/src/debugProtocol/events/requests/ExitChannelRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS } from '../Constants'; +import { COMMANDS } from '../../Constants'; import { ExitChannelRequest } from './ExitChannelRequest'; describe('ExitChannelRequest', () => { diff --git a/src/debugProtocol/requests/ExitChannelRequest.ts b/src/debugProtocol/events/requests/ExitChannelRequest.ts similarity index 83% rename from src/debugProtocol/requests/ExitChannelRequest.ts rename to src/debugProtocol/events/requests/ExitChannelRequest.ts index 1f464463..9813d098 100644 --- a/src/debugProtocol/requests/ExitChannelRequest.ts +++ b/src/debugProtocol/events/requests/ExitChannelRequest.ts @@ -1,8 +1,8 @@ import { SmartBuffer } from 'smart-buffer'; -import type { CommandData } from '../Constants'; -import { COMMANDS } from '../Constants'; -import { protocolUtils } from '../ProtocolUtil'; -import type { ProtocolRequest } from './ProtocolRequest'; +import type { RequestData } from '../../Constants'; +import { COMMANDS } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; +import type { ProtocolRequest } from '../ProtocolEvent'; export class ExitChannelRequest implements ProtocolRequest { diff --git a/src/debugProtocol/requests/HandshakeRequest.ts b/src/debugProtocol/events/requests/HandshakeRequest.ts similarity index 57% rename from src/debugProtocol/requests/HandshakeRequest.ts rename to src/debugProtocol/events/requests/HandshakeRequest.ts index 76ee1a47..4be71620 100644 --- a/src/debugProtocol/requests/HandshakeRequest.ts +++ b/src/debugProtocol/events/requests/HandshakeRequest.ts @@ -1,7 +1,8 @@ import { SmartBuffer } from 'smart-buffer'; -import { util } from '../../util'; -import { protocolUtils } from '../ProtocolUtil'; -import type { ProtocolRequest } from './ProtocolRequest'; +import { util } from '../../../util'; +import { protocolUtils } from '../../ProtocolUtil'; +import type { ProtocolRequest } from '../ProtocolEvent'; +import type { COMMANDS } from '../../Constants'; /** * The initial handshake sent by the client. This is just the `magic` to initiate the debug protocol session @@ -15,7 +16,7 @@ export class HandshakeRequest implements ProtocolRequest { return request; } - public static loadBuffer(buffer: Buffer) { + public static fromBuffer(buffer: Buffer) { const request = new HandshakeRequest(); protocolUtils.bufferLoaderHelper(request, buffer, 0, (smartBuffer) => { request.data.magic = util.readStringNT(smartBuffer); @@ -34,6 +35,13 @@ export class HandshakeRequest implements ProtocolRequest { public readOffset = -1; public data = { - magic: undefined as string + magic: undefined as string, + + //handshake requests aren't actually structured like like normal requests, but since they're the only unique type of request, + //just add dummy data for those fields + packetLength: undefined as number, + //hardcode the max integer value. This must be the same value as the HandshakeResponse class + requestId: Number.MAX_SAFE_INTEGER, + commandCode: undefined as COMMANDS }; } diff --git a/src/debugProtocol/requests/ListBreakpointsRequest.spec.ts b/src/debugProtocol/events/requests/ListBreakpointsRequest.spec.ts similarity index 94% rename from src/debugProtocol/requests/ListBreakpointsRequest.spec.ts rename to src/debugProtocol/events/requests/ListBreakpointsRequest.spec.ts index 0b401594..794f2862 100644 --- a/src/debugProtocol/requests/ListBreakpointsRequest.spec.ts +++ b/src/debugProtocol/events/requests/ListBreakpointsRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS } from '../Constants'; +import { COMMANDS } from '../../Constants'; import { ListBreakpointsRequest } from './ListBreakpointsRequest'; describe('ListBreakpointsRequest', () => { diff --git a/src/debugProtocol/requests/ListBreakpointsRequest.ts b/src/debugProtocol/events/requests/ListBreakpointsRequest.ts similarity index 83% rename from src/debugProtocol/requests/ListBreakpointsRequest.ts rename to src/debugProtocol/events/requests/ListBreakpointsRequest.ts index 6fa0b598..d1c8f8d1 100644 --- a/src/debugProtocol/requests/ListBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/ListBreakpointsRequest.ts @@ -1,8 +1,7 @@ import { SmartBuffer } from 'smart-buffer'; -import type { CommandData } from '../Constants'; -import { COMMANDS } from '../Constants'; -import { protocolUtils } from '../ProtocolUtil'; -import type { ProtocolRequest } from './ProtocolRequest'; +import { COMMANDS } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; +import type { ProtocolRequest } from '../ProtocolEvent'; export class ListBreakpointsRequest implements ProtocolRequest { diff --git a/src/debugProtocol/requests/RemoveBreakpointsCommand.spec.ts b/src/debugProtocol/events/requests/RemoveBreakpointsCommand.spec.ts similarity index 95% rename from src/debugProtocol/requests/RemoveBreakpointsCommand.spec.ts rename to src/debugProtocol/events/requests/RemoveBreakpointsCommand.spec.ts index b159c08e..982fea63 100644 --- a/src/debugProtocol/requests/RemoveBreakpointsCommand.spec.ts +++ b/src/debugProtocol/events/requests/RemoveBreakpointsCommand.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS } from '../Constants'; +import { COMMANDS } from '../../Constants'; import { RemoveBreakpointsRequest } from './RemoveBreakpointsRequest'; describe('RemoveBreakpointsRequest', () => { diff --git a/src/debugProtocol/requests/RemoveBreakpointsRequest.ts b/src/debugProtocol/events/requests/RemoveBreakpointsRequest.ts similarity index 90% rename from src/debugProtocol/requests/RemoveBreakpointsRequest.ts rename to src/debugProtocol/events/requests/RemoveBreakpointsRequest.ts index 34efbab6..0fc55114 100644 --- a/src/debugProtocol/requests/RemoveBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/RemoveBreakpointsRequest.ts @@ -1,8 +1,8 @@ import { SmartBuffer } from 'smart-buffer'; -import type { CommandData } from '../Constants'; -import { COMMANDS } from '../Constants'; -import { protocolUtils } from '../ProtocolUtil'; -import type { ProtocolRequest } from './ProtocolRequest'; +import type { RequestData } from '../../Constants'; +import { COMMANDS } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; +import type { ProtocolRequest } from '../ProtocolEvent'; export class RemoveBreakpointsRequest implements ProtocolRequest { diff --git a/src/debugProtocol/requests/StackTraceRequest.spec.ts b/src/debugProtocol/events/requests/StackTraceRequest.spec.ts similarity index 93% rename from src/debugProtocol/requests/StackTraceRequest.spec.ts rename to src/debugProtocol/events/requests/StackTraceRequest.spec.ts index fddc0c2a..7fab9635 100644 --- a/src/debugProtocol/requests/StackTraceRequest.spec.ts +++ b/src/debugProtocol/events/requests/StackTraceRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS, STEP_TYPE } from '../Constants'; +import { COMMANDS, STEP_TYPE } from '../../Constants'; import { StackTraceRequest } from './StackTraceRequest'; describe('StackTraceRequest', () => { diff --git a/src/debugProtocol/requests/StackTraceRequest.ts b/src/debugProtocol/events/requests/StackTraceRequest.ts similarity index 85% rename from src/debugProtocol/requests/StackTraceRequest.ts rename to src/debugProtocol/events/requests/StackTraceRequest.ts index c6fb69c9..fec5f964 100644 --- a/src/debugProtocol/requests/StackTraceRequest.ts +++ b/src/debugProtocol/events/requests/StackTraceRequest.ts @@ -1,8 +1,7 @@ import { SmartBuffer } from 'smart-buffer'; -import type { CommandData, STEP_TYPE } from '../Constants'; -import { COMMANDS } from '../Constants'; -import { protocolUtils } from '../ProtocolUtil'; -import type { ProtocolRequest } from './ProtocolRequest'; +import { COMMANDS } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; +import type { ProtocolRequest } from '../ProtocolEvent'; export class StackTraceRequest implements ProtocolRequest { diff --git a/src/debugProtocol/requests/StepRequest.spec.ts b/src/debugProtocol/events/requests/StepRequest.spec.ts similarity index 94% rename from src/debugProtocol/requests/StepRequest.spec.ts rename to src/debugProtocol/events/requests/StepRequest.spec.ts index cef83aeb..d0f76ab6 100644 --- a/src/debugProtocol/requests/StepRequest.spec.ts +++ b/src/debugProtocol/events/requests/StepRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS, STEP_TYPE } from '../Constants'; +import { COMMANDS, STEP_TYPE } from '../../Constants'; import { StepRequest } from './StepRequest'; describe('StepRequest', () => { diff --git a/src/debugProtocol/requests/StepRequest.ts b/src/debugProtocol/events/requests/StepRequest.ts similarity index 86% rename from src/debugProtocol/requests/StepRequest.ts rename to src/debugProtocol/events/requests/StepRequest.ts index f59bdbff..94cb4b4b 100644 --- a/src/debugProtocol/requests/StepRequest.ts +++ b/src/debugProtocol/events/requests/StepRequest.ts @@ -1,8 +1,8 @@ import { SmartBuffer } from 'smart-buffer'; -import type { CommandData, STEP_TYPE } from '../Constants'; -import { COMMANDS } from '../Constants'; -import { protocolUtils } from '../ProtocolUtil'; -import type { ProtocolRequest } from './ProtocolRequest'; +import type { RequestData, STEP_TYPE } from '../../Constants'; +import { COMMANDS } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; +import type { ProtocolRequest } from '../ProtocolEvent'; export class StepRequest implements ProtocolRequest { diff --git a/src/debugProtocol/requests/StopRequest.spec.ts b/src/debugProtocol/events/requests/StopRequest.spec.ts similarity index 92% rename from src/debugProtocol/requests/StopRequest.spec.ts rename to src/debugProtocol/events/requests/StopRequest.spec.ts index 2928a548..3696bc97 100644 --- a/src/debugProtocol/requests/StopRequest.spec.ts +++ b/src/debugProtocol/events/requests/StopRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS, STEP_TYPE } from '../Constants'; +import { COMMANDS, STEP_TYPE } from '../../Constants'; import { StopRequest } from './StopRequest'; describe('StopRequest', () => { diff --git a/src/debugProtocol/requests/StopRequest.ts b/src/debugProtocol/events/requests/StopRequest.ts similarity index 82% rename from src/debugProtocol/requests/StopRequest.ts rename to src/debugProtocol/events/requests/StopRequest.ts index 6441cad2..a1af7b63 100644 --- a/src/debugProtocol/requests/StopRequest.ts +++ b/src/debugProtocol/events/requests/StopRequest.ts @@ -1,8 +1,8 @@ import { SmartBuffer } from 'smart-buffer'; -import type { CommandData } from '../Constants'; -import { COMMANDS } from '../Constants'; -import { protocolUtils } from '../ProtocolUtil'; -import type { ProtocolRequest } from './ProtocolRequest'; +import type { RequestData } from '../../Constants'; +import { COMMANDS } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; +import type { ProtocolRequest } from '../ProtocolEvent'; export class StopRequest implements ProtocolRequest { diff --git a/src/debugProtocol/requests/ThreadsRequest.spec.ts b/src/debugProtocol/events/requests/ThreadsRequest.spec.ts similarity index 93% rename from src/debugProtocol/requests/ThreadsRequest.spec.ts rename to src/debugProtocol/events/requests/ThreadsRequest.spec.ts index c9b755a1..f71f98e4 100644 --- a/src/debugProtocol/requests/ThreadsRequest.spec.ts +++ b/src/debugProtocol/events/requests/ThreadsRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS } from '../Constants'; +import { COMMANDS } from '../../Constants'; import { ThreadsRequest } from './ThreadsRequest'; describe('ThreadsRequest', () => { diff --git a/src/debugProtocol/requests/ThreadsRequest.ts b/src/debugProtocol/events/requests/ThreadsRequest.ts similarity index 82% rename from src/debugProtocol/requests/ThreadsRequest.ts rename to src/debugProtocol/events/requests/ThreadsRequest.ts index 08aff476..4a768700 100644 --- a/src/debugProtocol/requests/ThreadsRequest.ts +++ b/src/debugProtocol/events/requests/ThreadsRequest.ts @@ -1,8 +1,8 @@ import { SmartBuffer } from 'smart-buffer'; -import type { CommandData } from '../Constants'; -import { COMMANDS } from '../Constants'; -import { protocolUtils } from '../ProtocolUtil'; -import type { ProtocolRequest } from './ProtocolRequest'; +import type { RequestData } from '../../Constants'; +import { COMMANDS } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; +import type { ProtocolRequest } from '../ProtocolEvent'; export class ThreadsRequest implements ProtocolRequest { diff --git a/src/debugProtocol/requests/VariablesRequest.spec.ts b/src/debugProtocol/events/requests/VariablesRequest.spec.ts similarity index 99% rename from src/debugProtocol/requests/VariablesRequest.spec.ts rename to src/debugProtocol/events/requests/VariablesRequest.spec.ts index c60fb81d..3ed54fd6 100644 --- a/src/debugProtocol/requests/VariablesRequest.spec.ts +++ b/src/debugProtocol/events/requests/VariablesRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS } from '../Constants'; +import { COMMANDS } from '../../Constants'; import { VariablesRequest } from './VariablesRequest'; describe('VariablesRequest', () => { diff --git a/src/debugProtocol/requests/VariablesRequest.ts b/src/debugProtocol/events/requests/VariablesRequest.ts similarity index 95% rename from src/debugProtocol/requests/VariablesRequest.ts rename to src/debugProtocol/events/requests/VariablesRequest.ts index 8e2c8a59..75932e16 100644 --- a/src/debugProtocol/requests/VariablesRequest.ts +++ b/src/debugProtocol/events/requests/VariablesRequest.ts @@ -1,10 +1,9 @@ /* eslint-disable no-bitwise */ import { SmartBuffer } from 'smart-buffer'; -import { COMMANDS, VARIABLE_REQUEST_FLAGS } from '../Constants'; -import { protocolUtils } from '../ProtocolUtil'; -import type { ProtocolRequest } from './ProtocolRequest'; -import * as semver from 'semver'; -import { util } from '../../util'; +import { COMMANDS, VARIABLE_REQUEST_FLAGS } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; +import type { ProtocolRequest } from '../ProtocolEvent'; +import { util } from '../../../util'; export class VariablesRequest implements ProtocolRequest { diff --git a/src/debugProtocol/events/responses/GenericResponse.spec.ts b/src/debugProtocol/events/responses/GenericResponse.spec.ts new file mode 100644 index 00000000..88e4b76a --- /dev/null +++ b/src/debugProtocol/events/responses/GenericResponse.spec.ts @@ -0,0 +1,37 @@ +import { GenericResponse } from './GenericResponse'; +import { expect } from 'chai'; +import { ERROR_CODES } from '../../Constants'; + +describe('GenericResponse', () => { + it('Handles a Protocol update events', () => { + let response = GenericResponse.fromJson({ + requestId: 3, + errorCode: ERROR_CODES.CANT_CONTINUE + }); + + expect(response.data).to.eql({ + packetLength: undefined, + errorCode: ERROR_CODES.CANT_CONTINUE, + requestId: 3 + }); + + response = GenericResponse.fromBuffer(response.toBuffer()); + expect( + response.data + ).to.eql({ + packetLength: 8, // 0 bytes -- this version of the response doesn't have a packet length + errorCode: ERROR_CODES.CANT_CONTINUE, // 4 bytes + requestId: 3 // 4 bytes + }); + + expect(response.readOffset).to.be.equal(8); + expect(response.success).to.be.equal(true); + }); + + it('Fails when buffer is incomplete', () => { + const buffer = Buffer.alloc(4); + buffer.writeUInt32LE(10); + const response = GenericResponse.fromBuffer(buffer); + expect(response.success).to.equal(false); + }); +}); diff --git a/src/debugProtocol/events/responses/GenericResponse.ts b/src/debugProtocol/events/responses/GenericResponse.ts new file mode 100644 index 00000000..68e382e6 --- /dev/null +++ b/src/debugProtocol/events/responses/GenericResponse.ts @@ -0,0 +1,43 @@ +import { SmartBuffer } from 'smart-buffer'; +import type { ERROR_CODES } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; + +export class GenericResponse { + public static fromJson(data: { + requestId: number; + errorCode: ERROR_CODES; + }) { + const response = new GenericResponse(); + protocolUtils.loadJson(response, data); + return response; + } + + public static fromBuffer(buffer: Buffer) { + const response = new GenericResponse(); + protocolUtils.bufferLoaderHelper(response, buffer, 8, (smartBuffer: SmartBuffer) => { + response.data.packetLength = 8; + response.data.requestId = smartBuffer.readUInt32LE(); // request_id + response.data.errorCode = smartBuffer.readUInt32LE(); // error_code + }); + return response; + } + + public toBuffer() { + const smartBuffer = new SmartBuffer(); + smartBuffer.writeUInt32LE(this.data.requestId); // request_id + smartBuffer.writeUInt32LE(this.data.errorCode); // error_code + this.data.packetLength = smartBuffer.writeOffset; + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = 0; + + public data = { + //this response doesn't actually contain packetLength, but we need to add it here just to make this response look like a regular response + packetLength: undefined as number, + requestId: Number.MAX_SAFE_INTEGER, + errorCode: undefined as ERROR_CODES + }; +} diff --git a/src/debugProtocol/events/responses/GenericResponseV3.spec.ts b/src/debugProtocol/events/responses/GenericResponseV3.spec.ts new file mode 100644 index 00000000..823c0843 --- /dev/null +++ b/src/debugProtocol/events/responses/GenericResponseV3.spec.ts @@ -0,0 +1,60 @@ +import { GenericResponseV3 } from './GenericResponseV3'; +import { expect } from 'chai'; +import { ERROR_CODES } from '../../Constants'; +import { SmartBuffer } from 'smart-buffer'; + +describe('GenericResponseV3', () => { + it('serializes and deserializes properly', () => { + const response = GenericResponseV3.fromJson({ + errorCode: ERROR_CODES.OK, + requestId: 3 + }); + + expect(response.data).to.eql({ + packetLength: undefined, + errorCode: ERROR_CODES.OK, + requestId: 3 + }); + + expect( + GenericResponseV3.fromBuffer(response.toBuffer()).data + ).to.eql({ + packetLength: 12, // 4 bytes + errorCode: ERROR_CODES.OK, // 4 bytes + requestId: 3 // 4 bytes + }); + }); + + it('consumes excess buffer data', () => { + const response = GenericResponseV3.fromJson({ + errorCode: ERROR_CODES.OK, + requestId: 3 + }); + + expect(response.data).to.eql({ + packetLength: undefined, + errorCode: ERROR_CODES.OK, + requestId: 3 + }); + + const buffer = SmartBuffer.fromBuffer( + //get a buffer without the packetLength + response.toBuffer().slice(4) + ); + while (buffer.writeOffset < 28) { + buffer.writeUInt32LE(1, buffer.length); + } + buffer.insertUInt32LE(buffer.length + 4, 0); //packet_length + + const newResponse = GenericResponseV3.fromBuffer(buffer.toBuffer()); + expect(newResponse.readOffset).to.eql(32); + + expect( + newResponse.data + ).to.eql({ + packetLength: 32, // 4 bytes + errorCode: ERROR_CODES.OK, // 4 bytes + requestId: 3 // 4 bytes + }); + }); +}); diff --git a/src/debugProtocol/events/responses/GenericResponseV3.ts b/src/debugProtocol/events/responses/GenericResponseV3.ts new file mode 100644 index 00000000..514de9c4 --- /dev/null +++ b/src/debugProtocol/events/responses/GenericResponseV3.ts @@ -0,0 +1,44 @@ +import { SmartBuffer } from 'smart-buffer'; +import type { ERROR_CODES } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; + +export class GenericResponseV3 { + public static fromJson(data: { + requestId: number; + errorCode: ERROR_CODES; + }) { + const response = new GenericResponseV3(); + protocolUtils.loadJson(response, data); + return response; + } + + public static fromBuffer(buffer: Buffer) { + const response = new GenericResponseV3(); + protocolUtils.bufferLoaderHelper(response, buffer, 12, (smartBuffer: SmartBuffer) => { + response.data.packetLength = smartBuffer.readUInt32LE(); // packet_length + response.data.requestId = smartBuffer.readUInt32LE(); // request_id + response.data.errorCode = smartBuffer.readUInt32LE(); // error_code + + //this is a generic response, so we don't actually know what the rest of the payload is. + //so just consume the rest of the payload as throwaway data + response.readOffset = response.data.packetLength; + }); + return response; + } + + public toBuffer() { + const smartBuffer = new SmartBuffer(); + protocolUtils.insertCommonResponseFields(this, smartBuffer); + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = 0; + + public data = { + packetLength: undefined as number, + requestId: Number.MAX_SAFE_INTEGER, + errorCode: undefined as ERROR_CODES + }; +} diff --git a/src/debugProtocol/events/responses/HandshakeResponse.spec.ts b/src/debugProtocol/events/responses/HandshakeResponse.spec.ts new file mode 100644 index 00000000..aca5c239 --- /dev/null +++ b/src/debugProtocol/events/responses/HandshakeResponse.spec.ts @@ -0,0 +1,49 @@ +import { HandshakeResponse } from './HandshakeResponse'; +import { Debugger } from '../../Debugger'; +import { createHandShakeResponse } from '../zzresponsesOld/responseCreationHelpers.spec'; +import { expect } from 'chai'; + +describe('HandshakeResponse', () => { + it('Handles a handshake response', () => { + const response = HandshakeResponse.fromJson({ + magic: 'not bsdebug', + protocolVersion: '1.0.0' + }); + + expect(response.data).to.eql({ + magic: 'not bsdebug', + protocolVersion: '1.0.0' + }); + + expect( + HandshakeResponse.fromBuffer(response.toBuffer()).data + ).to.eql({ + magic: 'not bsdebug', // 12 bytes + protocolVersion: '1.0.0' // 12 bytes (each number is sent as uint32) + }); + + expect(response.toBuffer().length).to.eql(24); + }); + + it('Fails when buffer is incomplete', () => { + let handshake = HandshakeResponse.fromBuffer( + //create a response + HandshakeResponse.fromJson({ + magic: Debugger.DEBUGGER_MAGIC, + protocolVersion: '1.0.0' + //slice a few bytes off the end + }).toBuffer().slice(-3) + ); + expect(handshake.success).to.equal(false); + }); + + it('Fails when the protocol version is equal to or greater then 3.0.0', () => { + const response = HandshakeResponse.fromJson({ + magic: 'not bsdebug', + protocolVersion: '3.0.0' + }); + + let handshakeV3 = HandshakeResponse.fromBuffer(response.toBuffer()); + expect(handshakeV3.success).to.equal(false); + }); +}); diff --git a/src/debugProtocol/events/responses/HandshakeResponse.ts b/src/debugProtocol/events/responses/HandshakeResponse.ts new file mode 100644 index 00000000..91fa655a --- /dev/null +++ b/src/debugProtocol/events/responses/HandshakeResponse.ts @@ -0,0 +1,83 @@ +import { SmartBuffer } from 'smart-buffer'; +import * as semver from 'semver'; +import { util } from '../../../util'; +import type { ProtocolEvent, ProtocolResponse } from '../ProtocolEvent'; +import { protocolUtils } from '../../ProtocolUtil'; +import { ERROR_CODES } from '../../Constants'; + +export class HandshakeResponse implements ProtocolResponse { + public static fromJson(data: { + magic: string; + protocolVersion: string; + }) { + const response = new HandshakeResponse(); + protocolUtils.loadJson(response, data); + // We only support version prior to v3 with this handshake + if (!semver.satisfies(response.data.protocolVersion, '<3.0.0')) { + response.success = false; + } + return response; + } + + public static fromBuffer(buffer: Buffer) { + const response = new HandshakeResponse(); + protocolUtils.bufferLoaderHelper(response, buffer, 20, (smartBuffer: SmartBuffer) => { + response.data.magic = util.readStringNT(smartBuffer); // magic_number + + response.data.protocolVersion = [ + smartBuffer.readInt32LE(), // protocol_major_version + smartBuffer.readInt32LE(), // protocol_minor_version + smartBuffer.readInt32LE() // protocol_patch_version + ].join('.'); + + // We only support version prior to v3 with this handshake + if (!semver.satisfies(response.data.protocolVersion, '<3.0.0')) { + throw new Error(`unsupported version ${response.data.protocolVersion}`); + } + return true; + }); + return response; + } + + protected loadJson(data: HandshakeResponse['data']) { + this.data = data; + this.success = true; + } + + public toBuffer() { + let buffer = new SmartBuffer(); + buffer.writeStringNT(this.data.magic); // magic_number + const [major, minor, patch] = (this.data.protocolVersion?.split('.') ?? ['0', '0', '0']).map(x => parseInt(x)); + buffer.writeUInt32LE(major); // protocol_major_version + buffer.writeUInt32LE(minor); // protocol_minor_version + buffer.writeUInt32LE(patch); // protocol_patch_version + + return buffer.toBuffer(); + } + + public watchPacketLength = false; // this will always be false for older protocol versions + + public success = false; + + public readOffset = 0; + + public data = { + /** + * The Roku Brightscript debug protocol identifier, which is the following 64-bit value :0x0067756265647362LU. + * + * This is equal to 29120988069524322LU or the following little-endian value: b'bsdebug\0. + */ + magic: undefined as string, + /** + * A semantic version string (i.e. `2.0.0`) + */ + protocolVersion: undefined as string, + + + //The handshake response isn't actually structured like like normal responses, but since they're the only unique response, just add dummy data for those fields + packetLength: undefined as number, + //hardcode the max integer value. This must be the same value as the HandshakeResponse class + requestId: Number.MAX_SAFE_INTEGER, + errorCode: ERROR_CODES.OK + }; +} diff --git a/src/debugProtocol/events/responses/HandshakeResponseV3.spec.ts b/src/debugProtocol/events/responses/HandshakeResponseV3.spec.ts new file mode 100644 index 00000000..c394df0f --- /dev/null +++ b/src/debugProtocol/events/responses/HandshakeResponseV3.spec.ts @@ -0,0 +1,83 @@ +import { HandshakeResponseV3 } from './HandshakeResponseV3'; +import { Debugger } from '../../Debugger'; +import { expect } from 'chai'; +import { SmartBuffer } from 'smart-buffer'; + +describe('HandshakeResponseV3', () => { + const date = new Date(2022, 0, 0); + it('Handles a handshake response', () => { + const response = HandshakeResponseV3.fromJson({ + magic: 'bsdebug', + protocolVersion: '3.0.0', + revisionTimestamp: date + }); + + expect(response.data).to.eql({ + magic: 'bsdebug', + protocolVersion: '3.0.0', + revisionTimestamp: date + }); + + expect( + HandshakeResponseV3.fromBuffer(response.toBuffer()).data + ).to.eql({ + magic: 'bsdebug', // 8 bytes + protocolVersion: '3.0.0', // 12 bytes (each number is sent as uint32) + //remaining_packet_length // 4 bytes + revisionTimestamp: date // 8 bytes (int64) + }); + + expect(response.toBuffer().length).to.eql(32); + }); + + it('Handles a extra packet length in handshake response', () => { + const response = HandshakeResponseV3.fromJson({ + magic: 'bsdebug', + protocolVersion: '3.0.0', + revisionTimestamp: date + }); + + //write some extra data to the buffer + const smartBuffer = SmartBuffer.fromBuffer(response.toBuffer()); + smartBuffer.writeStringNT('this is extra data'); + + const newResponse = HandshakeResponseV3.fromBuffer( + smartBuffer.toBuffer() + ); + expect(newResponse.success).to.be.true; + expect( + newResponse.data + ).to.eql({ + magic: 'bsdebug', // 8 bytes + protocolVersion: '3.0.0', // 12 bytes (each number is sent as uint32) + //remaining_packet_length // 4 bytes + revisionTimestamp: date // 8 bytes (int64) + }); + + expect(newResponse.readOffset).to.eql(32); + }); + + it('Fails when buffer is incomplete', () => { + let handshake = HandshakeResponseV3.fromBuffer( + //create a response + HandshakeResponseV3.fromJson({ + magic: Debugger.DEBUGGER_MAGIC, + protocolVersion: '1.0.0', + revisionTimestamp: date + //slice a few bytes off the end + }).toBuffer().slice(-3) + ); + expect(handshake.success).to.equal(false); + }); + + it('Fails when the protocol version is less then 3.0.0', () => { + const response = HandshakeResponseV3.fromJson({ + magic: 'not bsdebug', + protocolVersion: '3.0.0', + revisionTimestamp: date + }); + + let handshakeV3 = HandshakeResponseV3.fromBuffer(response.toBuffer()); + expect(handshakeV3.success).to.equal(false); + }); +}); diff --git a/src/debugProtocol/events/responses/HandshakeResponseV3.ts b/src/debugProtocol/events/responses/HandshakeResponseV3.ts new file mode 100644 index 00000000..ffb57ad9 --- /dev/null +++ b/src/debugProtocol/events/responses/HandshakeResponseV3.ts @@ -0,0 +1,117 @@ +import { SmartBuffer } from 'smart-buffer'; +import * as semver from 'semver'; +import { util } from '../../../util'; +import type { ProtocolEvent, ProtocolResponse } from '../ProtocolEvent'; +import { protocolUtils } from '../../ProtocolUtil'; +import { ERROR_CODES } from '../../Constants'; + +export class HandshakeResponseV3 implements ProtocolResponse { + + public static fromJson(data: { + magic: string; + protocolVersion: string; + revisionTimestamp: Date; + }) { + const response = new HandshakeResponseV3(); + protocolUtils.loadJson(response, data); + // We only support v3 or above with this handshake + if (!semver.satisfies(response.data.protocolVersion, '<3.0.0')) { + response.success = false; + } + return response; + } + + public static fromBuffer(buffer: Buffer) { + const response = new HandshakeResponseV3(); + protocolUtils.bufferLoaderHelper(response, buffer, 20, (smartBuffer: SmartBuffer) => { + response.data.magic = util.readStringNT(smartBuffer); // magic_number + + response.data.protocolVersion = [ + smartBuffer.readInt32LE(), // protocol_major_version + smartBuffer.readInt32LE(), // protocol_minor_version + smartBuffer.readInt32LE() // protocol_patch_version + ].join('.'); + + const legacyReadSize = smartBuffer.readOffset; + const remainingPacketLength = smartBuffer.readInt32LE(); // remaining_packet_length + + const requiredBufferSize = remainingPacketLength + legacyReadSize; + response.data.revisionTimestamp = new Date(Number(smartBuffer.readBigUInt64LE())); // platform_revision_timestamp + + if (smartBuffer.length < requiredBufferSize) { + throw new Error(`Missing buffer data according to the remaining packet length: ${smartBuffer.length}/${requiredBufferSize}`); + } + //set the buffer offset + smartBuffer.readOffset = requiredBufferSize; + + // We only support v3 or above with this handshake + if (!semver.satisfies(response.data.protocolVersion, '<3.0.0')) { + throw new Error(`unsupported version ${response.data.protocolVersion}`); + } + response.watchPacketLength = true; + }); + return response; + } + + /** + * Convert the data into a buffer + */ + public toBuffer() { + let smartBuffer = new SmartBuffer(); + smartBuffer.writeStringNT(this.data.magic); // magic_number + const [major, minor, patch] = (this.data.protocolVersion?.split('.') ?? ['0', '0', '0']).map(x => parseInt(x)); + smartBuffer.writeUInt32LE(major); // protocol_major_version + smartBuffer.writeUInt32LE(minor); // protocol_minor_version + smartBuffer.writeUInt32LE(patch); // protocol_patch_version + + //As of BrightScript debug protocol 3.0.0 (Roku OS 11.0), all packets from the debugging target include a packet_length. + //The length is always in bytes, and includes the packet_length field, itself. + //This field avoids the need for changes to the major version of the protocol because it allows a debugger client to + //read past data it does not understand and is not critical to debugger operations. + const remainingDataBuffer = new SmartBuffer(); + remainingDataBuffer.writeBigInt64LE(BigInt( + this.data.revisionTimestamp.getTime() + )); // platform_revision_timestamp + + smartBuffer.writeUInt32LE(remainingDataBuffer.writeOffset + 4); // remaining_packet_length + smartBuffer.writeBuffer(remainingDataBuffer.toBuffer()); + + return smartBuffer.toBuffer(); + } + + public watchPacketLength = false; // this will always be true for the new protocol versions + + public success = false; + + public readOffset = 0; + + public data = { + /** + * The Roku Brightscript debug protocol identifier, which is the following 64-bit value :0x0067756265647362LU. + * + * This is equal to 29120988069524322LU or the following little-endian value: b'bsdebug\0. + */ + magic: undefined as string, + /** + * A semantic version string (i.e. `2.0.0`) + */ + protocolVersion: undefined as string, + /** + * A platform-specific implementation timestamp (in milliseconds since epoch [1970-01-01T00:00:00.000Z]). + * + * As of BrightScript debug protocol 3.0.0 (Roku OS 11.0), a timestamp is sent to the debugger client in the initial handshake. + * This timestamp is platform-specific data that is included in the system software of the platform being debugged. + * It is changed by the platform's vendor when there is any change that affects the behavior of the debugger. + * + * The value can be used in manners similar to a build number, and is primarily used to differentiate between pre-release builds of the platform being debugged. + */ + revisionTimestamp: undefined as Date, + + + //The handshake response isn't actually structured like like normal responses, but since they're the only unique response, just add dummy data for those fields + packetLength: undefined as number, + //hardcode the max integer value. This must be the same value as the HandshakeResponse class + requestId: Number.MAX_SAFE_INTEGER, + errorCode: ERROR_CODES.OK + }; +} diff --git a/src/debugProtocol/events/responses/ListBreakpointsResponse.spec.ts b/src/debugProtocol/events/responses/ListBreakpointsResponse.spec.ts new file mode 100644 index 00000000..2a072d94 --- /dev/null +++ b/src/debugProtocol/events/responses/ListBreakpointsResponse.spec.ts @@ -0,0 +1,168 @@ +import { expect } from 'chai'; +import { ListBreakpointsResponse } from './ListBreakpointsResponse'; +import { ERROR_CODES } from '../../Constants'; +import { getRandomBuffer } from '../zzresponsesOld/responseCreationHelpers.spec'; + +describe('ListBreakpointsResponse', () => { + let response: ListBreakpointsResponse; + beforeEach(() => { + response = undefined; + }); + + it('serializes and deserializes multiple breakpoints properly', () => { + let response = ListBreakpointsResponse.fromJson({ + requestId: 3, + errorCode: ERROR_CODES.OK, + breakpoints: [{ + errorCode: ERROR_CODES.OK, + id: 10, + ignoreCount: 2 + }, { + errorCode: ERROR_CODES.OK, + id: 20, + ignoreCount: 3 + }] + }); + + expect(response.data).to.eql({ + packetLength: undefined, + requestId: 3, + errorCode: ERROR_CODES.OK, + breakpoints: [{ + errorCode: ERROR_CODES.OK, + id: 10, + ignoreCount: 2 + }, { + errorCode: ERROR_CODES.OK, + id: 20, + ignoreCount: 3 + }] + }); + + response = ListBreakpointsResponse.fromBuffer(response.toBuffer()); + + expect( + response.data + ).to.eql({ + packetLength: 40, // 4 bytes + requestId: 3, // 4 bytes, + errorCode: ERROR_CODES.OK, // 4 bytes + //num_breakpoints // 4 bytes + breakpoints: [{ + errorCode: ERROR_CODES.OK, // 4 bytes + id: 10, // 4 bytes + ignoreCount: 2 // 4 bytes + }, { + errorCode: ERROR_CODES.OK, // 4 bytes + id: 20, // 4 bytes + ignoreCount: 3 // 4 bytes + }] + }); + }); + + it('handles empty breakpoints array', () => { + let response = ListBreakpointsResponse.fromJson({ + requestId: 3, + errorCode: ERROR_CODES.OK, + breakpoints: [] + }); + + expect(response.data).to.eql({ + packetLength: undefined, + requestId: 3, + errorCode: ERROR_CODES.OK, + breakpoints: [] + }); + + response = ListBreakpointsResponse.fromBuffer(response.toBuffer()); + + expect( + response.data + ).to.eql({ + packetLength: 16, // 4 bytes + requestId: 3, // 4 bytes, + errorCode: ERROR_CODES.OK, // 4 bytes + //num_breakpoints // 4 bytes + breakpoints: [] + }); + }); + + it('handles empty buffer', () => { + response = ListBreakpointsResponse.fromBuffer(null); + //Great, it didn't explode! + expect(response.success).to.be.false; + }); + + it('handles undersized buffers', () => { + response = ListBreakpointsResponse.fromBuffer( + getRandomBuffer(0) + ); + expect(response.success).to.be.false; + + response = ListBreakpointsResponse.fromBuffer( + getRandomBuffer(1) + ); + expect(response.success).to.be.false; + + response = ListBreakpointsResponse.fromBuffer( + getRandomBuffer(11) + ); + expect(response.success).to.be.false; + }); + + it('gracefully handles mismatched breakpoint count', () => { + let buffer = ListBreakpointsResponse.fromJson({ + requestId: 3, + errorCode: ERROR_CODES.OK, + breakpoints: [{ + errorCode: ERROR_CODES.OK, + id: 1, + ignoreCount: 0 + }] + }).toBuffer(); + + //set num_breakpoints to 2 instead of 1 + buffer = Buffer.concat([ + buffer.slice(0, 12), + Buffer.from([2, 0, 0, 0]), + buffer.slice(16) + ]); + + const response = ListBreakpointsResponse.fromBuffer(buffer); + expect(response.success).to.be.false; + expect(response.data.breakpoints).to.eql([{ + errorCode: ERROR_CODES.OK, + id: 1, + ignoreCount: 0 + }]); + }); + + it('handles malformed breakpoint data', () => { + let buffer = ListBreakpointsResponse.fromJson({ + requestId: 3, + errorCode: ERROR_CODES.OK, + breakpoints: [{ + errorCode: ERROR_CODES.OK, + id: 1, + ignoreCount: 0 + }, { + errorCode: ERROR_CODES.OK, + id: 2, + ignoreCount: 0 + }] + }).toBuffer(); + + //set num_breakpoints to 2 instead of 1 + buffer = Buffer.concat([ + buffer.slice(0, buffer.length - 3) + ]); + + const response = ListBreakpointsResponse.fromBuffer(buffer); + expect(response.success).to.be.false; + expect(response.data.breakpoints).to.eql([{ + errorCode: ERROR_CODES.OK, + id: 1, + ignoreCount: 0 + }]); + }); +}); diff --git a/src/debugProtocol/events/responses/ListBreakpointsResponse.ts b/src/debugProtocol/events/responses/ListBreakpointsResponse.ts new file mode 100644 index 00000000..f53a3da8 --- /dev/null +++ b/src/debugProtocol/events/responses/ListBreakpointsResponse.ts @@ -0,0 +1,120 @@ +import { SmartBuffer } from 'smart-buffer'; +import { ERROR_CODES } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; + +export class ListBreakpointsResponse { + + public static fromJson(data: { + requestId: number; + errorCode: ERROR_CODES; + breakpoints: BreakpointInfo[]; + }) { + const response = new ListBreakpointsResponse(); + protocolUtils.loadJson(response, data); + response.data.breakpoints ??= []; + return response; + } + + public static fromBuffer(buffer: Buffer) { + const response = new ListBreakpointsResponse(); + protocolUtils.bufferLoaderHelper(response, buffer, 12, (smartBuffer: SmartBuffer) => { + protocolUtils.loadCommonResponseFields(response, smartBuffer); + const numBreakpoints = smartBuffer.readUInt32LE(); // num_breakpoints + + response.data.breakpoints = []; + + // build the list of BreakpointInfo + for (let i = 0; i < numBreakpoints; i++) { + const breakpoint = {} as BreakpointInfo; + // breakpoint_id - The ID assigned to the breakpoint. An ID greater than 0 indicates an active breakpoint. An ID of 0 denotes that the breakpoint has an error. + breakpoint.id = smartBuffer.readUInt32LE(); + // error_code - Indicates whether the breakpoint was successfully returned. + breakpoint.errorCode = smartBuffer.readUInt32LE(); + + if (breakpoint.id > 0) { + // This value is only present if the breakpoint_id is valid. + // ignore_count - Current state, decreases as breakpoint is executed. + breakpoint.ignoreCount = smartBuffer.readUInt32LE(); + } + response.data.breakpoints.push(breakpoint); + } + return response.data.breakpoints.length === numBreakpoints; + }); + return response; + } + + public toBuffer() { + const smartBuffer = new SmartBuffer(); + smartBuffer.writeUInt32LE(this.data.breakpoints?.length ?? 0); // num_breakpoints + for (const breakpoint of this.data.breakpoints ?? []) { + smartBuffer.writeUInt32LE(breakpoint.id); // breakpoint_id + smartBuffer.writeUInt32LE(breakpoint.errorCode); // error_code + //if this breakpoint has no errors, then write its ignore_count + if (breakpoint.id > 0) { + smartBuffer.writeUInt32LE(breakpoint.ignoreCount); // ignore_count + } + } + protocolUtils.insertCommonResponseFields(this, smartBuffer); + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = 0; + + public data = { + breakpoints: undefined as BreakpointInfo[], + + // response fields + packetLength: undefined as number, + requestId: undefined as number, + errorCode: undefined as ERROR_CODES + }; +} + +export interface BreakpointInfo { + /** + * The ID assigned to the breakpoint. An ID greater than 0 indicates an active breakpoint. An ID of 0 denotes that the breakpoint has an error. + */ + id: number; + /** + * Indicates whether the breakpoint was successfully returned. This may be one of the following values: + * - `0` (`'OK'`) - The breakpoint_id is valid. + * - `5` (`'INVALID_ARGS'`) - The breakpoint could not be returned. + */ + errorCode: number; + /** + * Current state, decreases as breakpoint is executed. This argument is only present if the breakpoint_id is valid. + */ + ignoreCount: number; +} + +export class BreakpointInfo2 { + constructor(bufferReader: SmartBuffer) { + // breakpoint_id - The ID assigned to the breakpoint. An ID greater than 0 indicates an active breakpoint. An ID of 0 denotes that the breakpoint has an error. + this.breakpointId = bufferReader.readUInt32LE(); + // error_code - Indicates whether the breakpoint was successfully returned. + this.errorCode = bufferReader.readUInt32LE(); + + if (this.breakpointId > 0) { + // This argument is only present if the breakpoint_id is valid. + // ignore_count - Current state, decreases as breakpoint is executed. + this.hitCount = bufferReader.readUInt32LE(); + } + this.success = true; + } + + public get isVerified() { + return this.breakpointId > 0; + } + public success = false; + public breakpointId: number; + public errorCode: number; + /** + * The textual description of the error code + */ + public get errorText() { + return ERROR_CODES[this.errorCode]; + } + public hitCount: number; +} diff --git a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts new file mode 100644 index 00000000..ccc0775f --- /dev/null +++ b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts @@ -0,0 +1,38 @@ +import { expect } from 'chai'; +import { ERROR_CODES, STOP_REASONS, UPDATE_TYPES } from '../../Constants'; +import { AllThreadsStoppedUpdate } from './AllThreadsStoppedUpdate'; + +describe('AllThreadsStoppedUpdate', () => { + it('serializes and deserializes properly', () => { + const command = AllThreadsStoppedUpdate.fromJson({ + primaryThreadIndex: 1, + errorCode: ERROR_CODES.OK, + stopReason: STOP_REASONS.BREAK, + stopReasonDetail: 'because' + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 0, + errorCode: ERROR_CODES.OK, + updateType: UPDATE_TYPES.ALL_THREADS_STOPPED, + + primaryThreadIndex: 1, + stopReason: STOP_REASONS.BREAK, + stopReasonDetail: 'because' + }); + + expect( + AllThreadsStoppedUpdate.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 29, // 4 bytes + requestId: 0, // 4 bytes + errorCode: ERROR_CODES.OK, // 4 bytes + updateType: UPDATE_TYPES.ALL_THREADS_STOPPED, // 4 bytes + + primaryThreadIndex: 1, // 4 bytes + stopReason: STOP_REASONS.BREAK, // 1 bytes + stopReasonDetail: 'because' // 8 bytes + }); + }); +}); diff --git a/src/debugProtocol/responses/updates/AllThreadsStoppedUpdateResponse.ts b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts similarity index 54% rename from src/debugProtocol/responses/updates/AllThreadsStoppedUpdateResponse.ts rename to src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts index 0fa3b955..5d401e5a 100644 --- a/src/debugProtocol/responses/updates/AllThreadsStoppedUpdateResponse.ts +++ b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts @@ -1,60 +1,65 @@ import { SmartBuffer } from 'smart-buffer'; +import type { ERROR_CODES } from '../../Constants'; import { STOP_REASONS, UPDATE_TYPES } from '../../Constants'; import { util } from '../../../util'; -import { ProtocolResponse } from '../ProtocolResponse'; -import { UpdateResponse } from './UpdateResponse'; +import { protocolUtils } from '../../ProtocolUtil'; +import type { ProtocolUpdate } from '../ProtocolEvent'; /** * All threads are stopped and an ALL_THREADS_STOPPED message is sent to the debugging client. * * The data field includes information on why the threads were stopped. */ -export class AllThreadsStoppedUpdateResponse extends UpdateResponse { - public constructor(arg: Buffer | Pick) { - super(); - if (Buffer.isBuffer(arg)) { - this.loadBuffer(arg); - } else { - this.loadJson(arg); - } +export class AllThreadsStoppedUpdate implements ProtocolUpdate { + + public static fromJson(data: { + errorCode: ERROR_CODES; + primaryThreadIndex: number; + stopReason: number; + stopReasonDetail: string; + }) { + const response = new AllThreadsStoppedUpdate(); + protocolUtils.loadJson(response, data); + return response; } - private loadBuffer(buffer: Buffer) { - this.bufferLoaderHelper(buffer, 12, UPDATE_TYPES.ALL_THREADS_STOPPED, (smartBuffer) => { - //bail if it's not the update type we wanted - if (this.data.updateType !== UPDATE_TYPES.ALL_THREADS_STOPPED) { - return false; - } - this.data.primaryThreadIndex = smartBuffer.readInt32LE(); - this.data.stopReason = getStopReason(smartBuffer.readUInt8()); - this.data.stopReasonDetail = util.readStringNT(smartBuffer); + public static fromBuffer(buffer: Buffer) { + const response = new AllThreadsStoppedUpdate(); + protocolUtils.bufferLoaderHelper(response, buffer, 16, (smartBuffer) => { + protocolUtils.loadCommonUpdateFields(response, smartBuffer, UPDATE_TYPES.ALL_THREADS_STOPPED); + + response.data.primaryThreadIndex = smartBuffer.readInt32LE(); + response.data.stopReason = getStopReason(smartBuffer.readUInt8()); + response.data.stopReasonDetail = util.readStringNT(smartBuffer); }); + return response; } public toBuffer() { - let buffer = new SmartBuffer(); - buffer.writeUInt32LE(this.data.requestId); // request_id - buffer.writeUInt32LE(this.data.errorCode); // error_code - buffer.writeUInt32LE(this.data.updateType); // update_type + let smartBuffer = new SmartBuffer(); - buffer.writeUInt32LE(this.data.primaryThreadIndex); // update_type - buffer.writeUInt8(this.data.stopReason); // stop_reason - buffer.writeStringNT(this.data.stopReasonDetail); //stop_reason_detail + smartBuffer.writeInt32LE(this.data.primaryThreadIndex); // primary_thread_index + smartBuffer.writeUInt8(this.data.stopReason); // stop_reason + smartBuffer.writeStringNT(this.data.stopReasonDetail); //stop_reason_detail - return this.getBufferWithPacketLength(buffer); + protocolUtils.insertCommonUpdateFields(this, smartBuffer); + return smartBuffer.toBuffer(); } + public success = false; + + public readOffset = -1; + public data = { - // DebuggerUpdate fields + primaryThreadIndex: undefined as number, + stopReason: undefined as STOP_REASONS, + stopReasonDetail: undefined as string, + + //common props packetLength: undefined as number, - requestId: undefined as number, - errorCode: undefined as number, - updateType: UPDATE_TYPES.ALL_THREADS_STOPPED, - - //ALL_THREADS_STOPPED fields - primaryThreadIndex: -1, - stopReason: -1, - stopReasonDetail: undefined as string + requestId: 0, //all updates have requestId === 0 + errorCode: undefined as ERROR_CODES, + updateType: UPDATE_TYPES.ALL_THREADS_STOPPED }; } diff --git a/src/debugProtocol/events/updates/UpdateResponse.ts b/src/debugProtocol/events/updates/UpdateResponse.ts new file mode 100644 index 00000000..5407fffb --- /dev/null +++ b/src/debugProtocol/events/updates/UpdateResponse.ts @@ -0,0 +1,13 @@ +import type { SmartBuffer } from 'smart-buffer'; +import type { UPDATE_TYPES } from '../../Constants'; +import { ProtocolResponse } from '../zzresponsesOld/ProtocolResponse'; + +export abstract class UpdateResponse extends ProtocolResponse { + public abstract data: { + packetLength: number; + requestId: number; + errorCode: number; + updateType: number; + }; +} + diff --git a/src/debugProtocol/responses/AddBreakpointsResponse.ts b/src/debugProtocol/events/zzresponsesOld/AddBreakpointsResponse.ts similarity index 66% rename from src/debugProtocol/responses/AddBreakpointsResponse.ts rename to src/debugProtocol/events/zzresponsesOld/AddBreakpointsResponse.ts index 36a29585..2a5dda18 100644 --- a/src/debugProtocol/responses/AddBreakpointsResponse.ts +++ b/src/debugProtocol/events/zzresponsesOld/AddBreakpointsResponse.ts @@ -1,4 +1,4 @@ -import { ListBreakpointsResponse } from './ListBreakpointsResponse'; +import { ListBreakpointsResponse } from '../responses/ListBreakpointsResponse'; //There's currently no difference between this response and the ListBreakpoints response export class AddBreakpointsResponse extends ListBreakpointsResponse { } diff --git a/src/debugProtocol/responses/BreakpointErrorResponse.spec.ts b/src/debugProtocol/events/zzresponsesOld/BreakpointErrorResponse.spec.ts similarity index 97% rename from src/debugProtocol/responses/BreakpointErrorResponse.spec.ts rename to src/debugProtocol/events/zzresponsesOld/BreakpointErrorResponse.spec.ts index 6bb07222..0df639c1 100644 --- a/src/debugProtocol/responses/BreakpointErrorResponse.spec.ts +++ b/src/debugProtocol/events/zzresponsesOld/BreakpointErrorResponse.spec.ts @@ -1,7 +1,7 @@ import { createBreakpointErrorUpdateResponse } from './responseCreationHelpers.spec'; import { expect } from 'chai'; import { BreakpointErrorUpdateResponse } from './BreakpointErrorUpdateResponse'; -import { ERROR_CODES, UPDATE_TYPES } from '../Constants'; +import { ERROR_CODES, UPDATE_TYPES } from '../../Constants'; describe('BreakpointErrorUpdateResponse', () => { it('Handles zero errors', () => { diff --git a/src/debugProtocol/responses/BreakpointErrorUpdateResponse.ts b/src/debugProtocol/events/zzresponsesOld/BreakpointErrorUpdateResponse.ts similarity index 97% rename from src/debugProtocol/responses/BreakpointErrorUpdateResponse.ts rename to src/debugProtocol/events/zzresponsesOld/BreakpointErrorUpdateResponse.ts index f7e14fa3..a3b0b8cf 100644 --- a/src/debugProtocol/responses/BreakpointErrorUpdateResponse.ts +++ b/src/debugProtocol/events/zzresponsesOld/BreakpointErrorUpdateResponse.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; -import { util } from '../../util'; -import { UPDATE_TYPES } from '../Constants'; +import { util } from '../../../util'; +import { UPDATE_TYPES } from '../../Constants'; /** * Data sent as the data segment of message type: BREAKPOINT_ERROR diff --git a/src/debugProtocol/responses/ConnectIOPortResponse.ts b/src/debugProtocol/events/zzresponsesOld/ConnectIOPortResponse.ts similarity index 96% rename from src/debugProtocol/responses/ConnectIOPortResponse.ts rename to src/debugProtocol/events/zzresponsesOld/ConnectIOPortResponse.ts index e1bbbbe8..0df46c01 100644 --- a/src/debugProtocol/responses/ConnectIOPortResponse.ts +++ b/src/debugProtocol/events/zzresponsesOld/ConnectIOPortResponse.ts @@ -1,5 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import { UPDATE_TYPES } from '../Constants'; +import { UPDATE_TYPES } from '../../Constants'; export class ConnectIOPortResponse { diff --git a/src/debugProtocol/responses/ExecuteResponseV3.ts b/src/debugProtocol/events/zzresponsesOld/ExecuteResponseV3.ts similarity index 98% rename from src/debugProtocol/responses/ExecuteResponseV3.ts rename to src/debugProtocol/events/zzresponsesOld/ExecuteResponseV3.ts index aa67cd5a..9770ddb0 100644 --- a/src/debugProtocol/responses/ExecuteResponseV3.ts +++ b/src/debugProtocol/events/zzresponsesOld/ExecuteResponseV3.ts @@ -1,5 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import { util } from '../../util'; +import { util } from '../../../util'; export class ExecuteResponseV3 { constructor(buffer: Buffer) { diff --git a/src/debugProtocol/responses/RemoveBreakpointsResponse.ts b/src/debugProtocol/events/zzresponsesOld/RemoveBreakpointsResponse.ts similarity index 67% rename from src/debugProtocol/responses/RemoveBreakpointsResponse.ts rename to src/debugProtocol/events/zzresponsesOld/RemoveBreakpointsResponse.ts index a8c93194..5331762f 100644 --- a/src/debugProtocol/responses/RemoveBreakpointsResponse.ts +++ b/src/debugProtocol/events/zzresponsesOld/RemoveBreakpointsResponse.ts @@ -1,4 +1,4 @@ -import { ListBreakpointsResponse } from './ListBreakpointsResponse'; +import { ListBreakpointsResponse } from '../responses/ListBreakpointsResponse'; //There's currently no difference between this response and the ListBreakpoints response export class RemoveBreakpointsResponse extends ListBreakpointsResponse { } diff --git a/src/debugProtocol/responses/StackTraceResponse.ts b/src/debugProtocol/events/zzresponsesOld/StackTraceResponse.ts similarity index 98% rename from src/debugProtocol/responses/StackTraceResponse.ts rename to src/debugProtocol/events/zzresponsesOld/StackTraceResponse.ts index ce2caf95..de9aee0b 100644 --- a/src/debugProtocol/responses/StackTraceResponse.ts +++ b/src/debugProtocol/events/zzresponsesOld/StackTraceResponse.ts @@ -1,6 +1,6 @@ import * as path from 'path'; import { SmartBuffer } from 'smart-buffer'; -import { util } from '../../util'; +import { util } from '../../../util'; export class StackTraceResponse { diff --git a/src/debugProtocol/responses/StackTraceResponseV3.ts b/src/debugProtocol/events/zzresponsesOld/StackTraceResponseV3.ts similarity index 98% rename from src/debugProtocol/responses/StackTraceResponseV3.ts rename to src/debugProtocol/events/zzresponsesOld/StackTraceResponseV3.ts index 55c9289d..41e51b1e 100644 --- a/src/debugProtocol/responses/StackTraceResponseV3.ts +++ b/src/debugProtocol/events/zzresponsesOld/StackTraceResponseV3.ts @@ -1,6 +1,6 @@ import * as path from 'path'; import { SmartBuffer } from 'smart-buffer'; -import { util } from '../../util'; +import { util } from '../../../util'; export class StackTraceResponseV3 { diff --git a/src/debugProtocol/responses/ThreadsResponse.ts b/src/debugProtocol/events/zzresponsesOld/ThreadsResponse.ts similarity index 97% rename from src/debugProtocol/responses/ThreadsResponse.ts rename to src/debugProtocol/events/zzresponsesOld/ThreadsResponse.ts index de96943c..4b9761c1 100644 --- a/src/debugProtocol/responses/ThreadsResponse.ts +++ b/src/debugProtocol/events/zzresponsesOld/ThreadsResponse.ts @@ -1,7 +1,7 @@ import * as path from 'path'; import { SmartBuffer } from 'smart-buffer'; -import { STOP_REASONS } from '../Constants'; -import { util } from '../../util'; +import { STOP_REASONS } from '../../Constants'; +import { util } from '../../../util'; export class ThreadsResponse { diff --git a/src/debugProtocol/responses/UndefinedResponse.ts b/src/debugProtocol/events/zzresponsesOld/UndefinedResponse.ts similarity index 96% rename from src/debugProtocol/responses/UndefinedResponse.ts rename to src/debugProtocol/events/zzresponsesOld/UndefinedResponse.ts index 6dfae2aa..fca97712 100644 --- a/src/debugProtocol/responses/UndefinedResponse.ts +++ b/src/debugProtocol/events/zzresponsesOld/UndefinedResponse.ts @@ -1,5 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import { UPDATE_TYPES } from '../Constants'; +import { UPDATE_TYPES } from '../../Constants'; export class UndefinedResponse { diff --git a/src/debugProtocol/responses/UpdateThreadsResponse.ts b/src/debugProtocol/events/zzresponsesOld/UpdateThreadsResponse.ts similarity index 96% rename from src/debugProtocol/responses/UpdateThreadsResponse.ts rename to src/debugProtocol/events/zzresponsesOld/UpdateThreadsResponse.ts index ac0e7741..98e96472 100644 --- a/src/debugProtocol/responses/UpdateThreadsResponse.ts +++ b/src/debugProtocol/events/zzresponsesOld/UpdateThreadsResponse.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; -import { STOP_REASONS, UPDATE_TYPES } from '../Constants'; -import { util } from '../../util'; +import { STOP_REASONS, UPDATE_TYPES } from '../../Constants'; +import { util } from '../../../util'; export class UpdateThreadsResponse { diff --git a/src/debugProtocol/responses/VariableResponse.spec.ts b/src/debugProtocol/events/zzresponsesOld/VariableResponse.spec.ts similarity index 99% rename from src/debugProtocol/responses/VariableResponse.spec.ts rename to src/debugProtocol/events/zzresponsesOld/VariableResponse.spec.ts index b950d9bd..23dddceb 100644 --- a/src/debugProtocol/responses/VariableResponse.spec.ts +++ b/src/debugProtocol/events/zzresponsesOld/VariableResponse.spec.ts @@ -2,7 +2,7 @@ import { VariableResponse } from './VariableResponse'; import { createVariableResponse } from './responseCreationHelpers.spec'; import { expect } from 'chai'; -import { ERROR_CODES, VARIABLE_FLAGS, VARIABLE_TYPES } from '../Constants'; +import { ERROR_CODES, VARIABLE_FLAGS, VARIABLE_TYPES } from '../../Constants'; describe('VariableResponse', () => { diff --git a/src/debugProtocol/responses/VariableResponse.ts b/src/debugProtocol/events/zzresponsesOld/VariableResponse.ts similarity index 98% rename from src/debugProtocol/responses/VariableResponse.ts rename to src/debugProtocol/events/zzresponsesOld/VariableResponse.ts index fd268e03..25e5fe31 100644 --- a/src/debugProtocol/responses/VariableResponse.ts +++ b/src/debugProtocol/events/zzresponsesOld/VariableResponse.ts @@ -1,7 +1,7 @@ /* eslint-disable no-bitwise */ import { SmartBuffer } from 'smart-buffer'; -import { VARIABLE_FLAGS, VARIABLE_TYPES } from '../Constants'; -import { util } from '../../util'; +import { VARIABLE_FLAGS, VARIABLE_TYPES } from '../../Constants'; +import { util } from '../../../util'; export class VariableResponse { diff --git a/src/debugProtocol/responses/index.ts b/src/debugProtocol/events/zzresponsesOld/index.ts similarity index 65% rename from src/debugProtocol/responses/index.ts rename to src/debugProtocol/events/zzresponsesOld/index.ts index 121a7eab..8a537593 100644 --- a/src/debugProtocol/responses/index.ts +++ b/src/debugProtocol/events/zzresponsesOld/index.ts @@ -1,8 +1,6 @@ export * from './ConnectIOPortResponse'; -export * from './HandshakeResponse'; -export * from './HandshakeResponseV3'; -export * from './ProtocolEvent'; -export * from './ProtocolEventV3'; +export * from '../responses/GenericResponse'; +export * from '../responses/GenericResponseV3'; export * from './StackTraceResponse'; export * from './StackTraceResponseV3'; export * from './ThreadsResponse'; diff --git a/src/debugProtocol/responses/responseCreationHelpers.spec.ts b/src/debugProtocol/events/zzresponsesOld/responseCreationHelpers.spec.ts similarity index 98% rename from src/debugProtocol/responses/responseCreationHelpers.spec.ts rename to src/debugProtocol/events/zzresponsesOld/responseCreationHelpers.spec.ts index 452bf756..a48de7d2 100644 --- a/src/debugProtocol/responses/responseCreationHelpers.spec.ts +++ b/src/debugProtocol/events/zzresponsesOld/responseCreationHelpers.spec.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/no-loop-func */ /* eslint-disable no-bitwise */ import { SmartBuffer } from 'smart-buffer'; -import { ERROR_CODES, UPDATE_TYPES, VARIABLE_FLAGS, VARIABLE_TYPES } from '../Constants'; -import type { BreakpointInfo } from './ListBreakpointsResponse'; +import { ERROR_CODES, UPDATE_TYPES, VARIABLE_FLAGS, VARIABLE_TYPES } from '../../Constants'; +import type { BreakpointInfo } from '../responses/ListBreakpointsResponse'; interface Handshake { magic: string; @@ -113,7 +113,7 @@ export function createListBreakpointsResponse(params: { requestId?: number; erro buffer.writeUInt32LE(params.num_breakpoints ?? params.breakpoints?.length ?? 0); // num_breakpoints for (const breakpoint of params?.breakpoints ?? []) { - writeIfSet(breakpoint.breakpointId, x => buffer.writeUInt32LE(x)); + writeIfSet(breakpoint.id, x => buffer.writeUInt32LE(x)); writeIfSet(breakpoint.errorCode, x => buffer.writeUInt32LE(x)); writeIfSet(breakpoint.hitCount, x => buffer.writeUInt32LE(x)); } diff --git a/src/debugProtocol/requests/ProtocolRequest.ts b/src/debugProtocol/requests/ProtocolRequest.ts deleted file mode 100644 index d17b185e..00000000 --- a/src/debugProtocol/requests/ProtocolRequest.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { COMMANDS } from '../Constants'; - -export interface ProtocolRequest { - /** - * Was this event successful in parsing/ingesting the data in its constructor - */ - success: boolean; - - /** - * The number of bytes that were read from a buffer if this was a success - */ - readOffset: number; - - /** - * Serialize this event into Convert the current object into the debug protocol binary format, - * stored in a `Buffer` - */ - toBuffer(): Buffer; - - /** - * Contains the actual event data - */ - data: TData; -} - -export interface HasCommandCode { - commandCode: COMMANDS; -} diff --git a/src/debugProtocol/responses/HandshakeResponse.spec.ts b/src/debugProtocol/responses/HandshakeResponse.spec.ts deleted file mode 100644 index 03b2bdcc..00000000 --- a/src/debugProtocol/responses/HandshakeResponse.spec.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { HandshakeResponse } from './HandshakeResponse'; -import { Debugger } from '../Debugger'; -import { createHandShakeResponse } from './responseCreationHelpers.spec'; -import { expect } from 'chai'; - -describe('HandshakeResponse', () => { - it('Handles a handshake response', () => { - let mockResponse = createHandShakeResponse({ - magic: Debugger.DEBUGGER_MAGIC, - major: 1, - minor: 0, - patch: 0 - }); - - let handshake = new HandshakeResponse(mockResponse.toBuffer()); - expect(handshake.magic).to.be.equal(Debugger.DEBUGGER_MAGIC); - expect(handshake.majorVersion).to.be.equal(1); - expect(handshake.minorVersion).to.be.equal(0); - expect(handshake.patchVersion).to.be.equal(0); - expect(handshake.readOffset).to.be.equal(mockResponse.writeOffset); - expect(handshake.success).to.be.equal(true); - }); - - it('Fails when buffer is incomplete', () => { - let mockResponse = createHandShakeResponse({ - magic: Debugger.DEBUGGER_MAGIC, - major: 1, - minor: 0, - patch: 0 - }); - - let handshake = new HandshakeResponse(mockResponse.toBuffer().slice(-3)); - expect(handshake.success).to.equal(false); - }); - - it('Fails when the protocol version is equal to or greater then 3.0.0', () => { - let mockResponseV3 = createHandShakeResponse({ - magic: Debugger.DEBUGGER_MAGIC, - major: 3, - minor: 0, - patch: 0 - }); - - let handshakeV3 = new HandshakeResponse(mockResponseV3.toBuffer()); - expect(handshakeV3.success).to.equal(false); - - let mockResponseV301 = createHandShakeResponse({ - magic: Debugger.DEBUGGER_MAGIC, - major: 3, - minor: 0, - patch: 1 - }); - - let handshakeV301 = new HandshakeResponse(mockResponseV301.toBuffer()); - expect(handshakeV301.success).to.equal(false); - }); -}); diff --git a/src/debugProtocol/responses/HandshakeResponse.ts b/src/debugProtocol/responses/HandshakeResponse.ts deleted file mode 100644 index f459e34f..00000000 --- a/src/debugProtocol/responses/HandshakeResponse.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { SmartBuffer } from 'smart-buffer'; -import * as semver from 'semver'; -import { util } from '../../util'; -import { ProtocolResponse } from './ProtocolResponse'; - -export class HandshakeResponse extends ProtocolResponse { - - public constructor(json: HandshakeResponse['data']); - public constructor(buffer: Buffer); - public constructor(arg: Buffer | HandshakeResponse['data']) { - super(); - if (Buffer.isBuffer(arg)) { - this.loadFromBuffer(arg); - } else { - this.loadJson(arg); - } - } - - loadFromBuffer(buffer: Buffer) { - this.bufferLoaderHelper(buffer, 20, null, (smartBuffer: SmartBuffer) => { - this.data.magic = util.readStringNT(smartBuffer); // magic_number - this.data.majorVersion = smartBuffer.readInt32LE(); // protocol_major_version - this.data.minorVersion = smartBuffer.readInt32LE(); // protocol_minor_version - this.data.patchVersion = smartBuffer.readInt32LE(); // protocol_patch_version - - // We only support version prior to v3 with this handshake - if (!semver.satisfies(this.getVersion(), '<3.0.0')) { - throw new Error(`unsupported version ${this.getVersion()}`); - } - return true; - }); - } - - protected loadJson(data: HandshakeResponse['data']) { - this.data = data; - this.success = true; - } - - public toBuffer() { - let buffer = new SmartBuffer(); - buffer.writeStringNT(this.data.magic); // magic_number - buffer.writeUInt32LE(this.data.majorVersion); // protocol_major_version - buffer.writeUInt32LE(this.data.minorVersion); // protocol_minor_version - buffer.writeUInt32LE(this.data.patchVersion); // protocol_patch_version - - return buffer.toBuffer(); - } - - public watchPacketLength = false; // this will always be false for older protocol versions - public success = false; - public readOffset = 0; - public requestId = 0; - - getVersion() { - return [this.data.majorVersion, this.data.minorVersion, this.data.patchVersion].join('.'); - } - - public data = { - /** - * The Roku Brightscript debug protocol identifier, which is the following 64-bit value :0x0067756265647362LU. - * - * This is equal to 29120988069524322LU or the following little-endian value: b'bsdebug\0. - */ - magic: undefined as string, - majorVersion: -1, - minorVersion: -1, - patchVersion: -1 - }; -} diff --git a/src/debugProtocol/responses/HandshakeResponseV3.spec.ts b/src/debugProtocol/responses/HandshakeResponseV3.spec.ts deleted file mode 100644 index 8374a553..00000000 --- a/src/debugProtocol/responses/HandshakeResponseV3.spec.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { HandshakeResponseV3 } from './HandshakeResponseV3'; -import { Debugger } from '../Debugger'; -import { createHandShakeResponseV3 } from './responseCreationHelpers.spec'; -import { expect } from 'chai'; -import { SmartBuffer } from 'smart-buffer'; - -describe('HandshakeResponseV3', () => { - it('Handles a handshake response', () => { - let mockResponse = createHandShakeResponseV3({ - magic: Debugger.DEBUGGER_MAGIC, - major: 3, - minor: 0, - patch: 0, - revisionTimeStamp: Date.now() - }); - - let handshake = new HandshakeResponseV3(mockResponse.toBuffer()); - expect(handshake.magic).to.be.equal(Debugger.DEBUGGER_MAGIC); - expect(handshake.majorVersion).to.be.equal(3); - expect(handshake.minorVersion).to.be.equal(0); - expect(handshake.patchVersion).to.be.equal(0); - expect(handshake.readOffset).to.be.equal(mockResponse.writeOffset); - expect(handshake.success).to.be.equal(true); - }); - - it('Handles a extra packet length in handshake response', () => { - let extraData = new SmartBuffer(); - extraData.writeStringNT('this is extra data'); - extraData.writeUInt32LE(10); - - let mockResponse = createHandShakeResponseV3({ - magic: Debugger.DEBUGGER_MAGIC, - major: 3, - minor: 0, - patch: 0, - revisionTimeStamp: Date.now() - }, extraData.toBuffer()); - - const expectedReadOffset = mockResponse.writeOffset; - - // Write some extra data that the handshake should not include in the readOffSet - mockResponse.writeUInt32LE(123); - - let handshake = new HandshakeResponseV3(mockResponse.toBuffer()); - expect(handshake.magic).to.be.equal(Debugger.DEBUGGER_MAGIC); - expect(handshake.majorVersion).to.be.equal(3); - expect(handshake.minorVersion).to.be.equal(0); - expect(handshake.patchVersion).to.be.equal(0); - expect(handshake.readOffset).to.be.equal(expectedReadOffset); - expect(handshake.success).to.be.equal(true); - }); - - it('Fails when buffer is incomplete', () => { - let mockResponse = createHandShakeResponseV3({ - magic: Debugger.DEBUGGER_MAGIC, - major: 3, - minor: 0, - patch: 0, - revisionTimeStamp: Date.now() - }); - - let handshake = new HandshakeResponseV3(mockResponse.toBuffer().slice(-3)); - expect(handshake.success).to.equal(false); - }); - - it('Fails when the protocol version is less then 3.0.0', () => { - let mockResponseV3 = createHandShakeResponseV3({ - magic: Debugger.DEBUGGER_MAGIC, - major: 2, - minor: 0, - patch: 0, - revisionTimeStamp: Date.now() - }); - - let handshakeV3 = new HandshakeResponseV3(mockResponseV3.toBuffer()); - expect(handshakeV3.success).to.equal(false); - - let mockResponseV301 = createHandShakeResponseV3({ - magic: Debugger.DEBUGGER_MAGIC, - major: 2, - minor: 9, - patch: 9, - revisionTimeStamp: Date.now() - }); - - let handshakeV301 = new HandshakeResponseV3(mockResponseV301.toBuffer()); - expect(handshakeV301.success).to.equal(false); - }); -}); diff --git a/src/debugProtocol/responses/HandshakeResponseV3.ts b/src/debugProtocol/responses/HandshakeResponseV3.ts deleted file mode 100644 index 722688fb..00000000 --- a/src/debugProtocol/responses/HandshakeResponseV3.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { SmartBuffer } from 'smart-buffer'; -import * as semver from 'semver'; -import { util } from '../../util'; -import { ProtocolResponse } from './ProtocolResponse'; - -export class HandshakeResponseV3 extends ProtocolResponse { - - public constructor(json: HandshakeResponseV3['data']); - public constructor(buffer: Buffer); - public constructor(arg: Buffer | HandshakeResponseV3['data']) { - super(); - if (Buffer.isBuffer(arg)) { - this.loadFromBuffer(arg); - } else { - this.loadJson(arg); - } - } - - private loadFromBuffer(buffer: Buffer) { - this.bufferLoaderHelper(buffer, 20, null, (smartBuffer: SmartBuffer) => { - this.data.magic = util.readStringNT(smartBuffer); // debugger_magic - this.data.majorVersion = smartBuffer.readInt32LE(); // protocol_major_version - this.data.minorVersion = smartBuffer.readInt32LE(); // protocol_minor_version - this.data.patchVersion = smartBuffer.readInt32LE(); // protocol_patch_version - - const legacyReadSize = smartBuffer.readOffset; - const remainingPacketLength = smartBuffer.readInt32LE(); // remaining_packet_length - - const requiredBufferSize = remainingPacketLength + legacyReadSize; - this.data.revisionTimeStamp = new Date(Number(smartBuffer.readBigUInt64LE())); // platform_revision_timestamp - - if (smartBuffer.length < requiredBufferSize) { - throw new Error(`Missing buffer data according to the remaining packet length: ${smartBuffer.length}/${requiredBufferSize}`); - } - //set the buffer offset - smartBuffer.readOffset = requiredBufferSize; - - // We only support v3 or above with this handshake - if (!semver.satisfies(this.getVersion(), '>=3.0.0')) { - throw new Error(`unsupported version ${this.getVersion()}`); - } - this.watchPacketLength = true; - }); - } - - protected loadJson(data: HandshakeResponseV3['data']) { - super.loadJson(data); - this.watchPacketLength = true; - } - - /** - * Convert the data into a buffer - */ - public toBuffer() { - let buffer = new SmartBuffer(); - buffer.writeStringNT(this.data.magic); // magic_number - buffer.writeUInt32LE(this.data.majorVersion); // protocol_major_version - buffer.writeUInt32LE(this.data.minorVersion); // protocol_minor_version - buffer.writeUInt32LE(this.data.patchVersion); // protocol_patch_version - - //As of BrightScript debug protocol 3.0.0 (Roku OS 11.0), all packets from the debugging target include a packet_length. - //The length is always in bytes, and includes the packet_length field, itself. - //This field avoids the need for changes to the major version of the protocol because it allows a debugger client to - //read past data it does not understand and is not critical to debugger operations. - const remainingDataBuffer = new SmartBuffer(); - remainingDataBuffer.writeBigInt64LE(BigInt( - this.data.revisionTimeStamp.getTime() - )); // platform_revision_timestamp - - buffer.writeUInt32LE(remainingDataBuffer.writeOffset + 4); // remaining_packet_length - buffer.writeBuffer(remainingDataBuffer.toBuffer()); - - return buffer.toBuffer(); - } - - public watchPacketLength = false; // this will always be true for the new protocol versions - public success = false; - public readOffset = 0; - public requestId = 0; - - public getVersion() { - return [this.data.majorVersion, this.data.minorVersion, this.data.patchVersion].join('.'); - } - - public data = { - /** - * The Roku Brightscript debug protocol identifier, which is the following 64-bit value :0x0067756265647362LU. - * - * This is equal to 29120988069524322LU or the following little-endian value: b'bsdebug\0. - */ - magic: undefined as string, - majorVersion: -1, - minorVersion: -1, - patchVersion: -1, - /** - * A platform-specific implementation timestamp (in milliseconds since epoch [1970-01-01T00:00:00.000Z]). - * - * As of BrightScript debug protocol 3.0.0 (Roku OS 11.0), a timestamp is sent to the debugger client in the initial handshake. This timestamp is platform-specific data that is included in the system software of the platform being debugged. It is changed by the platform's vendor when there is any change that affects the behavior of the debugger. - * - * The value can be used in manners similar to a build number, and is primarily used to differentiate between pre-release builds of the platform being debugged. - */ - revisionTimeStamp: undefined as Date - }; -} diff --git a/src/debugProtocol/responses/ListBreakpointsResponse.spec.ts b/src/debugProtocol/responses/ListBreakpointsResponse.spec.ts deleted file mode 100644 index 1f756ab6..00000000 --- a/src/debugProtocol/responses/ListBreakpointsResponse.spec.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { createListBreakpointsResponse, getRandomBuffer } from './responseCreationHelpers.spec'; -import { expect } from 'chai'; -import { ListBreakpointsResponse } from './ListBreakpointsResponse'; -import { ERROR_CODES } from '../Constants'; - -describe('ListBreakpointsResponse', () => { - let response: ListBreakpointsResponse; - beforeEach(() => { - response = undefined; - }); - it('handles empty buffer', () => { - response = new ListBreakpointsResponse(null); - //Great, it didn't explode! - expect(response.success).to.be.false; - }); - - it('handles undersized buffers', () => { - response = new ListBreakpointsResponse( - getRandomBuffer(0) - ); - expect(response.success).to.be.false; - - response = new ListBreakpointsResponse( - getRandomBuffer(1) - ); - expect(response.success).to.be.false; - - response = new ListBreakpointsResponse( - getRandomBuffer(11) - ); - expect(response.success).to.be.false; - }); - - it('gracefully handles mismatched breakpoint count', () => { - const bp1 = { - breakpointId: 1, - errorCode: ERROR_CODES.OK, - hitCount: 0, - success: true - }; - response = new ListBreakpointsResponse( - createListBreakpointsResponse({ - requestId: 1, - num_breakpoints: 2, - breakpoints: [bp1] - }).toBuffer() - ); - expect(response.success).to.be.false; - expect(response.breakpoints).to.eql([bp1]); - }); - - it('handles malformed breakpoint data', () => { - const bp1 = { - breakpointId: 1, - errorCode: ERROR_CODES.OK, - hitCount: 2, - success: true - }; - response = new ListBreakpointsResponse( - createListBreakpointsResponse({ - requestId: 1, - num_breakpoints: 2, - breakpoints: [ - bp1, - { - //missing all other bp properties - breakpointId: 1 - } - ] - }).toBuffer() - ); - expect(response.success).to.be.false; - expect(response.breakpoints).to.eql([bp1]); - }); - - it('handles malformed breakpoint data', () => { - const bp1 = { - breakpointId: 0, - errorCode: ERROR_CODES.OK, - success: true - }; - response = new ListBreakpointsResponse( - createListBreakpointsResponse({ - requestId: 1, - num_breakpoints: 2, - breakpoints: [bp1] - }).toBuffer() - ); - expect(response.success).to.be.false; - //hitcount should not be set when bpId is zero - expect(response.breakpoints[0].hitCount).to.be.undefined; - //the breakpoint should not be verified if bpId === 0 - expect(response.breakpoints[0].isVerified).to.be.false; - }); - - it('reads breakpoint data properly', () => { - const bp1 = { - breakpointId: 1, - errorCode: ERROR_CODES.OK, - hitCount: 0, - success: true - }; - response = new ListBreakpointsResponse( - createListBreakpointsResponse({ - requestId: 1, - breakpoints: [bp1] - }).toBuffer() - ); - expect(response.success).to.be.true; - expect(response.breakpoints).to.eql([bp1]); - expect(response.breakpoints[0].isVerified).to.be.true; - }); - - it('reads breakpoint data properly', () => { - const bp1 = { - breakpointId: 1, - errorCode: ERROR_CODES.NOT_STOPPED, - hitCount: 0, - success: true - }; - response = new ListBreakpointsResponse( - createListBreakpointsResponse({ - requestId: 1, - breakpoints: [bp1] - }).toBuffer() - ); - expect( - response.breakpoints[0].errorText - ).to.eql( - ERROR_CODES[ERROR_CODES.NOT_STOPPED] - ); - }); -}); diff --git a/src/debugProtocol/responses/ListBreakpointsResponse.ts b/src/debugProtocol/responses/ListBreakpointsResponse.ts deleted file mode 100644 index ca17cd3c..00000000 --- a/src/debugProtocol/responses/ListBreakpointsResponse.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { SmartBuffer } from 'smart-buffer'; -import { ERROR_CODES } from '../Constants'; - -export class ListBreakpointsResponse { - - constructor(buffer: Buffer) { - // The minimum size of a request - if (buffer?.byteLength >= 12) { - try { - let bufferReader = SmartBuffer.fromBuffer(buffer); - this.requestId = bufferReader.readUInt32LE(); // request_id - - // Any request id less then one is an update and we should not process it here - if (this.requestId > 0) { - this.errorCode = bufferReader.readUInt32LE(); - this.numBreakpoints = bufferReader.readUInt32LE(); // num_breakpoints - The number of breakpoints in the breakpoints array. - - // build the list of BreakpointInfo - for (let i = 0; i < this.numBreakpoints; i++) { - let breakpointInfo = new BreakpointInfo(bufferReader); - // All the necessary data was present, so keep this item - this.breakpoints.push(breakpointInfo); - } - - this.readOffset = bufferReader.readOffset; - this.success = (this.breakpoints.length === this.numBreakpoints); - } - } catch (error) { - // Could not parse - } - } - } - public success = false; - public readOffset = 0; - - // response fields - public requestId = -1; - public numBreakpoints: number; - public breakpoints = [] as BreakpointInfo[]; - public data = -1; - public errorCode: ERROR_CODES; -} - -export class BreakpointInfo { - constructor(bufferReader: SmartBuffer) { - // breakpoint_id - The ID assigned to the breakpoint. An ID greater than 0 indicates an active breakpoint. An ID of 0 denotes that the breakpoint has an error. - this.breakpointId = bufferReader.readUInt32LE(); - // error_code - Indicates whether the breakpoint was successfully returned. - this.errorCode = bufferReader.readUInt32LE(); - - if (this.breakpointId > 0) { - // This argument is only present if the breakpoint_id is valid. - // ignore_count - Current state, decreases as breakpoint is executed. - this.hitCount = bufferReader.readUInt32LE(); - } - this.success = true; - } - - public get isVerified() { - return this.breakpointId > 0; - } - public success = false; - public breakpointId: number; - public errorCode: number; - /** - * The textual description of the error code - */ - public get errorText() { - return ERROR_CODES[this.errorCode]; - } - public hitCount: number; -} diff --git a/src/debugProtocol/responses/ProtocolEvent.spec.ts b/src/debugProtocol/responses/ProtocolEvent.spec.ts deleted file mode 100644 index c22bd317..00000000 --- a/src/debugProtocol/responses/ProtocolEvent.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { ProtocolEvent } from './ProtocolEvent'; -import { createHandShakeResponse, createProtocolEvent } from './responseCreationHelpers.spec'; -import { Debugger } from '../Debugger'; -import { expect } from 'chai'; -import { ERROR_CODES, UPDATE_TYPES } from '../Constants'; - -describe('ProtocolEvent', () => { - it('Handles a Protocol update events', () => { - let mockResponse = createProtocolEvent({ - requestId: 0, - errorCode: ERROR_CODES.CANT_CONTINUE, - updateType: UPDATE_TYPES.ALL_THREADS_STOPPED - }); - - let protocolEvent = new ProtocolEvent(mockResponse.toBuffer()); - expect(protocolEvent.requestId).to.be.equal(0); - expect(protocolEvent.errorCode).to.be.equal(ERROR_CODES.CANT_CONTINUE); - expect(protocolEvent.updateType).to.be.equal(UPDATE_TYPES.ALL_THREADS_STOPPED); - expect(protocolEvent.readOffset).to.be.equal(mockResponse.writeOffset); - expect(protocolEvent.success).to.be.equal(true); - }); - - it('Handles a Protocol response events', () => { - let mockResponse = createProtocolEvent({ - requestId: 1, - errorCode: ERROR_CODES.OK - }); - - let protocolEvent = new ProtocolEvent(mockResponse.toBuffer()); - expect(protocolEvent.requestId).to.be.equal(1); - expect(protocolEvent.errorCode).to.be.equal(ERROR_CODES.OK); - expect(protocolEvent.updateType).to.be.equal(-1); - expect(protocolEvent.readOffset).to.be.equal(mockResponse.writeOffset); - expect(protocolEvent.success).to.be.equal(true); - }); - - it('Fails when buffer is incomplete', () => { - let mockResponse = createHandShakeResponse({ - magic: Debugger.DEBUGGER_MAGIC, - major: 1, - minor: 0, - patch: 0 - }); - - let protocolEvent = new ProtocolEvent(mockResponse.toBuffer().slice(-3)); - expect(protocolEvent.success).to.equal(false); - }); -}); diff --git a/src/debugProtocol/responses/ProtocolEvent.ts b/src/debugProtocol/responses/ProtocolEvent.ts deleted file mode 100644 index cf77b290..00000000 --- a/src/debugProtocol/responses/ProtocolEvent.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { SmartBuffer } from 'smart-buffer'; - -export class ProtocolEvent { - constructor(buffer: Buffer) { - // The smallest a request response can be - if (buffer.byteLength >= 8) { - try { - let bufferReader = SmartBuffer.fromBuffer(buffer); - this.requestId = bufferReader.readUInt32LE(); // request_id - this.errorCode = bufferReader.readUInt32LE(); // error_code - - // Any request id less then one is an update and we should not process it here - if (this.requestId > 0) { - this.readOffset = bufferReader.readOffset; - } else if (this.requestId === 0) { - this.updateType = bufferReader.readUInt32LE(); - } - this.readOffset = bufferReader.readOffset; - this.success = true; - } catch (error) { - // Could not parse - } - } - } - - public success = false; - public readOffset = 0; - - // response fields - public packetLength = 0; - public requestId = -1; - public updateType = -1; - public errorCode = -1; - public data = -1; -} diff --git a/src/debugProtocol/responses/ProtocolEventV3.ts b/src/debugProtocol/responses/ProtocolEventV3.ts deleted file mode 100644 index a624d9a4..00000000 --- a/src/debugProtocol/responses/ProtocolEventV3.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { SmartBuffer } from 'smart-buffer'; - -export class ProtocolEventV3 { - constructor(buffer: Buffer) { - // The smallest a request response can be - if (buffer.byteLength >= 12) { - try { - let bufferReader = SmartBuffer.fromBuffer(buffer); - this.packetLength = bufferReader.readUInt32LE(); // packet_length - this.requestId = bufferReader.readUInt32LE(); // request_id - this.errorCode = bufferReader.readUInt32LE(); // error_code - - if (bufferReader.length < this.packetLength) { - throw new Error(`Incomplete packet. Bytes received: ${bufferReader.length}/${this.packetLength}`); - } - - // Any request id less then one is an update and we should not process it here - if (this.requestId > 0) { - this.readOffset = bufferReader.readOffset; - } else if (this.requestId === 0) { - this.updateType = bufferReader.readUInt32LE(); - } - this.readOffset = bufferReader.readOffset; - this.success = true; - } catch (error) { - // Could not parse - } - } - } - public success = false; - public readOffset = 0; - - // response fields - public packetLength = 0; - public requestId = -1; - public updateType = -1; - public errorCode = -1; - public data = -1; -} diff --git a/src/debugProtocol/responses/ProtocolResponse.ts b/src/debugProtocol/responses/ProtocolResponse.ts deleted file mode 100644 index c9985da9..00000000 --- a/src/debugProtocol/responses/ProtocolResponse.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { SmartBuffer } from 'smart-buffer'; - -export abstract class ProtocolResponse { - /** - * Was this class successful in parsing/ingesting the data in its constructor - */ - public success = false; - - /** - * The number of bytes that were read from a buffer if this was a success - */ - public readOffset: number; - - /** - * Convert the current object into the debug protocol binary format, - * stored in a `Buffer` - */ - public abstract toBuffer(): Buffer; - - /** - * Load the payload in json form - */ - protected loadJson(data: any) { - this.data = data; - this.success = true; - } - - /** - * Contains the actual response data - */ - public data: TData; - - /** - * Helper function for buffer loading. - * Handles things like try/catch, setting buffer read offset, etc - */ - protected bufferLoaderHelper(buffer: Buffer, minByteLength: number, data: Record | string | number | null, processor: (buffer: SmartBuffer) => boolean | void) { - // Required size of this processor - if (buffer.byteLength >= minByteLength) { - try { - let smartBuffer = SmartBuffer.fromBuffer(buffer); - - //have the processor consume the requred bytes. - this.success = (processor(smartBuffer) ?? true) as boolean; - - this.readOffset = smartBuffer.readOffset; - } catch (error) { - // Could not parse - this.readOffset = 0; - this.success = true; - } - } - } - - /** - * Given a buffer, prepend the packet_length based on the size of the buffer and return the new buffer - */ - protected getBufferWithPacketLength(smartBuffer: SmartBuffer) { - smartBuffer.insertUInt32LE(smartBuffer.length + 4, 0); - return smartBuffer.toBuffer(); - } -} diff --git a/src/debugProtocol/responses/updates/UpdateResponse.ts b/src/debugProtocol/responses/updates/UpdateResponse.ts deleted file mode 100644 index 9f49898b..00000000 --- a/src/debugProtocol/responses/updates/UpdateResponse.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { SmartBuffer } from 'smart-buffer'; -import type { UPDATE_TYPES } from '../../Constants'; -import { ProtocolResponse } from '../ProtocolResponse'; - -export abstract class UpdateResponse extends ProtocolResponse { - - protected bufferLoaderHelper(buffer: Buffer, minByteLength: number, updateType: UPDATE_TYPES, processor: (buffer: SmartBuffer) => boolean | void) { - //extract the common update information - super.bufferLoaderHelper(buffer, minByteLength + 12, null, (smartBuffer) => { - this.data.packetLength = smartBuffer.readUInt32LE(); // packet_length - this.data.requestId = smartBuffer.readUInt32LE(); // request_id - this.data.errorCode = smartBuffer.readUInt32LE(); // error_code - - if (smartBuffer.length < this.data.packetLength) { - throw new Error(`Incomplete packet. Bytes received: ${smartBuffer.length}/${this.data.packetLength}`); - } - - // requestId 0 means this is an update. - if (this.data.requestId === 0) { - this.data.updateType = smartBuffer.readUInt32LE(); - - //if this is not the update type we want, return false - if (this.data.updateType !== updateType) { - return false; - } - - } else { - //not an update. We should not proceed any further. - throw new Error('This is not an update'); - } - //call the specific update handler - return processor(smartBuffer); - }); - } - - public abstract data: { - packetLength: number; - requestId: number; - errorCode: number; - updateType: number; - }; -} - diff --git a/src/debugProtocol/server/DebugProtocolServer.ts b/src/debugProtocol/server/DebugProtocolServer.ts index a724750f..e3111a32 100644 --- a/src/debugProtocol/server/DebugProtocolServer.ts +++ b/src/debugProtocol/server/DebugProtocolServer.ts @@ -1,11 +1,10 @@ import { EventEmitter } from 'eventemitter3'; import * as Net from 'net'; -import { SmartBuffer } from 'smart-buffer'; import { ActionQueue } from '../../managers/ActionQueue'; -import { HandshakeRequest } from '../requests/HandshakeRequest'; -import type { ProtocolRequest } from '../requests/ProtocolRequest'; -import { HandshakeResponse, HandshakeResponseV3 } from '../responses'; -import type { ProtocolResponse } from '../responses/ProtocolResponse'; +import type { ProtocolRequest, ProtocolResponse } from '../events/ProtocolEvent'; +import { HandshakeRequest } from '../events/requests/HandshakeRequest'; +import { HandshakeResponse } from '../events/responses/HandshakeResponse'; +import { HandshakeResponseV3 } from '../events/responses/HandshakeResponseV3'; import PluginInterface from './PluginInterface'; import type { ProtocolPlugin } from './ProtocolPlugin'; @@ -111,7 +110,7 @@ export class DebugProtocolServer { let request: ProtocolRequest; //if we haven't seen the handshake yet, look for the handshake first if (!this.isHandshakeComplete) { - request = new HandshakeRequest(buffer); + request = HandshakeRequest.fromBuffer(buffer); if (request.success) { return request; } @@ -122,13 +121,11 @@ export class DebugProtocolServer { private getResponse(request: ProtocolRequest) { if (request instanceof HandshakeRequest) { - return new HandshakeResponseV3({ + return HandshakeResponseV3.fromJson({ magic: this.magic, - majorVersion: 3, - minorVersion: 1, - patchVersion: 0, + protocolVersion: '3.1.0', //TODO update this to an actual date from the device - revisionTimeStamp: new Date(2022, 1, 1) + revisionTimestamp: new Date(2022, 1, 1) }); } } @@ -147,7 +144,7 @@ export class DebugProtocolServer { } //trim the buffer now that the request has been processed - this.buffer = buffer.slice(request.readOffset); + this.buffer = buffer.slice((request as ProtocolRequest).readOffset); //now ask the plugin to provide a response for the given request let { response } = await this.plugins.emit('provideResponse', { diff --git a/src/debugProtocol/server/ProtocolPlugin.ts b/src/debugProtocol/server/ProtocolPlugin.ts index 5fe130f3..203d4784 100644 --- a/src/debugProtocol/server/ProtocolPlugin.ts +++ b/src/debugProtocol/server/ProtocolPlugin.ts @@ -1,7 +1,7 @@ import type { DebugProtocolServer } from './DebugProtocolServer'; -import type { ProtocolResponse } from '../responses/ProtocolResponse'; +import type { ProtocolResponse } from '../events/zzresponsesOld/ProtocolResponse'; import type { Socket } from 'net'; -import type { ProtocolRequest } from '../requests/ProtocolRequest'; +import type { ProtocolRequest } from '../events/requests/ProtocolRequest'; export interface ProtocolPlugin { onClientConnected?: Handler; diff --git a/src/index.ts b/src/index.ts index a1a6abef..6bd1aa55 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,7 @@ export * from './ComponentLibraryServer'; export * from './CompileErrorProcessor'; export * from './debugProtocol/Constants'; export * from './debugProtocol/Debugger'; -export * from './debugProtocol/responses'; +export * from './debugProtocol/events/zzresponsesOld'; export * from './FileUtils'; export * from './managers/ProjectManager'; export * from './RendezvousTracker'; From c56ab79234264fe610e2cc5405e98c4c93981d77 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Mon, 10 Oct 2022 22:37:43 -0400 Subject: [PATCH 07/74] Add more updates --- src/debugProtocol/Constants.ts | 1 - src/debugProtocol/Debugger.ts | 256 +++++++++--------- .../AddBreakpointsResponse.ts | 2 +- .../events/responses/HandshakeResponse.ts | 2 - .../events/responses/HandshakeResponseV3.ts | 3 - .../RemoveBreakpointsResponse.ts | 2 +- .../updates/AllThreadsStoppedUpdate.spec.ts | 1 - .../events/updates/AllThreadsStoppedUpdate.ts | 82 +----- .../updates/BreakpointErrorUpdate.spec.ts | 63 +++++ .../events/updates/BreakpointErrorUpdate.ts | 114 ++++++++ .../events/updates/CompileErrorUpdate.spec.ts | 40 +++ .../events/updates/CompileErrorUpdate.ts | 93 +++++++ .../events/updates/IOPortOpenedUpdate.spec.ts | 32 +++ .../events/updates/IOPortOpenedUpdate.ts | 50 ++++ .../updates/ThreadAttachedUpdate.spec.ts | 38 +++ .../events/updates/ThreadAttachedUpdate.ts | 56 ++++ .../events/updates/UpdateResponse.ts | 13 - .../BreakpointErrorResponse.spec.ts | 2 +- .../BreakpointErrorUpdateResponse.ts | 82 ------ .../zzresponsesOld/ConnectIOPortResponse.ts | 38 --- .../zzresponsesOld/UndefinedResponse.ts | 38 --- .../zzresponsesOld/UpdateThreadsResponse.ts | 97 ------- .../events/zzresponsesOld/index.ts | 5 +- 23 files changed, 638 insertions(+), 472 deletions(-) rename src/debugProtocol/events/{zzresponsesOld => responses}/AddBreakpointsResponse.ts (66%) rename src/debugProtocol/events/{zzresponsesOld => responses}/RemoveBreakpointsResponse.ts (67%) create mode 100644 src/debugProtocol/events/updates/BreakpointErrorUpdate.spec.ts create mode 100644 src/debugProtocol/events/updates/BreakpointErrorUpdate.ts create mode 100644 src/debugProtocol/events/updates/CompileErrorUpdate.spec.ts create mode 100644 src/debugProtocol/events/updates/CompileErrorUpdate.ts create mode 100644 src/debugProtocol/events/updates/IOPortOpenedUpdate.spec.ts create mode 100644 src/debugProtocol/events/updates/IOPortOpenedUpdate.ts create mode 100644 src/debugProtocol/events/updates/ThreadAttachedUpdate.spec.ts create mode 100644 src/debugProtocol/events/updates/ThreadAttachedUpdate.ts delete mode 100644 src/debugProtocol/events/updates/UpdateResponse.ts delete mode 100644 src/debugProtocol/events/zzresponsesOld/BreakpointErrorUpdateResponse.ts delete mode 100644 src/debugProtocol/events/zzresponsesOld/ConnectIOPortResponse.ts delete mode 100644 src/debugProtocol/events/zzresponsesOld/UndefinedResponse.ts delete mode 100644 src/debugProtocol/events/zzresponsesOld/UpdateThreadsResponse.ts diff --git a/src/debugProtocol/Constants.ts b/src/debugProtocol/Constants.ts index 17169659..733c5975 100644 --- a/src/debugProtocol/Constants.ts +++ b/src/debugProtocol/Constants.ts @@ -144,4 +144,3 @@ export interface RequestData { requestId: number; commandCode: number; } - \ No newline at end of file diff --git a/src/debugProtocol/Debugger.ts b/src/debugProtocol/Debugger.ts index 0619d0db..fdeafe13 100644 --- a/src/debugProtocol/Debugger.ts +++ b/src/debugProtocol/Debugger.ts @@ -1,30 +1,15 @@ import * as Net from 'net'; import * as EventEmitter from 'eventemitter3'; import * as semver from 'semver'; -import type { - ThreadAttached, - ThreadsStopped -} from './events/zzresponsesOld'; -import { - ConnectIOPortResponse, - ProtocolEvent, - ProtocolEventV3, - StackTraceResponse, - StackTraceResponseV3, - ThreadsResponse, - UndefinedResponse, - UpdateThreadsResponse, - VariableResponse -} from './events/zzresponsesOld'; import { PROTOCOL_ERROR_CODES, COMMANDS, STEP_TYPE, STOP_REASONS, VARIABLE_REQUEST_FLAGS, ERROR_CODES, UPDATE_TYPES } from './Constants'; import { SmartBuffer } from 'smart-buffer'; import { logger } from '../logging'; import { ExecuteResponseV3 } from './events/zzresponsesOld/ExecuteResponseV3'; import { ListBreakpointsResponse } from './events/responses/ListBreakpointsResponse'; -import { AddBreakpointsResponse } from './events/zzresponsesOld/AddBreakpointsResponse'; -import { RemoveBreakpointsResponse } from './events/zzresponsesOld/RemoveBreakpointsResponse'; +import { AddBreakpointsResponse } from './events/responses/AddBreakpointsResponse'; +import { RemoveBreakpointsResponse } from './events/responses/RemoveBreakpointsResponse'; import { util } from '../util'; -import { BreakpointErrorUpdateResponse } from './events/zzresponsesOld/BreakpointErrorUpdateResponse'; +import { BreakpointErrorUpdate, BreakpointErrorUpdateResponse } from './events/updates/BreakpointErrorUpdate'; import { ContinueRequest } from './events/requests/ContinueRequest'; import { StopRequest } from './events/requests/StopRequest'; import { ExitChannelRequest } from './events/requests/ExitChannelRequest'; @@ -41,6 +26,11 @@ import type { ProtocolRequest, ProtocolResponse, ProtocolUpdate } from './events import { HandshakeResponse } from './events/responses/HandshakeResponse'; import { HandshakeResponseV3 } from './events/responses/HandshakeResponseV3'; import { HandshakeRequest } from './events/requests/HandshakeRequest'; +import { GenericResponseV3 } from './events/responses/GenericResponseV3'; +import { GenericResponse, IOPortOpenedUpdate, StackTraceResponse, StackTraceResponseV3, ThreadAttachedUpdate, ThreadsResponse, UndefinedResponse, VariableResponse } from './events/zzresponsesOld'; +import { AllThreadsStoppedUpdate } from './events/updates/AllThreadsStoppedUpdate'; +import { buffer } from 'rxjs'; +import { CompileErrorUpdate } from './events/updates/CompileErrorUpdate'; export class Debugger { @@ -68,6 +58,10 @@ export class Debugger { public scriptTitle: string; public isHandshakeComplete = false; public connectedToIoPort = false; + /** + * Debug protocol version 3.0.0 introduced a packet_length to all responses. Prior to that, most responses had no packet length at all. + * This field indicates whether we should be looking for packet_length or not in the responses we get from the device + */ public watchPacketLength = false; public protocolVersion: string; public primaryThread: number; @@ -139,6 +133,7 @@ export class Debugger { } private emit(eventName: 'response', data: { request: ProtocolRequest; response: ProtocolResponse }); + private emit(eventName: 'update', data: { update: ProtocolUpdate }); private emit(eventName: 'suspend' | 'runtime-error', data: UpdateThreadsResponse); private emit(eventName: 'app-exit' | 'cannot-continue' | 'close' | 'data' | 'handshake-verified' | 'io-output' | 'protocol-version' | 'start', data?); private emit(eventName: string, data?) { @@ -226,7 +221,7 @@ export class Debugger { public async continue() { if (this.stopped) { this.stopped = false; - return this.makeRequest( + return this.makeRequest( ContinueRequest.fromJson({ requestId: this.totalRequests++ }) @@ -236,7 +231,7 @@ export class Debugger { public async pause(force = false) { if (!this.stopped || force) { - return this.makeRequest( + return this.makeRequest( StopRequest.fromJson({ requestId: this.totalRequests++ }) @@ -245,7 +240,7 @@ export class Debugger { } public async exitChannel() { - return this.makeRequest( + return this.makeRequest( ExitChannelRequest.fromJson({ requestId: this.totalRequests++ }) @@ -264,7 +259,7 @@ export class Debugger { return this.step(STEP_TYPE.STEP_TYPE_OUT, threadIndex); } - private async step(stepType: STEP_TYPE, threadIndex: number): Promise { + private async step(stepType: STEP_TYPE, threadIndex: number): Promise { this.logger.log('[step]', { stepType: STEP_TYPE[stepType], threadId: threadIndex, stopped: this.stopped }); let buffer = new SmartBuffer({ size: 17 }); @@ -272,14 +267,14 @@ export class Debugger { buffer.writeUInt8(stepType); // step_type if (this.stopped) { this.stopped = false; - let stepResult = await this.makeRequest( + let stepResult = await this.makeRequest( StepRequest.fromJson({ requestId: this.totalRequests++, stepType: stepType, threadIndex: threadIndex }) ); - if (stepResult.errorCode === ERROR_CODES.OK) { + if (stepResult.data.errorCode === ERROR_CODES.OK) { // this.stopped = true; // this.emit('suspend'); } else { @@ -394,7 +389,7 @@ export class Debugger { ); } } - return new AddBreakpointsResponse(null); + return AddBreakpointsResponse.fromBuffer(null); } public async listBreakpoints(): Promise { @@ -413,7 +408,7 @@ export class Debugger { }); return this.makeRequest(command); } - return new RemoveBreakpointsResponse(null); + return RemoveBreakpointsResponse.fromJson(null); } private async makeRequest(request: ProtocolRequest) { @@ -446,6 +441,20 @@ export class Debugger { } const event = this.getResponseOrUpdate(this.buffer); + if (!event.success) { + //TODO do something about this + } + //TODO do something about this too + if (event.data.requestId > this.totalRequests) { + this.removedProcessedBytes(genericResponse, slicedBuffer, packetLength); + return true; + } + + if (event.data.errorCode !== ERROR_CODES.OK) { + this.logger.error(event.data.errorCode, event); + this.removedProcessedBytes(genericResponse, buffer, packetLength); + return true; + } //we got a response if (event) { @@ -462,9 +471,10 @@ export class Debugger { //remove the processed data from the buffer this.buffer = this.buffer.slice(event.readOffset); this.logger.debug('[raw]', `requestId=${event.data.requestId}`, request, event.constructor?.name ?? '', event); - } + //TODO remove processed bytes no matter what the response was + // process again (will run recursively until the buffer is empty) this.process(); } @@ -487,83 +497,99 @@ export class Debugger { } } - let debuggerRequestResponse = this.watchPacketLength ? new ProtocolEventV3(buffer) : new ProtocolEvent(buffer); - let packetLength = debuggerRequestResponse.packetLength; - let slicedBuffer = packetLength ? buffer.slice(4) : buffer; + //try to get a response + let result: ProtocolResponse | ProtocolUpdate; - this.logger.log(`incoming bytes: ${buffer.length}`, debuggerRequestResponse); - if (debuggerRequestResponse.success) { - if (debuggerRequestResponse.requestId > this.totalRequests) { - this.removedProcessedBytes(debuggerRequestResponse, slicedBuffer, packetLength); - return true; - } + let genericResponse = this.watchPacketLength ? GenericResponseV3.fromBuffer(buffer) : GenericResponse.fromBuffer(buffer); + // a nonzero requestId means this is a response to a request that we sent + if (genericResponse.data.requestId !== 0) { + //requestId 0 means this is an update + result = this.getResponse(genericResponse); + } else { + result = this.getUpdate(genericResponse); + this.emit('update', result); + } + if (result) { + return + } + } - if (debuggerRequestResponse.errorCode !== ERROR_CODES.OK) { - this.logger.error(debuggerRequestResponse.errorCode, debuggerRequestResponse); - this.removedProcessedBytes(debuggerRequestResponse, buffer, packetLength); - return true; - } + private getResponse(genericResponse: GenericResponseV3): ProtocolResponse { + const request = this.activeRequests1.get(genericResponse.data.requestId); + if (!request) { + return; + } + switch (request.data.commandCode) { + case COMMANDS.STOP: + case COMMANDS.CONTINUE: + case COMMANDS.STEP: + case COMMANDS.EXIT_CHANNEL: + return genericResponse; + case COMMANDS.EXECUTE: + return new ExecuteResponseV3(this.buffer); + case COMMANDS.ADD_BREAKPOINTS: + case COMMANDS.ADD_CONDITIONAL_BREAKPOINTS: + return new AddBreakpointsResponse(this.buffer); + case COMMANDS.LIST_BREAKPOINTS: + return ListBreakpointsResponse.fromBuffer(this.buffer); + case COMMANDS.REMOVE_BREAKPOINTS: + return RemoveBreakpointsResponse.fromBuffer(this.buffer); + case COMMANDS.VARIABLES: + return new VariableResponse(this.buffer); + case COMMANDS.STACKTRACE: + return this.checkResponse( + packetLength ? new StackTraceResponseV3(slicedBuffer) : new StackTraceResponse(slicedBuffer), + buffer, + packetLength); + case COMMANDS.THREADS: + return new ThreadsResponse(this.buffer); + default: + return undefined; + } + } - if (debuggerRequestResponse.updateType > 0) { - this.logger.log('Update Type:', UPDATE_TYPES[debuggerRequestResponse.updateType]); - switch (debuggerRequestResponse.updateType) { - case UPDATE_TYPES.IO_PORT_OPENED: - return this.connectToIoPort(new ConnectIOPortResponse(slicedBuffer), buffer, packetLength); - case UPDATE_TYPES.ALL_THREADS_STOPPED: - case UPDATE_TYPES.THREAD_ATTACHED: - let debuggerUpdateThreads = new UpdateThreadsResponse(slicedBuffer); - if (debuggerUpdateThreads.success) { - this.handleThreadsUpdate(debuggerUpdateThreads); - this.removedProcessedBytes(debuggerUpdateThreads, slicedBuffer, packetLength); - return true; - } - return false; - case UPDATE_TYPES.UNDEF: - return this.checkResponse(new UndefinedResponse(slicedBuffer), buffer, packetLength); - case UPDATE_TYPES.BREAKPOINT_ERROR: - const response = new BreakpointErrorUpdateResponse(slicedBuffer); - //we do nothing with breakpoint errors at this time. - return this.checkResponse(response, buffer, packetLength); - case UPDATE_TYPES.COMPILE_ERROR: - return this.checkResponse(new UndefinedResponse(slicedBuffer), buffer, packetLength); - default: - return this.checkResponse(new UndefinedResponse(slicedBuffer), buffer, packetLength); - } - } else { - const request = this.activeRequests1.get(debuggerRequestResponse.requestId); - this.logger.log('Command Type:', COMMANDS[request.data.commandCode]); - switch (request.data.commandCode) { - case COMMANDS.STOP: - case COMMANDS.CONTINUE: - case COMMANDS.STEP: - case COMMANDS.EXIT_CHANNEL: - this.removedProcessedBytes(debuggerRequestResponse, buffer, packetLength); - return true; - case COMMANDS.EXECUTE: - return this.checkResponse(new ExecuteResponseV3(slicedBuffer), buffer, packetLength); - case COMMANDS.ADD_BREAKPOINTS: - case COMMANDS.ADD_CONDITIONAL_BREAKPOINTS: - return this.checkResponse(new AddBreakpointsResponse(slicedBuffer), buffer, packetLength); - case COMMANDS.LIST_BREAKPOINTS: - return this.checkResponse(new ListBreakpointsResponse(slicedBuffer), buffer, packetLength); - case COMMANDS.REMOVE_BREAKPOINTS: - return this.checkResponse(new RemoveBreakpointsResponse(slicedBuffer), buffer, packetLength); - case COMMANDS.VARIABLES: - return this.checkResponse(new VariableResponse(slicedBuffer), buffer, packetLength); - case COMMANDS.STACKTRACE: - return this.checkResponse( - packetLength ? new StackTraceResponseV3(slicedBuffer) : new StackTraceResponse(slicedBuffer), - buffer, - packetLength); - case COMMANDS.THREADS: - return this.checkResponse(new ThreadsResponse(slicedBuffer), buffer, packetLength); - default: - return this.checkResponse(debuggerRequestResponse, buffer, packetLength); + private getUpdate(genericResponse: GenericResponseV3): ProtocolUpdate { + //read the update_type from the buffer (save some buffer parsing time by narrowing to the exact update type) + const updateType = this.buffer.readUInt32LE(genericResponse.readOffset) as UPDATE_TYPES; + + this.logger.log('Update Type:', updateType, UPDATE_TYPES[updateType]); + switch (updateType) { + case UPDATE_TYPES.IO_PORT_OPENED: + //TODO handle this + return IOPortOpenedUpdate.fromBuffer(this.buffer); + case UPDATE_TYPES.ALL_THREADS_STOPPED: + return AllThreadsStoppedUpdate.fromBuffer(this.buffer); + case UPDATE_TYPES.THREAD_ATTACHED: + return ThreadAttachedUpdate.fromBuffer(this.buffer); + case UPDATE_TYPES.BREAKPOINT_ERROR: + //we do nothing with breakpoint errors at this time. + return BreakpointErrorUpdate.fromBuffer(this.buffer); + case UPDATE_TYPES.COMPILE_ERROR: + return CompileErrorUpdate.fromBuffer(this.buffer); + default: + return undefined; + } + } + + private handleUpdate(update: ProtocolUpdate) { + if (update instanceof AllThreadsStoppedUpdate || update instanceof ThreadAttachedUpdate) { + this.stopped = true; + let stopReason = update.data.stopReason; + let eventName: 'runtime-error' | 'suspend' = stopReason === STOP_REASONS.RUNTIME_ERROR ? 'runtime-error' : 'suspend'; + + if (update.data.updateType === UPDATE_TYPES.ALL_THREADS_STOPPED) { + if (stopReason === STOP_REASONS.RUNTIME_ERROR || stopReason === STOP_REASONS.BREAK || stopReason === STOP_REASONS.STOP_STATEMENT) { + this.primaryThread = (update.data as ThreadsStopped).primaryThreadIndex; + this.stackFrameIndex = 0; + this.emit(eventName, update); } + } else if (stopReason === STOP_REASONS.RUNTIME_ERROR || stopReason === STOP_REASONS.BREAK || stopReason === STOP_REASONS.STOP_STATEMENT) { + this.primaryThread = (update.data as ThreadAttached).threadIndex; + this.emit(eventName, update); } + } else if (update instanceof IOPortOpenedUpdate) { + this.connectToIoPort(update); } - - return false; } private checkResponse(responseClass: { requestId: number; readOffset: number; success: boolean }, unhandledData: Buffer, packetLength = 0) { @@ -599,7 +625,7 @@ export class Debugger { this.protocolVersion = response.data.protocolVersion; this.logger.log('Protocol Version:', this.protocolVersion); - this.watchPacketLength = response.watchPacketLength; + this.watchPacketLength = semver.satisfies(this.protocolVersion, '>=3.0.0'); let handshakeVerified = true; @@ -635,14 +661,17 @@ export class Debugger { } } - private connectToIoPort(connectIoPortResponse: ConnectIOPortResponse, unhandledData: Buffer, packetLength = 0) { - this.logger.log('Connecting to IO port. response status success =', connectIoPortResponse.success); - if (connectIoPortResponse.success) { + private connectToIoPort(update: IOPortOpenedUpdate, unhandledData: Buffer, packetLength = 0) { + this.logger.log('Connecting to IO port. response status success =', update.success); + if (update.success) { // Create a new TCP client. this.ioClient = new Net.Socket(); // Send a connection request to the server. - this.logger.log('Connect to IO Port: port', connectIoPortResponse.data, 'host', this.options.host); - this.ioClient.connect({ port: connectIoPortResponse.data, host: this.options.host }, () => { + this.logger.log('Connect to IO Port: port', update.data, 'host', this.options.host); + this.ioClient.connect({ + port: update.data.port, + host: this.options.host + }, () => { // If there is no error, the server has accepted the request this.logger.log('TCP connection established with the IO Port.'); this.connectedToIoPort = true; @@ -676,29 +705,12 @@ export class Debugger { }); }); - this.removedProcessedBytes(connectIoPortResponse, unhandledData, packetLength); + this.removedProcessedBytes(update, unhandledData, packetLength); return true; } return false; } - private handleThreadsUpdate(update: UpdateThreadsResponse) { - this.stopped = true; - let stopReason = update.data.stopReason; - let eventName: 'runtime-error' | 'suspend' = stopReason === STOP_REASONS.RUNTIME_ERROR ? 'runtime-error' : 'suspend'; - - if (update.updateType === UPDATE_TYPES.ALL_THREADS_STOPPED) { - if (stopReason === STOP_REASONS.RUNTIME_ERROR || stopReason === STOP_REASONS.BREAK || stopReason === STOP_REASONS.STOP_STATEMENT) { - this.primaryThread = (update.data as ThreadsStopped).primaryThreadIndex; - this.stackFrameIndex = 0; - this.emit(eventName, update); - } - } else if (stopReason === STOP_REASONS.RUNTIME_ERROR || stopReason === STOP_REASONS.BREAK || stopReason === STOP_REASONS.STOP_STATEMENT) { - this.primaryThread = (update.data as ThreadAttached).threadIndex; - this.emit(eventName, update); - } - } - public destroy() { this.shutdown('close'); } diff --git a/src/debugProtocol/events/zzresponsesOld/AddBreakpointsResponse.ts b/src/debugProtocol/events/responses/AddBreakpointsResponse.ts similarity index 66% rename from src/debugProtocol/events/zzresponsesOld/AddBreakpointsResponse.ts rename to src/debugProtocol/events/responses/AddBreakpointsResponse.ts index 2a5dda18..36a29585 100644 --- a/src/debugProtocol/events/zzresponsesOld/AddBreakpointsResponse.ts +++ b/src/debugProtocol/events/responses/AddBreakpointsResponse.ts @@ -1,4 +1,4 @@ -import { ListBreakpointsResponse } from '../responses/ListBreakpointsResponse'; +import { ListBreakpointsResponse } from './ListBreakpointsResponse'; //There's currently no difference between this response and the ListBreakpoints response export class AddBreakpointsResponse extends ListBreakpointsResponse { } diff --git a/src/debugProtocol/events/responses/HandshakeResponse.ts b/src/debugProtocol/events/responses/HandshakeResponse.ts index 91fa655a..52ec1a6f 100644 --- a/src/debugProtocol/events/responses/HandshakeResponse.ts +++ b/src/debugProtocol/events/responses/HandshakeResponse.ts @@ -55,8 +55,6 @@ export class HandshakeResponse implements ProtocolResponse { return buffer.toBuffer(); } - public watchPacketLength = false; // this will always be false for older protocol versions - public success = false; public readOffset = 0; diff --git a/src/debugProtocol/events/responses/HandshakeResponseV3.ts b/src/debugProtocol/events/responses/HandshakeResponseV3.ts index ffb57ad9..d2fe603f 100644 --- a/src/debugProtocol/events/responses/HandshakeResponseV3.ts +++ b/src/debugProtocol/events/responses/HandshakeResponseV3.ts @@ -48,7 +48,6 @@ export class HandshakeResponseV3 implements ProtocolResponse { if (!semver.satisfies(response.data.protocolVersion, '<3.0.0')) { throw new Error(`unsupported version ${response.data.protocolVersion}`); } - response.watchPacketLength = true; }); return response; } @@ -79,8 +78,6 @@ export class HandshakeResponseV3 implements ProtocolResponse { return smartBuffer.toBuffer(); } - public watchPacketLength = false; // this will always be true for the new protocol versions - public success = false; public readOffset = 0; diff --git a/src/debugProtocol/events/zzresponsesOld/RemoveBreakpointsResponse.ts b/src/debugProtocol/events/responses/RemoveBreakpointsResponse.ts similarity index 67% rename from src/debugProtocol/events/zzresponsesOld/RemoveBreakpointsResponse.ts rename to src/debugProtocol/events/responses/RemoveBreakpointsResponse.ts index 5331762f..a8c93194 100644 --- a/src/debugProtocol/events/zzresponsesOld/RemoveBreakpointsResponse.ts +++ b/src/debugProtocol/events/responses/RemoveBreakpointsResponse.ts @@ -1,4 +1,4 @@ -import { ListBreakpointsResponse } from '../responses/ListBreakpointsResponse'; +import { ListBreakpointsResponse } from './ListBreakpointsResponse'; //There's currently no difference between this response and the ListBreakpoints response export class RemoveBreakpointsResponse extends ListBreakpointsResponse { } diff --git a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts index ccc0775f..c4a67bbb 100644 --- a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts +++ b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts @@ -6,7 +6,6 @@ describe('AllThreadsStoppedUpdate', () => { it('serializes and deserializes properly', () => { const command = AllThreadsStoppedUpdate.fromJson({ primaryThreadIndex: 1, - errorCode: ERROR_CODES.OK, stopReason: STOP_REASONS.BREAK, stopReasonDetail: 'because' }); diff --git a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts index 5d401e5a..1e8a8ac5 100644 --- a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts +++ b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; -import type { ERROR_CODES } from '../../Constants'; -import { STOP_REASONS, UPDATE_TYPES } from '../../Constants'; +import type { STOP_REASONS } from '../../Constants'; +import { ERROR_CODES, UPDATE_TYPES } from '../../Constants'; import { util } from '../../../util'; import { protocolUtils } from '../../ProtocolUtil'; import type { ProtocolUpdate } from '../ProtocolEvent'; @@ -13,26 +13,25 @@ import type { ProtocolUpdate } from '../ProtocolEvent'; export class AllThreadsStoppedUpdate implements ProtocolUpdate { public static fromJson(data: { - errorCode: ERROR_CODES; primaryThreadIndex: number; stopReason: number; stopReasonDetail: string; }) { - const response = new AllThreadsStoppedUpdate(); - protocolUtils.loadJson(response, data); - return response; + const update = new AllThreadsStoppedUpdate(); + protocolUtils.loadJson(update, data); + return update; } public static fromBuffer(buffer: Buffer) { - const response = new AllThreadsStoppedUpdate(); - protocolUtils.bufferLoaderHelper(response, buffer, 16, (smartBuffer) => { - protocolUtils.loadCommonUpdateFields(response, smartBuffer, UPDATE_TYPES.ALL_THREADS_STOPPED); + const update = new AllThreadsStoppedUpdate(); + protocolUtils.bufferLoaderHelper(update, buffer, 16, (smartBuffer) => { + protocolUtils.loadCommonUpdateFields(update, smartBuffer, update.data.updateType); - response.data.primaryThreadIndex = smartBuffer.readInt32LE(); - response.data.stopReason = getStopReason(smartBuffer.readUInt8()); - response.data.stopReasonDetail = util.readStringNT(smartBuffer); + update.data.primaryThreadIndex = smartBuffer.readInt32LE(); + update.data.stopReason = smartBuffer.readUInt8(); + update.data.stopReasonDetail = util.readStringNT(smartBuffer); }); - return response; + return update; } public toBuffer() { @@ -58,62 +57,7 @@ export class AllThreadsStoppedUpdate implements ProtocolUpdate { //common props packetLength: undefined as number, requestId: 0, //all updates have requestId === 0 - errorCode: undefined as ERROR_CODES, + errorCode: ERROR_CODES.OK, updateType: UPDATE_TYPES.ALL_THREADS_STOPPED }; } - -export class ThreadsStopped { - - constructor(bufferReader: SmartBuffer) { - if (bufferReader.length >= bufferReader.readOffset + 6) { - this.primaryThreadIndex = bufferReader.readInt32LE(); - this.stopReason = getStopReason(bufferReader.readUInt8()); - this.stopReasonDetail = util.readStringNT(bufferReader); - this.success = true; - } - } - public success = false; - - // response fields - public primaryThreadIndex = -1; - public stopReason = -1; - public stopReasonDetail: string; -} - -export class ThreadAttached { - - constructor(bufferReader: SmartBuffer) { - if (bufferReader.length >= bufferReader.readOffset + 6) { - this.threadIndex = bufferReader.readInt32LE(); - this.stopReason = getStopReason(bufferReader.readUInt8()); - this.stopReasonDetail = util.readStringNT(bufferReader); - this.success = true; - } - } - public success = false; - - // response fields - public threadIndex = -1; - public stopReason = -1; - public stopReasonDetail: string; -} - -function getStopReason(value: number): STOP_REASONS { - switch (value) { - case STOP_REASONS.BREAK: - return STOP_REASONS.BREAK; - case STOP_REASONS.NORMAL_EXIT: - return STOP_REASONS.NORMAL_EXIT; - case STOP_REASONS.NOT_STOPPED: - return STOP_REASONS.NOT_STOPPED; - case STOP_REASONS.RUNTIME_ERROR: - return STOP_REASONS.RUNTIME_ERROR; - case STOP_REASONS.STOP_STATEMENT: - return STOP_REASONS.STOP_STATEMENT; - case STOP_REASONS.UNDEFINED: - return STOP_REASONS.UNDEFINED; - default: - return STOP_REASONS.UNDEFINED; - } -} diff --git a/src/debugProtocol/events/updates/BreakpointErrorUpdate.spec.ts b/src/debugProtocol/events/updates/BreakpointErrorUpdate.spec.ts new file mode 100644 index 00000000..7f8c52f3 --- /dev/null +++ b/src/debugProtocol/events/updates/BreakpointErrorUpdate.spec.ts @@ -0,0 +1,63 @@ +import { expect } from 'chai'; +import { ERROR_CODES, UPDATE_TYPES } from '../../Constants'; +import { BreakpointErrorUpdate } from './BreakpointErrorUpdate'; + +describe('BreakpointErrorUpdate', () => { + it('serializes and deserializes properly', () => { + const command = BreakpointErrorUpdate.fromJson({ + breakpointId: 3, + compileErrors: [ + 'compile 1' + ], + runtimeErrors: [ + 'runtime 1' + ], + otherErrors: [ + 'other 1' + ] + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 0, + errorCode: ERROR_CODES.OK, + updateType: UPDATE_TYPES.BREAKPOINT_ERROR, + + breakpointId: 3, + compileErrors: [ + 'compile 1' + ], + runtimeErrors: [ + 'runtime 1' + ], + otherErrors: [ + 'other 1' + ] + }); + + expect( + BreakpointErrorUpdate.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 64, // 4 bytes + requestId: 0, // 4 bytes + errorCode: ERROR_CODES.OK, // 4 bytes + updateType: UPDATE_TYPES.BREAKPOINT_ERROR, // 4 bytes + + //flags // 4 bytes + + breakpointId: 3, // 4 bytes + // num_compile_errors // 4 bytes + compileErrors: [ + 'compile 1' // 10 bytes + ], + // num_runtime_errors // 4 bytes + runtimeErrors: [ + 'runtime 1' // 10 bytes + ], + // num_other_errors // 4 bytes + otherErrors: [ + 'other 1' // 8 bytes + ] + }); + }); +}); diff --git a/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts b/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts new file mode 100644 index 00000000..853c6b89 --- /dev/null +++ b/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts @@ -0,0 +1,114 @@ +import { SmartBuffer } from 'smart-buffer'; +import { util } from '../../../util'; +import { ERROR_CODES, UPDATE_TYPES } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; + +/** + * Data sent as the data segment of message type: BREAKPOINT_ERROR + ``` + struct BreakpointErrorUpdateData { + uint32 flags; // Always 0, reserved for future use + uint32 breakpoint_id; + uint32 num_compile_errors; + utf8z[num_compile_errors] compile_errors; + uint32 num_runtime_errors; + utf8z[num_runtime_errors] runtime_errors; + uint32 num_other_errors; // E.g., permissions errors + utf8z[num_other_errors] other_errors; + } + ``` +*/ +export class BreakpointErrorUpdate { + + public static fromJson(data: { + breakpointId: number; + compileErrors: string[]; + runtimeErrors: string[]; + otherErrors: string[]; + }) { + const update = new BreakpointErrorUpdate(); + protocolUtils.loadJson(update, data); + update.data.compileErrors ??= []; + update.data.runtimeErrors ??= []; + update.data.otherErrors ??= []; + return update; + } + + public static fromBuffer(buffer: Buffer) { + const update = new BreakpointErrorUpdate(); + protocolUtils.bufferLoaderHelper(update, buffer, 20, (smartBuffer) => { + protocolUtils.loadCommonUpdateFields(update, smartBuffer, update.data.updateType); + + smartBuffer.readUInt32LE(); // flags - always 0, reserved for future use + update.data.breakpointId = smartBuffer.readUInt32LE(); // breakpoint_id + + const compileErrorCount = smartBuffer.readUInt32LE(); // num_compile_errors + update.data.compileErrors = []; + for (let i = 0; i < compileErrorCount; i++) { + update.data.compileErrors.push( + util.readStringNT(smartBuffer) + ); + } + + const runtimeErrorCount = smartBuffer.readUInt32LE(); // num_runtime_errors + update.data.runtimeErrors = []; + for (let i = 0; i < runtimeErrorCount; i++) { + update.data.runtimeErrors.push( + util.readStringNT(smartBuffer) + ); + } + + const otherErrorCount = smartBuffer.readUInt32LE(); // num_other_errors + update.data.otherErrors = []; + for (let i = 0; i < otherErrorCount; i++) { + update.data.otherErrors.push( + util.readStringNT(smartBuffer) + ); + } + }); + return update; + } + + public toBuffer() { + let smartBuffer = new SmartBuffer(); + + smartBuffer.writeInt32LE(0); // flags - always 0, reserved for future use + smartBuffer.writeUInt32LE(this.data.breakpointId); // breakpoint_id + + smartBuffer.writeUInt32LE(this.data.compileErrors?.length ?? 0); // num_compile_errors + for (let error of this.data.compileErrors ?? []) { + smartBuffer.writeStringNT(error); + } + + smartBuffer.writeUInt32LE(this.data.runtimeErrors?.length ?? 0); // num_runtime_errors + for (let error of this.data.runtimeErrors ?? []) { + smartBuffer.writeStringNT(error); + } + + smartBuffer.writeUInt32LE(this.data.otherErrors?.length ?? 0); // num_other_errors + for (let error of this.data.otherErrors ?? []) { + smartBuffer.writeStringNT(error); + } + + protocolUtils.insertCommonUpdateFields(this, smartBuffer); + + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = -1; + + public data = { + breakpointId: undefined as number, + compileErrors: undefined as string[], + runtimeErrors: undefined as string[], + otherErrors: undefined as string[], + + //common props + packetLength: undefined as number, + requestId: 0, //all updates have requestId === 0 + errorCode: ERROR_CODES.OK, + updateType: UPDATE_TYPES.BREAKPOINT_ERROR + }; +} diff --git a/src/debugProtocol/events/updates/CompileErrorUpdate.spec.ts b/src/debugProtocol/events/updates/CompileErrorUpdate.spec.ts new file mode 100644 index 00000000..ddf10102 --- /dev/null +++ b/src/debugProtocol/events/updates/CompileErrorUpdate.spec.ts @@ -0,0 +1,40 @@ +import { expect } from 'chai'; +import { ERROR_CODES, STOP_REASONS, UPDATE_TYPES } from '../../Constants'; +import { CompileErrorUpdate } from './CompileErrorUpdate'; + +describe('CompileErrorUpdate', () => { + it('serializes and deserializes properly', () => { + const command = CompileErrorUpdate.fromJson({ + errorMessage: 'crashed', + filePath: 'pkg:/source/main.brs', + libraryName: 'complib1', + lineNumber: 3 + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 0, + errorCode: ERROR_CODES.OK, + updateType: UPDATE_TYPES.COMPILE_ERROR, + + errorMessage: 'crashed', + filePath: 'pkg:/source/main.brs', + libraryName: 'complib1', + lineNumber: 3 + }); + + expect( + CompileErrorUpdate.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 58, // 4 bytes + requestId: 0, // 4 bytes + errorCode: ERROR_CODES.OK, // 4 bytes + updateType: UPDATE_TYPES.COMPILE_ERROR, // 4 bytes + + errorMessage: 'crashed', // 8 bytes + filePath: 'pkg:/source/main.brs', // 21 bytes + libraryName: 'complib1', // 9 bytes + lineNumber: 3 // 4 bytes + }); + }); +}); diff --git a/src/debugProtocol/events/updates/CompileErrorUpdate.ts b/src/debugProtocol/events/updates/CompileErrorUpdate.ts new file mode 100644 index 00000000..f147d869 --- /dev/null +++ b/src/debugProtocol/events/updates/CompileErrorUpdate.ts @@ -0,0 +1,93 @@ +import { SmartBuffer } from 'smart-buffer'; +import { util } from '../../../util'; +import { ERROR_CODES, UPDATE_TYPES } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; + +/** + * A COMPILE_ERROR is sent if a compilation error occurs. In this case, the update_type field in a DebuggerUpdate message is set to + * COMPILE_ERROR, and the data field contains a structure named CompileErrorUpdateData that provides the reason for the error. + * The CompileErrorUpdateData structure has the following syntax: + ``` + struct CompileErrorUpdateData { + uint32 flags; // Always 0, reserved for future use + utf8z error_string; + utf8z file_spec; + uint32 line_number; + utf8z library_name; + } + ``` +*/ +export class CompileErrorUpdate { + + public static fromJson(data: { + errorMessage: string; + filePath: string; + lineNumber: number; + libraryName: string; + }) { + const update = new CompileErrorUpdate(); + protocolUtils.loadJson(update, data); + return update; + } + + public static fromBuffer(buffer: Buffer) { + const update = new CompileErrorUpdate(); + protocolUtils.bufferLoaderHelper(update, buffer, 20, (smartBuffer) => { + protocolUtils.loadCommonUpdateFields(update, smartBuffer, update.data.updateType); + + update.data.errorMessage = util.readStringNT(smartBuffer); // error_string + update.data.filePath = util.readStringNT(smartBuffer); // file_spec + update.data.lineNumber = smartBuffer.readUInt32LE(); // line_number + update.data.libraryName = util.readStringNT(smartBuffer); // library_name + }); + return update; + } + + public toBuffer() { + let smartBuffer = new SmartBuffer(); + + smartBuffer.writeStringNT(this.data.errorMessage); // error_string + smartBuffer.writeStringNT(this.data.filePath); // file_spec + smartBuffer.writeUInt32LE(this.data.lineNumber); // line_number + smartBuffer.writeStringNT(this.data.libraryName); // library_name + + protocolUtils.insertCommonUpdateFields(this, smartBuffer); + + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = -1; + + public data = { + /** + * A text message describing the compiler error. + * + * This is completely unrelated to the DebuggerUpdate.errorCode field. + */ + errorMessage: undefined as string, + /** + * A simple file path indicating where the compiler error occurred. It maps to all matching file paths in the channel or its libraries + * + * `"pkg:/"` specifies a file in the channel + * + * `"lib://"` specifies a file in a library. + */ + filePath: undefined as string, + /** + * The 1-based line number where the compile error occurred. + */ + lineNumber: undefined as number, + /** + * The name of the library where the compile error occurred. + */ + libraryName: undefined as string, + + //common props + packetLength: undefined as number, + requestId: 0, //all updates have requestId === 0 + errorCode: ERROR_CODES.OK, + updateType: UPDATE_TYPES.COMPILE_ERROR + }; +} diff --git a/src/debugProtocol/events/updates/IOPortOpenedUpdate.spec.ts b/src/debugProtocol/events/updates/IOPortOpenedUpdate.spec.ts new file mode 100644 index 00000000..560726cd --- /dev/null +++ b/src/debugProtocol/events/updates/IOPortOpenedUpdate.spec.ts @@ -0,0 +1,32 @@ +import { expect } from 'chai'; +import { ERROR_CODES, STOP_REASONS, UPDATE_TYPES } from '../../Constants'; +import { IOPortOpenedUpdate } from './IOPortOpenedUpdate'; + +describe.only('IOPortOpenedUpdate', () => { + it('serializes and deserializes properly', () => { + const command = IOPortOpenedUpdate.fromJson({ + port: 1234, + errorCode: ERROR_CODES.OK + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 0, + errorCode: ERROR_CODES.OK, + updateType: UPDATE_TYPES.IO_PORT_OPENED, + + port: 1234 + }); + + expect( + IOPortOpenedUpdate.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 20, // 4 bytes + requestId: 0, // 4 bytes + errorCode: ERROR_CODES.OK, // 4 bytes + updateType: UPDATE_TYPES.IO_PORT_OPENED, // 4 bytes + + port: 1234 // 4 bytes + }); + }); +}); diff --git a/src/debugProtocol/events/updates/IOPortOpenedUpdate.ts b/src/debugProtocol/events/updates/IOPortOpenedUpdate.ts new file mode 100644 index 00000000..cbbb7f43 --- /dev/null +++ b/src/debugProtocol/events/updates/IOPortOpenedUpdate.ts @@ -0,0 +1,50 @@ +import { SmartBuffer } from 'smart-buffer'; +import { ERROR_CODES, UPDATE_TYPES } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; + +export class IOPortOpenedUpdate { + + public static fromJson(data: { + port: number; + }) { + const update = new IOPortOpenedUpdate(); + protocolUtils.loadJson(update, data); + return update; + } + + public static fromBuffer(buffer: Buffer) { + const update = new IOPortOpenedUpdate(); + protocolUtils.bufferLoaderHelper(update, buffer, 16, (smartBuffer) => { + protocolUtils.loadCommonUpdateFields(update, smartBuffer, update.data.updateType); + + update.data.port = smartBuffer.readInt32LE(); + }); + return update; + } + + public toBuffer() { + let smartBuffer = new SmartBuffer(); + + smartBuffer.writeInt32LE(this.data.port); // primary_thread_index + + protocolUtils.insertCommonUpdateFields(this, smartBuffer); + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = 0; + + public data = { + /** + * The port number to which the debugging client should connect to read the script's output + */ + port: undefined as number, + + //common props + packetLength: undefined as number, + requestId: 0, //all updates have requestId === 0 + errorCode: ERROR_CODES.OK, + updateType: UPDATE_TYPES.IO_PORT_OPENED + }; +} diff --git a/src/debugProtocol/events/updates/ThreadAttachedUpdate.spec.ts b/src/debugProtocol/events/updates/ThreadAttachedUpdate.spec.ts new file mode 100644 index 00000000..d8c9f4b6 --- /dev/null +++ b/src/debugProtocol/events/updates/ThreadAttachedUpdate.spec.ts @@ -0,0 +1,38 @@ +import { expect } from 'chai'; +import { ERROR_CODES, STOP_REASONS, UPDATE_TYPES } from '../../Constants'; +import { ThreadAttachedUpdate } from './ThreadAttachedUpdate'; + +describe('AllThreadsStoppedUpdate', () => { + it('serializes and deserializes properly', () => { + const update = ThreadAttachedUpdate.fromJson({ + threadIndex: 1, + errorCode: ERROR_CODES.OK, + stopReason: STOP_REASONS.BREAK, + stopReasonDetail: 'because' + }); + + expect(update.data).to.eql({ + packetLength: undefined, + requestId: 0, + errorCode: ERROR_CODES.OK, + updateType: UPDATE_TYPES.THREAD_ATTACHED, + + threadIndex: 1, + stopReason: STOP_REASONS.BREAK, + stopReasonDetail: 'because' + }); + + expect( + ThreadAttachedUpdate.fromBuffer(update.toBuffer()).data + ).to.eql({ + packetLength: 29, // 4 bytes + requestId: 0, // 4 bytes + errorCode: ERROR_CODES.OK, // 4 bytes + updateType: UPDATE_TYPES.THREAD_ATTACHED, // 4 bytes + + threadIndex: 1, // 4 bytes + stopReason: STOP_REASONS.BREAK, // 1 bytes + stopReasonDetail: 'because' // 8 bytes + }); + }); +}); diff --git a/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts b/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts new file mode 100644 index 00000000..c093c8f1 --- /dev/null +++ b/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts @@ -0,0 +1,56 @@ +import { SmartBuffer } from 'smart-buffer'; +import type { STOP_REASONS } from '../../Constants'; +import { ERROR_CODES, UPDATE_TYPES } from '../../Constants'; +import { util } from '../../../util'; +import { protocolUtils } from '../../ProtocolUtil'; + +export class ThreadAttachedUpdate { + + public static fromJson(data: { + threadIndex: number; + stopReason: number; + stopReasonDetail: string; + }) { + const update = new ThreadAttachedUpdate(); + protocolUtils.loadJson(update, data); + return update; + } + + public static fromBuffer(buffer: Buffer) { + const update = new ThreadAttachedUpdate(); + protocolUtils.bufferLoaderHelper(update, buffer, 12, (smartBuffer) => { + protocolUtils.loadCommonUpdateFields(update, smartBuffer, update.data.updateType); + update.data.threadIndex = smartBuffer.readInt32LE(); + update.data.stopReason = smartBuffer.readUInt8(); + update.data.stopReasonDetail = util.readStringNT(smartBuffer); + }); + return update; + } + + public toBuffer() { + const smartBuffer = new SmartBuffer(); + + smartBuffer.writeInt32LE(this.data.threadIndex); + smartBuffer.writeUInt8(this.data.stopReason); + smartBuffer.writeStringNT(this.data.stopReasonDetail); + + protocolUtils.insertCommonUpdateFields(this, smartBuffer); + + return smartBuffer.toBuffer(); + } + + public success = false; + public readOffset = 0; + + public data = { + threadIndex: undefined as number, + stopReason: undefined as STOP_REASONS, + stopReasonDetail: undefined as string, + + //common props + packetLength: undefined as number, + requestId: 0, //all updates have requestId === 0 + errorCode: ERROR_CODES.OK, + updateType: UPDATE_TYPES.THREAD_ATTACHED + }; +} diff --git a/src/debugProtocol/events/updates/UpdateResponse.ts b/src/debugProtocol/events/updates/UpdateResponse.ts deleted file mode 100644 index 5407fffb..00000000 --- a/src/debugProtocol/events/updates/UpdateResponse.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { SmartBuffer } from 'smart-buffer'; -import type { UPDATE_TYPES } from '../../Constants'; -import { ProtocolResponse } from '../zzresponsesOld/ProtocolResponse'; - -export abstract class UpdateResponse extends ProtocolResponse { - public abstract data: { - packetLength: number; - requestId: number; - errorCode: number; - updateType: number; - }; -} - diff --git a/src/debugProtocol/events/zzresponsesOld/BreakpointErrorResponse.spec.ts b/src/debugProtocol/events/zzresponsesOld/BreakpointErrorResponse.spec.ts index 0df639c1..0ca09033 100644 --- a/src/debugProtocol/events/zzresponsesOld/BreakpointErrorResponse.spec.ts +++ b/src/debugProtocol/events/zzresponsesOld/BreakpointErrorResponse.spec.ts @@ -1,6 +1,6 @@ import { createBreakpointErrorUpdateResponse } from './responseCreationHelpers.spec'; import { expect } from 'chai'; -import { BreakpointErrorUpdateResponse } from './BreakpointErrorUpdateResponse'; +import { BreakpointErrorUpdateResponse } from '../updates/BreakpointErrorUpdate'; import { ERROR_CODES, UPDATE_TYPES } from '../../Constants'; describe('BreakpointErrorUpdateResponse', () => { diff --git a/src/debugProtocol/events/zzresponsesOld/BreakpointErrorUpdateResponse.ts b/src/debugProtocol/events/zzresponsesOld/BreakpointErrorUpdateResponse.ts deleted file mode 100644 index a3b0b8cf..00000000 --- a/src/debugProtocol/events/zzresponsesOld/BreakpointErrorUpdateResponse.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { SmartBuffer } from 'smart-buffer'; -import { util } from '../../../util'; -import { UPDATE_TYPES } from '../../Constants'; - -/** - * Data sent as the data segment of message type: BREAKPOINT_ERROR - ``` - struct BreakpointErrorUpdateData { - uint32 flags; // Always 0, reserved for future use - uint32 breakpoint_id; - uint32 num_compile_errors; - utf8z[num_compile_errors] compile_errors; - uint32 num_runtime_errors; - utf8z[num_runtime_errors] runtime_errors; - uint32 num_other_errors; // E.g., permissions errors - utf8z[num_other_errors] other_errors; - } - ``` -*/ -export class BreakpointErrorUpdateResponse { - - constructor(buffer: Buffer) { - // The minimum size of a undefined response - if (buffer.byteLength >= 12) { - let bufferReader = SmartBuffer.fromBuffer(buffer); - this.requestId = bufferReader.readUInt32LE(); - - // Updates will always have an id of zero because we didn't ask for this information - if (this.requestId === 0) { - this.errorCode = bufferReader.readUInt32LE(); - this.updateType = bufferReader.readUInt32LE(); - } - if (this.updateType === UPDATE_TYPES.BREAKPOINT_ERROR) { - try { - this.flags = bufferReader.readUInt32LE(); // flags - always 0, reserved for future use - this.breakpointId = bufferReader.readUInt32LE(); // breakpoint_id - - this.compileErrorCount = bufferReader.readUInt32LE(); // num_compile_errors - for (let i = 0; i < this.compileErrorCount; i++) { - this.compileErrors.push( - util.readStringNT(bufferReader) - ); - } - - this.runtimeErrorCount = bufferReader.readUInt32LE(); // num_runtime_errors - for (let i = 0; i < this.runtimeErrorCount; i++) { - this.runtimeErrors.push( - util.readStringNT(bufferReader) - ); - } - - this.otherErrorCount = bufferReader.readUInt32LE(); // num_other_errors - for (let i = 0; i < this.otherErrorCount; i++) { - this.otherErrors.push( - util.readStringNT(bufferReader) - ); - } - this.success = true; - } catch (error) { - // Could not process - } - } - } - } - public success = false; - public readOffset = 0; - public requestId = -1; - public errorCode = -1; - public updateType = -1; - - public flags: number; - public breakpointId: number; - - public compileErrorCount: number; - public compileErrors: string[] = []; - - public runtimeErrorCount: number; - public runtimeErrors: string[] = []; - - public otherErrorCount: number; - public otherErrors: string[] = []; -} diff --git a/src/debugProtocol/events/zzresponsesOld/ConnectIOPortResponse.ts b/src/debugProtocol/events/zzresponsesOld/ConnectIOPortResponse.ts deleted file mode 100644 index 0df46c01..00000000 --- a/src/debugProtocol/events/zzresponsesOld/ConnectIOPortResponse.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { SmartBuffer } from 'smart-buffer'; -import { UPDATE_TYPES } from '../../Constants'; - -export class ConnectIOPortResponse { - - constructor(buffer: Buffer) { - // The minimum size of a connect to IO port request - if (buffer.byteLength >= 16) { - try { - let bufferReader = SmartBuffer.fromBuffer(buffer); - this.requestId = bufferReader.readUInt32LE(); // request_id - - // Updates will always have an id of zero because we didn't ask for this information - if (this.requestId === 0) { - this.errorCode = bufferReader.readUInt32LE(); // error_code - this.updateType = bufferReader.readUInt32LE(); // update_type - - // Only handle IO port events in this class - if (this.updateType === UPDATE_TYPES.IO_PORT_OPENED) { - this.data = bufferReader.readUInt32LE(); // data - this.readOffset = bufferReader.readOffset; - this.success = true; - } - } - } catch (error) { - // Could not parse - } - } - } - public success = false; - public readOffset = 0; - - // response fields - public requestId = -1; - public errorCode = -1; - public updateType = -1; - public data = -1; -} diff --git a/src/debugProtocol/events/zzresponsesOld/UndefinedResponse.ts b/src/debugProtocol/events/zzresponsesOld/UndefinedResponse.ts deleted file mode 100644 index fca97712..00000000 --- a/src/debugProtocol/events/zzresponsesOld/UndefinedResponse.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { SmartBuffer } from 'smart-buffer'; -import { UPDATE_TYPES } from '../../Constants'; - -export class UndefinedResponse { - - constructor(buffer: Buffer) { - // The minimum size of a undefined response - if (buffer.byteLength >= 12) { - try { - let bufferReader = SmartBuffer.fromBuffer(buffer); - this.requestId = bufferReader.readUInt32LE(); - - // Updates will always have an id of zero because we didn't ask for this information - if (this.requestId === 0) { - this.errorCode = bufferReader.readUInt32LE(); - this.updateType = bufferReader.readUInt32LE(); - - // Only handle undefined events in this class - if (this.updateType === UPDATE_TYPES.UNDEF) { - this.data = bufferReader.readUInt8(); - this.readOffset = bufferReader.readOffset; - this.success = true; - } - } - } catch (error) { - // Could not process - } - } - } - public success = false; - public readOffset = 0; - - // response fields - public requestId = -1; - public errorCode = -1; - public updateType = -1; - public data = -1; -} diff --git a/src/debugProtocol/events/zzresponsesOld/UpdateThreadsResponse.ts b/src/debugProtocol/events/zzresponsesOld/UpdateThreadsResponse.ts deleted file mode 100644 index 98e96472..00000000 --- a/src/debugProtocol/events/zzresponsesOld/UpdateThreadsResponse.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { SmartBuffer } from 'smart-buffer'; -import { STOP_REASONS, UPDATE_TYPES } from '../../Constants'; -import { util } from '../../../util'; - -export class UpdateThreadsResponse { - - constructor(buffer: Buffer) { - if (buffer.byteLength >= 12) { - try { - let bufferReader = SmartBuffer.fromBuffer(buffer); - this.requestId = bufferReader.readUInt32LE(); - if (this.requestId === 0) { - this.errorCode = bufferReader.readUInt32LE(); - this.updateType = bufferReader.readUInt32LE(); - - let threadsUpdate: ThreadAttached | ThreadsStopped; - if (this.updateType === UPDATE_TYPES.ALL_THREADS_STOPPED) { - threadsUpdate = new ThreadsStopped(bufferReader); - } else if (this.updateType === UPDATE_TYPES.THREAD_ATTACHED) { - threadsUpdate = new ThreadAttached(bufferReader); - } - - if (threadsUpdate?.success) { - this.data = threadsUpdate; - this.readOffset = bufferReader.readOffset; - this.success = true; - } - } - } catch (error) { - // Can't be parsed - } - } - } - public success = false; - public readOffset = 0; - - // response fields - public requestId = -1; - public errorCode = -1; - public updateType = -1; - public data: ThreadAttached | ThreadsStopped; -} - -export class ThreadsStopped { - - constructor(bufferReader: SmartBuffer) { - if (bufferReader.length >= bufferReader.readOffset + 6) { - this.primaryThreadIndex = bufferReader.readInt32LE(); - this.stopReason = getStopReason(bufferReader.readUInt8()); - this.stopReasonDetail = util.readStringNT(bufferReader); - this.success = true; - } - } - public success = false; - - // response fields - public primaryThreadIndex = -1; - public stopReason = -1; - public stopReasonDetail: string; -} - -export class ThreadAttached { - - constructor(bufferReader: SmartBuffer) { - if (bufferReader.length >= bufferReader.readOffset + 6) { - this.threadIndex = bufferReader.readInt32LE(); - this.stopReason = getStopReason(bufferReader.readUInt8()); - this.stopReasonDetail = util.readStringNT(bufferReader); - this.success = true; - } - } - public success = false; - - // response fields - public threadIndex = -1; - public stopReason = -1; - public stopReasonDetail: string; -} - -function getStopReason(value: number): STOP_REASONS { - switch (value) { - case STOP_REASONS.BREAK: - return STOP_REASONS.BREAK; - case STOP_REASONS.NORMAL_EXIT: - return STOP_REASONS.NORMAL_EXIT; - case STOP_REASONS.NOT_STOPPED: - return STOP_REASONS.NOT_STOPPED; - case STOP_REASONS.RUNTIME_ERROR: - return STOP_REASONS.RUNTIME_ERROR; - case STOP_REASONS.STOP_STATEMENT: - return STOP_REASONS.STOP_STATEMENT; - case STOP_REASONS.UNDEFINED: - return STOP_REASONS.UNDEFINED; - default: - return STOP_REASONS.UNDEFINED; - } -} diff --git a/src/debugProtocol/events/zzresponsesOld/index.ts b/src/debugProtocol/events/zzresponsesOld/index.ts index 8a537593..36c2c84c 100644 --- a/src/debugProtocol/events/zzresponsesOld/index.ts +++ b/src/debugProtocol/events/zzresponsesOld/index.ts @@ -1,9 +1,8 @@ -export * from './ConnectIOPortResponse'; +export * from '../updates/IOPortOpenedUpdate'; export * from '../responses/GenericResponse'; export * from '../responses/GenericResponseV3'; export * from './StackTraceResponse'; export * from './StackTraceResponseV3'; export * from './ThreadsResponse'; -export * from './UndefinedResponse'; -export * from './UpdateThreadsResponse'; +export * from '../updates/ThreadAttachedUpdate'; export * from './VariableResponse'; From 1b2c9f545549d634db93e318af75091db98fd8d1 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Tue, 11 Oct 2022 08:45:33 -0400 Subject: [PATCH 08/74] Move readStringNT to protocolUtils. Rename Debugger to DebugProtocolClient --- .vscode/settings.json | 2 +- src/adapters/DebugProtocolAdapter.spec.ts | 6 +- src/adapters/DebugProtocolAdapter.ts | 8 +- .../MockDebugProtocolServer.spec.ts | 3 +- src/debugProtocol/ProtocolUtil.ts | 21 +++++ .../DebugProtocolClient.spec.ts} | 36 ++++---- .../DebugProtocolClient.ts} | 91 +++++++++---------- .../events/requests/AddBreakpointsRequest.ts | 2 +- .../AddConditionalBreakpointsRequest.ts | 4 +- .../events/requests/ExecuteRequest.ts | 2 +- .../events/requests/HandshakeRequest.ts | 2 +- .../events/requests/VariablesRequest.ts | 2 +- .../responses/HandshakeResponse.spec.ts | 4 +- .../events/responses/HandshakeResponse.ts | 2 +- .../responses/HandshakeResponseV3.spec.ts | 4 +- .../events/responses/HandshakeResponseV3.ts | 2 +- .../responses/ListBreakpointsResponse.ts | 32 +------ .../events/updates/AllThreadsStoppedUpdate.ts | 2 +- .../updates/BreakpointErrorUpdate.spec.ts | 40 ++++++++ .../events/updates/BreakpointErrorUpdate.ts | 6 +- .../events/updates/CompileErrorUpdate.ts | 8 +- .../events/updates/ThreadAttachedUpdate.ts | 2 +- .../BreakpointErrorResponse.spec.ts | 61 ------------- .../zzresponsesOld/ExecuteResponseV3.ts | 60 ------------ .../zzresponsesOld/StackTraceResponse.ts | 5 +- .../zzresponsesOld/StackTraceResponseV3.ts | 5 +- .../events/zzresponsesOld/ThreadsResponse.ts | 9 +- .../events/zzresponsesOld/VariableResponse.ts | 7 +- src/index.ts | 4 +- src/util.ts | 21 ----- 30 files changed, 173 insertions(+), 280 deletions(-) rename src/debugProtocol/{Debugger.spec.ts => client/DebugProtocolClient.spec.ts} (92%) rename src/debugProtocol/{Debugger.ts => client/DebugProtocolClient.ts} (91%) delete mode 100644 src/debugProtocol/events/zzresponsesOld/BreakpointErrorResponse.spec.ts delete mode 100644 src/debugProtocol/events/zzresponsesOld/ExecuteResponseV3.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 049d514f..19a6f424 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "files.trimTrailingWhitespace": false, + "files.trimTrailingWhitespace": true, "[markdown]": { "files.trimTrailingWhitespace": false }, diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index 5287f83b..91c4593d 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { Debugger } from '../debugProtocol/Debugger'; +import { DebugProtocolClient } from '../debugProtocol/client/DebugProtocolClient'; import { DebugProtocolAdapter } from './DebugProtocolAdapter'; import { createSandbox } from 'sinon'; import type { VariableInfo } from '../debugProtocol/events/zzresponsesOld'; @@ -10,7 +10,7 @@ const sinon = createSandbox(); describe('DebugProtocolAdapter', () => { let adapter: DebugProtocolAdapter; - let socketDebugger: Debugger; + let socketDebugger: DebugProtocolClient; beforeEach(() => { adapter = new DebugProtocolAdapter( @@ -20,7 +20,7 @@ describe('DebugProtocolAdapter', () => { undefined, undefined ); - socketDebugger = new Debugger(undefined); + socketDebugger = new DebugProtocolClient(undefined); adapter['socketDebugger'] = socketDebugger; }); diff --git a/src/adapters/DebugProtocolAdapter.ts b/src/adapters/DebugProtocolAdapter.ts index 9fdc2963..7fe4f6c1 100644 --- a/src/adapters/DebugProtocolAdapter.ts +++ b/src/adapters/DebugProtocolAdapter.ts @@ -1,5 +1,5 @@ -import type { ConstructorOptions, ProtocolVersionDetails } from '../debugProtocol/Debugger'; -import { Debugger } from '../debugProtocol/Debugger'; +import type { ConstructorOptions, ProtocolVersionDetails } from '../debugProtocol/client/DebugProtocolClient'; +import { DebugProtocolClient } from '../debugProtocol/client/DebugProtocolClient'; import * as EventEmitter from 'events'; import { Socket } from 'net'; import type { BSDebugDiagnostic } from '../CompileErrorProcessor'; @@ -63,7 +63,7 @@ export class DebugProtocolAdapter { private emitter: EventEmitter; private chanperfTracker: ChanperfTracker; private rendezvousTracker: RendezvousTracker; - private socketDebugger: Debugger; + private socketDebugger: DebugProtocolClient; private nextFrameId = 1; private stackFramesCache: Record = {}; @@ -188,7 +188,7 @@ export class DebugProtocolAdapter { */ public async connect() { let deferred = defer(); - this.socketDebugger = new Debugger(this.options); + this.socketDebugger = new DebugProtocolClient(this.options); try { // Emit IO from the debugger. // eslint-disable-next-line @typescript-eslint/no-misused-promises diff --git a/src/debugProtocol/MockDebugProtocolServer.spec.ts b/src/debugProtocol/MockDebugProtocolServer.spec.ts index 1339ad26..11866bc6 100644 --- a/src/debugProtocol/MockDebugProtocolServer.spec.ts +++ b/src/debugProtocol/MockDebugProtocolServer.spec.ts @@ -4,6 +4,7 @@ import { ReplaySubject } from 'rxjs'; import { SmartBuffer } from 'smart-buffer'; import type { Deferred } from '../util'; import { util, defer } from '../util'; +import { protocolUtils } from './ProtocolUtil'; export class MockDebugProtocolServer { /** @@ -137,7 +138,7 @@ class WaitForMagicAction extends Action { public process(client: Client) { const b = SmartBuffer.fromBuffer(client.buffer); try { - const str = util.readStringNT(b); + const str = protocolUtils.readStringNT(b); this.deferred.resolve(str); client.buffer = client.buffer.slice(b.readOffset); return Promise.resolve(true); diff --git a/src/debugProtocol/ProtocolUtil.ts b/src/debugProtocol/ProtocolUtil.ts index b4eaad5d..11c5f8b2 100644 --- a/src/debugProtocol/ProtocolUtil.ts +++ b/src/debugProtocol/ProtocolUtil.ts @@ -113,6 +113,27 @@ export class ProtocolUtils { update.data.packetLength = smartBuffer.writeOffset; return smartBuffer; } + + /** + * Tries to read a string from the buffer and will throw an error if there is no null terminator. + * @param {SmartBuffer} bufferReader + */ + public readStringNT(bufferReader: SmartBuffer): string { + // Find next null character (if one is not found, throw) + let buffer = bufferReader.toBuffer(); + let foundNullTerminator = false; + for (let i = bufferReader.readOffset; i < buffer.length; i++) { + if (buffer[i] === 0x00) { + foundNullTerminator = true; + break; + } + } + + if (!foundNullTerminator) { + throw new Error('Could not read buffer string as there is no null terminator.'); + } + return bufferReader.readStringNT(); + } } export const protocolUtils = new ProtocolUtils(); diff --git a/src/debugProtocol/Debugger.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts similarity index 92% rename from src/debugProtocol/Debugger.spec.ts rename to src/debugProtocol/client/DebugProtocolClient.spec.ts index 428024bc..5dff8da4 100644 --- a/src/debugProtocol/Debugger.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -1,25 +1,25 @@ -import { Debugger } from './Debugger'; +import { DebugProtocolClient } from './DebugProtocolClient'; import { expect } from 'chai'; import type { SmartBuffer } from 'smart-buffer'; -import { MockDebugProtocolServer } from './MockDebugProtocolServer.spec'; +import { MockDebugProtocolServer } from '../MockDebugProtocolServer.spec'; import { createSandbox } from 'sinon'; -import { createHandShakeResponse, createHandShakeResponseV3, createProtocolEventV3 } from './events/zzresponsesOld/responseCreationHelpers.spec'; -import { HandshakeResponse, HandshakeResponseV3, ProtocolEventV3 } from './events/zzresponsesOld'; -import { ERROR_CODES, STOP_REASONS, UPDATE_TYPES, VARIABLE_REQUEST_FLAGS } from './Constants'; -import { DebugProtocolServer, DebugProtocolServerOptions } from './server/DebugProtocolServer'; +import { createHandShakeResponse, createHandShakeResponseV3, createProtocolEventV3 } from '../events/zzresponsesOld/responseCreationHelpers.spec'; +import { HandshakeResponse, HandshakeResponseV3, ProtocolEventV3 } from '../events/zzresponsesOld'; +import { ERROR_CODES, STOP_REASONS, UPDATE_TYPES, VARIABLE_REQUEST_FLAGS } from '../Constants'; +import { DebugProtocolServer, DebugProtocolServerOptions } from '../server/DebugProtocolServer'; import * as portfinder from 'portfinder'; -import { util } from '../util'; -import type { BeforeSendResponseEvent, ProtocolPlugin, ProvideResponseEvent } from './server/ProtocolPlugin'; -import { Handler, OnClientConnectedEvent, ProvideRequestEvent } from './server/ProtocolPlugin'; +import { util } from '../../util'; +import type { BeforeSendResponseEvent, ProtocolPlugin, ProvideResponseEvent } from '../server/ProtocolPlugin'; +import { Handler, OnClientConnectedEvent, ProvideRequestEvent } from '../server/ProtocolPlugin'; import type { ProtocolResponse } from './events/zzresponsesOld/ProtocolResponse'; import type { ProtocolRequest } from './events/requests/ProtocolRequest'; -import { HandshakeRequest } from './events/requests/HandshakeRequest'; -import { AllThreadsStoppedUpdateResponse } from './events/updates/AllThreadsStoppedUpdate'; +import { HandshakeRequest } from '../events/requests/HandshakeRequest'; +import { AllThreadsStoppedUpdateResponse } from '../events/updates/AllThreadsStoppedUpdate'; const sinon = createSandbox(); -describe('debugProtocol Debugger', () => { - let bsDebugger: Debugger; +describe('DebugProtocolClient', () => { + let bsDebugger: DebugProtocolClient; let roku: MockDebugProtocolServer; beforeEach(async () => { @@ -27,7 +27,7 @@ describe('debugProtocol Debugger', () => { roku = new MockDebugProtocolServer(); await roku.initialize(); - bsDebugger = new Debugger({ + bsDebugger = new DebugProtocolClient({ host: 'localhost', controllerPort: roku.controllerPort }); @@ -46,7 +46,7 @@ describe('debugProtocol Debugger', () => { void bsDebugger.connect(); void roku.processActions(); let magic = await action.promise; - expect(magic).to.equal(Debugger.DEBUGGER_MAGIC); + expect(magic).to.equal(DebugProtocolClient.DEBUGGER_MAGIC); }); it('validates magic from server on connect', async () => { @@ -182,7 +182,7 @@ class TestPlugin implements ProtocolPlugin { describe.skip('Debugger new tests', () => { let server: DebugProtocolServer; - let client: Debugger; + let client: DebugProtocolClient; let plugin: TestPlugin; const options = { controllerPort: undefined as number, @@ -197,7 +197,7 @@ describe.skip('Debugger new tests', () => { plugin = server.plugins.add(new TestPlugin()); await server.start(); - client = new Debugger(options); + client = new DebugProtocolClient(options); //disable logging for tests because they clutter the test output client['logger'].logLevel = 'off'; }); @@ -251,7 +251,7 @@ describe.skip('Debugger new tests', () => { expect(client.isHandshakeComplete).to.be.equal(false); plugin.pushResponse(new HandshakeResponse({ - magic: Debugger.DEBUGGER_MAGIC, + magic: DebugProtocolClient.DEBUGGER_MAGIC, majorVersion: 1, minorVersion: 0, patchVersion: 0 diff --git a/src/debugProtocol/Debugger.ts b/src/debugProtocol/client/DebugProtocolClient.ts similarity index 91% rename from src/debugProtocol/Debugger.ts rename to src/debugProtocol/client/DebugProtocolClient.ts index fdeafe13..19079bc9 100644 --- a/src/debugProtocol/Debugger.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -1,40 +1,40 @@ import * as Net from 'net'; import * as EventEmitter from 'eventemitter3'; import * as semver from 'semver'; -import { PROTOCOL_ERROR_CODES, COMMANDS, STEP_TYPE, STOP_REASONS, VARIABLE_REQUEST_FLAGS, ERROR_CODES, UPDATE_TYPES } from './Constants'; +import { PROTOCOL_ERROR_CODES, COMMANDS, STEP_TYPE, STOP_REASONS, VARIABLE_REQUEST_FLAGS, ERROR_CODES, UPDATE_TYPES } from '../Constants'; import { SmartBuffer } from 'smart-buffer'; -import { logger } from '../logging'; -import { ExecuteResponseV3 } from './events/zzresponsesOld/ExecuteResponseV3'; -import { ListBreakpointsResponse } from './events/responses/ListBreakpointsResponse'; -import { AddBreakpointsResponse } from './events/responses/AddBreakpointsResponse'; -import { RemoveBreakpointsResponse } from './events/responses/RemoveBreakpointsResponse'; -import { util } from '../util'; -import { BreakpointErrorUpdate, BreakpointErrorUpdateResponse } from './events/updates/BreakpointErrorUpdate'; -import { ContinueRequest } from './events/requests/ContinueRequest'; -import { StopRequest } from './events/requests/StopRequest'; -import { ExitChannelRequest } from './events/requests/ExitChannelRequest'; -import { StepRequest } from './events/requests/StepRequest'; -import { RemoveBreakpointsRequest } from './events/requests/RemoveBreakpointsRequest'; -import { ListBreakpointsRequest } from './events/requests/ListBreakpointsRequest'; -import { VariablesRequest } from './events/requests/VariablesRequest'; -import { StackTraceRequest } from './events/requests/StackTraceRequest'; -import { ThreadsRequest } from './events/requests/ThreadsRequest'; -import { ExecuteRequest } from './events/requests/ExecuteRequest'; -import { AddBreakpointsRequest } from './events/requests/AddBreakpointsRequest'; -import { AddConditionalBreakpointsRequest } from './events/requests/AddConditionalBreakpointsRequest'; -import type { ProtocolRequest, ProtocolResponse, ProtocolUpdate } from './events/ProtocolEvent'; -import { HandshakeResponse } from './events/responses/HandshakeResponse'; -import { HandshakeResponseV3 } from './events/responses/HandshakeResponseV3'; -import { HandshakeRequest } from './events/requests/HandshakeRequest'; -import { GenericResponseV3 } from './events/responses/GenericResponseV3'; -import { GenericResponse, IOPortOpenedUpdate, StackTraceResponse, StackTraceResponseV3, ThreadAttachedUpdate, ThreadsResponse, UndefinedResponse, VariableResponse } from './events/zzresponsesOld'; -import { AllThreadsStoppedUpdate } from './events/updates/AllThreadsStoppedUpdate'; +import { logger } from '../../logging'; +import { ExecuteResponseV3 } from '../events/responses/ExecuteResponseV3'; +import { ListBreakpointsResponse } from '../events/responses/ListBreakpointsResponse'; +import { AddBreakpointsResponse } from '../events/responses/AddBreakpointsResponse'; +import { RemoveBreakpointsResponse } from '../events/responses/RemoveBreakpointsResponse'; +import { util } from '../../util'; +import { BreakpointErrorUpdate } from '../events/updates/BreakpointErrorUpdate'; +import { ContinueRequest } from '../events/requests/ContinueRequest'; +import { StopRequest } from '../events/requests/StopRequest'; +import { ExitChannelRequest } from '../events/requests/ExitChannelRequest'; +import { StepRequest } from '../events/requests/StepRequest'; +import { RemoveBreakpointsRequest } from '../events/requests/RemoveBreakpointsRequest'; +import { ListBreakpointsRequest } from '../events/requests/ListBreakpointsRequest'; +import { VariablesRequest } from '../events/requests/VariablesRequest'; +import { StackTraceRequest } from '../events/requests/StackTraceRequest'; +import { ThreadsRequest } from '../events/requests/ThreadsRequest'; +import { ExecuteRequest } from '../events/requests/ExecuteRequest'; +import { AddBreakpointsRequest } from '../events/requests/AddBreakpointsRequest'; +import { AddConditionalBreakpointsRequest } from '../events/requests/AddConditionalBreakpointsRequest'; +import type { ProtocolRequest, ProtocolResponse, ProtocolUpdate } from '../events/ProtocolEvent'; +import { HandshakeResponse } from '../events/responses/HandshakeResponse'; +import { HandshakeResponseV3 } from '../events/responses/HandshakeResponseV3'; +import { HandshakeRequest } from '../events/requests/HandshakeRequest'; +import { GenericResponseV3 } from '../events/responses/GenericResponseV3'; +import { GenericResponse, IOPortOpenedUpdate, StackTraceResponse, StackTraceResponseV3, ThreadAttachedUpdate, ThreadsResponse, UndefinedResponse, VariableResponse } from '../events/zzresponsesOld'; +import { AllThreadsStoppedUpdate } from '../events/updates/AllThreadsStoppedUpdate'; import { buffer } from 'rxjs'; -import { CompileErrorUpdate } from './events/updates/CompileErrorUpdate'; +import { CompileErrorUpdate } from '../events/updates/CompileErrorUpdate'; -export class Debugger { +export class DebugProtocolClient { - private logger = logger.createLogger(`[${Debugger.name}]`); + private logger = logger.createLogger(`[${DebugProtocolClient.name}]`); public get isStopped(): boolean { return this.stopped; @@ -110,7 +110,7 @@ export class Debugger { public once(eventName: 'handshake-verified'): Promise; public once(eventName: string) { return new Promise((resolve) => { - const disconnect = this.on(eventName as Parameters[0], (...args) => { + const disconnect = this.on(eventName as Parameters[0], (...args) => { disconnect(); resolve(...args); }); @@ -118,7 +118,8 @@ export class Debugger { } public on(eventName: 'app-exit' | 'cannot-continue' | 'close' | 'start', handler: () => void); - public on(eventName: 'data', handler: (data: ProtocolResponse | ProtocolUpdate) => void); + public on(eventName: 'response', handler: (update: ProtocolResponse) => void); + public on(eventName: 'update', handler: (update: ProtocolUpdate) => void); public on(eventName: 'runtime-error' | 'suspend', handler: (data: UpdateThreadsResponse) => void); public on(eventName: 'io-output', handler: (output: string) => void); public on(eventName: 'protocol-version', handler: (data: ProtocolVersionDetails) => void); @@ -132,8 +133,8 @@ export class Debugger { }; } - private emit(eventName: 'response', data: { request: ProtocolRequest; response: ProtocolResponse }); - private emit(eventName: 'update', data: { update: ProtocolUpdate }); + private emit(eventName: 'response', response: ProtocolResponse); + private emit(eventName: 'update', update: ProtocolUpdate); private emit(eventName: 'suspend' | 'runtime-error', data: UpdateThreadsResponse); private emit(eventName: 'app-exit' | 'cannot-continue' | 'close' | 'data' | 'handshake-verified' | 'io-output' | 'protocol-version' | 'start', data?); private emit(eventName: string, data?) { @@ -204,12 +205,16 @@ export class Debugger { this.shutdown('close'); }); + //subscribe to all unsolicited updates + this.on('update', this.handleUpdate.bind(this)); + //send the magic, which triggers the debug session this.logger.log('Sending magic to server'); + //send the handshake request, and wait for the handshake response from the device const response = await this.makeRequest( HandshakeRequest.fromJson({ - magic: Debugger.DEBUGGER_MAGIC + magic: DebugProtocolClient.DEBUGGER_MAGIC }) ); @@ -418,7 +423,7 @@ export class Debugger { this.activeRequests1.set(requestId, request); return new Promise((resolve, reject) => { - let unsubscribe = this.on('data', (event) => { + let unsubscribe = this.on('response', (event) => { if (event.data.requestId === requestId) { unsubscribe(); resolve(event as T); @@ -474,6 +479,7 @@ export class Debugger { } //TODO remove processed bytes no matter what the response was + //TODO if the event's readOffset is larger than the current buffer, we haven't received enough data yet. Don't clear the buffer // process again (will run recursively until the buffer is empty) this.process(); @@ -497,20 +503,13 @@ export class Debugger { } } - //try to get a response - let result: ProtocolResponse | ProtocolUpdate; - let genericResponse = this.watchPacketLength ? GenericResponseV3.fromBuffer(buffer) : GenericResponse.fromBuffer(buffer); // a nonzero requestId means this is a response to a request that we sent if (genericResponse.data.requestId !== 0) { //requestId 0 means this is an update - result = this.getResponse(genericResponse); + return this.getResponse(genericResponse); } else { - result = this.getUpdate(genericResponse); - this.emit('update', result); - } - if (result) { - return + return this.getUpdate(genericResponse); } } @@ -619,7 +618,7 @@ export class Debugger { * Verify all the handshake data */ private verifyHandshake(response: HandshakeResponse | HandshakeResponseV3): boolean { - if (Debugger.DEBUGGER_MAGIC === response.data.magic) { + if (DebugProtocolClient.DEBUGGER_MAGIC === response.data.magic) { this.logger.log('Magic is valid.'); this.protocolVersion = response.data.protocolVersion; diff --git a/src/debugProtocol/events/requests/AddBreakpointsRequest.ts b/src/debugProtocol/events/requests/AddBreakpointsRequest.ts index 7c69e4e9..2a912948 100644 --- a/src/debugProtocol/events/requests/AddBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/AddBreakpointsRequest.ts @@ -34,7 +34,7 @@ export class AddBreakpointsRequest implements ProtocolRequest { request.data.breakpoints = []; for (let i = 0; i < numBreakpoints; i++) { request.data.breakpoints.push({ - filePath: util.readStringNT(smartBuffer), // file_path + filePath: protocolUtils.readStringNT(smartBuffer), // file_path lineNumber: smartBuffer.readUInt32LE(), // line_number ignoreCount: smartBuffer.readUInt32LE() // ignore_count }); diff --git a/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts b/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts index bea4247e..12fef719 100644 --- a/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts @@ -36,10 +36,10 @@ export class AddConditionalBreakpointsRequest implements ProtocolRequest { request.data.breakpoints = []; for (let i = 0; i < numBreakpoints; i++) { request.data.breakpoints.push({ - filePath: util.readStringNT(smartBuffer), // file_path + filePath: protocolUtils.readStringNT(smartBuffer), // file_path lineNumber: smartBuffer.readUInt32LE(), // line_number ignoreCount: smartBuffer.readUInt32LE(), // ignore_count - conditionalExpression: util.readStringNT(smartBuffer) // cond_expr + conditionalExpression: protocolUtils.readStringNT(smartBuffer) // cond_expr }); } }); diff --git a/src/debugProtocol/events/requests/ExecuteRequest.ts b/src/debugProtocol/events/requests/ExecuteRequest.ts index da9be107..de5dbc09 100644 --- a/src/debugProtocol/events/requests/ExecuteRequest.ts +++ b/src/debugProtocol/events/requests/ExecuteRequest.ts @@ -24,7 +24,7 @@ export class ExecuteRequest implements ProtocolRequest { request.data.threadIndex = smartBuffer.readUInt32LE(); // thread_index request.data.stackFrameIndex = smartBuffer.readUInt32LE(); // stack_frame_index - request.data.sourceCode = smartBuffer.readStringNT(); // source_code + request.data.sourceCode = protocolUtils.readStringNT(smartBuffer); // source_code }); return request; } diff --git a/src/debugProtocol/events/requests/HandshakeRequest.ts b/src/debugProtocol/events/requests/HandshakeRequest.ts index 4be71620..7a698442 100644 --- a/src/debugProtocol/events/requests/HandshakeRequest.ts +++ b/src/debugProtocol/events/requests/HandshakeRequest.ts @@ -19,7 +19,7 @@ export class HandshakeRequest implements ProtocolRequest { public static fromBuffer(buffer: Buffer) { const request = new HandshakeRequest(); protocolUtils.bufferLoaderHelper(request, buffer, 0, (smartBuffer) => { - request.data.magic = util.readStringNT(smartBuffer); + request.data.magic = protocolUtils.readStringNT(smartBuffer); }); return request; } diff --git a/src/debugProtocol/events/requests/VariablesRequest.ts b/src/debugProtocol/events/requests/VariablesRequest.ts index 75932e16..26ceea08 100644 --- a/src/debugProtocol/events/requests/VariablesRequest.ts +++ b/src/debugProtocol/events/requests/VariablesRequest.ts @@ -47,7 +47,7 @@ export class VariablesRequest implements ProtocolRequest { if (variablePathLength > 0) { for (let i = 0; i < variablePathLength; i++) { request.data.variablePathEntries.push({ - name: util.readStringNT(smartBuffer), // variable_path_entries - optional + name: protocolUtils.readStringNT(smartBuffer), // variable_path_entries - optional isCaseSensitive: true }); } diff --git a/src/debugProtocol/events/responses/HandshakeResponse.spec.ts b/src/debugProtocol/events/responses/HandshakeResponse.spec.ts index aca5c239..e60184a3 100644 --- a/src/debugProtocol/events/responses/HandshakeResponse.spec.ts +++ b/src/debugProtocol/events/responses/HandshakeResponse.spec.ts @@ -1,5 +1,5 @@ import { HandshakeResponse } from './HandshakeResponse'; -import { Debugger } from '../../Debugger'; +import { DebugProtocolClient } from '../../client/DebugProtocolClient'; import { createHandShakeResponse } from '../zzresponsesOld/responseCreationHelpers.spec'; import { expect } from 'chai'; @@ -29,7 +29,7 @@ describe('HandshakeResponse', () => { let handshake = HandshakeResponse.fromBuffer( //create a response HandshakeResponse.fromJson({ - magic: Debugger.DEBUGGER_MAGIC, + magic: DebugProtocolClient.DEBUGGER_MAGIC, protocolVersion: '1.0.0' //slice a few bytes off the end }).toBuffer().slice(-3) diff --git a/src/debugProtocol/events/responses/HandshakeResponse.ts b/src/debugProtocol/events/responses/HandshakeResponse.ts index 52ec1a6f..78948cec 100644 --- a/src/debugProtocol/events/responses/HandshakeResponse.ts +++ b/src/debugProtocol/events/responses/HandshakeResponse.ts @@ -22,7 +22,7 @@ export class HandshakeResponse implements ProtocolResponse { public static fromBuffer(buffer: Buffer) { const response = new HandshakeResponse(); protocolUtils.bufferLoaderHelper(response, buffer, 20, (smartBuffer: SmartBuffer) => { - response.data.magic = util.readStringNT(smartBuffer); // magic_number + response.data.magic = protocolUtils.readStringNT(smartBuffer); // magic_number response.data.protocolVersion = [ smartBuffer.readInt32LE(), // protocol_major_version diff --git a/src/debugProtocol/events/responses/HandshakeResponseV3.spec.ts b/src/debugProtocol/events/responses/HandshakeResponseV3.spec.ts index c394df0f..32c19cf9 100644 --- a/src/debugProtocol/events/responses/HandshakeResponseV3.spec.ts +++ b/src/debugProtocol/events/responses/HandshakeResponseV3.spec.ts @@ -1,5 +1,5 @@ import { HandshakeResponseV3 } from './HandshakeResponseV3'; -import { Debugger } from '../../Debugger'; +import { DebugProtocolClient } from '../../client/DebugProtocolClient'; import { expect } from 'chai'; import { SmartBuffer } from 'smart-buffer'; @@ -61,7 +61,7 @@ describe('HandshakeResponseV3', () => { let handshake = HandshakeResponseV3.fromBuffer( //create a response HandshakeResponseV3.fromJson({ - magic: Debugger.DEBUGGER_MAGIC, + magic: DebugProtocolClient.DEBUGGER_MAGIC, protocolVersion: '1.0.0', revisionTimestamp: date //slice a few bytes off the end diff --git a/src/debugProtocol/events/responses/HandshakeResponseV3.ts b/src/debugProtocol/events/responses/HandshakeResponseV3.ts index d2fe603f..84b3430e 100644 --- a/src/debugProtocol/events/responses/HandshakeResponseV3.ts +++ b/src/debugProtocol/events/responses/HandshakeResponseV3.ts @@ -24,7 +24,7 @@ export class HandshakeResponseV3 implements ProtocolResponse { public static fromBuffer(buffer: Buffer) { const response = new HandshakeResponseV3(); protocolUtils.bufferLoaderHelper(response, buffer, 20, (smartBuffer: SmartBuffer) => { - response.data.magic = util.readStringNT(smartBuffer); // magic_number + response.data.magic = protocolUtils.readStringNT(smartBuffer); // magic_number response.data.protocolVersion = [ smartBuffer.readInt32LE(), // protocol_major_version diff --git a/src/debugProtocol/events/responses/ListBreakpointsResponse.ts b/src/debugProtocol/events/responses/ListBreakpointsResponse.ts index f53a3da8..0ce270b9 100644 --- a/src/debugProtocol/events/responses/ListBreakpointsResponse.ts +++ b/src/debugProtocol/events/responses/ListBreakpointsResponse.ts @@ -1,5 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import { ERROR_CODES } from '../../Constants'; +import type { ERROR_CODES } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; export class ListBreakpointsResponse { @@ -88,33 +88,3 @@ export interface BreakpointInfo { */ ignoreCount: number; } - -export class BreakpointInfo2 { - constructor(bufferReader: SmartBuffer) { - // breakpoint_id - The ID assigned to the breakpoint. An ID greater than 0 indicates an active breakpoint. An ID of 0 denotes that the breakpoint has an error. - this.breakpointId = bufferReader.readUInt32LE(); - // error_code - Indicates whether the breakpoint was successfully returned. - this.errorCode = bufferReader.readUInt32LE(); - - if (this.breakpointId > 0) { - // This argument is only present if the breakpoint_id is valid. - // ignore_count - Current state, decreases as breakpoint is executed. - this.hitCount = bufferReader.readUInt32LE(); - } - this.success = true; - } - - public get isVerified() { - return this.breakpointId > 0; - } - public success = false; - public breakpointId: number; - public errorCode: number; - /** - * The textual description of the error code - */ - public get errorText() { - return ERROR_CODES[this.errorCode]; - } - public hitCount: number; -} diff --git a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts index 1e8a8ac5..25525afa 100644 --- a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts +++ b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts @@ -29,7 +29,7 @@ export class AllThreadsStoppedUpdate implements ProtocolUpdate { update.data.primaryThreadIndex = smartBuffer.readInt32LE(); update.data.stopReason = smartBuffer.readUInt8(); - update.data.stopReasonDetail = util.readStringNT(smartBuffer); + update.data.stopReasonDetail = protocolUtils.readStringNT(smartBuffer); }); return update; } diff --git a/src/debugProtocol/events/updates/BreakpointErrorUpdate.spec.ts b/src/debugProtocol/events/updates/BreakpointErrorUpdate.spec.ts index 7f8c52f3..9273497a 100644 --- a/src/debugProtocol/events/updates/BreakpointErrorUpdate.spec.ts +++ b/src/debugProtocol/events/updates/BreakpointErrorUpdate.spec.ts @@ -60,4 +60,44 @@ describe('BreakpointErrorUpdate', () => { ] }); }); + + it('Handles zero errors', () => { + const command = BreakpointErrorUpdate.fromJson({ + breakpointId: 3, + compileErrors: [], + runtimeErrors: [], + otherErrors: [] + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 0, + errorCode: ERROR_CODES.OK, + updateType: UPDATE_TYPES.BREAKPOINT_ERROR, + + breakpointId: 3, + compileErrors: [], + runtimeErrors: [], + otherErrors: [] + }); + + expect( + BreakpointErrorUpdate.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 36, // 4 bytes + requestId: 0, // 4 bytes + errorCode: ERROR_CODES.OK, // 4 bytes + updateType: UPDATE_TYPES.BREAKPOINT_ERROR, // 4 bytes + + //flags // 4 bytes + + breakpointId: 3, // 4 bytes + // num_compile_errors // 4 bytes + compileErrors: [], + // num_runtime_errors // 4 bytes + runtimeErrors: [], + // num_other_errors // 4 bytes + otherErrors: [] + }); + }); }); diff --git a/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts b/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts index 853c6b89..099510db 100644 --- a/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts +++ b/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts @@ -46,7 +46,7 @@ export class BreakpointErrorUpdate { update.data.compileErrors = []; for (let i = 0; i < compileErrorCount; i++) { update.data.compileErrors.push( - util.readStringNT(smartBuffer) + protocolUtils.readStringNT(smartBuffer) ); } @@ -54,7 +54,7 @@ export class BreakpointErrorUpdate { update.data.runtimeErrors = []; for (let i = 0; i < runtimeErrorCount; i++) { update.data.runtimeErrors.push( - util.readStringNT(smartBuffer) + protocolUtils.readStringNT(smartBuffer) ); } @@ -62,7 +62,7 @@ export class BreakpointErrorUpdate { update.data.otherErrors = []; for (let i = 0; i < otherErrorCount; i++) { update.data.otherErrors.push( - util.readStringNT(smartBuffer) + protocolUtils.readStringNT(smartBuffer) ); } }); diff --git a/src/debugProtocol/events/updates/CompileErrorUpdate.ts b/src/debugProtocol/events/updates/CompileErrorUpdate.ts index f147d869..70c80a08 100644 --- a/src/debugProtocol/events/updates/CompileErrorUpdate.ts +++ b/src/debugProtocol/events/updates/CompileErrorUpdate.ts @@ -35,10 +35,10 @@ export class CompileErrorUpdate { protocolUtils.bufferLoaderHelper(update, buffer, 20, (smartBuffer) => { protocolUtils.loadCommonUpdateFields(update, smartBuffer, update.data.updateType); - update.data.errorMessage = util.readStringNT(smartBuffer); // error_string - update.data.filePath = util.readStringNT(smartBuffer); // file_spec + update.data.errorMessage = protocolUtils.readStringNT(smartBuffer); // error_string + update.data.filePath = protocolUtils.readStringNT(smartBuffer); // file_spec update.data.lineNumber = smartBuffer.readUInt32LE(); // line_number - update.data.libraryName = util.readStringNT(smartBuffer); // library_name + update.data.libraryName = protocolUtils.readStringNT(smartBuffer); // library_name }); return update; } @@ -63,7 +63,7 @@ export class CompileErrorUpdate { public data = { /** * A text message describing the compiler error. - * + * * This is completely unrelated to the DebuggerUpdate.errorCode field. */ errorMessage: undefined as string, diff --git a/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts b/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts index c093c8f1..cb81477e 100644 --- a/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts +++ b/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts @@ -22,7 +22,7 @@ export class ThreadAttachedUpdate { protocolUtils.loadCommonUpdateFields(update, smartBuffer, update.data.updateType); update.data.threadIndex = smartBuffer.readInt32LE(); update.data.stopReason = smartBuffer.readUInt8(); - update.data.stopReasonDetail = util.readStringNT(smartBuffer); + update.data.stopReasonDetail = protocolUtils.readStringNT(smartBuffer); }); return update; } diff --git a/src/debugProtocol/events/zzresponsesOld/BreakpointErrorResponse.spec.ts b/src/debugProtocol/events/zzresponsesOld/BreakpointErrorResponse.spec.ts deleted file mode 100644 index 0ca09033..00000000 --- a/src/debugProtocol/events/zzresponsesOld/BreakpointErrorResponse.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { createBreakpointErrorUpdateResponse } from './responseCreationHelpers.spec'; -import { expect } from 'chai'; -import { BreakpointErrorUpdateResponse } from '../updates/BreakpointErrorUpdate'; -import { ERROR_CODES, UPDATE_TYPES } from '../../Constants'; - -describe('BreakpointErrorUpdateResponse', () => { - it('Handles zero errors', () => { - const smartBuffer = createBreakpointErrorUpdateResponse({ - flags: 0, - breakpoint_id: 23, - errorCode: ERROR_CODES.OK, - compile_errors: [], - runtime_errors: [], - other_errors: [], - includePacketLength: false - }); - const rawBuffer = smartBuffer.toBuffer(); - let update = new BreakpointErrorUpdateResponse( - rawBuffer - ); - expect(update.requestId).to.eql(0); - expect(update.errorCode).to.eql(0); - expect(update.updateType).to.eql(UPDATE_TYPES.BREAKPOINT_ERROR); - expect(update.breakpointId).to.eql(23); - expect(update.flags).to.eql(0); - expect(update.success).to.eql(true); - - expect(update.compileErrorCount).to.eql(0); - expect(update.compileErrors).to.eql([]); - - expect(update.runtimeErrorCount).to.eql(0); - expect(update.runtimeErrors).to.eql([]); - - expect(update.otherErrorCount).to.eql(0); - expect(update.otherErrors).to.eql([]); - }); - - it('Handles many errors', () => { - const smartBuffer = createBreakpointErrorUpdateResponse({ - flags: 0, - breakpoint_id: 23, - errorCode: ERROR_CODES.OK, - compile_errors: ['compile error 1'], - runtime_errors: ['runtime error 1', 'runtime error 2'], - other_errors: ['other error 1', 'other error 2', 'other error 3'], - includePacketLength: false - }); - const rawBuffer = smartBuffer.toBuffer(); - let update = new BreakpointErrorUpdateResponse( - rawBuffer - ); - expect(update.compileErrorCount).to.eql(1); - expect(update.compileErrors).to.eql(['compile error 1']); - - expect(update.runtimeErrorCount).to.eql(2); - expect(update.runtimeErrors).to.eql(['runtime error 1', 'runtime error 2']); - - expect(update.otherErrorCount).to.eql(3); - expect(update.otherErrors).to.eql(['other error 1', 'other error 2', 'other error 3']); - }); -}); diff --git a/src/debugProtocol/events/zzresponsesOld/ExecuteResponseV3.ts b/src/debugProtocol/events/zzresponsesOld/ExecuteResponseV3.ts deleted file mode 100644 index 9770ddb0..00000000 --- a/src/debugProtocol/events/zzresponsesOld/ExecuteResponseV3.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { SmartBuffer } from 'smart-buffer'; -import { util } from '../../../util'; - -export class ExecuteResponseV3 { - constructor(buffer: Buffer) { - // The smallest a request response can be - if (buffer.byteLength >= 12) { - try { - let bufferReader = SmartBuffer.fromBuffer(buffer); - this.requestId = bufferReader.readUInt32LE(); // request_id - this.errorCode = bufferReader.readUInt32LE(); // error_code - this.executeSuccess = bufferReader.readUInt8() !== 0; //execute_success - this.runtimeStopCode = bufferReader.readUInt8(); //runtime_stop_code - - this.compileErrors = new ExecuteErrors(bufferReader); - this.runtimeErrors = new ExecuteErrors(bufferReader); - this.otherErrors = new ExecuteErrors(bufferReader); - - this.success = this.compileErrors.success && this.runtimeErrors.success && this.otherErrors.success; - this.readOffset = bufferReader.readOffset; - } catch (error) { - // Could not parse - } - } - } - public success = false; - public readOffset = 0; - /** - * true if code ran and completed without errors, false otherwise - */ - public executeSuccess = false; - public runtimeStopCode: number; - - public compileErrors: ExecuteErrors; - public runtimeErrors: ExecuteErrors; - public otherErrors: ExecuteErrors; - - // response fields - public requestId = -1; - public errorCode = -1; -} - -class ExecuteErrors { - public constructor(bufferReader: SmartBuffer) { - if (bufferReader.length >= 4) { - const errorCount = bufferReader.readUInt32LE(); - for (let i = 0; i < errorCount; i++) { - const message = util.readStringNT(bufferReader); - if (message) { - this.messages.push(message); - } - } - this.success = this.messages.length === errorCount; - } - } - - public success = false; - - public messages: string[] = []; -} diff --git a/src/debugProtocol/events/zzresponsesOld/StackTraceResponse.ts b/src/debugProtocol/events/zzresponsesOld/StackTraceResponse.ts index de9aee0b..aec09b04 100644 --- a/src/debugProtocol/events/zzresponsesOld/StackTraceResponse.ts +++ b/src/debugProtocol/events/zzresponsesOld/StackTraceResponse.ts @@ -1,6 +1,7 @@ import * as path from 'path'; import { SmartBuffer } from 'smart-buffer'; import { util } from '../../../util'; +import { protocolUtils } from '../../ProtocolUtil'; export class StackTraceResponse { @@ -47,8 +48,8 @@ export class StackEntry { constructor(bufferReader: SmartBuffer) { this.lineNumber = bufferReader.readUInt32LE(); // NOTE: this is documented as being function name then file name but it is being returned by the device backwards. - this.fileName = util.readStringNT(bufferReader); - this.functionName = util.readStringNT(bufferReader); + this.fileName = protocolUtils.readStringNT(bufferReader); + this.functionName = protocolUtils.readStringNT(bufferReader); let fileExtension = path.extname(this.fileName).toLowerCase(); // NOTE:Make sure we have a full valid path (?? can be valid because the device might not know the file). diff --git a/src/debugProtocol/events/zzresponsesOld/StackTraceResponseV3.ts b/src/debugProtocol/events/zzresponsesOld/StackTraceResponseV3.ts index 41e51b1e..8aae2956 100644 --- a/src/debugProtocol/events/zzresponsesOld/StackTraceResponseV3.ts +++ b/src/debugProtocol/events/zzresponsesOld/StackTraceResponseV3.ts @@ -1,6 +1,7 @@ import * as path from 'path'; import { SmartBuffer } from 'smart-buffer'; import { util } from '../../../util'; +import { protocolUtils } from '../../ProtocolUtil'; export class StackTraceResponseV3 { @@ -47,8 +48,8 @@ export class StackEntryV3 { constructor(bufferReader: SmartBuffer) { this.lineNumber = bufferReader.readUInt32LE(); // NOTE: this is documented as being function name then file name but it is being returned by the device backwards. - this.functionName = util.readStringNT(bufferReader); - this.fileName = util.readStringNT(bufferReader); + this.functionName = protocolUtils.readStringNT(bufferReader); + this.fileName = protocolUtils.readStringNT(bufferReader); let fileExtension = path.extname(this.fileName).toLowerCase(); // NOTE:Make sure we have a full valid path (?? can be valid because the device might not know the file). diff --git a/src/debugProtocol/events/zzresponsesOld/ThreadsResponse.ts b/src/debugProtocol/events/zzresponsesOld/ThreadsResponse.ts index 4b9761c1..571c2c94 100644 --- a/src/debugProtocol/events/zzresponsesOld/ThreadsResponse.ts +++ b/src/debugProtocol/events/zzresponsesOld/ThreadsResponse.ts @@ -2,6 +2,7 @@ import * as path from 'path'; import { SmartBuffer } from 'smart-buffer'; import { STOP_REASONS } from '../../Constants'; import { util } from '../../../util'; +import { protocolUtils } from '../../ProtocolUtil'; export class ThreadsResponse { @@ -50,11 +51,11 @@ export class ThreadInfo { // eslint-disable-next-line no-bitwise this.isPrimary = (bufferReader.readUInt32LE() & 0x01) > 0; this.stopReason = STOP_REASONS[bufferReader.readUInt8()]; - this.stopReasonDetail = util.readStringNT(bufferReader); + this.stopReasonDetail = protocolUtils.readStringNT(bufferReader); this.lineNumber = bufferReader.readUInt32LE(); - this.functionName = util.readStringNT(bufferReader); - this.fileName = util.readStringNT(bufferReader); - this.codeSnippet = util.readStringNT(bufferReader); + this.functionName = protocolUtils.readStringNT(bufferReader); + this.fileName = protocolUtils.readStringNT(bufferReader); + this.codeSnippet = protocolUtils.readStringNT(bufferReader); let fileExtension = path.extname(this.fileName).toLowerCase(); // NOTE: Make sure we have a full valid path (?? can be valid because the device might not know the file) and that we have a codeSnippet. diff --git a/src/debugProtocol/events/zzresponsesOld/VariableResponse.ts b/src/debugProtocol/events/zzresponsesOld/VariableResponse.ts index 25e5fe31..4a98d389 100644 --- a/src/debugProtocol/events/zzresponsesOld/VariableResponse.ts +++ b/src/debugProtocol/events/zzresponsesOld/VariableResponse.ts @@ -2,6 +2,7 @@ import { SmartBuffer } from 'smart-buffer'; import { VARIABLE_FLAGS, VARIABLE_TYPES } from '../../Constants'; import { util } from '../../../util'; +import { protocolUtils } from '../../ProtocolUtil'; export class VariableResponse { @@ -62,7 +63,7 @@ export class VariableInfo { if (this.isNameHere) { // YAY we have a name. Pull it out of the buffer. - this.name = util.readStringNT(bufferReader); + this.name = protocolUtils.readStringNT(bufferReader); } if (this.isRefCounted) { @@ -85,13 +86,13 @@ export class VariableInfo { case 'String': case 'Subroutine': case 'Function': - this.value = util.readStringNT(bufferReader); + this.value = protocolUtils.readStringNT(bufferReader); this.success = true; break; case 'Subtyped_Object': let names = []; for (let i = 0; i < 2; i++) { - names.push(util.readStringNT(bufferReader)); + names.push(protocolUtils.readStringNT(bufferReader)); } if (names.length === 2) { diff --git a/src/index.ts b/src/index.ts index 6bd1aa55..b6d14de8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,12 @@ export * from './managers/BreakpointManager'; export * from './LaunchConfiguration'; -export * from './debugProtocol/Debugger'; +export * from './debugProtocol/client/DebugProtocolClient'; export * from './debugSession/BrightScriptDebugSession'; export * from './debugSession/Events'; export * from './ComponentLibraryServer'; export * from './CompileErrorProcessor'; export * from './debugProtocol/Constants'; -export * from './debugProtocol/Debugger'; +export * from './debugProtocol/client/DebugProtocolClient'; export * from './debugProtocol/events/zzresponsesOld'; export * from './FileUtils'; export * from './managers/ProjectManager'; diff --git a/src/util.ts b/src/util.ts index 26976a8e..2716e6a5 100644 --- a/src/util.ts +++ b/src/util.ts @@ -120,27 +120,6 @@ class Util { }); } - /** - * Tries to read a string from the buffer and will throw an error if there is no null terminator. - * @param {SmartBuffer} bufferReader - */ - public readStringNT(bufferReader: SmartBuffer): string { - // Find next null character (if one is not found, throw) - let buffer = bufferReader.toBuffer(); - let foundNullTerminator = false; - for (let i = bufferReader.readOffset; i < buffer.length; i++) { - if (buffer[i] === 0x00) { - foundNullTerminator = true; - break; - } - } - - if (!foundNullTerminator) { - throw new Error('Could not read buffer string as there is no null terminator.'); - } - return bufferReader.readStringNT(); - } - /** * A reference to the current debug session. Used for logging, and set in the debug session constructor */ From b479dc77825ed5a5c9ad5efc8ae6b1ef3b52fd37 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Tue, 11 Oct 2022 11:54:33 -0400 Subject: [PATCH 09/74] Migrate more responses to new structure --- src/adapters/DebugProtocolAdapter.spec.ts | 6 +- src/adapters/DebugProtocolAdapter.ts | 4 +- src/debugProtocol/Constants.ts | 49 ++--- .../client/DebugProtocolClient.spec.ts | 6 +- .../client/DebugProtocolClient.ts | 36 ++-- .../responses/ExecuteV3Response.spec.ts | 105 ++++++++++ .../events/responses/ExecuteV3Response.ts | 115 +++++++++++ ...seV3.spec.ts => GenericV3Response.spec.ts} | 12 +- ...ericResponseV3.ts => GenericV3Response.ts} | 6 +- ...V3.spec.ts => HandshakeV3Response.spec.ts} | 20 +- ...keResponseV3.ts => HandshakeV3Response.ts} | 9 +- .../responses/ListBreakpointsResponse.spec.ts | 9 +- .../responses/ListBreakpointsResponse.ts | 5 +- .../responses/StackTraceResponse.spec.ts | 108 +++++++++++ .../events/responses/StackTraceResponse.ts | 81 ++++++++ .../responses/StackTraceV3Response.spec.ts | 159 ++++++++++++++++ .../events/responses/StackTraceV3Response.ts | 88 +++++++++ .../events/responses/ThreadsResponse.spec.ts | 180 ++++++++++++++++++ .../events/responses/ThreadsResponse.ts | 119 ++++++++++++ .../updates/AllThreadsStoppedUpdate.spec.ts | 8 +- .../events/updates/AllThreadsStoppedUpdate.ts | 4 +- .../events/updates/CompileErrorUpdate.spec.ts | 2 +- .../events/updates/IOPortOpenedUpdate.spec.ts | 7 +- .../updates/ThreadAttachedUpdate.spec.ts | 8 +- .../events/updates/ThreadAttachedUpdate.ts | 4 +- .../zzresponsesOld/StackTraceResponse.ts | 64 ------- .../zzresponsesOld/StackTraceResponseV3.ts | 64 ------- .../events/zzresponsesOld/ThreadsResponse.ts | 74 ------- .../zzresponsesOld/VariableResponse.spec.ts | 58 ------ .../events/zzresponsesOld/VariableResponse.ts | 164 ---------------- .../events/zzresponsesOld/index.ts | 10 +- .../responseCreationHelpers.spec.ts | 2 +- .../server/DebugProtocolServer.ts | 6 +- src/util.ts | 7 + 34 files changed, 1056 insertions(+), 543 deletions(-) create mode 100644 src/debugProtocol/events/responses/ExecuteV3Response.spec.ts create mode 100644 src/debugProtocol/events/responses/ExecuteV3Response.ts rename src/debugProtocol/events/responses/{GenericResponseV3.spec.ts => GenericV3Response.spec.ts} (81%) rename src/debugProtocol/events/responses/{GenericResponseV3.ts => GenericV3Response.ts} (91%) rename src/debugProtocol/events/responses/{HandshakeResponseV3.spec.ts => HandshakeV3Response.spec.ts} (80%) rename src/debugProtocol/events/responses/{HandshakeResponseV3.ts => HandshakeV3Response.ts} (94%) create mode 100644 src/debugProtocol/events/responses/StackTraceResponse.spec.ts create mode 100644 src/debugProtocol/events/responses/StackTraceResponse.ts create mode 100644 src/debugProtocol/events/responses/StackTraceV3Response.spec.ts create mode 100644 src/debugProtocol/events/responses/StackTraceV3Response.ts create mode 100644 src/debugProtocol/events/responses/ThreadsResponse.spec.ts create mode 100644 src/debugProtocol/events/responses/ThreadsResponse.ts delete mode 100644 src/debugProtocol/events/zzresponsesOld/StackTraceResponse.ts delete mode 100644 src/debugProtocol/events/zzresponsesOld/StackTraceResponseV3.ts delete mode 100644 src/debugProtocol/events/zzresponsesOld/ThreadsResponse.ts delete mode 100644 src/debugProtocol/events/zzresponsesOld/VariableResponse.spec.ts delete mode 100644 src/debugProtocol/events/zzresponsesOld/VariableResponse.ts diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index 91c4593d..b62dece3 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -4,7 +4,7 @@ import { DebugProtocolClient } from '../debugProtocol/client/DebugProtocolClient import { DebugProtocolAdapter } from './DebugProtocolAdapter'; import { createSandbox } from 'sinon'; import type { VariableInfo } from '../debugProtocol/events/zzresponsesOld'; -import { VariableResponse } from '../debugProtocol/events/zzresponsesOld'; +import { VariablesResponse } from '../debugProtocol/events/zzresponsesOld'; import { ERROR_CODES } from './../debugProtocol/Constants'; const sinon = createSandbox(); @@ -25,11 +25,11 @@ describe('DebugProtocolAdapter', () => { }); describe('getVariable', () => { - let response: VariableResponse; + let response: VariablesResponse; let variables: Partial[]; beforeEach(() => { - response = new VariableResponse(Buffer.alloc(5)); + response = new VariablesResponse(Buffer.alloc(5)); response.errorCode = ERROR_CODES.OK; variables = []; sinon.stub(adapter as any, 'getStackFrameById').returns({}); diff --git a/src/adapters/DebugProtocolAdapter.ts b/src/adapters/DebugProtocolAdapter.ts index 7fe4f6c1..720a7ec9 100644 --- a/src/adapters/DebugProtocolAdapter.ts +++ b/src/adapters/DebugProtocolAdapter.ts @@ -9,7 +9,7 @@ import { RendezvousTracker } from '../RendezvousTracker'; import type { ChanperfData } from '../ChanperfTracker'; import { ChanperfTracker } from '../ChanperfTracker'; import type { SourceLocation } from '../managers/LocationManager'; -import { ERROR_CODES, PROTOCOL_ERROR_CODES, STOP_REASONS } from '../debugProtocol/Constants'; +import { ERROR_CODES, PROTOCOL_ERROR_CODES, StopReasonCode } from '../debugProtocol/Constants'; import { defer, util } from '../util'; import { logger } from '../logging'; import * as semver from 'semver'; @@ -242,7 +242,7 @@ export class DebugProtocolAdapter { console.debug('hasRuntimeError!!', data); this.emit('runtime-error', { message: data.data.stopReasonDetail, - errorCode: STOP_REASONS[data.data.stopReason] + errorCode: StopReasonCode[data.data.stopReason] }); }); diff --git a/src/debugProtocol/Constants.ts b/src/debugProtocol/Constants.ts index 733c5975..028f34d7 100644 --- a/src/debugProtocol/Constants.ts +++ b/src/debugProtocol/Constants.ts @@ -36,13 +36,14 @@ export enum ERROR_CODES { INVALID_ARGS = 5 } -export enum STOP_REASONS { - UNDEFINED = 0, - NOT_STOPPED = 1, - NORMAL_EXIT = 2, - STOP_STATEMENT = 3, - BREAK = 4, - RUNTIME_ERROR = 5 +export type StopReason = 'Undefined' | 'NotStopped' | 'NormalExit' | 'StopStatement' | 'Break' | 'RuntimeError'; +export enum StopReasonCode { + Undefined = 0, + NotStopped = 1, + NormalExit = 2, + StopStatement = 3, + Break = 4, + RuntimeError = 5 } export enum UPDATE_TYPES { @@ -72,53 +73,35 @@ export enum VARIABLE_FLAGS { * value is a child of the requested variable * e.g., an element of an array or field of an AA */ - isChildKey = 0x01, + isChildKey = 1, /** * value is constant */ - isConst = 0x02, + isConst = 2, /** * The referenced value is a container (e.g., a list or array) */ - isContainer = 0x04, + isContainer = 4, /** * The name is included in this VariableInfo */ - isNameHere = 0x08, + isNameHere = 8, /** * value is reference-counted. */ - isRefCounted = 0x10, + isRefCounted = 16, /** * value is included in this VariableInfo */ - isValueHere = 0x20, + isValueHere = 32, /** * Value is container, key lookup is case sensitive * @since protocol 3.1.0 */ - isKeysCaseSensitive = 0x40 + isKeysCaseSensitive = 64 } -export enum VARIABLE_TYPES { - AA = 1, - Array = 2, - Boolean = 3, - Double = 4, - Float = 5, - Function = 6, - Integer = 7, - Interface = 8, - Invalid = 9, - List = 10, - Long_Integer = 11, - Object = 12, - String = 13, - Subroutine = 14, - Subtyped_Object = 15, - Uninitialized = 16, - Unknown = 17 -} + //#endregion export function getUpdateType(value: number): UPDATE_TYPES { diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index 5dff8da4..081c1642 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -5,7 +5,7 @@ import { MockDebugProtocolServer } from '../MockDebugProtocolServer.spec'; import { createSandbox } from 'sinon'; import { createHandShakeResponse, createHandShakeResponseV3, createProtocolEventV3 } from '../events/zzresponsesOld/responseCreationHelpers.spec'; import { HandshakeResponse, HandshakeResponseV3, ProtocolEventV3 } from '../events/zzresponsesOld'; -import { ERROR_CODES, STOP_REASONS, UPDATE_TYPES, VARIABLE_REQUEST_FLAGS } from '../Constants'; +import { ERROR_CODES, StopReasonCode, UPDATE_TYPES, VARIABLE_REQUEST_FLAGS } from '../Constants'; import { DebugProtocolServer, DebugProtocolServerOptions } from '../server/DebugProtocolServer'; import * as portfinder from 'portfinder'; import { util } from '../../util'; @@ -269,14 +269,14 @@ describe.skip('Debugger new tests', () => { await server.sendUpdate( new AllThreadsStoppedUpdateResponse({ primaryThreadIndex: 1, - stopReason: STOP_REASONS.BREAK, + stopReason: StopReasonCode.Break, stopReasonDetail: 'test' }) ); const event = await client.once('suspend'); expect(event.data).include({ primaryThreadIndex: 1, - stopReason: STOP_REASONS.BREAK, + stopReason: StopReasonCode.Break, stopReasonDetail: 'test' }); // let protocolEvent = createProtocolEventV3({ diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 19079bc9..3738b64b 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -1,10 +1,10 @@ import * as Net from 'net'; import * as EventEmitter from 'eventemitter3'; import * as semver from 'semver'; -import { PROTOCOL_ERROR_CODES, COMMANDS, STEP_TYPE, STOP_REASONS, VARIABLE_REQUEST_FLAGS, ERROR_CODES, UPDATE_TYPES } from '../Constants'; +import { PROTOCOL_ERROR_CODES, COMMANDS, STEP_TYPE, StopReasonCode, VARIABLE_REQUEST_FLAGS, ERROR_CODES, UPDATE_TYPES } from '../Constants'; import { SmartBuffer } from 'smart-buffer'; import { logger } from '../../logging'; -import { ExecuteResponseV3 } from '../events/responses/ExecuteResponseV3'; +import { ExecuteV3Response } from '../events/responses/ExecuteV3Response'; import { ListBreakpointsResponse } from '../events/responses/ListBreakpointsResponse'; import { AddBreakpointsResponse } from '../events/responses/AddBreakpointsResponse'; import { RemoveBreakpointsResponse } from '../events/responses/RemoveBreakpointsResponse'; @@ -24,10 +24,10 @@ import { AddBreakpointsRequest } from '../events/requests/AddBreakpointsRequest' import { AddConditionalBreakpointsRequest } from '../events/requests/AddConditionalBreakpointsRequest'; import type { ProtocolRequest, ProtocolResponse, ProtocolUpdate } from '../events/ProtocolEvent'; import { HandshakeResponse } from '../events/responses/HandshakeResponse'; -import { HandshakeResponseV3 } from '../events/responses/HandshakeResponseV3'; +import { HandshakeV3Response } from '../events/responses/HandshakeV3Response'; import { HandshakeRequest } from '../events/requests/HandshakeRequest'; -import { GenericResponseV3 } from '../events/responses/GenericResponseV3'; -import { GenericResponse, IOPortOpenedUpdate, StackTraceResponse, StackTraceResponseV3, ThreadAttachedUpdate, ThreadsResponse, UndefinedResponse, VariableResponse } from '../events/zzresponsesOld'; +import { GenericV3Response } from '../events/responses/GenericV3Response'; +import { GenericResponse, IOPortOpenedUpdate, StackTraceResponse, StackTraceResponseV3, ThreadAttachedUpdate, ThreadsResponse, UndefinedResponse, VariablesResponse } from '../events/zzresponsesOld'; import { AllThreadsStoppedUpdate } from '../events/updates/AllThreadsStoppedUpdate'; import { buffer } from 'rxjs'; import { CompileErrorUpdate } from '../events/updates/CompileErrorUpdate'; @@ -212,7 +212,7 @@ export class DebugProtocolClient { this.logger.log('Sending magic to server'); //send the handshake request, and wait for the handshake response from the device - const response = await this.makeRequest( + const response = await this.makeRequest( HandshakeRequest.fromJson({ magic: DebugProtocolClient.DEBUGGER_MAGIC }) @@ -356,13 +356,13 @@ export class DebugProtocolClient { //starting in protocol v3.1.0, it supports marking certain path items as case-insensitive (i.e. parts of DottedGet expressions) enableCaseInsensitivityFlag: semver.satisfies(this.protocolVersion, '>=3.1.0') && variablePathEntries.length > 0 }); - return this.makeRequest(request); + return this.makeRequest(request); } } public async executeCommand(sourceCode: string, stackFrameIndex: number = this.stackFrameIndex, threadIndex: number = this.primaryThread) { if (this.stopped && threadIndex > -1) { - return this.makeRequest( + return this.makeRequest( ExecuteRequest.fromJson({ requestId: this.totalRequests++, threadIndex: threadIndex, @@ -492,7 +492,7 @@ export class DebugProtocolClient { //if we haven't seen a handshake yet, try to convert the buffer into a handshake if (!this.isHandshakeComplete) { //try building the v3 handshake response first - let handshakev3 = HandshakeResponseV3.fromBuffer(buffer); + let handshakev3 = HandshakeV3Response.fromBuffer(buffer); if (handshakev3.success) { return handshakev3; } @@ -503,7 +503,7 @@ export class DebugProtocolClient { } } - let genericResponse = this.watchPacketLength ? GenericResponseV3.fromBuffer(buffer) : GenericResponse.fromBuffer(buffer); + let genericResponse = this.watchPacketLength ? GenericV3Response.fromBuffer(buffer) : GenericResponse.fromBuffer(buffer); // a nonzero requestId means this is a response to a request that we sent if (genericResponse.data.requestId !== 0) { //requestId 0 means this is an update @@ -513,7 +513,7 @@ export class DebugProtocolClient { } } - private getResponse(genericResponse: GenericResponseV3): ProtocolResponse { + private getResponse(genericResponse: GenericV3Response): ProtocolResponse { const request = this.activeRequests1.get(genericResponse.data.requestId); if (!request) { return; @@ -525,7 +525,7 @@ export class DebugProtocolClient { case COMMANDS.EXIT_CHANNEL: return genericResponse; case COMMANDS.EXECUTE: - return new ExecuteResponseV3(this.buffer); + return new ExecuteV3Response(this.buffer); case COMMANDS.ADD_BREAKPOINTS: case COMMANDS.ADD_CONDITIONAL_BREAKPOINTS: return new AddBreakpointsResponse(this.buffer); @@ -534,7 +534,7 @@ export class DebugProtocolClient { case COMMANDS.REMOVE_BREAKPOINTS: return RemoveBreakpointsResponse.fromBuffer(this.buffer); case COMMANDS.VARIABLES: - return new VariableResponse(this.buffer); + return new VariablesResponse(this.buffer); case COMMANDS.STACKTRACE: return this.checkResponse( packetLength ? new StackTraceResponseV3(slicedBuffer) : new StackTraceResponse(slicedBuffer), @@ -547,7 +547,7 @@ export class DebugProtocolClient { } } - private getUpdate(genericResponse: GenericResponseV3): ProtocolUpdate { + private getUpdate(genericResponse: GenericV3Response): ProtocolUpdate { //read the update_type from the buffer (save some buffer parsing time by narrowing to the exact update type) const updateType = this.buffer.readUInt32LE(genericResponse.readOffset) as UPDATE_TYPES; @@ -574,15 +574,15 @@ export class DebugProtocolClient { if (update instanceof AllThreadsStoppedUpdate || update instanceof ThreadAttachedUpdate) { this.stopped = true; let stopReason = update.data.stopReason; - let eventName: 'runtime-error' | 'suspend' = stopReason === STOP_REASONS.RUNTIME_ERROR ? 'runtime-error' : 'suspend'; + let eventName: 'runtime-error' | 'suspend' = stopReason === StopReasonCode.RuntimeError ? 'runtime-error' : 'suspend'; if (update.data.updateType === UPDATE_TYPES.ALL_THREADS_STOPPED) { - if (stopReason === STOP_REASONS.RUNTIME_ERROR || stopReason === STOP_REASONS.BREAK || stopReason === STOP_REASONS.STOP_STATEMENT) { + if (stopReason === StopReasonCode.RuntimeError || stopReason === StopReasonCode.Break || stopReason === StopReasonCode.StopStatement) { this.primaryThread = (update.data as ThreadsStopped).primaryThreadIndex; this.stackFrameIndex = 0; this.emit(eventName, update); } - } else if (stopReason === STOP_REASONS.RUNTIME_ERROR || stopReason === STOP_REASONS.BREAK || stopReason === STOP_REASONS.STOP_STATEMENT) { + } else if (stopReason === StopReasonCode.RuntimeError || stopReason === StopReasonCode.Break || stopReason === StopReasonCode.StopStatement) { this.primaryThread = (update.data as ThreadAttached).threadIndex; this.emit(eventName, update); } @@ -617,7 +617,7 @@ export class DebugProtocolClient { /** * Verify all the handshake data */ - private verifyHandshake(response: HandshakeResponse | HandshakeResponseV3): boolean { + private verifyHandshake(response: HandshakeResponse | HandshakeV3Response): boolean { if (DebugProtocolClient.DEBUGGER_MAGIC === response.data.magic) { this.logger.log('Magic is valid.'); diff --git a/src/debugProtocol/events/responses/ExecuteV3Response.spec.ts b/src/debugProtocol/events/responses/ExecuteV3Response.spec.ts new file mode 100644 index 00000000..fd68b9a8 --- /dev/null +++ b/src/debugProtocol/events/responses/ExecuteV3Response.spec.ts @@ -0,0 +1,105 @@ +import { expect } from 'chai'; +import { ERROR_CODES, StopReasonCode, UPDATE_TYPES } from '../../Constants'; +import { ExecuteV3Response } from './ExecuteV3Response'; + +describe('ExecuteV3Response', () => { + it('serializes and deserializes properly', () => { + const command = ExecuteV3Response.fromJson({ + requestId: 3, + executeSuccess: true, + runtimeStopCode: StopReasonCode.Break, + compileErrors: [ + 'compile 1' + ], + runtimeErrors: [ + 'runtime 1' + ], + otherErrors: [ + 'other 1' + ] + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 3, + errorCode: ERROR_CODES.OK, + + executeSuccess: true, + runtimeStopCode: StopReasonCode.Break, + compileErrors: [ + 'compile 1' + ], + runtimeErrors: [ + 'runtime 1' + ], + otherErrors: [ + 'other 1' + ] + }); + + expect( + ExecuteV3Response.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 54, // 4 bytes + requestId: 3, // 4 bytes + errorCode: ERROR_CODES.OK, // 4 bytes + + executeSuccess: true, // 1 byte + runtimeStopCode: StopReasonCode.Break, // 1 byte + + // num_compile_errors // 4 bytes + compileErrors: [ + 'compile 1' // 10 bytes + ], + // num_runtime_errors // 4 bytes + runtimeErrors: [ + 'runtime 1' // 10 bytes + ], + // num_other_errors // 4 bytes + otherErrors: [ + 'other 1' // 8 bytes + ] + }); + }); + + it('Handles zero errors', () => { + const command = ExecuteV3Response.fromJson({ + requestId: 3, + executeSuccess: true, + runtimeStopCode: StopReasonCode.Break, + + compileErrors: [], + runtimeErrors: [], + otherErrors: [] + }); + + expect(command.data).to.eql({ + packetLength: undefined, + requestId: 3, + errorCode: ERROR_CODES.OK, + + executeSuccess: true, + runtimeStopCode: StopReasonCode.Break, + compileErrors: [], + runtimeErrors: [], + otherErrors: [] + }); + + expect( + ExecuteV3Response.fromBuffer(command.toBuffer()).data + ).to.eql({ + packetLength: 26, // 4 bytes + requestId: 3, // 4 bytes + errorCode: ERROR_CODES.OK, // 4 bytes + + executeSuccess: true, // 1 byte + runtimeStopCode: StopReasonCode.Break, // 1 byte + // num_compile_errors // 4 bytes + compileErrors: [], + // num_runtime_errors // 4 bytes + runtimeErrors: [], + // num_other_errors // 4 bytes + otherErrors: [] + }); + }); +}); diff --git a/src/debugProtocol/events/responses/ExecuteV3Response.ts b/src/debugProtocol/events/responses/ExecuteV3Response.ts new file mode 100644 index 00000000..5f1693e3 --- /dev/null +++ b/src/debugProtocol/events/responses/ExecuteV3Response.ts @@ -0,0 +1,115 @@ +import { SmartBuffer } from 'smart-buffer'; +import type { StopReasonCode } from '../../Constants'; +import { ERROR_CODES } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; + +export class ExecuteV3Response { + public static fromJson(data: { + requestId: number; + executeSuccess: boolean; + runtimeStopCode: StopReasonCode; + compileErrors: string[]; + runtimeErrors: string[]; + otherErrors: string[]; + }) { + const response = new ExecuteV3Response(); + protocolUtils.loadJson(response, data); + response.data.compileErrors ??= []; + response.data.runtimeErrors ??= []; + response.data.otherErrors ??= []; + return response; + } + + public static fromBuffer(buffer: Buffer) { + const response = new ExecuteV3Response(); + protocolUtils.bufferLoaderHelper(response, buffer, 8, (smartBuffer: SmartBuffer) => { + protocolUtils.loadCommonResponseFields(response, smartBuffer); + + response.data.executeSuccess = smartBuffer.readUInt8() !== 0; //execute_success + response.data.runtimeStopCode = smartBuffer.readUInt8(); //runtime_stop_code + + const compileErrorCount = smartBuffer.readUInt32LE(); // num_compile_errors + response.data.compileErrors = []; + for (let i = 0; i < compileErrorCount; i++) { + response.data.compileErrors.push( + protocolUtils.readStringNT(smartBuffer) + ); + } + + const runtimeErrorCount = smartBuffer.readUInt32LE(); // num_runtime_errors + response.data.runtimeErrors = []; + for (let i = 0; i < runtimeErrorCount; i++) { + response.data.runtimeErrors.push( + protocolUtils.readStringNT(smartBuffer) + ); + } + + const otherErrorCount = smartBuffer.readUInt32LE(); // num_other_errors + response.data.otherErrors = []; + for (let i = 0; i < otherErrorCount; i++) { + response.data.otherErrors.push( + protocolUtils.readStringNT(smartBuffer) + ); + } + }); + return response; + } + + public toBuffer() { + const smartBuffer = new SmartBuffer(); + + smartBuffer.writeUInt8(this.data.executeSuccess ? 1 : 0); //execute_success + smartBuffer.writeUInt8(this.data.runtimeStopCode); //runtime_stop_code + + smartBuffer.writeUInt32LE(this.data.compileErrors?.length ?? 0); // num_compile_errors + for (let error of this.data.compileErrors ?? []) { + smartBuffer.writeStringNT(error); + } + + smartBuffer.writeUInt32LE(this.data.runtimeErrors?.length ?? 0); // num_runtime_errors + for (let error of this.data.runtimeErrors ?? []) { + smartBuffer.writeStringNT(error); + } + + smartBuffer.writeUInt32LE(this.data.otherErrors?.length ?? 0); // num_other_errors + for (let error of this.data.otherErrors ?? []) { + smartBuffer.writeStringNT(error); + } + + protocolUtils.insertCommonResponseFields(this, smartBuffer); + + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = 0; + + public data = { + /** + * Indicates whether the code ran and completed without errors (true) + */ + executeSuccess: undefined as boolean, + /** + * A StopReason enum. + */ + runtimeStopCode: undefined as StopReasonCode, + /** + * The list of compile-time errors. + */ + compileErrors: undefined as string[], + /** + * The list of runtime errors. + */ + runtimeErrors: undefined as string[], + /** + * The list of other errors. + */ + otherErrors: undefined as string[], + + //common props + packetLength: undefined as number, + requestId: undefined as number, + errorCode: ERROR_CODES.OK + }; +} diff --git a/src/debugProtocol/events/responses/GenericResponseV3.spec.ts b/src/debugProtocol/events/responses/GenericV3Response.spec.ts similarity index 81% rename from src/debugProtocol/events/responses/GenericResponseV3.spec.ts rename to src/debugProtocol/events/responses/GenericV3Response.spec.ts index 823c0843..49f481f1 100644 --- a/src/debugProtocol/events/responses/GenericResponseV3.spec.ts +++ b/src/debugProtocol/events/responses/GenericV3Response.spec.ts @@ -1,11 +1,11 @@ -import { GenericResponseV3 } from './GenericResponseV3'; +import { GenericV3Response } from './GenericV3Response'; import { expect } from 'chai'; import { ERROR_CODES } from '../../Constants'; import { SmartBuffer } from 'smart-buffer'; -describe('GenericResponseV3', () => { +describe('GenericV3Response', () => { it('serializes and deserializes properly', () => { - const response = GenericResponseV3.fromJson({ + const response = GenericV3Response.fromJson({ errorCode: ERROR_CODES.OK, requestId: 3 }); @@ -17,7 +17,7 @@ describe('GenericResponseV3', () => { }); expect( - GenericResponseV3.fromBuffer(response.toBuffer()).data + GenericV3Response.fromBuffer(response.toBuffer()).data ).to.eql({ packetLength: 12, // 4 bytes errorCode: ERROR_CODES.OK, // 4 bytes @@ -26,7 +26,7 @@ describe('GenericResponseV3', () => { }); it('consumes excess buffer data', () => { - const response = GenericResponseV3.fromJson({ + const response = GenericV3Response.fromJson({ errorCode: ERROR_CODES.OK, requestId: 3 }); @@ -46,7 +46,7 @@ describe('GenericResponseV3', () => { } buffer.insertUInt32LE(buffer.length + 4, 0); //packet_length - const newResponse = GenericResponseV3.fromBuffer(buffer.toBuffer()); + const newResponse = GenericV3Response.fromBuffer(buffer.toBuffer()); expect(newResponse.readOffset).to.eql(32); expect( diff --git a/src/debugProtocol/events/responses/GenericResponseV3.ts b/src/debugProtocol/events/responses/GenericV3Response.ts similarity index 91% rename from src/debugProtocol/events/responses/GenericResponseV3.ts rename to src/debugProtocol/events/responses/GenericV3Response.ts index 514de9c4..d0b4e91c 100644 --- a/src/debugProtocol/events/responses/GenericResponseV3.ts +++ b/src/debugProtocol/events/responses/GenericV3Response.ts @@ -2,18 +2,18 @@ import { SmartBuffer } from 'smart-buffer'; import type { ERROR_CODES } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; -export class GenericResponseV3 { +export class GenericV3Response { public static fromJson(data: { requestId: number; errorCode: ERROR_CODES; }) { - const response = new GenericResponseV3(); + const response = new GenericV3Response(); protocolUtils.loadJson(response, data); return response; } public static fromBuffer(buffer: Buffer) { - const response = new GenericResponseV3(); + const response = new GenericV3Response(); protocolUtils.bufferLoaderHelper(response, buffer, 12, (smartBuffer: SmartBuffer) => { response.data.packetLength = smartBuffer.readUInt32LE(); // packet_length response.data.requestId = smartBuffer.readUInt32LE(); // request_id diff --git a/src/debugProtocol/events/responses/HandshakeResponseV3.spec.ts b/src/debugProtocol/events/responses/HandshakeV3Response.spec.ts similarity index 80% rename from src/debugProtocol/events/responses/HandshakeResponseV3.spec.ts rename to src/debugProtocol/events/responses/HandshakeV3Response.spec.ts index 32c19cf9..4ca8ef42 100644 --- a/src/debugProtocol/events/responses/HandshakeResponseV3.spec.ts +++ b/src/debugProtocol/events/responses/HandshakeV3Response.spec.ts @@ -1,12 +1,12 @@ -import { HandshakeResponseV3 } from './HandshakeResponseV3'; +import { HandshakeV3Response } from './HandshakeV3Response'; import { DebugProtocolClient } from '../../client/DebugProtocolClient'; import { expect } from 'chai'; import { SmartBuffer } from 'smart-buffer'; -describe('HandshakeResponseV3', () => { +describe('HandshakeV3Response', () => { const date = new Date(2022, 0, 0); it('Handles a handshake response', () => { - const response = HandshakeResponseV3.fromJson({ + const response = HandshakeV3Response.fromJson({ magic: 'bsdebug', protocolVersion: '3.0.0', revisionTimestamp: date @@ -19,7 +19,7 @@ describe('HandshakeResponseV3', () => { }); expect( - HandshakeResponseV3.fromBuffer(response.toBuffer()).data + HandshakeV3Response.fromBuffer(response.toBuffer()).data ).to.eql({ magic: 'bsdebug', // 8 bytes protocolVersion: '3.0.0', // 12 bytes (each number is sent as uint32) @@ -31,7 +31,7 @@ describe('HandshakeResponseV3', () => { }); it('Handles a extra packet length in handshake response', () => { - const response = HandshakeResponseV3.fromJson({ + const response = HandshakeV3Response.fromJson({ magic: 'bsdebug', protocolVersion: '3.0.0', revisionTimestamp: date @@ -41,7 +41,7 @@ describe('HandshakeResponseV3', () => { const smartBuffer = SmartBuffer.fromBuffer(response.toBuffer()); smartBuffer.writeStringNT('this is extra data'); - const newResponse = HandshakeResponseV3.fromBuffer( + const newResponse = HandshakeV3Response.fromBuffer( smartBuffer.toBuffer() ); expect(newResponse.success).to.be.true; @@ -58,9 +58,9 @@ describe('HandshakeResponseV3', () => { }); it('Fails when buffer is incomplete', () => { - let handshake = HandshakeResponseV3.fromBuffer( + let handshake = HandshakeV3Response.fromBuffer( //create a response - HandshakeResponseV3.fromJson({ + HandshakeV3Response.fromJson({ magic: DebugProtocolClient.DEBUGGER_MAGIC, protocolVersion: '1.0.0', revisionTimestamp: date @@ -71,13 +71,13 @@ describe('HandshakeResponseV3', () => { }); it('Fails when the protocol version is less then 3.0.0', () => { - const response = HandshakeResponseV3.fromJson({ + const response = HandshakeV3Response.fromJson({ magic: 'not bsdebug', protocolVersion: '3.0.0', revisionTimestamp: date }); - let handshakeV3 = HandshakeResponseV3.fromBuffer(response.toBuffer()); + let handshakeV3 = HandshakeV3Response.fromBuffer(response.toBuffer()); expect(handshakeV3.success).to.equal(false); }); }); diff --git a/src/debugProtocol/events/responses/HandshakeResponseV3.ts b/src/debugProtocol/events/responses/HandshakeV3Response.ts similarity index 94% rename from src/debugProtocol/events/responses/HandshakeResponseV3.ts rename to src/debugProtocol/events/responses/HandshakeV3Response.ts index 84b3430e..ed4b6df2 100644 --- a/src/debugProtocol/events/responses/HandshakeResponseV3.ts +++ b/src/debugProtocol/events/responses/HandshakeV3Response.ts @@ -1,18 +1,17 @@ import { SmartBuffer } from 'smart-buffer'; import * as semver from 'semver'; -import { util } from '../../../util'; -import type { ProtocolEvent, ProtocolResponse } from '../ProtocolEvent'; +import type { ProtocolResponse } from '../ProtocolEvent'; import { protocolUtils } from '../../ProtocolUtil'; import { ERROR_CODES } from '../../Constants'; -export class HandshakeResponseV3 implements ProtocolResponse { +export class HandshakeV3Response implements ProtocolResponse { public static fromJson(data: { magic: string; protocolVersion: string; revisionTimestamp: Date; }) { - const response = new HandshakeResponseV3(); + const response = new HandshakeV3Response(); protocolUtils.loadJson(response, data); // We only support v3 or above with this handshake if (!semver.satisfies(response.data.protocolVersion, '<3.0.0')) { @@ -22,7 +21,7 @@ export class HandshakeResponseV3 implements ProtocolResponse { } public static fromBuffer(buffer: Buffer) { - const response = new HandshakeResponseV3(); + const response = new HandshakeV3Response(); protocolUtils.bufferLoaderHelper(response, buffer, 20, (smartBuffer: SmartBuffer) => { response.data.magic = protocolUtils.readStringNT(smartBuffer); // magic_number diff --git a/src/debugProtocol/events/responses/ListBreakpointsResponse.spec.ts b/src/debugProtocol/events/responses/ListBreakpointsResponse.spec.ts index 2a072d94..71199755 100644 --- a/src/debugProtocol/events/responses/ListBreakpointsResponse.spec.ts +++ b/src/debugProtocol/events/responses/ListBreakpointsResponse.spec.ts @@ -4,11 +4,6 @@ import { ERROR_CODES } from '../../Constants'; import { getRandomBuffer } from '../zzresponsesOld/responseCreationHelpers.spec'; describe('ListBreakpointsResponse', () => { - let response: ListBreakpointsResponse; - beforeEach(() => { - response = undefined; - }); - it('serializes and deserializes multiple breakpoints properly', () => { let response = ListBreakpointsResponse.fromJson({ requestId: 3, @@ -88,13 +83,13 @@ describe('ListBreakpointsResponse', () => { }); it('handles empty buffer', () => { - response = ListBreakpointsResponse.fromBuffer(null); + const response = ListBreakpointsResponse.fromBuffer(null); //Great, it didn't explode! expect(response.success).to.be.false; }); it('handles undersized buffers', () => { - response = ListBreakpointsResponse.fromBuffer( + let response = ListBreakpointsResponse.fromBuffer( getRandomBuffer(0) ); expect(response.success).to.be.false; diff --git a/src/debugProtocol/events/responses/ListBreakpointsResponse.ts b/src/debugProtocol/events/responses/ListBreakpointsResponse.ts index 0ce270b9..214d2d2a 100644 --- a/src/debugProtocol/events/responses/ListBreakpointsResponse.ts +++ b/src/debugProtocol/events/responses/ListBreakpointsResponse.ts @@ -1,12 +1,11 @@ import { SmartBuffer } from 'smart-buffer'; -import type { ERROR_CODES } from '../../Constants'; +import { ERROR_CODES } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; export class ListBreakpointsResponse { public static fromJson(data: { requestId: number; - errorCode: ERROR_CODES; breakpoints: BreakpointInfo[]; }) { const response = new ListBreakpointsResponse(); @@ -68,7 +67,7 @@ export class ListBreakpointsResponse { // response fields packetLength: undefined as number, requestId: undefined as number, - errorCode: undefined as ERROR_CODES + errorCode: ERROR_CODES.OK }; } diff --git a/src/debugProtocol/events/responses/StackTraceResponse.spec.ts b/src/debugProtocol/events/responses/StackTraceResponse.spec.ts new file mode 100644 index 00000000..405ee659 --- /dev/null +++ b/src/debugProtocol/events/responses/StackTraceResponse.spec.ts @@ -0,0 +1,108 @@ +import { expect } from 'chai'; +import { StackTraceResponse } from './StackTraceResponse'; +import { ERROR_CODES } from '../../Constants'; +import { getRandomBuffer } from '../zzresponsesOld/responseCreationHelpers.spec'; + +describe('StackTraceResponse', () => { + it('serializes and deserializes multiple breakpoints properly', () => { + let response = StackTraceResponse.fromJson({ + requestId: 3, + entries: [{ + lineNumber: 2, + functionName: 'main', + filePath: 'pkg:/source/main.brs' + }, { + lineNumber: 1, + functionName: 'libFunc', + filePath: 'pkg:/source/lib.brs' + }] + }); + + expect(response.data).to.eql({ + packetLength: undefined, + requestId: 3, + errorCode: ERROR_CODES.OK, + entries: [{ + lineNumber: 2, + functionName: 'main', + filePath: 'pkg:/source/main.brs' + }, { + lineNumber: 1, + functionName: 'libFunc', + filePath: 'pkg:/source/lib.brs' + }] + }); + + response = StackTraceResponse.fromBuffer(response.toBuffer()); + + expect( + response.data + ).to.eql({ + packetLength: undefined, // 0 bytes + requestId: 3, // 4 bytes, + errorCode: ERROR_CODES.OK, // 4 bytes + // num_entries // 4 bytes + entries: [{ + lineNumber: 2, // 4 bytes + functionName: 'main', // 5 bytes + filePath: 'pkg:/source/main.brs' // 21 bytes + }, { + lineNumber: 1, // 4 bytes + functionName: 'libFunc', // 8 bytes + filePath: 'pkg:/source/lib.brs' // 20 bytes + }] + }); + + expect(response.readOffset).to.eql(74); + }); + + it('handles empty entries array', () => { + let response = StackTraceResponse.fromJson({ + requestId: 3, + entries: [] + }); + + expect(response.data).to.eql({ + packetLength: undefined, + requestId: 3, + errorCode: ERROR_CODES.OK, + entries: [] + }); + + response = StackTraceResponse.fromBuffer(response.toBuffer()); + + expect( + response.data + ).to.eql({ + packetLength: undefined, // 0 bytes + requestId: 3, // 4 bytes, + errorCode: ERROR_CODES.OK, // 4 bytes + // num_entries // 4 bytes + entries: [] + }); + expect(response.readOffset).to.eql(12); + }); + + it('handles empty buffer', () => { + const response = StackTraceResponse.fromBuffer(null); + //Great, it didn't explode! + expect(response.success).to.be.false; + }); + + it('handles undersized buffers', () => { + let response = StackTraceResponse.fromBuffer( + getRandomBuffer(0) + ); + expect(response.success).to.be.false; + + response = StackTraceResponse.fromBuffer( + getRandomBuffer(1) + ); + expect(response.success).to.be.false; + + response = StackTraceResponse.fromBuffer( + getRandomBuffer(11) + ); + expect(response.success).to.be.false; + }); +}); diff --git a/src/debugProtocol/events/responses/StackTraceResponse.ts b/src/debugProtocol/events/responses/StackTraceResponse.ts new file mode 100644 index 00000000..93389b2a --- /dev/null +++ b/src/debugProtocol/events/responses/StackTraceResponse.ts @@ -0,0 +1,81 @@ +import { SmartBuffer } from 'smart-buffer'; +import { ERROR_CODES } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; +import type { StackEntry } from './StackTraceV3Response'; + +export class StackTraceResponse { + + public static fromJson(data: { + requestId: number; + entries: StackEntry[]; + }) { + const response = new StackTraceResponse(); + protocolUtils.loadJson(response, data); + response.data.entries ??= []; + return response; + } + + public static fromBuffer(buffer: Buffer) { + const response = new StackTraceResponse(); + protocolUtils.bufferLoaderHelper(response, buffer, 12, (smartBuffer: SmartBuffer) => { + response.data.requestId = smartBuffer.readUInt32LE(); // request_id + response.data.errorCode = smartBuffer.readUInt32LE(); // error_code + + const stackSize = smartBuffer.readUInt32LE(); // stack_size + + response.data.entries = []; + + // build the list of BreakpointInfo + for (let i = 0; i < stackSize; i++) { + const entry = {} as StackEntry; + entry.lineNumber = smartBuffer.readUInt32LE(); + // NOTE: this is documented as being function name then file name but it is being returned by the device backwards. + entry.filePath = protocolUtils.readStringNT(smartBuffer); + entry.functionName = protocolUtils.readStringNT(smartBuffer); + + // TODO do we need this anymore? + // let fileExtension = path.extname(this.fileName).toLowerCase(); + // // NOTE:Make sure we have a full valid path (?? can be valid because the device might not know the file). + // entry.success = (fileExtension === '.brs' || fileExtension === '.xml' || this.fileName === '??'); + response.data.entries.push(entry); + } + }); + return response; + } + + public toBuffer() { + const smartBuffer = new SmartBuffer(); + smartBuffer.writeUInt32LE(this.data.requestId); // request_id + smartBuffer.writeUInt32LE(this.data.errorCode); // error_code + + smartBuffer.writeUInt32LE(this.data.entries?.length ?? 0); // stack_size + for (const entry of this.data.entries ?? []) { + smartBuffer.writeUInt32LE(entry.lineNumber); // line_number + // NOTE: this is documented as being function name then file name but it is being returned by the device backwards. + smartBuffer.writeStringNT(entry.filePath); // file_path + smartBuffer.writeStringNT(entry.functionName); // function_name + } + + this.data.packetLength = smartBuffer.writeOffset; + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = 0; + + public data = { + /** + * An array of StrackEntry structs. entries[0] contains the last function called; + * entries[stack_size-1] contains the first function called. + * Debugging clients may reverse the entries to match developer expectations. + */ + entries: undefined as StackEntry[], + + // response fields + packetLength: undefined as number, + requestId: undefined as number, + errorCode: ERROR_CODES.OK + }; + +} diff --git a/src/debugProtocol/events/responses/StackTraceV3Response.spec.ts b/src/debugProtocol/events/responses/StackTraceV3Response.spec.ts new file mode 100644 index 00000000..99f247ef --- /dev/null +++ b/src/debugProtocol/events/responses/StackTraceV3Response.spec.ts @@ -0,0 +1,159 @@ +import { expect } from 'chai'; +import { StackTraceV3Response } from './StackTraceV3Response'; +import { ERROR_CODES } from '../../Constants'; +import { getRandomBuffer } from '../zzresponsesOld/responseCreationHelpers.spec'; + +describe('StackTraceV3Response', () => { + it('serializes and deserializes multiple breakpoints properly', () => { + let response = StackTraceV3Response.fromJson({ + requestId: 3, + entries: [{ + lineNumber: 2, + functionName: 'main', + filePath: 'pkg:/source/main.brs' + }, { + lineNumber: 1, + functionName: 'libFunc', + filePath: 'pkg:/source/lib.brs' + }] + }); + + expect(response.data).to.eql({ + packetLength: undefined, + requestId: 3, + errorCode: ERROR_CODES.OK, + entries: [{ + lineNumber: 2, + functionName: 'main', + filePath: 'pkg:/source/main.brs' + }, { + lineNumber: 1, + functionName: 'libFunc', + filePath: 'pkg:/source/lib.brs' + }] + }); + + response = StackTraceV3Response.fromBuffer(response.toBuffer()); + + expect( + response.data + ).to.eql({ + packetLength: 78, // 4 bytes + requestId: 3, // 4 bytes, + errorCode: ERROR_CODES.OK, // 4 bytes + // num_entries // 4 bytes + entries: [{ + lineNumber: 2, // 4 bytes + functionName: 'main', // 5 bytes + filePath: 'pkg:/source/main.brs' // 21 bytes + }, { + lineNumber: 1, // 4 bytes + functionName: 'libFunc', // 8 bytes + filePath: 'pkg:/source/lib.brs' // 20 bytes + }] + }); + }); + + it('handles empty entries array', () => { + let response = StackTraceV3Response.fromJson({ + requestId: 3, + entries: [] + }); + + expect(response.data).to.eql({ + packetLength: undefined, + requestId: 3, + errorCode: ERROR_CODES.OK, + entries: [] + }); + + response = StackTraceV3Response.fromBuffer(response.toBuffer()); + + expect( + response.data + ).to.eql({ + packetLength: 16, // 4 bytes + requestId: 3, // 4 bytes, + errorCode: ERROR_CODES.OK, // 4 bytes + // num_entries // 4 bytes + entries: [] + }); + }); + + it('handles empty buffer', () => { + const response = StackTraceV3Response.fromBuffer(null); + //Great, it didn't explode! + expect(response.success).to.be.false; + }); + + it('handles undersized buffers', () => { + let response = StackTraceV3Response.fromBuffer( + getRandomBuffer(0) + ); + expect(response.success).to.be.false; + + response = StackTraceV3Response.fromBuffer( + getRandomBuffer(1) + ); + expect(response.success).to.be.false; + + response = StackTraceV3Response.fromBuffer( + getRandomBuffer(11) + ); + expect(response.success).to.be.false; + }); + + it('gracefully handles mismatched breakpoint count', () => { + let buffer = StackTraceV3Response.fromJson({ + requestId: 3, + entries: [{ + lineNumber: 2, + functionName: 'main', + filePath: 'pkg:/source/main.brs' + }] + }).toBuffer(); + + //set num_breakpoints to 2 instead of 1 + buffer = Buffer.concat([ + buffer.slice(0, 12), + Buffer.from([2, 0, 0, 0]), + buffer.slice(16) + ]); + + const response = StackTraceV3Response.fromBuffer(buffer); + expect(response.success).to.be.false; + expect(response.data.entries).to.eql([{ + lineNumber: 2, + functionName: 'main', + filePath: 'pkg:/source/main.brs' + }]); + }); + + it('handles malformed breakpoint data', () => { + let buffer = StackTraceV3Response.fromJson({ + requestId: 3, + entries: [{ + lineNumber: 2, + functionName: 'main', + filePath: 'pkg:/source/main.brs' + }, { + lineNumber: 1, + functionName: 'libFunc', + filePath: 'pkg:/source/lib.brs' + }] + }).toBuffer(); + + // remove some trailing data + buffer = Buffer.concat([ + buffer.slice(0, buffer.length - 3) + ]); + + const response = StackTraceV3Response.fromBuffer(buffer); + expect(response.success).to.be.false; + expect(response.data.entries).to.eql([{ + lineNumber: 2, + functionName: 'main', + filePath: 'pkg:/source/main.brs' + }]); + }); +}); diff --git a/src/debugProtocol/events/responses/StackTraceV3Response.ts b/src/debugProtocol/events/responses/StackTraceV3Response.ts new file mode 100644 index 00000000..77d2abda --- /dev/null +++ b/src/debugProtocol/events/responses/StackTraceV3Response.ts @@ -0,0 +1,88 @@ +import { SmartBuffer } from 'smart-buffer'; +import { ERROR_CODES } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; + +export class StackTraceV3Response { + + public static fromJson(data: { + requestId: number; + entries: StackEntry[]; + }) { + const response = new StackTraceV3Response(); + protocolUtils.loadJson(response, data); + response.data.entries ??= []; + return response; + } + + public static fromBuffer(buffer: Buffer) { + const response = new StackTraceV3Response(); + protocolUtils.bufferLoaderHelper(response, buffer, 16, (smartBuffer: SmartBuffer) => { + protocolUtils.loadCommonResponseFields(response, smartBuffer); + + const stackSize = smartBuffer.readUInt32LE(); // stack_size + + response.data.entries = []; + + // build the list of BreakpointInfo + for (let i = 0; i < stackSize; i++) { + const entry = {} as StackEntry; + entry.lineNumber = smartBuffer.readUInt32LE(); + entry.functionName = protocolUtils.readStringNT(smartBuffer); + entry.filePath = protocolUtils.readStringNT(smartBuffer); + + // TODO do we need this anymore? + // let fileExtension = path.extname(this.fileName).toLowerCase(); + // // NOTE:Make sure we have a full valid path (?? can be valid because the device might not know the file). + // entry.success = (fileExtension === '.brs' || fileExtension === '.xml' || this.fileName === '??'); + response.data.entries.push(entry); + } + }); + return response; + } + + public toBuffer() { + const smartBuffer = new SmartBuffer(); + smartBuffer.writeUInt32LE(this.data.entries?.length ?? 0); // stack_size + for (const entry of this.data.entries ?? []) { + smartBuffer.writeUInt32LE(entry.lineNumber); // line_number + smartBuffer.writeStringNT(entry.functionName); // function_name + smartBuffer.writeStringNT(entry.filePath); // file_path + } + protocolUtils.insertCommonResponseFields(this, smartBuffer); + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = 0; + + public data = { + /** + * An array of StrackEntry structs. entries[0] contains the last function called; + * entries[stack_size-1] contains the first function called. + * Debugging clients may reverse the entries to match developer expectations. + */ + entries: undefined as StackEntry[], + + // response fields + packetLength: undefined as number, + requestId: undefined as number, + errorCode: ERROR_CODES.OK + }; + +} + +export interface StackEntry { + /** + * The line number where the stop or failure occurred. + */ + lineNumber: number; + /** + * The function where the stop or failure occurred. + */ + functionName: string; + /** + * The file where the stop or failure occurred. + */ + filePath: string; +} diff --git a/src/debugProtocol/events/responses/ThreadsResponse.spec.ts b/src/debugProtocol/events/responses/ThreadsResponse.spec.ts new file mode 100644 index 00000000..c8c0f52b --- /dev/null +++ b/src/debugProtocol/events/responses/ThreadsResponse.spec.ts @@ -0,0 +1,180 @@ +import { expect } from 'chai'; +import { ThreadsResponse } from './ThreadsResponse'; +import { ERROR_CODES } from '../../Constants'; +import { getRandomBuffer } from '../zzresponsesOld/responseCreationHelpers.spec'; + +describe('ThreadsResponse', () => { + it('serializes and deserializes multiple breakpoints properly', () => { + let response = ThreadsResponse.fromJson({ + requestId: 3, + threads: [{ + isPrimary: true, + stopReason: 'Break', + stopReasonDetail: 'because', + lineNumber: 2, + functionName: 'main', + filePath: 'pkg:/source/main.brs', + codeSnippet: 'sub main()' + }] + }); + + expect(response.data).to.eql({ + packetLength: undefined, + requestId: 3, + errorCode: ERROR_CODES.OK, + threads: [{ + isPrimary: true, + stopReason: 'Break', + stopReasonDetail: 'because', + lineNumber: 2, + functionName: 'main', + filePath: 'pkg:/source/main.brs', + codeSnippet: 'sub main()' + }] + }); + + response = ThreadsResponse.fromBuffer(response.toBuffer()); + + expect( + response.data + ).to.eql({ + packetLength: 70, // 4 bytes + requestId: 3, // 4 bytes, + errorCode: ERROR_CODES.OK, // 4 bytes + // threads_count // 4 bytes + threads: [{ + // flags // 4 bytes + isPrimary: true, // 0 bytes - part of flags + stopReason: 'Break', // 1 byte + stopReasonDetail: 'because', // 8 bytes + lineNumber: 2, // 4 bytes + functionName: 'main', // 5 bytes + filePath: 'pkg:/source/main.brs', // 21 bytes + codeSnippet: 'sub main()' // 11 bytes + }] + }); + }); + + it('handles empty entries array', () => { + let response = ThreadsResponse.fromJson({ + requestId: 3, + threads: [] + }); + + expect(response.data).to.eql({ + packetLength: undefined, + requestId: 3, + errorCode: ERROR_CODES.OK, + threads: [] + }); + + response = ThreadsResponse.fromBuffer(response.toBuffer()); + + expect( + response.data + ).to.eql({ + packetLength: 16, // 4 bytes + requestId: 3, // 4 bytes, + errorCode: ERROR_CODES.OK, // 4 bytes + // threads_count // 4 bytes + threads: [] + }); + }); + + it('handles empty buffer', () => { + const response = ThreadsResponse.fromBuffer(null); + //Great, it didn't explode! + expect(response.success).to.be.false; + }); + + it('handles undersized buffers', () => { + let response = ThreadsResponse.fromBuffer( + getRandomBuffer(0) + ); + expect(response.success).to.be.false; + + response = ThreadsResponse.fromBuffer( + getRandomBuffer(1) + ); + expect(response.success).to.be.false; + + response = ThreadsResponse.fromBuffer( + getRandomBuffer(11) + ); + expect(response.success).to.be.false; + }); + + it('gracefully handles mismatched breakpoint count', () => { + let buffer = ThreadsResponse.fromJson({ + requestId: 3, + threads: [{ + isPrimary: true, + stopReason: 'Break', + stopReasonDetail: 'because', + lineNumber: 2, + functionName: 'main', + filePath: 'pkg:/source/main.brs', + codeSnippet: 'sub main()' + }] + }).toBuffer(); + + //set num_breakpoints to 2 instead of 1 + buffer = Buffer.concat([ + buffer.slice(0, 12), + Buffer.from([2, 0, 0, 0]), + buffer.slice(16) + ]); + + const response = ThreadsResponse.fromBuffer(buffer); + expect(response.success).to.be.false; + expect(response.data.threads).to.eql([{ + isPrimary: true, + stopReason: 'Break', + stopReasonDetail: 'because', + lineNumber: 2, + functionName: 'main', + filePath: 'pkg:/source/main.brs', + codeSnippet: 'sub main()' + }]); + }); + + it('handles malformed breakpoint data', () => { + let buffer = ThreadsResponse.fromJson({ + requestId: 3, + threads: [{ + isPrimary: true, + stopReason: 'Break', + stopReasonDetail: 'because', + lineNumber: 2, + functionName: 'main', + filePath: 'pkg:/source/main.brs', + codeSnippet: 'sub main()' + }, { + isPrimary: true, + stopReason: 'Break', + stopReasonDetail: 'because', + lineNumber: 3, + functionName: 'main', + filePath: 'pkg:/source/main.brs', + codeSnippet: 'sub main()' + }] + }).toBuffer(); + + // remove some trailing data + buffer = Buffer.concat([ + buffer.slice(0, buffer.length - 3) + ]); + + const response = ThreadsResponse.fromBuffer(buffer); + expect(response.success).to.be.false; + expect(response.data.threads).to.eql([{ + isPrimary: true, + stopReason: 'Break', + stopReasonDetail: 'because', + lineNumber: 2, + functionName: 'main', + filePath: 'pkg:/source/main.brs', + codeSnippet: 'sub main()' + }]); + }); +}); diff --git a/src/debugProtocol/events/responses/ThreadsResponse.ts b/src/debugProtocol/events/responses/ThreadsResponse.ts new file mode 100644 index 00000000..80542807 --- /dev/null +++ b/src/debugProtocol/events/responses/ThreadsResponse.ts @@ -0,0 +1,119 @@ +/* eslint-disable no-bitwise */ +import { SmartBuffer } from 'smart-buffer'; +import type { StopReason } from '../../Constants'; +import { ERROR_CODES, StopReasonCode } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; + +export class ThreadsResponse { + public static fromJson(data: { + requestId: number; + threads: ThreadInfo[]; + }) { + const response = new ThreadsResponse(); + protocolUtils.loadJson(response, data); + response.data.threads ??= []; + return response; + } + + public static fromBuffer(buffer: Buffer) { + const response = new ThreadsResponse(); + protocolUtils.bufferLoaderHelper(response, buffer, 16, (smartBuffer: SmartBuffer) => { + protocolUtils.loadCommonResponseFields(response, smartBuffer); + + const threadsCount = smartBuffer.readUInt32LE(); // threads_count + + response.data.threads = []; + + // build the list of threads + for (let i = 0; i < threadsCount; i++) { + const thread = {} as ThreadInfo; + // NOTE: The docs say the flags should be both unit8 AND uint32. In testing it seems like they are sending uint32 but meant to send unit8. + const flags = smartBuffer.readUInt32LE(); + thread.isPrimary = (flags & ThreadInfoFlags.isPrimary) > 0; + + thread.stopReason = StopReasonCode[smartBuffer.readUInt8()] as StopReason; // stop_reason + thread.stopReasonDetail = protocolUtils.readStringNT(smartBuffer); // stop_reason_detail + thread.lineNumber = smartBuffer.readUInt32LE(); // line_number + thread.functionName = protocolUtils.readStringNT(smartBuffer); // function_name + thread.filePath = protocolUtils.readStringNT(smartBuffer); // file_path + thread.codeSnippet = protocolUtils.readStringNT(smartBuffer); // code_snippet + + response.data.threads.push(thread); + } + }); + return response; + } + + public toBuffer() { + const smartBuffer = new SmartBuffer(); + smartBuffer.writeUInt32LE(this.data.threads?.length ?? 0); // threads_count + for (const thread of this.data.threads ?? []) { + let flags = 0; + flags |= thread.isPrimary ? 1 : 0; + // NOTE: The docs say the flags should be both unit8 AND uint32. In testing it seems like they are sending uint32 but meant to send unit8. + smartBuffer.writeUInt32LE(flags); + + smartBuffer.writeUInt8(StopReasonCode[thread.stopReason]); // stop_reason + smartBuffer.writeStringNT(thread.stopReasonDetail); // stop_reason_detail + smartBuffer.writeUInt32LE(thread.lineNumber); // line_number + smartBuffer.writeStringNT(thread.functionName); // function_name + smartBuffer.writeStringNT(thread.filePath); // file_path + smartBuffer.writeStringNT(thread.codeSnippet); // code_snippet + } + protocolUtils.insertCommonResponseFields(this, smartBuffer); + return smartBuffer.toBuffer(); + } + + public success = false; + + public readOffset = 0; + + public data = { + /** + * An array of StrackEntry structs. entries[0] contains the last function called; + * entries[stack_size-1] contains the first function called. + * Debugging clients may reverse the entries to match developer expectations. + */ + threads: undefined as ThreadInfo[], + + // response fields + packetLength: undefined as number, + requestId: undefined as number, + errorCode: ERROR_CODES.OK + }; +} + +export interface ThreadInfo { + /** + * Indicates whether this thread likely caused the stop or failure + */ + isPrimary: boolean; + /** + * An enum describing why the thread was stopped. + */ + stopReason: StopReason; + /** + * Provides extra details about the stop (for example, "Divide by Zero", "STOP", "BREAK") + */ + stopReasonDetail: string; + /** + * The line number where the stop or failure occurred. + */ + lineNumber: number; + /** + * The function where the stop or failure occurred. + */ + functionName: string; + /** + * The file where the stop or failure occurred. + */ + filePath: string; + /** + * The code causing the stop or failure. + */ + codeSnippet: string; +} + +enum ThreadInfoFlags { + isPrimary = 0x01 +} diff --git a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts index c4a67bbb..bcdf5127 100644 --- a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts +++ b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts @@ -1,12 +1,12 @@ import { expect } from 'chai'; -import { ERROR_CODES, STOP_REASONS, UPDATE_TYPES } from '../../Constants'; +import { ERROR_CODES, StopReasonCode, UPDATE_TYPES } from '../../Constants'; import { AllThreadsStoppedUpdate } from './AllThreadsStoppedUpdate'; describe('AllThreadsStoppedUpdate', () => { it('serializes and deserializes properly', () => { const command = AllThreadsStoppedUpdate.fromJson({ primaryThreadIndex: 1, - stopReason: STOP_REASONS.BREAK, + stopReason: StopReasonCode.Break, stopReasonDetail: 'because' }); @@ -17,7 +17,7 @@ describe('AllThreadsStoppedUpdate', () => { updateType: UPDATE_TYPES.ALL_THREADS_STOPPED, primaryThreadIndex: 1, - stopReason: STOP_REASONS.BREAK, + stopReason: StopReasonCode.Break, stopReasonDetail: 'because' }); @@ -30,7 +30,7 @@ describe('AllThreadsStoppedUpdate', () => { updateType: UPDATE_TYPES.ALL_THREADS_STOPPED, // 4 bytes primaryThreadIndex: 1, // 4 bytes - stopReason: STOP_REASONS.BREAK, // 1 bytes + stopReason: StopReasonCode.Break, // 1 bytes stopReasonDetail: 'because' // 8 bytes }); }); diff --git a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts index 25525afa..c5d034a8 100644 --- a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts +++ b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts @@ -1,5 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import type { STOP_REASONS } from '../../Constants'; +import type { StopReasonCode } from '../../Constants'; import { ERROR_CODES, UPDATE_TYPES } from '../../Constants'; import { util } from '../../../util'; import { protocolUtils } from '../../ProtocolUtil'; @@ -51,7 +51,7 @@ export class AllThreadsStoppedUpdate implements ProtocolUpdate { public data = { primaryThreadIndex: undefined as number, - stopReason: undefined as STOP_REASONS, + stopReason: undefined as StopReasonCode, stopReasonDetail: undefined as string, //common props diff --git a/src/debugProtocol/events/updates/CompileErrorUpdate.spec.ts b/src/debugProtocol/events/updates/CompileErrorUpdate.spec.ts index ddf10102..8217e8b8 100644 --- a/src/debugProtocol/events/updates/CompileErrorUpdate.spec.ts +++ b/src/debugProtocol/events/updates/CompileErrorUpdate.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { ERROR_CODES, STOP_REASONS, UPDATE_TYPES } from '../../Constants'; +import { ERROR_CODES, StopReasonCode, UPDATE_TYPES } from '../../Constants'; import { CompileErrorUpdate } from './CompileErrorUpdate'; describe('CompileErrorUpdate', () => { diff --git a/src/debugProtocol/events/updates/IOPortOpenedUpdate.spec.ts b/src/debugProtocol/events/updates/IOPortOpenedUpdate.spec.ts index 560726cd..a04ed193 100644 --- a/src/debugProtocol/events/updates/IOPortOpenedUpdate.spec.ts +++ b/src/debugProtocol/events/updates/IOPortOpenedUpdate.spec.ts @@ -1,12 +1,11 @@ import { expect } from 'chai'; -import { ERROR_CODES, STOP_REASONS, UPDATE_TYPES } from '../../Constants'; +import { ERROR_CODES, StopReasonCode, UPDATE_TYPES } from '../../Constants'; import { IOPortOpenedUpdate } from './IOPortOpenedUpdate'; -describe.only('IOPortOpenedUpdate', () => { +describe('IOPortOpenedUpdate', () => { it('serializes and deserializes properly', () => { const command = IOPortOpenedUpdate.fromJson({ - port: 1234, - errorCode: ERROR_CODES.OK + port: 1234 }); expect(command.data).to.eql({ diff --git a/src/debugProtocol/events/updates/ThreadAttachedUpdate.spec.ts b/src/debugProtocol/events/updates/ThreadAttachedUpdate.spec.ts index d8c9f4b6..b007512a 100644 --- a/src/debugProtocol/events/updates/ThreadAttachedUpdate.spec.ts +++ b/src/debugProtocol/events/updates/ThreadAttachedUpdate.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { ERROR_CODES, STOP_REASONS, UPDATE_TYPES } from '../../Constants'; +import { ERROR_CODES, StopReasonCode, UPDATE_TYPES } from '../../Constants'; import { ThreadAttachedUpdate } from './ThreadAttachedUpdate'; describe('AllThreadsStoppedUpdate', () => { @@ -7,7 +7,7 @@ describe('AllThreadsStoppedUpdate', () => { const update = ThreadAttachedUpdate.fromJson({ threadIndex: 1, errorCode: ERROR_CODES.OK, - stopReason: STOP_REASONS.BREAK, + stopReason: StopReasonCode.Break, stopReasonDetail: 'because' }); @@ -18,7 +18,7 @@ describe('AllThreadsStoppedUpdate', () => { updateType: UPDATE_TYPES.THREAD_ATTACHED, threadIndex: 1, - stopReason: STOP_REASONS.BREAK, + stopReason: StopReasonCode.Break, stopReasonDetail: 'because' }); @@ -31,7 +31,7 @@ describe('AllThreadsStoppedUpdate', () => { updateType: UPDATE_TYPES.THREAD_ATTACHED, // 4 bytes threadIndex: 1, // 4 bytes - stopReason: STOP_REASONS.BREAK, // 1 bytes + stopReason: StopReasonCode.Break, // 1 bytes stopReasonDetail: 'because' // 8 bytes }); }); diff --git a/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts b/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts index cb81477e..0453ce52 100644 --- a/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts +++ b/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts @@ -1,5 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import type { STOP_REASONS } from '../../Constants'; +import type { StopReasonCode } from '../../Constants'; import { ERROR_CODES, UPDATE_TYPES } from '../../Constants'; import { util } from '../../../util'; import { protocolUtils } from '../../ProtocolUtil'; @@ -44,7 +44,7 @@ export class ThreadAttachedUpdate { public data = { threadIndex: undefined as number, - stopReason: undefined as STOP_REASONS, + stopReason: undefined as StopReasonCode, stopReasonDetail: undefined as string, //common props diff --git a/src/debugProtocol/events/zzresponsesOld/StackTraceResponse.ts b/src/debugProtocol/events/zzresponsesOld/StackTraceResponse.ts deleted file mode 100644 index aec09b04..00000000 --- a/src/debugProtocol/events/zzresponsesOld/StackTraceResponse.ts +++ /dev/null @@ -1,64 +0,0 @@ -import * as path from 'path'; -import { SmartBuffer } from 'smart-buffer'; -import { util } from '../../../util'; -import { protocolUtils } from '../../ProtocolUtil'; - -export class StackTraceResponse { - - constructor(buffer: Buffer) { - // The smallest a stacktrace request response can be - if (buffer.byteLength >= 8) { - try { - let bufferReader = SmartBuffer.fromBuffer(buffer); - this.requestId = bufferReader.readUInt32LE(); - - // Any request id less then one is an update and we should not process it here - if (this.requestId > 0) { - this.errorCode = bufferReader.readUInt32LE(); - this.stackSize = bufferReader.readUInt32LE(); - - for (let i = 0; i < this.stackSize; i++) { - let stackEntry = new StackEntry(bufferReader); - if (stackEntry.success) { - // All the necessary stack entry data was present. Push to the entries array. - this.entries.push(stackEntry); - } - } - - this.readOffset = bufferReader.readOffset; - this.success = (this.entries.length === this.stackSize); - } - } catch (error) { - // Could not parse - } - } - } - public success = false; - public readOffset = 0; - - // response fields - public requestId = -1; - public errorCode = -1; - public stackSize = -1; - public entries = []; -} - -export class StackEntry { - - constructor(bufferReader: SmartBuffer) { - this.lineNumber = bufferReader.readUInt32LE(); - // NOTE: this is documented as being function name then file name but it is being returned by the device backwards. - this.fileName = protocolUtils.readStringNT(bufferReader); - this.functionName = protocolUtils.readStringNT(bufferReader); - - let fileExtension = path.extname(this.fileName).toLowerCase(); - // NOTE:Make sure we have a full valid path (?? can be valid because the device might not know the file). - this.success = (fileExtension === '.brs' || fileExtension === '.xml' || this.fileName === '??'); - } - public success = false; - - // response fields - public lineNumber = -1; - public functionName: string; - public fileName: string; -} diff --git a/src/debugProtocol/events/zzresponsesOld/StackTraceResponseV3.ts b/src/debugProtocol/events/zzresponsesOld/StackTraceResponseV3.ts deleted file mode 100644 index 8aae2956..00000000 --- a/src/debugProtocol/events/zzresponsesOld/StackTraceResponseV3.ts +++ /dev/null @@ -1,64 +0,0 @@ -import * as path from 'path'; -import { SmartBuffer } from 'smart-buffer'; -import { util } from '../../../util'; -import { protocolUtils } from '../../ProtocolUtil'; - -export class StackTraceResponseV3 { - - constructor(buffer: Buffer) { - // The smallest a stacktrace request response can be - if (buffer.byteLength >= 8) { - try { - let bufferReader = SmartBuffer.fromBuffer(buffer); - this.requestId = bufferReader.readUInt32LE(); - - // Any request id less then one is an update and we should not process it here - if (this.requestId > 0) { - this.errorCode = bufferReader.readUInt32LE(); - this.stackSize = bufferReader.readUInt32LE(); - - for (let i = 0; i < this.stackSize; i++) { - let stackEntry = new StackEntryV3(bufferReader); - if (stackEntry.success) { - // All the necessary stack entry data was present. Push to the entries array. - this.entries.push(stackEntry); - } - } - - this.readOffset = bufferReader.readOffset; - this.success = (this.entries.length === this.stackSize); - } - } catch (error) { - // Could not parse - } - } - } - public success = false; - public readOffset = 0; - - // response fields - public requestId = -1; - public errorCode = -1; - public stackSize = -1; - public entries = []; -} - -export class StackEntryV3 { - - constructor(bufferReader: SmartBuffer) { - this.lineNumber = bufferReader.readUInt32LE(); - // NOTE: this is documented as being function name then file name but it is being returned by the device backwards. - this.functionName = protocolUtils.readStringNT(bufferReader); - this.fileName = protocolUtils.readStringNT(bufferReader); - - let fileExtension = path.extname(this.fileName).toLowerCase(); - // NOTE:Make sure we have a full valid path (?? can be valid because the device might not know the file). - this.success = (fileExtension === '.brs' || fileExtension === '.xml' || this.fileName === '??'); - } - public success = false; - - // response fields - public lineNumber = -1; - public functionName: string; - public fileName: string; -} diff --git a/src/debugProtocol/events/zzresponsesOld/ThreadsResponse.ts b/src/debugProtocol/events/zzresponsesOld/ThreadsResponse.ts deleted file mode 100644 index 571c2c94..00000000 --- a/src/debugProtocol/events/zzresponsesOld/ThreadsResponse.ts +++ /dev/null @@ -1,74 +0,0 @@ -import * as path from 'path'; -import { SmartBuffer } from 'smart-buffer'; -import { STOP_REASONS } from '../../Constants'; -import { util } from '../../../util'; -import { protocolUtils } from '../../ProtocolUtil'; - -export class ThreadsResponse { - - constructor(buffer: Buffer) { - // The smallest a threads request response can be - if (buffer.byteLength >= 21) { - try { - let bufferReader = SmartBuffer.fromBuffer(buffer); - this.requestId = bufferReader.readUInt32LE(); - - // Any request id less then one is an update and we should not process it here - if (this.requestId > 0) { - this.errorCode = bufferReader.readUInt32LE(); - this.threadsCount = bufferReader.readUInt32LE(); - - for (let i = 0; i < this.threadsCount; i++) { - let stackEntry = new ThreadInfo(bufferReader); - if (stackEntry.success) { - // All the necessary stack entry data was present. Push to the entries array. - this.threads.push(stackEntry); - } - } - - this.readOffset = bufferReader.readOffset; - this.success = (this.threads.length === this.threadsCount); - } - } catch (error) { - // Could not parse - } - } - } - public success = false; - public readOffset = 0; - - // response fields - public requestId = -1; - public errorCode = -1; - public threadsCount = -1; - public threads = []; -} - -export class ThreadInfo { - - constructor(bufferReader: SmartBuffer) { - // NOTE: The docs say the flags should be unit8 and uint32. In testing it seems like they are sending uint32 but meant to send unit8. - // eslint-disable-next-line no-bitwise - this.isPrimary = (bufferReader.readUInt32LE() & 0x01) > 0; - this.stopReason = STOP_REASONS[bufferReader.readUInt8()]; - this.stopReasonDetail = protocolUtils.readStringNT(bufferReader); - this.lineNumber = bufferReader.readUInt32LE(); - this.functionName = protocolUtils.readStringNT(bufferReader); - this.fileName = protocolUtils.readStringNT(bufferReader); - this.codeSnippet = protocolUtils.readStringNT(bufferReader); - - let fileExtension = path.extname(this.fileName).toLowerCase(); - // NOTE: Make sure we have a full valid path (?? can be valid because the device might not know the file) and that we have a codeSnippet. - this.success = (fileExtension === '.brs' || fileExtension === '.xml' || this.fileName === '??') && this.codeSnippet.length > 1; - } - public success = false; - - // response fields - public isPrimary: boolean; - public stopReason: string; - public stopReasonDetail: string; - public lineNumber = -1; - public functionName: string; - public fileName: string; - public codeSnippet: string; -} diff --git a/src/debugProtocol/events/zzresponsesOld/VariableResponse.spec.ts b/src/debugProtocol/events/zzresponsesOld/VariableResponse.spec.ts deleted file mode 100644 index 23dddceb..00000000 --- a/src/debugProtocol/events/zzresponsesOld/VariableResponse.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* eslint-disable no-bitwise */ -import { VariableResponse } from './VariableResponse'; -import { createVariableResponse } from './responseCreationHelpers.spec'; -import { expect } from 'chai'; -import { ERROR_CODES, VARIABLE_FLAGS, VARIABLE_TYPES } from '../../Constants'; - -describe('VariableResponse', () => { - - it('Properly parses invalid variable', () => { - let buffer = createVariableResponse({ - requestId: 2, - errorCode: ERROR_CODES.OK, - variables: [{ - name: 'person', - refCount: 2, - isConst: false, - variableType: VARIABLE_TYPES.AA, - keyType: VARIABLE_TYPES.String, - value: undefined, - children: [{ - name: 'firstName', - refCount: 1, - value: 'Bob', - variableType: VARIABLE_TYPES.String, - isConst: false - }, { - name: 'lastName', - refCount: 1, - value: undefined, - variableType: VARIABLE_TYPES.Invalid, - isConst: false - }] - }], - includePacketLength: false - }); - - let response = new VariableResponse(buffer.toBuffer()); - expect( - response.variables?.map(x => ({ - name: x.name, - value: x.value, - isContainer: x.isContainer - })) - ).to.eql([{ - name: 'person', - value: null, - isContainer: true - }, { - name: 'firstName', - value: 'Bob', - isContainer: false - }, { - name: 'lastName', - value: 'Invalid', - isContainer: false - }]); - }); -}); diff --git a/src/debugProtocol/events/zzresponsesOld/VariableResponse.ts b/src/debugProtocol/events/zzresponsesOld/VariableResponse.ts deleted file mode 100644 index 4a98d389..00000000 --- a/src/debugProtocol/events/zzresponsesOld/VariableResponse.ts +++ /dev/null @@ -1,164 +0,0 @@ -/* eslint-disable no-bitwise */ -import { SmartBuffer } from 'smart-buffer'; -import { VARIABLE_FLAGS, VARIABLE_TYPES } from '../../Constants'; -import { util } from '../../../util'; -import { protocolUtils } from '../../ProtocolUtil'; - -export class VariableResponse { - - constructor(buffer: Buffer) { - // Minimum variable request response size - if (buffer.byteLength >= 13) { - try { - let bufferReader = SmartBuffer.fromBuffer(buffer); - this.requestId = bufferReader.readUInt32LE(); - - // Any request id less then one is an update and we should not process it here - if (this.requestId > 0) { - this.errorCode = bufferReader.readUInt32LE(); - this.numVariables = bufferReader.readUInt32LE(); - - // iterate over each variable in the buffer data and create a Variable Info object - for (let i = 0; i < this.numVariables; i++) { - let variableInfo = new VariableInfo(bufferReader); - if (variableInfo.success) { - // All the necessary variable data was present. Push to the variables array. - this.variables.push(variableInfo); - } - } - - this.readOffset = bufferReader.readOffset; - this.success = (this.variables.length === this.numVariables); - } - } catch (error) { - // Could not process - } - } - } - public success = false; - public readOffset = 0; - - // response fields - public requestId = -1; - public errorCode = -1; - public numVariables = -1; - public variables = [] as VariableInfo[]; -} - -export class VariableInfo { - - constructor(bufferReader: SmartBuffer) { - if (bufferReader.length >= 13) { - let bitwiseMask = bufferReader.readUInt8(); - - // Determine the different variable properties - this.isChildKey = (bitwiseMask & VARIABLE_FLAGS.isChildKey) > 0; - this.isConst = (bitwiseMask & VARIABLE_FLAGS.isConst) > 0; - this.isContainer = (bitwiseMask & VARIABLE_FLAGS.isContainer) > 0; - this.isNameHere = (bitwiseMask & VARIABLE_FLAGS.isNameHere) > 0; - this.isRefCounted = (bitwiseMask & VARIABLE_FLAGS.isRefCounted) > 0; - this.isValueHere = (bitwiseMask & VARIABLE_FLAGS.isValueHere) > 0; - - this.variableType = VARIABLE_TYPES[bufferReader.readUInt8()]; - - if (this.isNameHere) { - // YAY we have a name. Pull it out of the buffer. - this.name = protocolUtils.readStringNT(bufferReader); - } - - if (this.isRefCounted) { - // This variables reference counts are tracked and we can pull it from the buffer. - this.refCount = bufferReader.readUInt32LE(); - } - - if (this.isContainer) { - // It is a form of container object. - // Are the key strings or integers for example - this.keyType = VARIABLE_TYPES[bufferReader.readUInt8()]; - // Equivalent to length on arrays - this.elementCount = bufferReader.readUInt32LE(); - } - - // Pull out the variable data based on the type if that type returns a value - switch (this.variableType) { - case 'Interface': - case 'Object': - case 'String': - case 'Subroutine': - case 'Function': - this.value = protocolUtils.readStringNT(bufferReader); - this.success = true; - break; - case 'Subtyped_Object': - let names = []; - for (let i = 0; i < 2; i++) { - names.push(protocolUtils.readStringNT(bufferReader)); - } - - if (names.length === 2) { - this.value = names.join('; '); - this.success = true; - } - break; - case 'Boolean': - this.value = (bufferReader.readUInt8() > 0); - this.success = true; - break; - case 'Double': - this.value = bufferReader.readDoubleLE(); - this.success = true; - break; - case 'Float': - this.value = bufferReader.readFloatLE(); - this.success = true; - break; - case 'Integer': - this.value = bufferReader.readInt32LE(); - this.success = true; - break; - case 'LongInteger': - this.value = bufferReader.readBigInt64LE(); - this.success = true; - break; - case 'Uninitialized': - this.value = 'Uninitialized'; - this.success = true; - break; - case 'Unknown': - this.value = 'Unknown'; - this.success = true; - break; - case 'Invalid': - this.value = 'Invalid'; - this.success = true; - break; - case 'AA': - case 'Array': - case 'List': - this.value = null; - this.success = true; - break; - default: - this.value = null; - this.success = false; - } - } - } - public success = false; - - // response flags - public isChildKey: boolean; - public isConst: boolean; - public isContainer: boolean; - public isNameHere: boolean; - public isRefCounted: boolean; - public isValueHere: boolean; - - // response fields - public variableType: string; - public name: string; - public refCount = -1; - public keyType: string; - public elementCount = -1; - public value: number | string | boolean | bigint | null; -} diff --git a/src/debugProtocol/events/zzresponsesOld/index.ts b/src/debugProtocol/events/zzresponsesOld/index.ts index 36c2c84c..82f02c4d 100644 --- a/src/debugProtocol/events/zzresponsesOld/index.ts +++ b/src/debugProtocol/events/zzresponsesOld/index.ts @@ -1,8 +1,8 @@ export * from '../updates/IOPortOpenedUpdate'; export * from '../responses/GenericResponse'; -export * from '../responses/GenericResponseV3'; -export * from './StackTraceResponse'; -export * from './StackTraceResponseV3'; -export * from './ThreadsResponse'; +export * from '../responses/GenericV3Response'; +export * from '../responses/StackTraceResponse'; +export * from '../responses/StackTraceV3Response'; +export * from '../responses/ThreadsResponse'; export * from '../updates/ThreadAttachedUpdate'; -export * from './VariableResponse'; +export * from '../responses/VariableResponse'; diff --git a/src/debugProtocol/events/zzresponsesOld/responseCreationHelpers.spec.ts b/src/debugProtocol/events/zzresponsesOld/responseCreationHelpers.spec.ts index a48de7d2..3a6ba7fe 100644 --- a/src/debugProtocol/events/zzresponsesOld/responseCreationHelpers.spec.ts +++ b/src/debugProtocol/events/zzresponsesOld/responseCreationHelpers.spec.ts @@ -284,7 +284,7 @@ function writeIfSet(value: T, writer: (x: T) => R, defaultValue?: T) { export function getRandomBuffer(byteCount: number) { const result = new SmartBuffer(); for (let i = 0; i < byteCount; i++) { - result.writeUInt32LE(i); + result.writeUInt8(i); } return result.toBuffer(); } diff --git a/src/debugProtocol/server/DebugProtocolServer.ts b/src/debugProtocol/server/DebugProtocolServer.ts index e3111a32..15f4311e 100644 --- a/src/debugProtocol/server/DebugProtocolServer.ts +++ b/src/debugProtocol/server/DebugProtocolServer.ts @@ -4,7 +4,7 @@ import { ActionQueue } from '../../managers/ActionQueue'; import type { ProtocolRequest, ProtocolResponse } from '../events/ProtocolEvent'; import { HandshakeRequest } from '../events/requests/HandshakeRequest'; import { HandshakeResponse } from '../events/responses/HandshakeResponse'; -import { HandshakeResponseV3 } from '../events/responses/HandshakeResponseV3'; +import { HandshakeV3Response } from '../events/responses/HandshakeV3Response'; import PluginInterface from './PluginInterface'; import type { ProtocolPlugin } from './ProtocolPlugin'; @@ -121,7 +121,7 @@ export class DebugProtocolServer { private getResponse(request: ProtocolRequest) { if (request instanceof HandshakeRequest) { - return HandshakeResponseV3.fromJson({ + return HandshakeV3Response.fromJson({ magic: this.magic, protocolVersion: '3.1.0', //TODO update this to an actual date from the device @@ -160,7 +160,7 @@ export class DebugProtocolServer { } //the client should send a magic string to kick off the debugger - if ((response instanceof HandshakeResponse || response instanceof HandshakeResponseV3) && request.data.magic === this.magic) { + if ((response instanceof HandshakeResponse || response instanceof HandshakeV3Response) && request.data.magic === this.magic) { this.isHandshakeComplete = true; } diff --git a/src/util.ts b/src/util.ts index 2716e6a5..17850514 100644 --- a/src/util.ts +++ b/src/util.ts @@ -378,6 +378,13 @@ class Util { return cancel; } + + /** + * Is the given value null or undefined + */ + public isNullish(value: any) { + return value === undefined || value === null; + } } export function defer() { From 9d39c00d893456401dd662d1d406977e9d488e1c Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Tue, 11 Oct 2022 16:05:24 -0400 Subject: [PATCH 10/74] Many more tweaks. VariablesRequest mostly functional now --- src/adapters/DebugProtocolAdapter.spec.ts | 4 +- src/adapters/DebugProtocolAdapter.ts | 41 +- src/debugProtocol/Constants.ts | 35 +- .../client/DebugProtocolClient.spec.ts | 50 ++- .../client/DebugProtocolClient.ts | 21 +- .../responses/ExecuteV3Response.spec.ts | 10 +- .../events/responses/ExecuteV3Response.ts | 4 +- .../events/responses/GenericResponse.spec.ts | 8 +- .../events/responses/GenericResponse.ts | 6 +- .../responses/GenericV3Response.spec.ts | 14 +- .../events/responses/GenericV3Response.ts | 6 +- .../responses/HandshakeResponse.spec.ts | 1 - .../events/responses/HandshakeResponse.ts | 4 +- .../events/responses/HandshakeV3Response.ts | 4 +- .../responses/ListBreakpointsResponse.spec.ts | 38 +- .../responses/ListBreakpointsResponse.ts | 4 +- .../responses/StackTraceResponse.spec.ts | 12 +- .../events/responses/StackTraceResponse.ts | 4 +- .../responses/StackTraceV3Response.spec.ts | 12 +- .../events/responses/StackTraceV3Response.ts | 4 +- .../events/responses/ThreadsResponse.spec.ts | 12 +- .../events/responses/ThreadsResponse.ts | 4 +- .../responses/VariablesResponse.spec.ts | 103 +++++ .../events/responses/VariablesResponse.ts | 374 ++++++++++++++++++ .../updates/AllThreadsStoppedUpdate.spec.ts | 6 +- .../events/updates/AllThreadsStoppedUpdate.ts | 4 +- .../updates/BreakpointErrorUpdate.spec.ts | 10 +- .../events/updates/BreakpointErrorUpdate.ts | 4 +- .../events/updates/CompileErrorUpdate.spec.ts | 6 +- .../events/updates/CompileErrorUpdate.ts | 4 +- .../events/updates/IOPortOpenedUpdate.spec.ts | 6 +- .../events/updates/IOPortOpenedUpdate.ts | 4 +- .../updates/ThreadAttachedUpdate.spec.ts | 7 +- .../events/updates/ThreadAttachedUpdate.ts | 4 +- .../events/zzresponsesOld/index.ts | 8 - .../responseCreationHelpers.spec.ts | 290 -------------- .../responseCreationHelpers.spec.ts | 1 + src/debugProtocol/server/ProtocolPlugin.ts | 3 +- src/index.ts | 1 - src/testHelpers.spec.ts | 12 + 40 files changed, 649 insertions(+), 496 deletions(-) create mode 100644 src/debugProtocol/events/responses/VariablesResponse.spec.ts create mode 100644 src/debugProtocol/events/responses/VariablesResponse.ts delete mode 100644 src/debugProtocol/events/zzresponsesOld/index.ts delete mode 100644 src/debugProtocol/events/zzresponsesOld/responseCreationHelpers.spec.ts create mode 100644 src/debugProtocol/responseCreationHelpers.spec.ts diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index b62dece3..e18067d9 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -5,7 +5,7 @@ import { DebugProtocolAdapter } from './DebugProtocolAdapter'; import { createSandbox } from 'sinon'; import type { VariableInfo } from '../debugProtocol/events/zzresponsesOld'; import { VariablesResponse } from '../debugProtocol/events/zzresponsesOld'; -import { ERROR_CODES } from './../debugProtocol/Constants'; +import { ErrorCode } from './../debugProtocol/Constants'; const sinon = createSandbox(); describe('DebugProtocolAdapter', () => { @@ -30,7 +30,7 @@ describe('DebugProtocolAdapter', () => { beforeEach(() => { response = new VariablesResponse(Buffer.alloc(5)); - response.errorCode = ERROR_CODES.OK; + response.errorCode = ErrorCode.OK; variables = []; sinon.stub(adapter as any, 'getStackFrameById').returns({}); sinon.stub(socketDebugger, 'getVariables').callsFake(() => { diff --git a/src/adapters/DebugProtocolAdapter.ts b/src/adapters/DebugProtocolAdapter.ts index 720a7ec9..ee9b76a1 100644 --- a/src/adapters/DebugProtocolAdapter.ts +++ b/src/adapters/DebugProtocolAdapter.ts @@ -9,7 +9,7 @@ import { RendezvousTracker } from '../RendezvousTracker'; import type { ChanperfData } from '../ChanperfTracker'; import { ChanperfTracker } from '../ChanperfTracker'; import type { SourceLocation } from '../managers/LocationManager'; -import { ERROR_CODES, PROTOCOL_ERROR_CODES, StopReasonCode } from '../debugProtocol/Constants'; +import { ErrorCode, PROTOCOL_ERROR_CODES, StopReasonCode } from '../debugProtocol/Constants'; import { defer, util } from '../util'; import { logger } from '../logging'; import * as semver from 'semver'; @@ -17,6 +17,7 @@ import type { AdapterOptions, HighLevelType, RokuAdapterEvaluateResponse } from import type { BreakpointManager } from '../managers/BreakpointManager'; import type { ProjectManager } from '../managers/ProjectManager'; import { ActionQueue } from '../managers/ActionQueue'; +import { VariableType } from '../debugProtocol/events/responses/VariablesResponse'; /** * A class that connects to a Roku device over telnet debugger port and provides a standardized way of interacting with it. @@ -390,7 +391,7 @@ export class DebugProtocolAdapter { const response = await this.socketDebugger.executeCommand(command, stackFrame.frameIndex, stackFrame.threadIndex); this.logger.info('evaluate response', { command, response }); - if (response.executeSuccess) { + if (response.data.executeSuccess) { return { message: undefined, type: 'message' @@ -417,14 +418,14 @@ export class DebugProtocolAdapter { let thread = await this.getThreadByThreadId(threadId); let frames: StackFrame[] = []; let stackTraceData = await this.socketDebugger.stackTrace(threadId); - for (let i = 0; i < stackTraceData.stackSize; i++) { - let frameData = stackTraceData.entries[i]; + for (let i = 0; i < stackTraceData.data.entries.length; i++) { + let frameData = stackTraceData.data.entries[i]; let stackFrame: StackFrame = { frameId: this.nextFrameId++, // frame index is the reverse of the returned order. - frameIndex: stackTraceData.stackSize - i - 1, + frameIndex: stackTraceData.data.entries.length - i - 1, threadIndex: threadId, - filePath: frameData.fileName, + filePath: frameData.filePath, lineNumber: frameData.lineNumber, // eslint-disable-next-line no-nested-ternary functionIdentifier: this.cleanUpFunctionName(i === 0 ? (frameData.functionName) ? frameData.functionName : thread.functionName : frameData.functionName) @@ -476,7 +477,7 @@ export class DebugProtocolAdapter { let response = await this.socketDebugger.getVariables(variablePath, withChildren, frame.frameIndex, frame.threadIndex); - if (this.enableVariablesLowerCaseRetry && response.errorCode !== ERROR_CODES.OK) { + if (this.enableVariablesLowerCaseRetry && response.data.errorCode !== ErrorCode.OK) { // Temporary workaround related to casing issues over the protocol logger.log(`Retrying expression as lower case:`, expression); variablePath = expression === '' ? [] : util.getVariablePath(expression?.toLowerCase()); @@ -484,13 +485,13 @@ export class DebugProtocolAdapter { } - if (response.errorCode === ERROR_CODES.OK) { + if (response.data.errorCode === ErrorCode.OK) { let mainContainer: EvaluateContainer; let children: EvaluateContainer[] = []; let firstHandled = false; - for (let variable of response.variables) { + for (let variable of response.data.variables) { let value; - let variableType = variable.variableType; + let variableType = variable.type; if (variable.value === null) { value = 'roInvalid'; } else if (variableType === 'String') { @@ -499,12 +500,12 @@ export class DebugProtocolAdapter { value = variable.value; } - if (variableType === 'Subtyped_Object') { + if (variableType === VariableType.SubtypedObject) { //subtyped objects can only have string values let parts = (variable.value as string).split('; '); variableType = `${parts[0]} (${parts[1]})`; } else if (variableType === 'AA') { - variableType = 'AssociativeArray'; + variableType = VariableType.AA; } let container = { @@ -578,16 +579,16 @@ export class DebugProtocolAdapter { } return this.resolve('threads', async () => { let threads: Thread[] = []; - let threadsData = await this.socketDebugger.threads(); + let threadsResponse = await this.socketDebugger.threads(); - for (let i = 0; i < threadsData.threadsCount; i++) { - let threadInfo = threadsData.threads[i]; + for (let i = 0; i < threadsResponse.data.threads.length; i++) { + let threadInfo = threadsResponse.data.threads[i]; let thread = { // NOTE: On THREAD_ATTACHED events the threads request is marking the wrong thread as primary. // NOTE: Rely on the thead index from the threads update event. isSelected: this.socketDebugger.primaryThread === i, // isSelected: threadInfo.isPrimary, - filePath: threadInfo.fileName, + filePath: threadInfo.filePath, functionName: threadInfo.functionName, lineNumber: threadInfo.lineNumber + 1, //protocol is 0-based but 1-based is expected lineContents: threadInfo.codeSnippet, @@ -680,7 +681,7 @@ export class DebugProtocolAdapter { diff.removed.map(x => x.deviceId) ); //return true to mark this action as complete, or false to retry the task again in the future - return response.success && response.errorCode === ERROR_CODES.OK; + return response.success && response.data.errorCode === ErrorCode.OK; }); } @@ -700,10 +701,10 @@ export class DebugProtocolAdapter { //send these new breakpoints to the device await this.actionQueue.run(async () => { const response = await this.socketDebugger.addBreakpoints(breakpointsToSendToDevice); - if (response.errorCode === ERROR_CODES.OK) { + if (response.data.errorCode === ErrorCode.OK) { //mark the breakpoints as verified - for (let i = 0; i < response.breakpoints.length; i++) { - const deviceBreakpoint = response.breakpoints[i]; + for (let i = 0; i < response.data.breakpoints.length; i++) { + const deviceBreakpoint = response.data.breakpoints[i]; if (deviceBreakpoint.isVerified) { this.breakpointManager.verifyBreakpoint( breakpointsToSendToDevice[i].key, diff --git a/src/debugProtocol/Constants.ts b/src/debugProtocol/Constants.ts index 028f34d7..95f92719 100644 --- a/src/debugProtocol/Constants.ts +++ b/src/debugProtocol/Constants.ts @@ -27,7 +27,7 @@ export enum STEP_TYPE { } //#region RESPONSE CONSTS -export enum ERROR_CODES { +export enum ErrorCode { OK = 0, OTHER_ERR = 1, UNDEFINED_COMMAND = 2, @@ -68,39 +68,6 @@ export enum VARIABLE_REQUEST_FLAGS { CASE_SENSITIVITY_OPTIONS = 0x02 } -export enum VARIABLE_FLAGS { - /** - * value is a child of the requested variable - * e.g., an element of an array or field of an AA - */ - isChildKey = 1, - /** - * value is constant - */ - isConst = 2, - /** - * The referenced value is a container (e.g., a list or array) - */ - isContainer = 4, - /** - * The name is included in this VariableInfo - */ - isNameHere = 8, - /** - * value is reference-counted. - */ - isRefCounted = 16, - /** - * value is included in this VariableInfo - */ - isValueHere = 32, - /** - * Value is container, key lookup is case sensitive - * @since protocol 3.1.0 - */ - isKeysCaseSensitive = 64 -} - //#endregion diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index 081c1642..98db4f94 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -3,18 +3,16 @@ import { expect } from 'chai'; import type { SmartBuffer } from 'smart-buffer'; import { MockDebugProtocolServer } from '../MockDebugProtocolServer.spec'; import { createSandbox } from 'sinon'; -import { createHandShakeResponse, createHandShakeResponseV3, createProtocolEventV3 } from '../events/zzresponsesOld/responseCreationHelpers.spec'; -import { HandshakeResponse, HandshakeResponseV3, ProtocolEventV3 } from '../events/zzresponsesOld'; -import { ERROR_CODES, StopReasonCode, UPDATE_TYPES, VARIABLE_REQUEST_FLAGS } from '../Constants'; -import { DebugProtocolServer, DebugProtocolServerOptions } from '../server/DebugProtocolServer'; +import { StopReasonCode, VARIABLE_REQUEST_FLAGS } from '../Constants'; +import { DebugProtocolServer } from '../server/DebugProtocolServer'; import * as portfinder from 'portfinder'; import { util } from '../../util'; import type { BeforeSendResponseEvent, ProtocolPlugin, ProvideResponseEvent } from '../server/ProtocolPlugin'; -import { Handler, OnClientConnectedEvent, ProvideRequestEvent } from '../server/ProtocolPlugin'; -import type { ProtocolResponse } from './events/zzresponsesOld/ProtocolResponse'; -import type { ProtocolRequest } from './events/requests/ProtocolRequest'; import { HandshakeRequest } from '../events/requests/HandshakeRequest'; -import { AllThreadsStoppedUpdateResponse } from '../events/updates/AllThreadsStoppedUpdate'; +import type { ProtocolResponse, ProtocolRequest } from '../events/ProtocolEvent'; +import { HandshakeResponse } from '../events/responses/HandshakeResponse'; +import { HandshakeV3Response } from '../events/responses/HandshakeV3Response'; +import { AllThreadsStoppedUpdate } from '../events/updates/AllThreadsStoppedUpdate'; const sinon = createSandbox(); @@ -217,11 +215,9 @@ describe.skip('Debugger new tests', () => { await client.connect(); expect(plugin.responses[0].data).to.eql({ magic: 'bsdebug', - majorVersion: 3, - minorVersion: 1, - patchVersion: 0, - revisionTimeStamp: new Date(2022, 1, 1) - } as HandshakeResponseV3['data']); + protocolVersion: '3.1.0', + revisionTimestamp: new Date(2022, 1, 1) + } as HandshakeV3Response['data']); //version 3.0 includes packet length, so these should be true now expect(client.watchPacketLength).to.be.equal(true); @@ -229,13 +225,13 @@ describe.skip('Debugger new tests', () => { }); it('throws on magic mismatch', async () => { - plugin.pushResponse(new HandshakeResponseV3({ - magic: 'not correct magic', - majorVersion: 3, - minorVersion: 1, - patchVersion: 0, - revisionTimeStamp: new Date(2022, 1, 1) - })); + plugin.pushResponse( + HandshakeV3Response.fromJson({ + magic: 'not correct magic', + protocolVersion: '3.1.0', + revisionTimestamp: new Date(2022, 1, 1) + }) + ); const verifyHandshakePromise = client.once('handshake-verified'); @@ -250,12 +246,12 @@ describe.skip('Debugger new tests', () => { expect(client.watchPacketLength).to.be.equal(false); expect(client.isHandshakeComplete).to.be.equal(false); - plugin.pushResponse(new HandshakeResponse({ - magic: DebugProtocolClient.DEBUGGER_MAGIC, - majorVersion: 1, - minorVersion: 0, - patchVersion: 0 - })); + plugin.pushResponse( + HandshakeResponse.fromJson({ + magic: DebugProtocolClient.DEBUGGER_MAGIC, + protocolVersion: '1.0.0' + }) + ); await client.connect(); @@ -267,7 +263,7 @@ describe.skip('Debugger new tests', () => { await client.connect(); await server.sendUpdate( - new AllThreadsStoppedUpdateResponse({ + AllThreadsStoppedUpdate.fromJson({ primaryThreadIndex: 1, stopReason: StopReasonCode.Break, stopReasonDetail: 'test' diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 3738b64b..f6f71557 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -1,7 +1,7 @@ import * as Net from 'net'; import * as EventEmitter from 'eventemitter3'; import * as semver from 'semver'; -import { PROTOCOL_ERROR_CODES, COMMANDS, STEP_TYPE, StopReasonCode, VARIABLE_REQUEST_FLAGS, ERROR_CODES, UPDATE_TYPES } from '../Constants'; +import { PROTOCOL_ERROR_CODES, COMMANDS, STEP_TYPE, StopReasonCode, VARIABLE_REQUEST_FLAGS, ErrorCode, UPDATE_TYPES } from '../Constants'; import { SmartBuffer } from 'smart-buffer'; import { logger } from '../../logging'; import { ExecuteV3Response } from '../events/responses/ExecuteV3Response'; @@ -27,10 +27,15 @@ import { HandshakeResponse } from '../events/responses/HandshakeResponse'; import { HandshakeV3Response } from '../events/responses/HandshakeV3Response'; import { HandshakeRequest } from '../events/requests/HandshakeRequest'; import { GenericV3Response } from '../events/responses/GenericV3Response'; -import { GenericResponse, IOPortOpenedUpdate, StackTraceResponse, StackTraceResponseV3, ThreadAttachedUpdate, ThreadsResponse, UndefinedResponse, VariablesResponse } from '../events/zzresponsesOld'; import { AllThreadsStoppedUpdate } from '../events/updates/AllThreadsStoppedUpdate'; import { buffer } from 'rxjs'; import { CompileErrorUpdate } from '../events/updates/CompileErrorUpdate'; +import { GenericResponse } from '../events/responses/GenericResponse'; +import { StackTraceResponse } from '../events/responses/StackTraceResponse'; +import { ThreadsResponse } from '../events/responses/ThreadsResponse'; +import { VariablesResponse } from '../events/responses/VariablesResponse'; +import { IOPortOpenedUpdate } from '../events/updates/IOPortOpenedUpdate'; +import { ThreadAttachedUpdate } from '../events/updates/ThreadAttachedUpdate'; export class DebugProtocolClient { @@ -279,7 +284,7 @@ export class DebugProtocolClient { threadIndex: threadIndex }) ); - if (stepResult.data.errorCode === ERROR_CODES.OK) { + if (stepResult.data.errorCode === ErrorCode.OK) { // this.stopped = true; // this.emit('suspend'); } else { @@ -297,7 +302,7 @@ export class DebugProtocolClient { requestId: this.totalRequests++ })); - if (result.errorCode === ERROR_CODES.OK) { + if (result.errorCode === ErrorCode.OK) { //older versions of the debug protocol had issues with maintaining the active thread, so our workaround is to keep track of it elsewhere if (this.enableThreadHoppingWorkaround) { //ignore the `isPrimary` flag on threads @@ -455,7 +460,7 @@ export class DebugProtocolClient { return true; } - if (event.data.errorCode !== ERROR_CODES.OK) { + if (event.data.errorCode !== ErrorCode.OK) { this.logger.error(event.data.errorCode, event); this.removedProcessedBytes(genericResponse, buffer, packetLength); return true; @@ -578,12 +583,12 @@ export class DebugProtocolClient { if (update.data.updateType === UPDATE_TYPES.ALL_THREADS_STOPPED) { if (stopReason === StopReasonCode.RuntimeError || stopReason === StopReasonCode.Break || stopReason === StopReasonCode.StopStatement) { - this.primaryThread = (update.data as ThreadsStopped).primaryThreadIndex; + this.primaryThread = (update.data as AllThreadsStoppedUpdate).data.primaryThreadIndex; this.stackFrameIndex = 0; this.emit(eventName, update); } } else if (stopReason === StopReasonCode.RuntimeError || stopReason === StopReasonCode.Break || stopReason === StopReasonCode.StopStatement) { - this.primaryThread = (update.data as ThreadAttached).threadIndex; + this.primaryThread = (update.data as ThreadAttachedUpdate).data.threadIndex; this.emit(eventName, update); } } else if (update instanceof IOPortOpenedUpdate) { @@ -611,7 +616,7 @@ export class DebugProtocolClient { this.buffer = unhandledData.slice(packetLength ? packetLength : response.readOffset); this.logger.debug('[raw]', `requestId=${response?.requestId}`, request, (response as any)?.constructor?.name ?? '', response); - this.process(this.buffer); + this.process(); } /** diff --git a/src/debugProtocol/events/responses/ExecuteV3Response.spec.ts b/src/debugProtocol/events/responses/ExecuteV3Response.spec.ts index fd68b9a8..b4ed0ec4 100644 --- a/src/debugProtocol/events/responses/ExecuteV3Response.spec.ts +++ b/src/debugProtocol/events/responses/ExecuteV3Response.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { ERROR_CODES, StopReasonCode, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, StopReasonCode, UPDATE_TYPES } from '../../Constants'; import { ExecuteV3Response } from './ExecuteV3Response'; describe('ExecuteV3Response', () => { @@ -22,7 +22,7 @@ describe('ExecuteV3Response', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 3, - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, executeSuccess: true, runtimeStopCode: StopReasonCode.Break, @@ -42,7 +42,7 @@ describe('ExecuteV3Response', () => { ).to.eql({ packetLength: 54, // 4 bytes requestId: 3, // 4 bytes - errorCode: ERROR_CODES.OK, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes executeSuccess: true, // 1 byte runtimeStopCode: StopReasonCode.Break, // 1 byte @@ -76,7 +76,7 @@ describe('ExecuteV3Response', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 3, - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, executeSuccess: true, runtimeStopCode: StopReasonCode.Break, @@ -90,7 +90,7 @@ describe('ExecuteV3Response', () => { ).to.eql({ packetLength: 26, // 4 bytes requestId: 3, // 4 bytes - errorCode: ERROR_CODES.OK, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes executeSuccess: true, // 1 byte runtimeStopCode: StopReasonCode.Break, // 1 byte diff --git a/src/debugProtocol/events/responses/ExecuteV3Response.ts b/src/debugProtocol/events/responses/ExecuteV3Response.ts index 5f1693e3..4cd7360f 100644 --- a/src/debugProtocol/events/responses/ExecuteV3Response.ts +++ b/src/debugProtocol/events/responses/ExecuteV3Response.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; import type { StopReasonCode } from '../../Constants'; -import { ERROR_CODES } from '../../Constants'; +import { ErrorCode } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; export class ExecuteV3Response { @@ -110,6 +110,6 @@ export class ExecuteV3Response { //common props packetLength: undefined as number, requestId: undefined as number, - errorCode: ERROR_CODES.OK + errorCode: ErrorCode.OK }; } diff --git a/src/debugProtocol/events/responses/GenericResponse.spec.ts b/src/debugProtocol/events/responses/GenericResponse.spec.ts index 88e4b76a..78ba8e74 100644 --- a/src/debugProtocol/events/responses/GenericResponse.spec.ts +++ b/src/debugProtocol/events/responses/GenericResponse.spec.ts @@ -1,17 +1,17 @@ import { GenericResponse } from './GenericResponse'; import { expect } from 'chai'; -import { ERROR_CODES } from '../../Constants'; +import { ErrorCode } from '../../Constants'; describe('GenericResponse', () => { it('Handles a Protocol update events', () => { let response = GenericResponse.fromJson({ requestId: 3, - errorCode: ERROR_CODES.CANT_CONTINUE + errorCode: ErrorCode.CANT_CONTINUE }); expect(response.data).to.eql({ packetLength: undefined, - errorCode: ERROR_CODES.CANT_CONTINUE, + errorCode: ErrorCode.CANT_CONTINUE, requestId: 3 }); @@ -20,7 +20,7 @@ describe('GenericResponse', () => { response.data ).to.eql({ packetLength: 8, // 0 bytes -- this version of the response doesn't have a packet length - errorCode: ERROR_CODES.CANT_CONTINUE, // 4 bytes + errorCode: ErrorCode.CANT_CONTINUE, // 4 bytes requestId: 3 // 4 bytes }); diff --git a/src/debugProtocol/events/responses/GenericResponse.ts b/src/debugProtocol/events/responses/GenericResponse.ts index 68e382e6..2cd2bbf5 100644 --- a/src/debugProtocol/events/responses/GenericResponse.ts +++ b/src/debugProtocol/events/responses/GenericResponse.ts @@ -1,11 +1,11 @@ import { SmartBuffer } from 'smart-buffer'; -import type { ERROR_CODES } from '../../Constants'; +import type { ErrorCode } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; export class GenericResponse { public static fromJson(data: { requestId: number; - errorCode: ERROR_CODES; + errorCode: ErrorCode; }) { const response = new GenericResponse(); protocolUtils.loadJson(response, data); @@ -38,6 +38,6 @@ export class GenericResponse { //this response doesn't actually contain packetLength, but we need to add it here just to make this response look like a regular response packetLength: undefined as number, requestId: Number.MAX_SAFE_INTEGER, - errorCode: undefined as ERROR_CODES + errorCode: undefined as ErrorCode }; } diff --git a/src/debugProtocol/events/responses/GenericV3Response.spec.ts b/src/debugProtocol/events/responses/GenericV3Response.spec.ts index 49f481f1..f5edd363 100644 --- a/src/debugProtocol/events/responses/GenericV3Response.spec.ts +++ b/src/debugProtocol/events/responses/GenericV3Response.spec.ts @@ -1,18 +1,18 @@ import { GenericV3Response } from './GenericV3Response'; import { expect } from 'chai'; -import { ERROR_CODES } from '../../Constants'; +import { ErrorCode } from '../../Constants'; import { SmartBuffer } from 'smart-buffer'; describe('GenericV3Response', () => { it('serializes and deserializes properly', () => { const response = GenericV3Response.fromJson({ - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, requestId: 3 }); expect(response.data).to.eql({ packetLength: undefined, - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, requestId: 3 }); @@ -20,20 +20,20 @@ describe('GenericV3Response', () => { GenericV3Response.fromBuffer(response.toBuffer()).data ).to.eql({ packetLength: 12, // 4 bytes - errorCode: ERROR_CODES.OK, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes requestId: 3 // 4 bytes }); }); it('consumes excess buffer data', () => { const response = GenericV3Response.fromJson({ - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, requestId: 3 }); expect(response.data).to.eql({ packetLength: undefined, - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, requestId: 3 }); @@ -53,7 +53,7 @@ describe('GenericV3Response', () => { newResponse.data ).to.eql({ packetLength: 32, // 4 bytes - errorCode: ERROR_CODES.OK, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes requestId: 3 // 4 bytes }); }); diff --git a/src/debugProtocol/events/responses/GenericV3Response.ts b/src/debugProtocol/events/responses/GenericV3Response.ts index d0b4e91c..e3df8bf7 100644 --- a/src/debugProtocol/events/responses/GenericV3Response.ts +++ b/src/debugProtocol/events/responses/GenericV3Response.ts @@ -1,11 +1,11 @@ import { SmartBuffer } from 'smart-buffer'; -import type { ERROR_CODES } from '../../Constants'; +import type { ErrorCode } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; export class GenericV3Response { public static fromJson(data: { requestId: number; - errorCode: ERROR_CODES; + errorCode: ErrorCode; }) { const response = new GenericV3Response(); protocolUtils.loadJson(response, data); @@ -39,6 +39,6 @@ export class GenericV3Response { public data = { packetLength: undefined as number, requestId: Number.MAX_SAFE_INTEGER, - errorCode: undefined as ERROR_CODES + errorCode: undefined as ErrorCode }; } diff --git a/src/debugProtocol/events/responses/HandshakeResponse.spec.ts b/src/debugProtocol/events/responses/HandshakeResponse.spec.ts index e60184a3..f4dff8f0 100644 --- a/src/debugProtocol/events/responses/HandshakeResponse.spec.ts +++ b/src/debugProtocol/events/responses/HandshakeResponse.spec.ts @@ -1,6 +1,5 @@ import { HandshakeResponse } from './HandshakeResponse'; import { DebugProtocolClient } from '../../client/DebugProtocolClient'; -import { createHandShakeResponse } from '../zzresponsesOld/responseCreationHelpers.spec'; import { expect } from 'chai'; describe('HandshakeResponse', () => { diff --git a/src/debugProtocol/events/responses/HandshakeResponse.ts b/src/debugProtocol/events/responses/HandshakeResponse.ts index 78948cec..e65f2280 100644 --- a/src/debugProtocol/events/responses/HandshakeResponse.ts +++ b/src/debugProtocol/events/responses/HandshakeResponse.ts @@ -3,7 +3,7 @@ import * as semver from 'semver'; import { util } from '../../../util'; import type { ProtocolEvent, ProtocolResponse } from '../ProtocolEvent'; import { protocolUtils } from '../../ProtocolUtil'; -import { ERROR_CODES } from '../../Constants'; +import { ErrorCode } from '../../Constants'; export class HandshakeResponse implements ProtocolResponse { public static fromJson(data: { @@ -76,6 +76,6 @@ export class HandshakeResponse implements ProtocolResponse { packetLength: undefined as number, //hardcode the max integer value. This must be the same value as the HandshakeResponse class requestId: Number.MAX_SAFE_INTEGER, - errorCode: ERROR_CODES.OK + errorCode: ErrorCode.OK }; } diff --git a/src/debugProtocol/events/responses/HandshakeV3Response.ts b/src/debugProtocol/events/responses/HandshakeV3Response.ts index ed4b6df2..4431e0c6 100644 --- a/src/debugProtocol/events/responses/HandshakeV3Response.ts +++ b/src/debugProtocol/events/responses/HandshakeV3Response.ts @@ -2,7 +2,7 @@ import { SmartBuffer } from 'smart-buffer'; import * as semver from 'semver'; import type { ProtocolResponse } from '../ProtocolEvent'; import { protocolUtils } from '../../ProtocolUtil'; -import { ERROR_CODES } from '../../Constants'; +import { ErrorCode } from '../../Constants'; export class HandshakeV3Response implements ProtocolResponse { @@ -108,6 +108,6 @@ export class HandshakeV3Response implements ProtocolResponse { packetLength: undefined as number, //hardcode the max integer value. This must be the same value as the HandshakeResponse class requestId: Number.MAX_SAFE_INTEGER, - errorCode: ERROR_CODES.OK + errorCode: ErrorCode.OK }; } diff --git a/src/debugProtocol/events/responses/ListBreakpointsResponse.spec.ts b/src/debugProtocol/events/responses/ListBreakpointsResponse.spec.ts index 71199755..cfa4bd57 100644 --- a/src/debugProtocol/events/responses/ListBreakpointsResponse.spec.ts +++ b/src/debugProtocol/events/responses/ListBreakpointsResponse.spec.ts @@ -1,19 +1,18 @@ import { expect } from 'chai'; import { ListBreakpointsResponse } from './ListBreakpointsResponse'; -import { ERROR_CODES } from '../../Constants'; -import { getRandomBuffer } from '../zzresponsesOld/responseCreationHelpers.spec'; +import { ErrorCode } from '../../Constants'; +import { getRandomBuffer } from '../../../testHelpers.spec'; describe('ListBreakpointsResponse', () => { it('serializes and deserializes multiple breakpoints properly', () => { let response = ListBreakpointsResponse.fromJson({ requestId: 3, - errorCode: ERROR_CODES.OK, breakpoints: [{ - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, id: 10, ignoreCount: 2 }, { - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, id: 20, ignoreCount: 3 }] @@ -22,13 +21,13 @@ describe('ListBreakpointsResponse', () => { expect(response.data).to.eql({ packetLength: undefined, requestId: 3, - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, breakpoints: [{ - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, id: 10, ignoreCount: 2 }, { - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, id: 20, ignoreCount: 3 }] @@ -41,14 +40,14 @@ describe('ListBreakpointsResponse', () => { ).to.eql({ packetLength: 40, // 4 bytes requestId: 3, // 4 bytes, - errorCode: ERROR_CODES.OK, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes //num_breakpoints // 4 bytes breakpoints: [{ - errorCode: ERROR_CODES.OK, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes id: 10, // 4 bytes ignoreCount: 2 // 4 bytes }, { - errorCode: ERROR_CODES.OK, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes id: 20, // 4 bytes ignoreCount: 3 // 4 bytes }] @@ -58,14 +57,13 @@ describe('ListBreakpointsResponse', () => { it('handles empty breakpoints array', () => { let response = ListBreakpointsResponse.fromJson({ requestId: 3, - errorCode: ERROR_CODES.OK, breakpoints: [] }); expect(response.data).to.eql({ packetLength: undefined, requestId: 3, - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, breakpoints: [] }); @@ -76,7 +74,7 @@ describe('ListBreakpointsResponse', () => { ).to.eql({ packetLength: 16, // 4 bytes requestId: 3, // 4 bytes, - errorCode: ERROR_CODES.OK, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes //num_breakpoints // 4 bytes breakpoints: [] }); @@ -108,9 +106,8 @@ describe('ListBreakpointsResponse', () => { it('gracefully handles mismatched breakpoint count', () => { let buffer = ListBreakpointsResponse.fromJson({ requestId: 3, - errorCode: ERROR_CODES.OK, breakpoints: [{ - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, id: 1, ignoreCount: 0 }] @@ -126,7 +123,7 @@ describe('ListBreakpointsResponse', () => { const response = ListBreakpointsResponse.fromBuffer(buffer); expect(response.success).to.be.false; expect(response.data.breakpoints).to.eql([{ - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, id: 1, ignoreCount: 0 }]); @@ -135,13 +132,12 @@ describe('ListBreakpointsResponse', () => { it('handles malformed breakpoint data', () => { let buffer = ListBreakpointsResponse.fromJson({ requestId: 3, - errorCode: ERROR_CODES.OK, breakpoints: [{ - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, id: 1, ignoreCount: 0 }, { - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, id: 2, ignoreCount: 0 }] @@ -155,7 +151,7 @@ describe('ListBreakpointsResponse', () => { const response = ListBreakpointsResponse.fromBuffer(buffer); expect(response.success).to.be.false; expect(response.data.breakpoints).to.eql([{ - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, id: 1, ignoreCount: 0 }]); diff --git a/src/debugProtocol/events/responses/ListBreakpointsResponse.ts b/src/debugProtocol/events/responses/ListBreakpointsResponse.ts index 214d2d2a..140d839b 100644 --- a/src/debugProtocol/events/responses/ListBreakpointsResponse.ts +++ b/src/debugProtocol/events/responses/ListBreakpointsResponse.ts @@ -1,5 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import { ERROR_CODES } from '../../Constants'; +import { ErrorCode } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; export class ListBreakpointsResponse { @@ -67,7 +67,7 @@ export class ListBreakpointsResponse { // response fields packetLength: undefined as number, requestId: undefined as number, - errorCode: ERROR_CODES.OK + errorCode: ErrorCode.OK }; } diff --git a/src/debugProtocol/events/responses/StackTraceResponse.spec.ts b/src/debugProtocol/events/responses/StackTraceResponse.spec.ts index 405ee659..94e37f7d 100644 --- a/src/debugProtocol/events/responses/StackTraceResponse.spec.ts +++ b/src/debugProtocol/events/responses/StackTraceResponse.spec.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { StackTraceResponse } from './StackTraceResponse'; -import { ERROR_CODES } from '../../Constants'; -import { getRandomBuffer } from '../zzresponsesOld/responseCreationHelpers.spec'; +import { ErrorCode } from '../../Constants'; +import { getRandomBuffer } from '../../responseCreationHelpers.spec'; describe('StackTraceResponse', () => { it('serializes and deserializes multiple breakpoints properly', () => { @@ -21,7 +21,7 @@ describe('StackTraceResponse', () => { expect(response.data).to.eql({ packetLength: undefined, requestId: 3, - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, entries: [{ lineNumber: 2, functionName: 'main', @@ -40,7 +40,7 @@ describe('StackTraceResponse', () => { ).to.eql({ packetLength: undefined, // 0 bytes requestId: 3, // 4 bytes, - errorCode: ERROR_CODES.OK, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes // num_entries // 4 bytes entries: [{ lineNumber: 2, // 4 bytes @@ -65,7 +65,7 @@ describe('StackTraceResponse', () => { expect(response.data).to.eql({ packetLength: undefined, requestId: 3, - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, entries: [] }); @@ -76,7 +76,7 @@ describe('StackTraceResponse', () => { ).to.eql({ packetLength: undefined, // 0 bytes requestId: 3, // 4 bytes, - errorCode: ERROR_CODES.OK, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes // num_entries // 4 bytes entries: [] }); diff --git a/src/debugProtocol/events/responses/StackTraceResponse.ts b/src/debugProtocol/events/responses/StackTraceResponse.ts index 93389b2a..d1cbd2bd 100644 --- a/src/debugProtocol/events/responses/StackTraceResponse.ts +++ b/src/debugProtocol/events/responses/StackTraceResponse.ts @@ -1,5 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import { ERROR_CODES } from '../../Constants'; +import { ErrorCode } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; import type { StackEntry } from './StackTraceV3Response'; @@ -75,7 +75,7 @@ export class StackTraceResponse { // response fields packetLength: undefined as number, requestId: undefined as number, - errorCode: ERROR_CODES.OK + errorCode: ErrorCode.OK }; } diff --git a/src/debugProtocol/events/responses/StackTraceV3Response.spec.ts b/src/debugProtocol/events/responses/StackTraceV3Response.spec.ts index 99f247ef..f2e130dd 100644 --- a/src/debugProtocol/events/responses/StackTraceV3Response.spec.ts +++ b/src/debugProtocol/events/responses/StackTraceV3Response.spec.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { StackTraceV3Response } from './StackTraceV3Response'; -import { ERROR_CODES } from '../../Constants'; -import { getRandomBuffer } from '../zzresponsesOld/responseCreationHelpers.spec'; +import { ErrorCode } from '../../Constants'; +import { getRandomBuffer } from '../../../testHelpers.spec'; describe('StackTraceV3Response', () => { it('serializes and deserializes multiple breakpoints properly', () => { @@ -21,7 +21,7 @@ describe('StackTraceV3Response', () => { expect(response.data).to.eql({ packetLength: undefined, requestId: 3, - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, entries: [{ lineNumber: 2, functionName: 'main', @@ -40,7 +40,7 @@ describe('StackTraceV3Response', () => { ).to.eql({ packetLength: 78, // 4 bytes requestId: 3, // 4 bytes, - errorCode: ERROR_CODES.OK, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes // num_entries // 4 bytes entries: [{ lineNumber: 2, // 4 bytes @@ -63,7 +63,7 @@ describe('StackTraceV3Response', () => { expect(response.data).to.eql({ packetLength: undefined, requestId: 3, - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, entries: [] }); @@ -74,7 +74,7 @@ describe('StackTraceV3Response', () => { ).to.eql({ packetLength: 16, // 4 bytes requestId: 3, // 4 bytes, - errorCode: ERROR_CODES.OK, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes // num_entries // 4 bytes entries: [] }); diff --git a/src/debugProtocol/events/responses/StackTraceV3Response.ts b/src/debugProtocol/events/responses/StackTraceV3Response.ts index 77d2abda..bb97e8e5 100644 --- a/src/debugProtocol/events/responses/StackTraceV3Response.ts +++ b/src/debugProtocol/events/responses/StackTraceV3Response.ts @@ -1,5 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import { ERROR_CODES } from '../../Constants'; +import { ErrorCode } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; export class StackTraceV3Response { @@ -67,7 +67,7 @@ export class StackTraceV3Response { // response fields packetLength: undefined as number, requestId: undefined as number, - errorCode: ERROR_CODES.OK + errorCode: ErrorCode.OK }; } diff --git a/src/debugProtocol/events/responses/ThreadsResponse.spec.ts b/src/debugProtocol/events/responses/ThreadsResponse.spec.ts index c8c0f52b..04729802 100644 --- a/src/debugProtocol/events/responses/ThreadsResponse.spec.ts +++ b/src/debugProtocol/events/responses/ThreadsResponse.spec.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { ThreadsResponse } from './ThreadsResponse'; -import { ERROR_CODES } from '../../Constants'; -import { getRandomBuffer } from '../zzresponsesOld/responseCreationHelpers.spec'; +import { ErrorCode } from '../../Constants'; +import { getRandomBuffer } from '../../responseCreationHelpers.spec'; describe('ThreadsResponse', () => { it('serializes and deserializes multiple breakpoints properly', () => { @@ -21,7 +21,7 @@ describe('ThreadsResponse', () => { expect(response.data).to.eql({ packetLength: undefined, requestId: 3, - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, threads: [{ isPrimary: true, stopReason: 'Break', @@ -40,7 +40,7 @@ describe('ThreadsResponse', () => { ).to.eql({ packetLength: 70, // 4 bytes requestId: 3, // 4 bytes, - errorCode: ERROR_CODES.OK, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes // threads_count // 4 bytes threads: [{ // flags // 4 bytes @@ -64,7 +64,7 @@ describe('ThreadsResponse', () => { expect(response.data).to.eql({ packetLength: undefined, requestId: 3, - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, threads: [] }); @@ -75,7 +75,7 @@ describe('ThreadsResponse', () => { ).to.eql({ packetLength: 16, // 4 bytes requestId: 3, // 4 bytes, - errorCode: ERROR_CODES.OK, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes // threads_count // 4 bytes threads: [] }); diff --git a/src/debugProtocol/events/responses/ThreadsResponse.ts b/src/debugProtocol/events/responses/ThreadsResponse.ts index 80542807..3f2b64c2 100644 --- a/src/debugProtocol/events/responses/ThreadsResponse.ts +++ b/src/debugProtocol/events/responses/ThreadsResponse.ts @@ -1,7 +1,7 @@ /* eslint-disable no-bitwise */ import { SmartBuffer } from 'smart-buffer'; import type { StopReason } from '../../Constants'; -import { ERROR_CODES, StopReasonCode } from '../../Constants'; +import { ErrorCode, StopReasonCode } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; export class ThreadsResponse { @@ -79,7 +79,7 @@ export class ThreadsResponse { // response fields packetLength: undefined as number, requestId: undefined as number, - errorCode: ERROR_CODES.OK + errorCode: ErrorCode.OK }; } diff --git a/src/debugProtocol/events/responses/VariablesResponse.spec.ts b/src/debugProtocol/events/responses/VariablesResponse.spec.ts new file mode 100644 index 00000000..d897f12c --- /dev/null +++ b/src/debugProtocol/events/responses/VariablesResponse.spec.ts @@ -0,0 +1,103 @@ +/* eslint-disable no-bitwise */ +import { VariablesResponse, VariableType } from './VariablesResponse'; +import { expect } from 'chai'; +import { ErrorCode } from '../../Constants'; + +describe('VariablesResponse', () => { + + it.only('Properly parses invalid variable', () => { + let response = VariablesResponse.fromJson({ + requestId: 2, + variables: [{ + name: 'person', + refCount: 2, + isConst: false, + isContainer: true, + type: VariableType.AA, + keyType: VariableType.String, + value: undefined, + children: [{ + name: 'firstName', + refCount: 1, + value: 'Bob', + type: VariableType.String, + isContainer: false, + isConst: false + }, { + name: 'lastName', + refCount: 1, + value: undefined, + isContainer: false, + type: VariableType.Invalid, + isConst: false + }] + }] + }); + + expect(response.data).to.eql({ + packetLength: undefined, + errorCode: ErrorCode.OK, + requestId: 2, + variables: [{ + name: 'person', + refCount: 2, + isConst: false, + isContainer: true, + type: VariableType.AA, + keyType: 'String', + value: undefined, + children: [{ + name: 'firstName', + refCount: 1, + value: 'Bob', + type: VariableType.String, + isContainer: false, + isConst: false + }, { + name: 'lastName', + refCount: 1, + value: undefined, + isContainer: false, + type: VariableType.Invalid, + isConst: false + }] + }] + }); + + response = VariablesResponse.fromBuffer(response.toBuffer()); + + expect(response.success).to.be.true; + + expect( + response.data + ).to.eql({ + packetLength: undefined, + errorCode: ErrorCode.OK, + requestId: 2, + variables: [{ + name: 'person', + refCount: 2, + isConst: false, + isContainer: true, + type: VariableType.AA, + keyType: 'String', + value: undefined, + children: [{ + name: 'firstName', + refCount: 1, + value: 'Bob', + type: VariableType.String, + isContainer: false, + isConst: false + }, { + name: 'lastName', + refCount: 1, + value: undefined, + isContainer: false, + type: VariableType.Invalid, + isConst: false + }] + }] + }); + }); +}); diff --git a/src/debugProtocol/events/responses/VariablesResponse.ts b/src/debugProtocol/events/responses/VariablesResponse.ts new file mode 100644 index 00000000..c008f256 --- /dev/null +++ b/src/debugProtocol/events/responses/VariablesResponse.ts @@ -0,0 +1,374 @@ +/* eslint-disable no-bitwise */ +import { SmartBuffer } from 'smart-buffer'; +import { util } from '../../../util'; +import { ErrorCode } from '../../Constants'; +import { protocolUtils } from '../../ProtocolUtil'; + +export class VariablesResponse { + + public static fromJson(data: { + requestId: number; + variables: Variable[]; + }) { + const response = new VariablesResponse(); + protocolUtils.loadJson(response, data); + response.data.variables ??= []; + return response; + } + + public static fromBuffer(buffer: Buffer) { + const response = new VariablesResponse(); + protocolUtils.bufferLoaderHelper(response, buffer, 12, (smartBuffer: SmartBuffer) => { + protocolUtils.loadCommonResponseFields(response, smartBuffer); + const numVariables = smartBuffer.readUInt32LE(); // num_variables + + + const variables: Array = []; + let latestContainer: Variable; + let variableCount = 0; + // build the list of BreakpointInfo + for (let i = 0; i < numVariables; i++) { + const variable = response.readVariable(smartBuffer); + variableCount++; + if (variable.isChildKey === false) { + latestContainer = variable as any; + delete variable.childCount; + latestContainer.children = []; + variables.push(variable); + } else if (latestContainer) { + latestContainer.children.push(variable); + } else { + variables.push(variable); + } + delete variable.isChildKey; + } + response.data.variables = variables; + + return variableCount === numVariables; + }); + return response; + } + + private readVariable(smartBuffer: SmartBuffer): Variable & { isChildKey: boolean } { + if (smartBuffer.length < 13) { + throw new Error('Not enough bytes to create a variable'); + } + const variable = {} as Variable & { isChildKey: boolean }; + const flags = smartBuffer.readUInt8(); + + // Determine the different variable properties + variable.isChildKey = (flags & VariableFlags.isChildKey) > 0; + variable.isConst = (flags & VariableFlags.isConst) > 0; + variable.isContainer = (flags & VariableFlags.isContainer) > 0; + const isNameHere = (flags & VariableFlags.isNameHere) > 0; + const isRefCounted = (flags & VariableFlags.isRefCounted) > 0; + const isValueHere = (flags & VariableFlags.isValueHere) > 0; + + variable.type = VariableTypeCode[smartBuffer.readUInt8()] as VariableType; // variable_type + + if (isNameHere) { + // we have a name. Pull it out of the buffer. + variable.name = protocolUtils.readStringNT(smartBuffer); //name + } + + if (isRefCounted) { + // This variables reference counts are tracked and we can pull it from the buffer. + variable.refCount = smartBuffer.readUInt32LE(); + } + + if (variable.isContainer) { + // It is a form of container object. + // Are the key strings or integers for example + variable.keyType = VariableTypeCode[smartBuffer.readUInt8()] as VariableType; + // Equivalent to length on arrays + variable.childCount = smartBuffer.readUInt32LE(); + } + + if (isValueHere) { + // Pull out the variable data based on the type if that type returns a value + variable.value = this.readVariableValue(variable.type, smartBuffer); + } + return variable; + } + + private readVariableValue(variableType: VariableType, smartBuffer: SmartBuffer) { + switch (variableType) { + case VariableType.Interface: + case VariableType.Object: + case VariableType.String: + case VariableType.Subroutine: + case VariableType.Function: + return protocolUtils.readStringNT(smartBuffer); + case VariableType.SubtypedObject: + let names = []; + for (let i = 0; i < 2; i++) { + names.push(protocolUtils.readStringNT(smartBuffer)); + } + + if (names.length !== 2) { + throw new Error('Expected two names for subtyped object'); + } + return names.join('; '); + case VariableType.Boolean: + return smartBuffer.readUInt8() > 0; + case VariableType.Double: + return smartBuffer.readDoubleLE(); + case VariableType.Float: + return smartBuffer.readFloatLE(); + case VariableType.Integer: + return smartBuffer.readInt32LE(); + case VariableType.LongInteger: + return smartBuffer.readBigInt64LE(); + case VariableType.Uninitialized: + return ''; + case VariableType.Unknown: + return 'Unknown'; + case VariableType.Invalid: + return 'Invalid'; + case VariableType.AA: + case VariableType.Array: + case VariableType.List: + return null; + default: + throw new Error('Unable to determine the variable value'); + } + } + + public toBuffer() { + const smartBuffer = new SmartBuffer(); + //flatten the variables + const variables = [] as Variable[]; + for (let rootVariable of this.data.variables ?? []) { + variables.push(rootVariable); + //add all child variables to the array + for (const child of rootVariable.children ?? []) { + if (variables.includes(child) && Array.isArray(child.childCount)) { + throw new Error('This variable already exists in the list. You have a circular reference in your variables that needs to be resolved'); + } + variables.push(child); + } + } + smartBuffer.writeUInt32LE(variables.length ?? 0); // num_variables + for (const variable of variables) { + this.writeVariable(variable, smartBuffer); + } + protocolUtils.insertCommonResponseFields(this, smartBuffer); + return smartBuffer.toBuffer(); + } + + private writeVariable(variable: Variable, smartBuffer: SmartBuffer) { + + let flags = 0; + //variables that have children are NOT child keys themselves + flags |= Array.isArray(variable.children) ? 0 : VariableFlags.isChildKey; + flags |= variable.isConst ? VariableFlags.isConst : 0; + flags |= variable.isContainer ? VariableFlags.isContainer : 0; + + const isNameHere = !util.isNullish(variable.name); + flags |= isNameHere ? VariableFlags.isNameHere : 0; + + const isRefCounted = variable.refCount > 0; + flags |= isRefCounted ? VariableFlags.isRefCounted : 0; + + const isValueHere = !util.isNullish(variable.value); + flags |= isValueHere ? VariableFlags.isValueHere : 0; + + smartBuffer.writeUInt8(flags); //flags + smartBuffer.writeUInt8(VariableTypeCode[variable.type] as number); // variable_type + + if (isNameHere) { + smartBuffer.writeStringNT(variable.name); //name + } + + if (isRefCounted) { + smartBuffer.writeUInt32LE(variable.refCount); //ref_count + } + + if (variable.isContainer) { + smartBuffer.writeUInt8(VariableTypeCode[variable.keyType] as number); // key_type + // Equivalent to .length on arrays + smartBuffer.writeUInt32LE( + variable.children?.length ?? variable.childCount + ); // element_count + } + + if (isValueHere) { + // write the variable data based on the type + this.writeVariableValue(variable.type, variable.value, smartBuffer); + } + return variable; + } + + + private writeVariableValue(variableType: VariableType, value: any, smartBuffer: SmartBuffer) { + switch (variableType) { + case VariableType.Interface: + case VariableType.Object: + case VariableType.String: + case VariableType.Subroutine: + case VariableType.Function: + return smartBuffer.writeStringNT(value as string); + case VariableType.SubtypedObject: + let names = []; + for (let i = 0; i < 2; i++) { + names.push(protocolUtils.readStringNT(smartBuffer)); + } + + if (names.length !== 2) { + throw new Error('Expected two names for subtyped object'); + } + return names.join('; '); + case VariableType.Boolean: + return smartBuffer.writeUInt8(value === true ? 1 : 0); + case VariableType.Double: + return smartBuffer.writeDoubleLE(value as number); + case VariableType.Float: + return smartBuffer.writeFloatLE(value as number); + case VariableType.Integer: + return smartBuffer.writeInt32LE(value as number); + case VariableType.LongInteger: + return smartBuffer.writeBigInt64LE(value as bigint); + case VariableType.Uninitialized: + case VariableType.Unknown: + case VariableType.Invalid: + case VariableType.AA: + case VariableType.Array: + case VariableType.List: + return null; + default: + throw new Error('Unable to determine the variable value'); + } + } + + public success = false; + + public readOffset = 0; + + public data = { + variables: undefined as Variable[], + + // response fields + packetLength: undefined as number, + requestId: undefined as number, + errorCode: ErrorCode.OK + }; +} + +export enum VariableFlags { + /** + * value is a child of the requested variable + * e.g., an element of an array or field of an AA + */ + isChildKey = 1, + /** + * value is constant + */ + isConst = 2, + /** + * The referenced value is a container (e.g., a list or array) + */ + isContainer = 4, + /** + * The name is included in this VariableInfo + */ + isNameHere = 8, + /** + * value is reference-counted. + */ + isRefCounted = 16, + /** + * value is included in this VariableInfo + */ + isValueHere = 32, + /** + * Value is container, key lookup is case sensitive + * @since protocol 3.1.0 + */ + isKeysCaseSensitive = 64 +} + +/** + * Every type of variable supported by the protocol + */ +export enum VariableType { + AA = 'AA', + Array = 'Array', + Boolean = 'Boolean', + Double = 'Double', + Float = 'Float', + Function = 'Function', + Integer = 'Integer', + Interface = 'Interface', + Invalid = 'Invalid', + List = 'List', + LongInteger = 'LongInteger', + Object = 'Object', + String = 'String', + Subroutine = 'Subroutine', + SubtypedObject = 'SubtypedObject', + Uninitialized = 'Uninitialized', + Unknown = 'Unknown' +} + +/** + * An enum used to convert VariableType strings to their protocol integer value + */ +enum VariableTypeCode { + AA = 1, + Array = 2, + Boolean = 3, + Double = 4, + Float = 5, + Function = 6, + Integer = 7, + Interface = 8, + Invalid = 9, + List = 10, + LongInteger = 11, + Object = 12, + String = 13, + Subroutine = 14, + Subtyped_Object = 15, + Uninitialized = 16, + Unknown = 17 +} + +export interface Variable { + /** + * 0 means this var isn't refCountded, and will be omitted from reading and writing to buffer + */ + refCount: number; + /** + * I think this means "is this mutatable". Like an object would be isConst=false, but "some string" can only be replaced by a totally new string? But don't quote me on this + */ + isConst: boolean; + /** + * A type-dependent value based on the `variableType` field. It is not present for all types + */ + value: string | number | bigint | boolean | null; + /** + * The type of variable or value. + */ + type: VariableType; + /** + * The variable name. `undefined` means there was no variable name available + */ + name?: string; + /** + * If this variable is a container, what variable type are its keys? (integer for array, string for AA, etc...). + * TODO can we get roku to narrow this a bit? + */ + keyType?: VariableType; + /** + * Is this variable a container var (i.e. an array or object with children) + */ + isContainer: boolean; + /** + * If the variable is a container, it will have child elements. this is the number of those children. This field is ignored when serializing if `.children` is set + */ + childCount?: number; + /** + * The full list of children for this variable. This list may not be more than 2 total levels deep (i.e. `parent` -> `children`). Children may not have additional children. + */ + children?: Variable[]; +} diff --git a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts index bcdf5127..27b7bb86 100644 --- a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts +++ b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { ERROR_CODES, StopReasonCode, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, StopReasonCode, UPDATE_TYPES } from '../../Constants'; import { AllThreadsStoppedUpdate } from './AllThreadsStoppedUpdate'; describe('AllThreadsStoppedUpdate', () => { @@ -13,7 +13,7 @@ describe('AllThreadsStoppedUpdate', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 0, - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, updateType: UPDATE_TYPES.ALL_THREADS_STOPPED, primaryThreadIndex: 1, @@ -26,7 +26,7 @@ describe('AllThreadsStoppedUpdate', () => { ).to.eql({ packetLength: 29, // 4 bytes requestId: 0, // 4 bytes - errorCode: ERROR_CODES.OK, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes updateType: UPDATE_TYPES.ALL_THREADS_STOPPED, // 4 bytes primaryThreadIndex: 1, // 4 bytes diff --git a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts index c5d034a8..a34e0333 100644 --- a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts +++ b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; import type { StopReasonCode } from '../../Constants'; -import { ERROR_CODES, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, UPDATE_TYPES } from '../../Constants'; import { util } from '../../../util'; import { protocolUtils } from '../../ProtocolUtil'; import type { ProtocolUpdate } from '../ProtocolEvent'; @@ -57,7 +57,7 @@ export class AllThreadsStoppedUpdate implements ProtocolUpdate { //common props packetLength: undefined as number, requestId: 0, //all updates have requestId === 0 - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, updateType: UPDATE_TYPES.ALL_THREADS_STOPPED }; } diff --git a/src/debugProtocol/events/updates/BreakpointErrorUpdate.spec.ts b/src/debugProtocol/events/updates/BreakpointErrorUpdate.spec.ts index 9273497a..5b5b183c 100644 --- a/src/debugProtocol/events/updates/BreakpointErrorUpdate.spec.ts +++ b/src/debugProtocol/events/updates/BreakpointErrorUpdate.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { ERROR_CODES, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, UPDATE_TYPES } from '../../Constants'; import { BreakpointErrorUpdate } from './BreakpointErrorUpdate'; describe('BreakpointErrorUpdate', () => { @@ -20,7 +20,7 @@ describe('BreakpointErrorUpdate', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 0, - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, updateType: UPDATE_TYPES.BREAKPOINT_ERROR, breakpointId: 3, @@ -40,7 +40,7 @@ describe('BreakpointErrorUpdate', () => { ).to.eql({ packetLength: 64, // 4 bytes requestId: 0, // 4 bytes - errorCode: ERROR_CODES.OK, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes updateType: UPDATE_TYPES.BREAKPOINT_ERROR, // 4 bytes //flags // 4 bytes @@ -72,7 +72,7 @@ describe('BreakpointErrorUpdate', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 0, - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, updateType: UPDATE_TYPES.BREAKPOINT_ERROR, breakpointId: 3, @@ -86,7 +86,7 @@ describe('BreakpointErrorUpdate', () => { ).to.eql({ packetLength: 36, // 4 bytes requestId: 0, // 4 bytes - errorCode: ERROR_CODES.OK, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes updateType: UPDATE_TYPES.BREAKPOINT_ERROR, // 4 bytes //flags // 4 bytes diff --git a/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts b/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts index 099510db..8536a385 100644 --- a/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts +++ b/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; import { util } from '../../../util'; -import { ERROR_CODES, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, UPDATE_TYPES } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; /** @@ -108,7 +108,7 @@ export class BreakpointErrorUpdate { //common props packetLength: undefined as number, requestId: 0, //all updates have requestId === 0 - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, updateType: UPDATE_TYPES.BREAKPOINT_ERROR }; } diff --git a/src/debugProtocol/events/updates/CompileErrorUpdate.spec.ts b/src/debugProtocol/events/updates/CompileErrorUpdate.spec.ts index 8217e8b8..6f8a1727 100644 --- a/src/debugProtocol/events/updates/CompileErrorUpdate.spec.ts +++ b/src/debugProtocol/events/updates/CompileErrorUpdate.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { ERROR_CODES, StopReasonCode, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, StopReasonCode, UPDATE_TYPES } from '../../Constants'; import { CompileErrorUpdate } from './CompileErrorUpdate'; describe('CompileErrorUpdate', () => { @@ -14,7 +14,7 @@ describe('CompileErrorUpdate', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 0, - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, updateType: UPDATE_TYPES.COMPILE_ERROR, errorMessage: 'crashed', @@ -28,7 +28,7 @@ describe('CompileErrorUpdate', () => { ).to.eql({ packetLength: 58, // 4 bytes requestId: 0, // 4 bytes - errorCode: ERROR_CODES.OK, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes updateType: UPDATE_TYPES.COMPILE_ERROR, // 4 bytes errorMessage: 'crashed', // 8 bytes diff --git a/src/debugProtocol/events/updates/CompileErrorUpdate.ts b/src/debugProtocol/events/updates/CompileErrorUpdate.ts index 70c80a08..af1a5bcb 100644 --- a/src/debugProtocol/events/updates/CompileErrorUpdate.ts +++ b/src/debugProtocol/events/updates/CompileErrorUpdate.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; import { util } from '../../../util'; -import { ERROR_CODES, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, UPDATE_TYPES } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; /** @@ -87,7 +87,7 @@ export class CompileErrorUpdate { //common props packetLength: undefined as number, requestId: 0, //all updates have requestId === 0 - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, updateType: UPDATE_TYPES.COMPILE_ERROR }; } diff --git a/src/debugProtocol/events/updates/IOPortOpenedUpdate.spec.ts b/src/debugProtocol/events/updates/IOPortOpenedUpdate.spec.ts index a04ed193..a34afc82 100644 --- a/src/debugProtocol/events/updates/IOPortOpenedUpdate.spec.ts +++ b/src/debugProtocol/events/updates/IOPortOpenedUpdate.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { ERROR_CODES, StopReasonCode, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, StopReasonCode, UPDATE_TYPES } from '../../Constants'; import { IOPortOpenedUpdate } from './IOPortOpenedUpdate'; describe('IOPortOpenedUpdate', () => { @@ -11,7 +11,7 @@ describe('IOPortOpenedUpdate', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 0, - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, updateType: UPDATE_TYPES.IO_PORT_OPENED, port: 1234 @@ -22,7 +22,7 @@ describe('IOPortOpenedUpdate', () => { ).to.eql({ packetLength: 20, // 4 bytes requestId: 0, // 4 bytes - errorCode: ERROR_CODES.OK, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes updateType: UPDATE_TYPES.IO_PORT_OPENED, // 4 bytes port: 1234 // 4 bytes diff --git a/src/debugProtocol/events/updates/IOPortOpenedUpdate.ts b/src/debugProtocol/events/updates/IOPortOpenedUpdate.ts index cbbb7f43..346e5e22 100644 --- a/src/debugProtocol/events/updates/IOPortOpenedUpdate.ts +++ b/src/debugProtocol/events/updates/IOPortOpenedUpdate.ts @@ -1,5 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import { ERROR_CODES, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, UPDATE_TYPES } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; export class IOPortOpenedUpdate { @@ -44,7 +44,7 @@ export class IOPortOpenedUpdate { //common props packetLength: undefined as number, requestId: 0, //all updates have requestId === 0 - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, updateType: UPDATE_TYPES.IO_PORT_OPENED }; } diff --git a/src/debugProtocol/events/updates/ThreadAttachedUpdate.spec.ts b/src/debugProtocol/events/updates/ThreadAttachedUpdate.spec.ts index b007512a..c24e9c6e 100644 --- a/src/debugProtocol/events/updates/ThreadAttachedUpdate.spec.ts +++ b/src/debugProtocol/events/updates/ThreadAttachedUpdate.spec.ts @@ -1,12 +1,11 @@ import { expect } from 'chai'; -import { ERROR_CODES, StopReasonCode, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, StopReasonCode, UPDATE_TYPES } from '../../Constants'; import { ThreadAttachedUpdate } from './ThreadAttachedUpdate'; describe('AllThreadsStoppedUpdate', () => { it('serializes and deserializes properly', () => { const update = ThreadAttachedUpdate.fromJson({ threadIndex: 1, - errorCode: ERROR_CODES.OK, stopReason: StopReasonCode.Break, stopReasonDetail: 'because' }); @@ -14,7 +13,7 @@ describe('AllThreadsStoppedUpdate', () => { expect(update.data).to.eql({ packetLength: undefined, requestId: 0, - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, updateType: UPDATE_TYPES.THREAD_ATTACHED, threadIndex: 1, @@ -27,7 +26,7 @@ describe('AllThreadsStoppedUpdate', () => { ).to.eql({ packetLength: 29, // 4 bytes requestId: 0, // 4 bytes - errorCode: ERROR_CODES.OK, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes updateType: UPDATE_TYPES.THREAD_ATTACHED, // 4 bytes threadIndex: 1, // 4 bytes diff --git a/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts b/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts index 0453ce52..86aa9914 100644 --- a/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts +++ b/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; import type { StopReasonCode } from '../../Constants'; -import { ERROR_CODES, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, UPDATE_TYPES } from '../../Constants'; import { util } from '../../../util'; import { protocolUtils } from '../../ProtocolUtil'; @@ -50,7 +50,7 @@ export class ThreadAttachedUpdate { //common props packetLength: undefined as number, requestId: 0, //all updates have requestId === 0 - errorCode: ERROR_CODES.OK, + errorCode: ErrorCode.OK, updateType: UPDATE_TYPES.THREAD_ATTACHED }; } diff --git a/src/debugProtocol/events/zzresponsesOld/index.ts b/src/debugProtocol/events/zzresponsesOld/index.ts deleted file mode 100644 index 82f02c4d..00000000 --- a/src/debugProtocol/events/zzresponsesOld/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from '../updates/IOPortOpenedUpdate'; -export * from '../responses/GenericResponse'; -export * from '../responses/GenericV3Response'; -export * from '../responses/StackTraceResponse'; -export * from '../responses/StackTraceV3Response'; -export * from '../responses/ThreadsResponse'; -export * from '../updates/ThreadAttachedUpdate'; -export * from '../responses/VariableResponse'; diff --git a/src/debugProtocol/events/zzresponsesOld/responseCreationHelpers.spec.ts b/src/debugProtocol/events/zzresponsesOld/responseCreationHelpers.spec.ts deleted file mode 100644 index 3a6ba7fe..00000000 --- a/src/debugProtocol/events/zzresponsesOld/responseCreationHelpers.spec.ts +++ /dev/null @@ -1,290 +0,0 @@ -/* eslint-disable @typescript-eslint/no-loop-func */ -/* eslint-disable no-bitwise */ -import { SmartBuffer } from 'smart-buffer'; -import { ERROR_CODES, UPDATE_TYPES, VARIABLE_FLAGS, VARIABLE_TYPES } from '../../Constants'; -import type { BreakpointInfo } from '../responses/ListBreakpointsResponse'; - -interface Handshake { - magic: string; - major: number; - minor: number; - patch: number; -} - -export function createHandShakeResponse(handshake: Handshake): SmartBuffer { - let buffer = new SmartBuffer(); - buffer.writeStringNT(handshake.magic); // magic_number - buffer.writeUInt32LE(handshake.major); // protocol_major_version - buffer.writeUInt32LE(handshake.minor); // protocol_minor_version - buffer.writeUInt32LE(handshake.patch); // protocol_patch_version - return buffer; -} - -interface HandshakeV3 { - magic: string; - major: number; - minor: number; - patch: number; - - // populated by helper. - // commented out here for the sake of documenting it. - // remainingPacketLength: number; - - revisionTimeStamp: number; -} - -export function createHandShakeResponseV3(handshake: HandshakeV3, extraBufferData?: Buffer): SmartBuffer { - let buffer = new SmartBuffer(); - buffer.writeStringNT(handshake.magic); // magic_number - buffer.writeUInt32LE(handshake.major); // protocol_major_version - buffer.writeUInt32LE(handshake.minor); // protocol_minor_version - buffer.writeUInt32LE(handshake.patch); // protocol_patch_version - - let timeStampBuffer = new SmartBuffer(); - timeStampBuffer.writeBigInt64LE(BigInt(handshake.revisionTimeStamp)); // platform_revision_timestamp - - buffer.writeUInt32LE(timeStampBuffer.writeOffset + 4 + (extraBufferData ? extraBufferData.length : 0)); // remaining_packet_length - buffer.writeBuffer(timeStampBuffer.toBuffer()); - - if (extraBufferData) { - buffer.writeBuffer(extraBufferData); - } - - return buffer; -} - -interface ProtocolEvent { - requestId: number; - errorCode: ERROR_CODES; - updateType?: UPDATE_TYPES; -} - -export function createProtocolEvent(protocolEvent: ProtocolEvent, extraBufferData?: Buffer): SmartBuffer { - let buffer = new SmartBuffer(); - buffer.writeUInt32LE(protocolEvent.requestId); // request_id - buffer.writeUInt32LE(protocolEvent.errorCode); // error_code - - // If this is an update type make sure to add the update type value - if (protocolEvent.requestId === 0) { - buffer.writeInt32LE(protocolEvent.updateType); // update_type - } - - // write any extra data for testing - if (extraBufferData) { - buffer.writeBuffer(extraBufferData); - } - - return buffer; -} - -export function createProtocolEventV3(protocolEvent: ProtocolEvent, extraBufferData?: Buffer): SmartBuffer { - let buffer = new SmartBuffer(); - buffer.writeUInt32LE(protocolEvent.requestId); // request_id - buffer.writeUInt32LE(protocolEvent.errorCode); // error_code - - // If this is an update type make sure to add the update type value - if (protocolEvent.requestId === 0) { - buffer.writeInt32LE(protocolEvent.updateType); // update_type - } - - // write any extra data for testing - if (extraBufferData) { - buffer.writeBuffer(extraBufferData); - } - - return addPacketLength(buffer); -} - -/** - * Add packetLength to the beginning of the buffer - */ -function addPacketLength(buffer: SmartBuffer): SmartBuffer { - return buffer.insertUInt32LE(buffer.length + 4, 0); // packet_length - The size of the packet to be sent. -} - -/** - * Create a buffer for `ListBreakpointsResponse` - */ -export function createListBreakpointsResponse(params: { requestId?: number; errorCode?: number; num_breakpoints?: number; breakpoints?: Partial[]; extraBufferData?: Buffer }): SmartBuffer { - let buffer = new SmartBuffer(); - - writeIfSet(params.requestId, x => buffer.writeUInt32LE(x)); - writeIfSet(params.errorCode, x => buffer.writeUInt32LE(x)); - - buffer.writeUInt32LE(params.num_breakpoints ?? params.breakpoints?.length ?? 0); // num_breakpoints - for (const breakpoint of params?.breakpoints ?? []) { - writeIfSet(breakpoint.id, x => buffer.writeUInt32LE(x)); - writeIfSet(breakpoint.errorCode, x => buffer.writeUInt32LE(x)); - writeIfSet(breakpoint.hitCount, x => buffer.writeUInt32LE(x)); - } - - // write any extra data for testing - writeIfSet(params.extraBufferData, x => buffer.writeBuffer(x)); - - return addPacketLength(buffer); -} - -interface Variable { - variableType: VARIABLE_TYPES; - name: string; - flags?: number; - refCount: number; - isConst: boolean; - children?: Variable[]; - value: any; - keyType?: VARIABLE_TYPES; -} - -export function createVariableResponse(params: { - requestId?: number; variables?: Variable[]; errorCode?: number; extraBufferData?: Buffer; includePacketLength?: boolean; -}): SmartBuffer { - let buffer = new SmartBuffer(); - - writeIfSet(params.requestId, x => buffer.writeUInt32LE(x)); - writeIfSet(params.errorCode, x => buffer.writeUInt32LE(x)); - - const variables = [...params.variables]; - for (let i = 0; i < variables.length; i++) { - const variable = variables[i]; - if (variable.children) { - variables.splice(i + 1, 0, ...variable.children); - } - } - - writeIfSet(variables?.length, x => buffer.writeUInt32LE(x)); - - while (variables.length > 0) { - const variable = variables.shift(); - let flags = 0; - if (variable.isConst) { - flags |= VARIABLE_FLAGS.isConst; - } - if (variable.children) { - flags |= VARIABLE_FLAGS.isContainer; - } - if (variable.name !== undefined) { - flags |= VARIABLE_FLAGS.isNameHere; - } - if (variable.refCount !== undefined) { - flags |= VARIABLE_FLAGS.isRefCounted; - } - if (variable.value !== undefined) { - flags |= VARIABLE_FLAGS.isValueHere; - } - buffer.writeUInt8(flags); //flags - writeIfSet(variable.variableType, x => buffer.writeUInt8(x)); //variable_type - writeIfSet(variable.name, x => buffer.writeStringNT(variable.name)); - if (variable.refCount !== undefined) { - writeIfSet(variable.refCount, x => buffer.writeUInt32LE(variable.refCount)); - } - if (variable.children) { - for (const child of variable.children) { - child.flags = (child.flags ?? 0) | VARIABLE_FLAGS.isChildKey; - } - writeIfSet(variable.keyType, x => buffer.writeUInt8(variable.keyType)); - //element_count - writeIfSet(variable.children.length, x => buffer.writeUInt32LE(variable.keyType)); - } - - switch (variable.variableType) { - case VARIABLE_TYPES.Interface: - case VARIABLE_TYPES.Object: - case VARIABLE_TYPES.String: - case VARIABLE_TYPES.Subroutine: - case VARIABLE_TYPES.Function: - buffer.writeStringNT(variable.value); - break; - case VARIABLE_TYPES.Subtyped_Object: - buffer.writeStringNT(variable.value[0]); - buffer.writeStringNT(variable.value[1]); - break; - case VARIABLE_TYPES.Boolean: - buffer.writeUInt8(variable.value ? 1 : 0); - break; - case VARIABLE_TYPES.Double: - buffer.writeDoubleLE(variable.value); - break; - case VARIABLE_TYPES.Float: - buffer.writeFloatLE(variable.value); - break; - case VARIABLE_TYPES.Integer: - buffer.writeInt32LE(variable.value); - break; - case VARIABLE_TYPES.Long_Integer: - buffer.writeBigInt64LE(variable.value); - break; - default: - //nothing to write - break; - } - } - - if (params.includePacketLength) { - buffer = addPacketLength(buffer); - } - return buffer; -} - -/** - * Contains a list of breakpoint errors - */ -export function createBreakpointErrorUpdateResponse(params: { errorCode?: number; flags?: number; breakpoint_id?: number; compile_errors?: string[]; runtime_errors?: string[]; other_errors?: string[]; extraBufferData?: Buffer; includePacketLength?: boolean }): SmartBuffer { - let buffer = new SmartBuffer(); - - writeIfSet(0, x => buffer.writeUInt32LE(x)); //request_id - writeIfSet(ERROR_CODES.OK, x => buffer.writeUInt32LE(x)); //error_code - writeIfSet(UPDATE_TYPES.BREAKPOINT_ERROR, x => buffer.writeUInt32LE(x)); //update_type - - writeIfSet(params.flags, x => buffer.writeUInt32LE(x)); //flags - - writeIfSet(params.breakpoint_id, x => buffer.writeUInt32LE(x)); //breakpoint_id - - writeIfSet(params.compile_errors?.length, x => buffer.writeUInt32LE(x)); - for (const error of params.compile_errors ?? []) { - buffer.writeStringNT(error); - } - - writeIfSet(params.runtime_errors?.length, x => buffer.writeUInt32LE(x)); - for (const error of params.runtime_errors ?? []) { - buffer.writeStringNT(error); - } - - writeIfSet(params.other_errors?.length, x => buffer.writeUInt32LE(x)); - for (const error of params.other_errors ?? []) { - buffer.writeStringNT(error); - } - - // write any extra data for testing - writeIfSet(params.extraBufferData, x => buffer.writeBuffer(x)); - - if (params.includePacketLength) { - buffer = addPacketLength(buffer); - } - return buffer; -} - -/** - * If the value is undefined or null, skip the callback. - * All other values will cause the callback to be called - */ -function writeIfSet(value: T, writer: (x: T) => R, defaultValue?: T) { - if ( - //if we have a value - (value !== undefined && value !== null) || - //we don't have a value, but we have a default value - (defaultValue !== undefined && defaultValue !== null) - ) { - return writer(value); - } -} - -/** - * Build a buffer of `byteCount` size and fill it with random data - */ -export function getRandomBuffer(byteCount: number) { - const result = new SmartBuffer(); - for (let i = 0; i < byteCount; i++) { - result.writeUInt8(i); - } - return result.toBuffer(); -} diff --git a/src/debugProtocol/responseCreationHelpers.spec.ts b/src/debugProtocol/responseCreationHelpers.spec.ts new file mode 100644 index 00000000..957a632b --- /dev/null +++ b/src/debugProtocol/responseCreationHelpers.spec.ts @@ -0,0 +1 @@ +import { SmartBuffer } from 'smart-buffer'; diff --git a/src/debugProtocol/server/ProtocolPlugin.ts b/src/debugProtocol/server/ProtocolPlugin.ts index 203d4784..ade8cad8 100644 --- a/src/debugProtocol/server/ProtocolPlugin.ts +++ b/src/debugProtocol/server/ProtocolPlugin.ts @@ -1,7 +1,6 @@ import type { DebugProtocolServer } from './DebugProtocolServer'; -import type { ProtocolResponse } from '../events/zzresponsesOld/ProtocolResponse'; import type { Socket } from 'net'; -import type { ProtocolRequest } from '../events/requests/ProtocolRequest'; +import type { ProtocolRequest, ProtocolResponse } from '../events/ProtocolEvent'; export interface ProtocolPlugin { onClientConnected?: Handler; diff --git a/src/index.ts b/src/index.ts index b6d14de8..c48abc78 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,6 @@ export * from './ComponentLibraryServer'; export * from './CompileErrorProcessor'; export * from './debugProtocol/Constants'; export * from './debugProtocol/client/DebugProtocolClient'; -export * from './debugProtocol/events/zzresponsesOld'; export * from './FileUtils'; export * from './managers/ProjectManager'; export * from './RendezvousTracker'; diff --git a/src/testHelpers.spec.ts b/src/testHelpers.spec.ts index 69689a3e..26f594e5 100644 --- a/src/testHelpers.spec.ts +++ b/src/testHelpers.spec.ts @@ -1,5 +1,6 @@ import { expect } from 'chai'; import dedent = require('dedent'); +import { SmartBuffer } from 'smart-buffer'; /** * Forces all line endings to \n @@ -62,3 +63,14 @@ export function expectPickEquals(subjects: any[], patterns: any[]) { patterns ); } + +/** + * Build a buffer of `byteCount` size and fill it with random data + */ +export function getRandomBuffer(byteCount: number) { + const result = new SmartBuffer(); + for (let i = 0; i < byteCount; i++) { + result.writeUInt8(i); + } + return result.toBuffer(); +} From 29c4bc3c97388ea6e667328ff854e0a874a29bd8 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Tue, 11 Oct 2022 22:34:07 -0400 Subject: [PATCH 11/74] More fixes --- src/adapters/DebugProtocolAdapter.spec.ts | 11 +- .../client/DebugProtocolClient.ts | 190 +++++++++--------- src/debugProtocol/events/ProtocolEvent.ts | 6 +- .../events/responses/ThreadsResponse.ts | 5 +- .../responses/VariablesResponse.spec.ts | 47 +++-- .../events/updates/AllThreadsStoppedUpdate.ts | 9 +- .../events/updates/ThreadAttachedUpdate.ts | 3 + 7 files changed, 136 insertions(+), 135 deletions(-) diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index e18067d9..32f9d327 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -3,9 +3,7 @@ import { expect } from 'chai'; import { DebugProtocolClient } from '../debugProtocol/client/DebugProtocolClient'; import { DebugProtocolAdapter } from './DebugProtocolAdapter'; import { createSandbox } from 'sinon'; -import type { VariableInfo } from '../debugProtocol/events/zzresponsesOld'; -import { VariablesResponse } from '../debugProtocol/events/zzresponsesOld'; -import { ErrorCode } from './../debugProtocol/Constants'; +import { VariablesResponse } from '../debugProtocol/events/responses/VariablesResponse'; const sinon = createSandbox(); describe('DebugProtocolAdapter', () => { @@ -29,9 +27,10 @@ describe('DebugProtocolAdapter', () => { let variables: Partial[]; beforeEach(() => { - response = new VariablesResponse(Buffer.alloc(5)); - response.errorCode = ErrorCode.OK; - variables = []; + response = VariablesResponse.fromJson({ + requestId: 3, + variables: [] + }); sinon.stub(adapter as any, 'getStackFrameById').returns({}); sinon.stub(socketDebugger, 'getVariables').callsFake(() => { response.variables = variables as any; diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index f6f71557..9df669cf 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -22,7 +22,7 @@ import { ThreadsRequest } from '../events/requests/ThreadsRequest'; import { ExecuteRequest } from '../events/requests/ExecuteRequest'; import { AddBreakpointsRequest } from '../events/requests/AddBreakpointsRequest'; import { AddConditionalBreakpointsRequest } from '../events/requests/AddConditionalBreakpointsRequest'; -import type { ProtocolRequest, ProtocolResponse, ProtocolUpdate } from '../events/ProtocolEvent'; +import type { ProtocolEvent, ProtocolRequest, ProtocolResponse, ProtocolUpdate } from '../events/ProtocolEvent'; import { HandshakeResponse } from '../events/responses/HandshakeResponse'; import { HandshakeV3Response } from '../events/responses/HandshakeV3Response'; import { HandshakeRequest } from '../events/requests/HandshakeRequest'; @@ -36,6 +36,7 @@ import { ThreadsResponse } from '../events/responses/ThreadsResponse'; import { VariablesResponse } from '../events/responses/VariablesResponse'; import { IOPortOpenedUpdate } from '../events/updates/IOPortOpenedUpdate'; import { ThreadAttachedUpdate } from '../events/updates/ThreadAttachedUpdate'; +import { StackTraceV3Response } from '../events/responses/StackTraceV3Response'; export class DebugProtocolClient { @@ -108,8 +109,7 @@ export class DebugProtocolClient { * Get a promise that resolves after an event occurs exactly once */ public once(eventName: 'app-exit' | 'cannot-continue' | 'close' | 'start'): Promise; - public once(eventName: 'data'): Promise; - public once(eventName: 'runtime-error' | 'suspend'): Promise; + public once(eventName: 'runtime-error' | 'suspend'): Promise; public once(eventName: 'io-output'): Promise; public once(eventName: 'protocol-version'): Promise; public once(eventName: 'handshake-verified'): Promise; @@ -125,7 +125,7 @@ export class DebugProtocolClient { public on(eventName: 'app-exit' | 'cannot-continue' | 'close' | 'start', handler: () => void); public on(eventName: 'response', handler: (update: ProtocolResponse) => void); public on(eventName: 'update', handler: (update: ProtocolUpdate) => void); - public on(eventName: 'runtime-error' | 'suspend', handler: (data: UpdateThreadsResponse) => void); + public on(eventName: 'runtime-error' | 'suspend', handler: (data: AllThreadsStoppedUpdate | ThreadAttachedUpdate) => void); public on(eventName: 'io-output', handler: (output: string) => void); public on(eventName: 'protocol-version', handler: (data: ProtocolVersionDetails) => void); public on(eventName: 'handshake-verified', handler: (data: HandshakeResponse) => void); @@ -140,8 +140,8 @@ export class DebugProtocolClient { private emit(eventName: 'response', response: ProtocolResponse); private emit(eventName: 'update', update: ProtocolUpdate); - private emit(eventName: 'suspend' | 'runtime-error', data: UpdateThreadsResponse); - private emit(eventName: 'app-exit' | 'cannot-continue' | 'close' | 'data' | 'handshake-verified' | 'io-output' | 'protocol-version' | 'start', data?); + private emit(eventName: 'suspend' | 'runtime-error', data: AllThreadsStoppedUpdate | ThreadAttachedUpdate); + private emit(eventName: 'app-exit' | 'cannot-continue' | 'close' | 'handshake-verified' | 'io-output' | 'protocol-version' | 'start', data?); private emit(eventName: string, data?) { //emit these events on next tick, otherwise they will be processed immediately which could cause issues setTimeout(() => { @@ -211,13 +211,15 @@ export class DebugProtocolClient { }); //subscribe to all unsolicited updates - this.on('update', this.handleUpdate.bind(this)); + this.on('update', (update) => { + this.handleUpdate(update); + }); //send the magic, which triggers the debug session this.logger.log('Sending magic to server'); //send the handshake request, and wait for the handshake response from the device - const response = await this.makeRequest( + const response = await this.sendRequest( HandshakeRequest.fromJson({ magic: DebugProtocolClient.DEBUGGER_MAGIC }) @@ -231,7 +233,7 @@ export class DebugProtocolClient { public async continue() { if (this.stopped) { this.stopped = false; - return this.makeRequest( + return this.sendRequest( ContinueRequest.fromJson({ requestId: this.totalRequests++ }) @@ -241,7 +243,7 @@ export class DebugProtocolClient { public async pause(force = false) { if (!this.stopped || force) { - return this.makeRequest( + return this.sendRequest( StopRequest.fromJson({ requestId: this.totalRequests++ }) @@ -250,7 +252,7 @@ export class DebugProtocolClient { } public async exitChannel() { - return this.makeRequest( + return this.sendRequest( ExitChannelRequest.fromJson({ requestId: this.totalRequests++ }) @@ -277,7 +279,7 @@ export class DebugProtocolClient { buffer.writeUInt8(stepType); // step_type if (this.stopped) { this.stopped = false; - let stepResult = await this.makeRequest( + let stepResult = await this.sendRequest( StepRequest.fromJson({ requestId: this.totalRequests++, stepType: stepType, @@ -297,20 +299,21 @@ export class DebugProtocolClient { public async threads() { if (this.stopped) { - let result = await this.makeRequest( + let result = await this.sendRequest( ThreadsRequest.fromJson({ requestId: this.totalRequests++ - })); + }) + ); - if (result.errorCode === ErrorCode.OK) { + if (result.data.errorCode === ErrorCode.OK) { //older versions of the debug protocol had issues with maintaining the active thread, so our workaround is to keep track of it elsewhere if (this.enableThreadHoppingWorkaround) { //ignore the `isPrimary` flag on threads - this.logger.debug(`Ignoring the 'isPrimary' flag from threads because protocol version ${this.protocolVersion} and lower has a bug`); + this.logger.debug(`Ignoring the 'isPrimary' flag from threads because protocol version 3.0.0 and lower has a bug`); } else { //trust the debug protocol's `isPrimary` flag on threads - for (let i = 0; i < result.threadsCount; i++) { - let thread = result.threads[i]; + for (let i = 0; i < result.data.threads.length; i++) { + let thread = result.data.threads[i]; if (thread.isPrimary) { this.primaryThread = i; break; @@ -326,7 +329,7 @@ export class DebugProtocolClient { let buffer = new SmartBuffer({ size: 16 }); buffer.writeUInt32LE(threadIndex); // thread_index if (this.stopped && threadIndex > -1) { - return this.makeRequest( + return this.sendRequest( StackTraceRequest.fromJson({ requestId: this.totalRequests++, threadIndex: threadIndex @@ -361,13 +364,13 @@ export class DebugProtocolClient { //starting in protocol v3.1.0, it supports marking certain path items as case-insensitive (i.e. parts of DottedGet expressions) enableCaseInsensitivityFlag: semver.satisfies(this.protocolVersion, '>=3.1.0') && variablePathEntries.length > 0 }); - return this.makeRequest(request); + return this.sendRequest(request); } } public async executeCommand(sourceCode: string, stackFrameIndex: number = this.stackFrameIndex, threadIndex: number = this.primaryThread) { if (this.stopped && threadIndex > -1) { - return this.makeRequest( + return this.sendRequest( ExecuteRequest.fromJson({ requestId: this.totalRequests++, threadIndex: threadIndex, @@ -390,11 +393,11 @@ export class DebugProtocolClient { }; if (this.supportsConditionalBreakpoints) { - return this.makeRequest( + return this.sendRequest( AddBreakpointsRequest.fromJson(json) ); } else { - return this.makeRequest( + return this.sendRequest( AddConditionalBreakpointsRequest.fromJson(json) ); } @@ -403,7 +406,7 @@ export class DebugProtocolClient { } public async listBreakpoints(): Promise { - return this.makeRequest( + return this.sendRequest( ListBreakpointsRequest.fromJson({ requestId: this.totalRequests++ }) @@ -416,12 +419,15 @@ export class DebugProtocolClient { requestId: this.totalRequests++, breakpointIds: breakpointIds }); - return this.makeRequest(command); + return this.sendRequest(command); } return RemoveBreakpointsResponse.fromJson(null); } - private async makeRequest(request: ProtocolRequest) { + /** + * Send a request to the roku device, and get a promise that resolves once we have received the response + */ + private async sendRequest(request: ProtocolRequest) { this.totalRequests++; let requestId = this.totalRequests; @@ -431,11 +437,12 @@ export class DebugProtocolClient { let unsubscribe = this.on('response', (event) => { if (event.data.requestId === requestId) { unsubscribe(); + this.activeRequests1.delete(requestId); resolve(event as T); } }); - this.logger.debug('makeRequest', `requestId=${requestId}`, this.activeRequests1.get(requestId)); + this.logger.debug('makeRequest', `requestId=${requestId}`, request); if (this.controllerClient) { this.controllerClient.write(request.toBuffer()); } else { @@ -444,48 +451,44 @@ export class DebugProtocolClient { }); } - private process(): boolean { + private process(): void { if (this.buffer.length < 1) { // short circuit if the buffer is empty - return false; + return; } const event = this.getResponseOrUpdate(this.buffer); - if (!event.success) { + + //if the event failed to parse, or the buffer doesn't have enough bytes to satisfy the packetLength, exit here (new data will re-trigger this function) + if (!event || !event.success || event.data.packetLength > this.buffer.length) { //TODO do something about this - } - //TODO do something about this too - if (event.data.requestId > this.totalRequests) { - this.removedProcessedBytes(genericResponse, slicedBuffer, packetLength); - return true; + return; } + //we have a valid event. Clear the buffer of this data + this.buffer = this.buffer.slice(event.readOffset); + + //TODO why did we ever do this? Just to handle when we misread incoming data? I think this should be scrapped + // if (event.data.requestId > this.totalRequests) { + // this.removedProcessedBytes(genericResponse, slicedBuffer, packetLength); + // return true; + // } + if (event.data.errorCode !== ErrorCode.OK) { this.logger.error(event.data.errorCode, event); - this.removedProcessedBytes(genericResponse, buffer, packetLength); - return true; + return; } //we got a response if (event) { - //find any matching request for this response/update - const request = this.activeRequests1.get(event.data.requestId); - - if (request) { - // we received a response for this request, so remove the request from the list - this.activeRequests1.delete(event.data.requestId); + //emit the corresponding event + if (isProtocolUpdate(event)) { + this.emit('update', event); + } else { + this.emit('response', event); } - - this.emit('data', event); - - //remove the processed data from the buffer - this.buffer = this.buffer.slice(event.readOffset); - this.logger.debug('[raw]', `requestId=${event.data.requestId}`, request, event.constructor?.name ?? '', event); } - //TODO remove processed bytes no matter what the response was - //TODO if the event's readOffset is larger than the current buffer, we haven't received enough data yet. Don't clear the buffer - // process again (will run recursively until the buffer is empty) this.process(); } @@ -493,7 +496,7 @@ export class DebugProtocolClient { /** * Given a buffer, try to parse into a specific ProtocolResponse or ProtocolUpdate */ - private getResponseOrUpdate(buffer: Buffer): ProtocolResponse { + private getResponseOrUpdate(buffer: Buffer): ProtocolResponse | ProtocolUpdate { //if we haven't seen a handshake yet, try to convert the buffer into a handshake if (!this.isHandshakeComplete) { //try building the v3 handshake response first @@ -530,23 +533,20 @@ export class DebugProtocolClient { case COMMANDS.EXIT_CHANNEL: return genericResponse; case COMMANDS.EXECUTE: - return new ExecuteV3Response(this.buffer); + return ExecuteV3Response.fromBuffer(this.buffer); case COMMANDS.ADD_BREAKPOINTS: case COMMANDS.ADD_CONDITIONAL_BREAKPOINTS: - return new AddBreakpointsResponse(this.buffer); + return AddBreakpointsResponse.fromBuffer(this.buffer); case COMMANDS.LIST_BREAKPOINTS: return ListBreakpointsResponse.fromBuffer(this.buffer); case COMMANDS.REMOVE_BREAKPOINTS: return RemoveBreakpointsResponse.fromBuffer(this.buffer); case COMMANDS.VARIABLES: - return new VariablesResponse(this.buffer); + return VariablesResponse.fromBuffer(this.buffer); case COMMANDS.STACKTRACE: - return this.checkResponse( - packetLength ? new StackTraceResponseV3(slicedBuffer) : new StackTraceResponse(slicedBuffer), - buffer, - packetLength); + return this.watchPacketLength ? StackTraceV3Response.fromBuffer(this.buffer) : StackTraceResponse.fromBuffer(this.buffer); case COMMANDS.THREADS: - return new ThreadsResponse(this.buffer); + return ThreadsResponse.fromBuffer(this.buffer); default: return undefined; } @@ -575,50 +575,30 @@ export class DebugProtocolClient { } } + /** + * Handle/process any received updates from the debug protocol + */ private handleUpdate(update: ProtocolUpdate) { if (update instanceof AllThreadsStoppedUpdate || update instanceof ThreadAttachedUpdate) { this.stopped = true; - let stopReason = update.data.stopReason; - let eventName: 'runtime-error' | 'suspend' = stopReason === StopReasonCode.RuntimeError ? 'runtime-error' : 'suspend'; - - if (update.data.updateType === UPDATE_TYPES.ALL_THREADS_STOPPED) { - if (stopReason === StopReasonCode.RuntimeError || stopReason === StopReasonCode.Break || stopReason === StopReasonCode.StopStatement) { - this.primaryThread = (update.data as AllThreadsStoppedUpdate).data.primaryThreadIndex; - this.stackFrameIndex = 0; - this.emit(eventName, update); - } - } else if (stopReason === StopReasonCode.RuntimeError || stopReason === StopReasonCode.Break || stopReason === StopReasonCode.StopStatement) { - this.primaryThread = (update.data as ThreadAttachedUpdate).data.threadIndex; + let eventName: 'runtime-error' | 'suspend' = (update.data.stopReason === StopReasonCode.RuntimeError ? 'runtime-error' : 'suspend'); + + const isValidStopReason = [StopReasonCode.RuntimeError, StopReasonCode.Break, StopReasonCode.StopStatement].includes(update.data.stopReason); + + if (update instanceof AllThreadsStoppedUpdate && isValidStopReason) { + this.primaryThread = update.data.threadIndex; + this.stackFrameIndex = 0; + this.emit(eventName, update); + } else if (update instanceof ThreadAttachedUpdate && isValidStopReason) { + this.primaryThread = update.data.threadIndex; this.emit(eventName, update); } + } else if (update instanceof IOPortOpenedUpdate) { this.connectToIoPort(update); } } - private checkResponse(responseClass: { requestId: number; readOffset: number; success: boolean }, unhandledData: Buffer, packetLength = 0) { - if (responseClass.success) { - this.removedProcessedBytes(responseClass, unhandledData, packetLength); - return true; - } else if (packetLength > 0 && unhandledData.length >= packetLength) { - this.removedProcessedBytes(responseClass, unhandledData, packetLength); - } - return false; - } - - private removedProcessedBytes(response: { requestId?: number; readOffset: number }, unhandledData: Buffer, packetLength = 0) { - const request = this.activeRequests1.get(response.requestId); - if (response?.requestId > 0 && request) { - this.activeRequests1.delete(response.requestId); - } - - this.emit('data', response); - - this.buffer = unhandledData.slice(packetLength ? packetLength : response.readOffset); - this.logger.debug('[raw]', `requestId=${response?.requestId}`, request, (response as any)?.constructor?.name ?? '', response); - this.process(); - } - /** * Verify all the handshake data */ @@ -665,7 +645,10 @@ export class DebugProtocolClient { } } - private connectToIoPort(update: IOPortOpenedUpdate, unhandledData: Buffer, packetLength = 0) { + /** + * When the debugger emits the IOPortOpenedUpdate, we need to immediately connect to the IO port to start receiving that data + */ + private connectToIoPort(update: IOPortOpenedUpdate) { this.logger.log('Connecting to IO port. response status success =', update.success); if (update.success) { // Create a new TCP client. @@ -708,8 +691,6 @@ export class DebugProtocolClient { this.logger.error(err); }); }); - - this.removedProcessedBytes(update, unhandledData, packetLength); return true; } return false; @@ -787,3 +768,16 @@ export interface ConstructorOptions { */ controllerConnectMaxTime?: number; } + +/** + * Is the event a ProtocolUpdate update + */ +export function isProtocolUpdate(event: ProtocolUpdate | ProtocolResponse): event is ProtocolUpdate { + return event.data.requestId === 0; +} +/** + * Is the event a ProtocolResponse + */ +export function isProtocolResponse(event: ProtocolUpdate | ProtocolResponse): event is ProtocolResponse { + return event.data.requestId !== 0; +} diff --git a/src/debugProtocol/events/ProtocolEvent.ts b/src/debugProtocol/events/ProtocolEvent.ts index 23f382b9..24113bc4 100644 --- a/src/debugProtocol/events/ProtocolEvent.ts +++ b/src/debugProtocol/events/ProtocolEvent.ts @@ -32,7 +32,7 @@ export interface ProtocolRequestData { requestId: number; commandCode: COMMANDS; } -export type ProtocolRequest = ProtocolEvent; +export type ProtocolRequest = ProtocolEvent; /** * The fields that every ProtocolUpdateResponse must have @@ -43,7 +43,7 @@ export interface ProtocolUpdateData { errorCode: number; updateType: UPDATE_TYPES; } -export type ProtocolUpdate = ProtocolEvent; +export type ProtocolUpdate = ProtocolEvent; /** * The fields that every ProtocolResponse must have @@ -53,5 +53,5 @@ export interface ProtocolResponseData { requestId: number; errorCode: number; } -export type ProtocolResponse = ProtocolEvent; +export type ProtocolResponse = ProtocolEvent; diff --git a/src/debugProtocol/events/responses/ThreadsResponse.ts b/src/debugProtocol/events/responses/ThreadsResponse.ts index 3f2b64c2..ccd10e37 100644 --- a/src/debugProtocol/events/responses/ThreadsResponse.ts +++ b/src/debugProtocol/events/responses/ThreadsResponse.ts @@ -27,11 +27,10 @@ export class ThreadsResponse { // build the list of threads for (let i = 0; i < threadsCount; i++) { const thread = {} as ThreadInfo; - // NOTE: The docs say the flags should be both unit8 AND uint32. In testing it seems like they are sending uint32 but meant to send unit8. - const flags = smartBuffer.readUInt32LE(); + const flags = smartBuffer.readUInt8(); thread.isPrimary = (flags & ThreadInfoFlags.isPrimary) > 0; - thread.stopReason = StopReasonCode[smartBuffer.readUInt8()] as StopReason; // stop_reason + thread.stopReason = StopReasonCode[smartBuffer.readUInt32LE()] as StopReason; // stop_reason thread.stopReasonDetail = protocolUtils.readStringNT(smartBuffer); // stop_reason_detail thread.lineNumber = smartBuffer.readUInt32LE(); // line_number thread.functionName = protocolUtils.readStringNT(smartBuffer); // function_name diff --git a/src/debugProtocol/events/responses/VariablesResponse.spec.ts b/src/debugProtocol/events/responses/VariablesResponse.spec.ts index d897f12c..856bc9e0 100644 --- a/src/debugProtocol/events/responses/VariablesResponse.spec.ts +++ b/src/debugProtocol/events/responses/VariablesResponse.spec.ts @@ -71,31 +71,34 @@ describe('VariablesResponse', () => { expect( response.data ).to.eql({ - packetLength: undefined, - errorCode: ErrorCode.OK, - requestId: 2, + packetLength: undefined, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes + requestId: 2, // 4 bytes + // num_variables // 4 bytes variables: [{ - name: 'person', - refCount: 2, - isConst: false, - isContainer: true, - type: VariableType.AA, - keyType: 'String', - value: undefined, + // flags // 1 byte + name: 'person', // 7 bytes + refCount: 2, // 4 bytes + isConst: false, // 0 bytes -- part of flags + isContainer: true, // 0 bytes -- part of flags + type: VariableType.AA, // 1 byte + keyType: 'String', // 1 byte + // element_count // 4 bytes children: [{ - name: 'firstName', - refCount: 1, - value: 'Bob', - type: VariableType.String, - isContainer: false, - isConst: false + // flags // 1 byte + name: 'firstName', // 10 bytes + refCount: 1, // 4 bytes + value: 'Bob', // 4 bytes + type: VariableType.String, // 1 byte + isContainer: false, // 0 bytes --part of flags + isConst: false // 0 bytes -- part of flags }, { - name: 'lastName', - refCount: 1, - value: undefined, - isContainer: false, - type: VariableType.Invalid, - isConst: false + // flags // 1 byte + name: 'lastName', // 9 bytes + refCount: 1, // 4 bytes + type: VariableType.Invalid, // 1 byte + isContainer: false, // 0 bytes -- part of flags + isConst: false // 0 bytes -- part of flags }] }] }); diff --git a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts index a34e0333..a71c3162 100644 --- a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts +++ b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts @@ -27,7 +27,7 @@ export class AllThreadsStoppedUpdate implements ProtocolUpdate { protocolUtils.bufferLoaderHelper(update, buffer, 16, (smartBuffer) => { protocolUtils.loadCommonUpdateFields(update, smartBuffer, update.data.updateType); - update.data.primaryThreadIndex = smartBuffer.readInt32LE(); + update.data.threadIndex = smartBuffer.readInt32LE(); update.data.stopReason = smartBuffer.readUInt8(); update.data.stopReasonDetail = protocolUtils.readStringNT(smartBuffer); }); @@ -37,7 +37,7 @@ export class AllThreadsStoppedUpdate implements ProtocolUpdate { public toBuffer() { let smartBuffer = new SmartBuffer(); - smartBuffer.writeInt32LE(this.data.primaryThreadIndex); // primary_thread_index + smartBuffer.writeInt32LE(this.data.threadIndex); // primary_thread_index smartBuffer.writeUInt8(this.data.stopReason); // stop_reason smartBuffer.writeStringNT(this.data.stopReasonDetail); //stop_reason_detail @@ -50,7 +50,10 @@ export class AllThreadsStoppedUpdate implements ProtocolUpdate { public readOffset = -1; public data = { - primaryThreadIndex: undefined as number, + /** + * The index of the primary thread that triggered the stop + */ + threadIndex: undefined as number, stopReason: undefined as StopReasonCode, stopReasonDetail: undefined as string, diff --git a/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts b/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts index 86aa9914..03170ce8 100644 --- a/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts +++ b/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts @@ -43,6 +43,9 @@ export class ThreadAttachedUpdate { public readOffset = 0; public data = { + /** + * The index of the thread that was just attached + */ threadIndex: undefined as number, stopReason: undefined as StopReasonCode, stopReasonDetail: undefined as string, From 8dedad438143060a45e70b3b104b448dfae4825c Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 12 Oct 2022 11:09:51 -0400 Subject: [PATCH 12/74] Fix VariablesResponse --- .../responses/VariablesResponse.spec.ts | 124 ++++++++++++++++-- .../events/responses/VariablesResponse.ts | 35 ++++- 2 files changed, 142 insertions(+), 17 deletions(-) diff --git a/src/debugProtocol/events/responses/VariablesResponse.spec.ts b/src/debugProtocol/events/responses/VariablesResponse.spec.ts index 856bc9e0..11ebbd24 100644 --- a/src/debugProtocol/events/responses/VariablesResponse.spec.ts +++ b/src/debugProtocol/events/responses/VariablesResponse.spec.ts @@ -5,7 +5,7 @@ import { ErrorCode } from '../../Constants'; describe('VariablesResponse', () => { - it.only('Properly parses invalid variable', () => { + it('handles parent var with children', () => { let response = VariablesResponse.fromJson({ requestId: 2, variables: [{ @@ -71,7 +71,7 @@ describe('VariablesResponse', () => { expect( response.data ).to.eql({ - packetLength: undefined, // 4 bytes + packetLength: 69, // 4 bytes errorCode: ErrorCode.OK, // 4 bytes requestId: 2, // 4 bytes // num_variables // 4 bytes @@ -86,21 +86,125 @@ describe('VariablesResponse', () => { // element_count // 4 bytes children: [{ // flags // 1 byte + isContainer: false, // 0 bytes --part of flags + isConst: false, // 0 bytes -- part of flags + type: VariableType.String, // 1 byte name: 'firstName', // 10 bytes refCount: 1, // 4 bytes - value: 'Bob', // 4 bytes - type: VariableType.String, // 1 byte - isContainer: false, // 0 bytes --part of flags - isConst: false // 0 bytes -- part of flags + value: 'Bob' // 4 bytes }, { // flags // 1 byte - name: 'lastName', // 9 bytes - refCount: 1, // 4 bytes - type: VariableType.Invalid, // 1 byte isContainer: false, // 0 bytes -- part of flags - isConst: false // 0 bytes -- part of flags + isConst: false, // 0 bytes -- part of flags + type: VariableType.Invalid, // 1 byte + name: 'lastName', // 9 bytes + refCount: 1 // 4 bytes }] }] }); }); + + it('handles several root-level vars', () => { + let response = VariablesResponse.fromJson({ + requestId: 2, + variables: [{ + name: 'm', + refCount: 2, + isConst: false, + isContainer: true, + childCount: 3, + type: VariableType.AA, + keyType: VariableType.String, + value: undefined + }, { + name: 'nodes', + refCount: 2, + isConst: false, + isContainer: true, + childCount: 2, + type: VariableType.Array, + keyType: VariableType.Integer, + value: undefined + }, { + name: 'message', + refCount: 2, + isConst: false, + isContainer: false, + type: VariableType.String, + value: 'hello' + }] + }); + + expect(response.data).to.eql({ + packetLength: undefined, + errorCode: ErrorCode.OK, + requestId: 2, + variables: [{ + isConst: false, + isContainer: true, + type: VariableType.AA, + name: 'm', + refCount: 2, + keyType: VariableType.String, + childCount: 3, + value: undefined + }, { + isConst: false, + isContainer: true, + type: VariableType.Array, + name: 'nodes', + refCount: 2, + keyType: VariableType.Integer, + childCount: 2, + value: undefined + }, { + isConst: false, + isContainer: false, + type: VariableType.String, + name: 'message', + refCount: 2, + value: 'hello' + }] + }); + + response = VariablesResponse.fromBuffer(response.toBuffer()); + + expect(response.success).to.be.true; + + expect( + response.data + ).to.eql({ + packetLength: 66, // 4 bytes + errorCode: ErrorCode.OK, // 4 bytes + requestId: 2, // 4 bytes + // num_variables // 4 bytes + variables: [{ + // flags // 1 byte + isConst: false, // 0 bytes -- part of flags + isContainer: true, // 0 bytes -- part of flags + type: VariableType.AA, // 1 byte + name: 'm', // 2 bytes + refCount: 2, // 4 bytes + keyType: VariableType.String, // 1 byte + childCount: 3 // 4 bytes + }, { + // flags // 1 byte + isConst: false, // 0 bytes -- part of flags + isContainer: true, // 0 bytes -- part of flags + type: VariableType.Array, // 1 byte + name: 'nodes', // 6 bytes + refCount: 2, // 4 bytes + keyType: VariableType.Integer, // 1 byte + childCount: 2 // 4 bytes + }, { + // flags // 1 byte + isConst: false, // 0 bytes -- part of flags + isContainer: false, // 0 bytes -- part of flags + type: VariableType.String, // 1 byte + name: 'message', // 8 bytes + refCount: 2, // 4 bytes + value: 'hello' // 6 bytes + }] + }); + }); }); diff --git a/src/debugProtocol/events/responses/VariablesResponse.ts b/src/debugProtocol/events/responses/VariablesResponse.ts index c008f256..de4c5e19 100644 --- a/src/debugProtocol/events/responses/VariablesResponse.ts +++ b/src/debugProtocol/events/responses/VariablesResponse.ts @@ -13,6 +13,22 @@ export class VariablesResponse { const response = new VariablesResponse(); protocolUtils.loadJson(response, data); response.data.variables ??= []; + //validate that any object marked as `isContainer` either has an array of children or has an element count + for (const variable of response.flattenVariables(response.data.variables)) { + const hasChildrenArray = Array.isArray(variable.children); + if (variable.childCount > 0 || hasChildrenArray) { + variable.isContainer = true; + } + if (hasChildrenArray) { + variable.childCount = variable.children.length; + } + if (util.isNullish(variable.isContainer)) { + variable.isContainer = [VariableType.AA, VariableType.Array, VariableType.List, VariableType.Object, VariableType.SubtypedObject].includes(variable.type); + } + if (variable.isContainer && util.isNullish(variable.childCount) && !hasChildrenArray) { + throw new Error('Container variable must either have one of these properties set: childCount, children'); + } + } return response; } @@ -134,20 +150,25 @@ export class VariablesResponse { } } - public toBuffer() { - const smartBuffer = new SmartBuffer(); + private flattenVariables(variables: Variable[]) { //flatten the variables - const variables = [] as Variable[]; - for (let rootVariable of this.data.variables ?? []) { - variables.push(rootVariable); + const result = [] as Variable[]; + for (let rootVariable of variables ?? []) { + result.push(rootVariable); //add all child variables to the array for (const child of rootVariable.children ?? []) { - if (variables.includes(child) && Array.isArray(child.childCount)) { + if (result.includes(child) && Array.isArray(child.childCount)) { throw new Error('This variable already exists in the list. You have a circular reference in your variables that needs to be resolved'); } - variables.push(child); + result.push(child); } } + return result; + } + + public toBuffer() { + const smartBuffer = new SmartBuffer(); + const variables = this.flattenVariables(this.data.variables); smartBuffer.writeUInt32LE(variables.length ?? 0); // num_variables for (const variable of variables) { this.writeVariable(variable, smartBuffer); From 00c6f97c6376d00acad4bb601f2f7feca805345e Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 12 Oct 2022 13:15:05 -0400 Subject: [PATCH 13/74] Fix tsc errors --- src/adapters/DebugProtocolAdapter.spec.ts | 13 +++++++----- src/adapters/DebugProtocolAdapter.ts | 21 ++++++++++++------- .../client/DebugProtocolClient.ts | 7 +++---- .../AddConditionalBreakpointsResponse.ts | 4 ++++ .../responses/StackTraceResponse.spec.ts | 2 +- .../events/responses/ThreadsResponse.spec.ts | 2 +- .../server/DebugProtocolServer.ts | 4 ++-- 7 files changed, 33 insertions(+), 20 deletions(-) create mode 100644 src/debugProtocol/events/responses/AddConditionalBreakpointsResponse.ts diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index 32f9d327..c6edee92 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -3,6 +3,9 @@ import { expect } from 'chai'; import { DebugProtocolClient } from '../debugProtocol/client/DebugProtocolClient'; import { DebugProtocolAdapter } from './DebugProtocolAdapter'; import { createSandbox } from 'sinon'; +import type { Variable } from '../debugProtocol/events/responses/VariablesResponse'; +import { VariableType } from '../debugProtocol/events/responses/VariablesResponse'; +// eslint-disable-next-line @typescript-eslint/no-duplicate-imports import { VariablesResponse } from '../debugProtocol/events/responses/VariablesResponse'; const sinon = createSandbox(); @@ -24,7 +27,7 @@ describe('DebugProtocolAdapter', () => { describe('getVariable', () => { let response: VariablesResponse; - let variables: Partial[]; + let variables: Partial[]; beforeEach(() => { response = VariablesResponse.fromJson({ @@ -33,7 +36,7 @@ describe('DebugProtocolAdapter', () => { }); sinon.stub(adapter as any, 'getStackFrameById').returns({}); sinon.stub(socketDebugger, 'getVariables').callsFake(() => { - response.variables = variables as any; + response.data.variables = variables as any; return Promise.resolve(response); }); socketDebugger['stopped'] = true; @@ -57,9 +60,9 @@ describe('DebugProtocolAdapter', () => { it('works for object properties', async () => { variables.push( - { isContainer: true, elementCount: 2, isChildKey: false, variableType: 'AA' }, - { name: 'name', isChildKey: true }, - { name: 'age', isChildKey: true } + { isContainer: true, childCount: 2, variableType: VariableType.AA } as any, + { name: 'name', isChildKey: true } as any, + { name: 'age', isChildKey: true } as any ); const vars = await adapter.getVariable('person', 1, true); diff --git a/src/adapters/DebugProtocolAdapter.ts b/src/adapters/DebugProtocolAdapter.ts index ee9b76a1..f346a359 100644 --- a/src/adapters/DebugProtocolAdapter.ts +++ b/src/adapters/DebugProtocolAdapter.ts @@ -397,8 +397,13 @@ export class DebugProtocolAdapter { type: 'message' }; } else { + const messages = [ + ...response?.data?.compileErrors ?? [], + ...response?.data?.runtimeErrors ?? [], + ...response?.data?.otherErrors ?? [] + ]; return { - message: response.compileErrors.messages[0] ?? response.runtimeErrors.messages[0] ?? response.otherErrors.messages[0] ?? 'Unknown error executing command', + message: messages[0] ?? 'Unknown error executing command', type: 'error' }; } @@ -491,7 +496,7 @@ export class DebugProtocolAdapter { let firstHandled = false; for (let variable of response.data.variables) { let value; - let variableType = variable.type; + let variableType = variable.type as string; if (variable.value === null) { value = 'roInvalid'; } else if (variableType === 'String') { @@ -514,8 +519,10 @@ export class DebugProtocolAdapter { variablePath: variablePath, type: variableType, value: value, - keyType: variable.keyType, - elementCount: variable.elementCount + keyType: variable.keyType === VariableType.Integer ? 'Integer' : 'String', + highLevelType: null, + children: null, + elementCount: variable.childCount }; if (!firstHandled && variablePath.length > 0) { @@ -533,7 +540,7 @@ export class DebugProtocolAdapter { type: '', value: null, keyType: 'String', - elementCount: response.numVariables + elementCount: response.data.variables.length }; } @@ -705,10 +712,10 @@ export class DebugProtocolAdapter { //mark the breakpoints as verified for (let i = 0; i < response.data.breakpoints.length; i++) { const deviceBreakpoint = response.data.breakpoints[i]; - if (deviceBreakpoint.isVerified) { + if (deviceBreakpoint.errorCode === ErrorCode.OK) { this.breakpointManager.verifyBreakpoint( breakpointsToSendToDevice[i].key, - deviceBreakpoint.breakpointId + deviceBreakpoint.id ); } } diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 9df669cf..beece53e 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -1,7 +1,7 @@ import * as Net from 'net'; import * as EventEmitter from 'eventemitter3'; import * as semver from 'semver'; -import { PROTOCOL_ERROR_CODES, COMMANDS, STEP_TYPE, StopReasonCode, VARIABLE_REQUEST_FLAGS, ErrorCode, UPDATE_TYPES } from '../Constants'; +import { PROTOCOL_ERROR_CODES, COMMANDS, STEP_TYPE, StopReasonCode, ErrorCode, UPDATE_TYPES } from '../Constants'; import { SmartBuffer } from 'smart-buffer'; import { logger } from '../../logging'; import { ExecuteV3Response } from '../events/responses/ExecuteV3Response'; @@ -22,13 +22,12 @@ import { ThreadsRequest } from '../events/requests/ThreadsRequest'; import { ExecuteRequest } from '../events/requests/ExecuteRequest'; import { AddBreakpointsRequest } from '../events/requests/AddBreakpointsRequest'; import { AddConditionalBreakpointsRequest } from '../events/requests/AddConditionalBreakpointsRequest'; -import type { ProtocolEvent, ProtocolRequest, ProtocolResponse, ProtocolUpdate } from '../events/ProtocolEvent'; +import type { ProtocolRequest, ProtocolResponse, ProtocolUpdate } from '../events/ProtocolEvent'; import { HandshakeResponse } from '../events/responses/HandshakeResponse'; import { HandshakeV3Response } from '../events/responses/HandshakeV3Response'; import { HandshakeRequest } from '../events/requests/HandshakeRequest'; import { GenericV3Response } from '../events/responses/GenericV3Response'; import { AllThreadsStoppedUpdate } from '../events/updates/AllThreadsStoppedUpdate'; -import { buffer } from 'rxjs'; import { CompileErrorUpdate } from '../events/updates/CompileErrorUpdate'; import { GenericResponse } from '../events/responses/GenericResponse'; import { StackTraceResponse } from '../events/responses/StackTraceResponse'; @@ -438,7 +437,7 @@ export class DebugProtocolClient { if (event.data.requestId === requestId) { unsubscribe(); this.activeRequests1.delete(requestId); - resolve(event as T); + resolve(event as unknown as T); } }); diff --git a/src/debugProtocol/events/responses/AddConditionalBreakpointsResponse.ts b/src/debugProtocol/events/responses/AddConditionalBreakpointsResponse.ts new file mode 100644 index 00000000..56e9d585 --- /dev/null +++ b/src/debugProtocol/events/responses/AddConditionalBreakpointsResponse.ts @@ -0,0 +1,4 @@ +import { ListBreakpointsResponse } from './ListBreakpointsResponse'; + +//There's currently no difference between this response and the ListBreakpoints response +export class AddConditionalBreakpointsResponse extends ListBreakpointsResponse { } diff --git a/src/debugProtocol/events/responses/StackTraceResponse.spec.ts b/src/debugProtocol/events/responses/StackTraceResponse.spec.ts index 94e37f7d..6c5b07cc 100644 --- a/src/debugProtocol/events/responses/StackTraceResponse.spec.ts +++ b/src/debugProtocol/events/responses/StackTraceResponse.spec.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { StackTraceResponse } from './StackTraceResponse'; import { ErrorCode } from '../../Constants'; -import { getRandomBuffer } from '../../responseCreationHelpers.spec'; +import { getRandomBuffer } from '../../../testHelpers.spec'; describe('StackTraceResponse', () => { it('serializes and deserializes multiple breakpoints properly', () => { diff --git a/src/debugProtocol/events/responses/ThreadsResponse.spec.ts b/src/debugProtocol/events/responses/ThreadsResponse.spec.ts index 04729802..5cf3a2dd 100644 --- a/src/debugProtocol/events/responses/ThreadsResponse.spec.ts +++ b/src/debugProtocol/events/responses/ThreadsResponse.spec.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { ThreadsResponse } from './ThreadsResponse'; import { ErrorCode } from '../../Constants'; -import { getRandomBuffer } from '../../responseCreationHelpers.spec'; +import { getRandomBuffer } from '../../../testHelpers.spec'; describe('ThreadsResponse', () => { it('serializes and deserializes multiple breakpoints properly', () => { diff --git a/src/debugProtocol/server/DebugProtocolServer.ts b/src/debugProtocol/server/DebugProtocolServer.ts index 15f4311e..c83359a8 100644 --- a/src/debugProtocol/server/DebugProtocolServer.ts +++ b/src/debugProtocol/server/DebugProtocolServer.ts @@ -144,7 +144,7 @@ export class DebugProtocolServer { } //trim the buffer now that the request has been processed - this.buffer = buffer.slice((request as ProtocolRequest).readOffset); + this.buffer = buffer.slice(request.readOffset); //now ask the plugin to provide a response for the given request let { response } = await this.plugins.emit('provideResponse', { @@ -160,7 +160,7 @@ export class DebugProtocolServer { } //the client should send a magic string to kick off the debugger - if ((response instanceof HandshakeResponse || response instanceof HandshakeV3Response) && request.data.magic === this.magic) { + if ((response instanceof HandshakeResponse || response instanceof HandshakeV3Response) && response.data.magic === this.magic) { this.isHandshakeComplete = true; } From dff64ee70464eee46103b8a281794bdae3d44f06 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 12 Oct 2022 15:18:03 -0400 Subject: [PATCH 14/74] Fix handshake request/response issues. --- .../events/requests/HandshakeRequest.ts | 6 ++++- .../events/responses/HandshakeResponse.ts | 5 ++-- .../responses/HandshakeV3Response.spec.ts | 24 ++++++++++++++----- .../events/responses/HandshakeV3Response.ts | 17 ++++++------- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/debugProtocol/events/requests/HandshakeRequest.ts b/src/debugProtocol/events/requests/HandshakeRequest.ts index 7a698442..8cbd21b6 100644 --- a/src/debugProtocol/events/requests/HandshakeRequest.ts +++ b/src/debugProtocol/events/requests/HandshakeRequest.ts @@ -9,6 +9,10 @@ import type { COMMANDS } from '../../Constants'; * @since protocol v1.0.0 */ export class HandshakeRequest implements ProtocolRequest { + /** + * A hardcoded id for the handshake classes to help them flow through the request/response flow even though they don't look the same + */ + public static REQUEST_ID = 4294967295; public static fromJson(data: { magic: string }) { const request = new HandshakeRequest(); @@ -41,7 +45,7 @@ export class HandshakeRequest implements ProtocolRequest { //just add dummy data for those fields packetLength: undefined as number, //hardcode the max integer value. This must be the same value as the HandshakeResponse class - requestId: Number.MAX_SAFE_INTEGER, + requestId: HandshakeRequest.REQUEST_ID, commandCode: undefined as COMMANDS }; } diff --git a/src/debugProtocol/events/responses/HandshakeResponse.ts b/src/debugProtocol/events/responses/HandshakeResponse.ts index e65f2280..0c6a69c6 100644 --- a/src/debugProtocol/events/responses/HandshakeResponse.ts +++ b/src/debugProtocol/events/responses/HandshakeResponse.ts @@ -4,6 +4,7 @@ import { util } from '../../../util'; import type { ProtocolEvent, ProtocolResponse } from '../ProtocolEvent'; import { protocolUtils } from '../../ProtocolUtil'; import { ErrorCode } from '../../Constants'; +import { HandshakeRequest } from '../requests/HandshakeRequest'; export class HandshakeResponse implements ProtocolResponse { public static fromJson(data: { @@ -74,8 +75,8 @@ export class HandshakeResponse implements ProtocolResponse { //The handshake response isn't actually structured like like normal responses, but since they're the only unique response, just add dummy data for those fields packetLength: undefined as number, - //hardcode the max integer value. This must be the same value as the HandshakeResponse class - requestId: Number.MAX_SAFE_INTEGER, + //hardcode the max uint32 value. This must be the same value as the HandshakeRequest class + requestId: HandshakeRequest.REQUEST_ID, errorCode: ErrorCode.OK }; } diff --git a/src/debugProtocol/events/responses/HandshakeV3Response.spec.ts b/src/debugProtocol/events/responses/HandshakeV3Response.spec.ts index 4ca8ef42..cf025d09 100644 --- a/src/debugProtocol/events/responses/HandshakeV3Response.spec.ts +++ b/src/debugProtocol/events/responses/HandshakeV3Response.spec.ts @@ -2,6 +2,8 @@ import { HandshakeV3Response } from './HandshakeV3Response'; import { DebugProtocolClient } from '../../client/DebugProtocolClient'; import { expect } from 'chai'; import { SmartBuffer } from 'smart-buffer'; +import { ErrorCode } from '../../Constants'; +import { HandshakeRequest } from '../requests/HandshakeRequest'; describe('HandshakeV3Response', () => { const date = new Date(2022, 0, 0); @@ -13,6 +15,10 @@ describe('HandshakeV3Response', () => { }); expect(response.data).to.eql({ + packetLength: undefined, + errorCode: ErrorCode.OK, + requestId: HandshakeRequest.REQUEST_ID, + magic: 'bsdebug', protocolVersion: '3.0.0', revisionTimestamp: date @@ -21,6 +27,10 @@ describe('HandshakeV3Response', () => { expect( HandshakeV3Response.fromBuffer(response.toBuffer()).data ).to.eql({ + packetLength: undefined, + errorCode: ErrorCode.OK, + requestId: HandshakeRequest.REQUEST_ID, + magic: 'bsdebug', // 8 bytes protocolVersion: '3.0.0', // 12 bytes (each number is sent as uint32) //remaining_packet_length // 4 bytes @@ -30,7 +40,7 @@ describe('HandshakeV3Response', () => { expect(response.toBuffer().length).to.eql(32); }); - it('Handles a extra packet length in handshake response', () => { + it('Handles trailing buffer data in handshake response', () => { const response = HandshakeV3Response.fromJson({ magic: 'bsdebug', protocolVersion: '3.0.0', @@ -39,15 +49,17 @@ describe('HandshakeV3Response', () => { //write some extra data to the buffer const smartBuffer = SmartBuffer.fromBuffer(response.toBuffer()); - smartBuffer.writeStringNT('this is extra data'); + smartBuffer.writeStringNT('this is extra data', smartBuffer.length); - const newResponse = HandshakeV3Response.fromBuffer( - smartBuffer.toBuffer() - ); + const newResponse = HandshakeV3Response.fromBuffer(smartBuffer.toBuffer()); expect(newResponse.success).to.be.true; + expect( newResponse.data ).to.eql({ + packetLength: undefined, + errorCode: ErrorCode.OK, + requestId: HandshakeRequest.REQUEST_ID, magic: 'bsdebug', // 8 bytes protocolVersion: '3.0.0', // 12 bytes (each number is sent as uint32) //remaining_packet_length // 4 bytes @@ -73,7 +85,7 @@ describe('HandshakeV3Response', () => { it('Fails when the protocol version is less then 3.0.0', () => { const response = HandshakeV3Response.fromJson({ magic: 'not bsdebug', - protocolVersion: '3.0.0', + protocolVersion: '2.0.0', revisionTimestamp: date }); diff --git a/src/debugProtocol/events/responses/HandshakeV3Response.ts b/src/debugProtocol/events/responses/HandshakeV3Response.ts index 4431e0c6..aae9f43d 100644 --- a/src/debugProtocol/events/responses/HandshakeV3Response.ts +++ b/src/debugProtocol/events/responses/HandshakeV3Response.ts @@ -3,6 +3,7 @@ import * as semver from 'semver'; import type { ProtocolResponse } from '../ProtocolEvent'; import { protocolUtils } from '../../ProtocolUtil'; import { ErrorCode } from '../../Constants'; +import { HandshakeRequest } from '../requests/HandshakeRequest'; export class HandshakeV3Response implements ProtocolResponse { @@ -14,7 +15,7 @@ export class HandshakeV3Response implements ProtocolResponse { const response = new HandshakeV3Response(); protocolUtils.loadJson(response, data); // We only support v3 or above with this handshake - if (!semver.satisfies(response.data.protocolVersion, '<3.0.0')) { + if (semver.satisfies(response.data.protocolVersion, '<3.0.0')) { response.success = false; } return response; @@ -26,9 +27,9 @@ export class HandshakeV3Response implements ProtocolResponse { response.data.magic = protocolUtils.readStringNT(smartBuffer); // magic_number response.data.protocolVersion = [ - smartBuffer.readInt32LE(), // protocol_major_version - smartBuffer.readInt32LE(), // protocol_minor_version - smartBuffer.readInt32LE() // protocol_patch_version + smartBuffer.readUInt32LE(), // protocol_major_version + smartBuffer.readUInt32LE(), // protocol_minor_version + smartBuffer.readUInt32LE() // protocol_patch_version ].join('.'); const legacyReadSize = smartBuffer.readOffset; @@ -43,8 +44,8 @@ export class HandshakeV3Response implements ProtocolResponse { //set the buffer offset smartBuffer.readOffset = requiredBufferSize; - // We only support v3 or above with this handshake - if (!semver.satisfies(response.data.protocolVersion, '<3.0.0')) { + // We only support v3.0.0 or above with this handshake + if (semver.satisfies(response.data.protocolVersion, '<3.0.0')) { throw new Error(`unsupported version ${response.data.protocolVersion}`); } }); @@ -106,8 +107,8 @@ export class HandshakeV3Response implements ProtocolResponse { //The handshake response isn't actually structured like like normal responses, but since they're the only unique response, just add dummy data for those fields packetLength: undefined as number, - //hardcode the max integer value. This must be the same value as the HandshakeResponse class - requestId: Number.MAX_SAFE_INTEGER, + //hardcode the max uint32 integer value. This must be the same value as the HandshakeRequest class + requestId: HandshakeRequest.REQUEST_ID, errorCode: ErrorCode.OK }; } From fe00be50a11071b191b7223fac9e1046c34e1821 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 12 Oct 2022 21:36:42 -0400 Subject: [PATCH 15/74] PascalCase enums --- src/debugProtocol/Constants.ts | 220 ++++++++++---- .../MockDebugProtocolServer.spec.ts | 174 ----------- src/debugProtocol/ProtocolUtil.ts | 13 +- .../client/DebugProtocolClient.spec.ts | 280 +++++++----------- .../client/DebugProtocolClient.ts | 120 ++++---- src/debugProtocol/events/ProtocolEvent.ts | 6 +- .../requests/AddBreakpointsRequest.spec.ts | 10 +- .../events/requests/AddBreakpointsRequest.ts | 6 +- .../AddConditionalBreakpointsRequest.spec.ts | 10 +- .../AddConditionalBreakpointsRequest.ts | 4 +- .../events/requests/ContinueRequest.spec.ts | 6 +- .../events/requests/ContinueRequest.ts | 5 +- .../events/requests/ExecuteRequest.spec.ts | 6 +- .../events/requests/ExecuteRequest.ts | 5 +- .../requests/ExitChannelRequest.spec.ts | 6 +- .../events/requests/ExitChannelRequest.ts | 5 +- .../events/requests/HandshakeRequest.ts | 4 +- .../requests/ListBreakpointsRequest.spec.ts | 6 +- .../events/requests/ListBreakpointsRequest.ts | 4 +- .../requests/RemoveBreakpointsCommand.spec.ts | 6 +- .../requests/RemoveBreakpointsRequest.ts | 5 +- .../events/requests/StackTraceRequest.spec.ts | 6 +- .../events/requests/StackTraceRequest.ts | 4 +- .../events/requests/StepRequest.spec.ts | 12 +- .../events/requests/StepRequest.ts | 14 +- .../events/requests/StopRequest.spec.ts | 6 +- .../events/requests/StopRequest.ts | 5 +- .../events/requests/ThreadsRequest.spec.ts | 6 +- .../events/requests/ThreadsRequest.ts | 5 +- .../events/requests/VariablesRequest.spec.ts | 14 +- .../events/requests/VariablesRequest.ts | 4 +- .../responses/ExecuteV3Response.spec.ts | 2 +- .../events/responses/ThreadsResponse.spec.ts | 10 +- .../updates/AllThreadsStoppedUpdate.spec.ts | 8 +- .../events/updates/AllThreadsStoppedUpdate.ts | 6 +- .../updates/BreakpointErrorUpdate.spec.ts | 10 +- .../events/updates/BreakpointErrorUpdate.ts | 5 +- .../events/updates/CompileErrorUpdate.spec.ts | 6 +- .../events/updates/CompileErrorUpdate.ts | 5 +- .../events/updates/IOPortOpenedUpdate.spec.ts | 6 +- .../events/updates/IOPortOpenedUpdate.ts | 4 +- .../updates/ThreadAttachedUpdate.spec.ts | 6 +- .../events/updates/ThreadAttachedUpdate.ts | 4 +- 43 files changed, 465 insertions(+), 584 deletions(-) delete mode 100644 src/debugProtocol/MockDebugProtocolServer.spec.ts diff --git a/src/debugProtocol/Constants.ts b/src/debugProtocol/Constants.ts index 95f92719..3ee0a566 100644 --- a/src/debugProtocol/Constants.ts +++ b/src/debugProtocol/Constants.ts @@ -4,29 +4,109 @@ export enum PROTOCOL_ERROR_CODES { NOT_SUPPORTED = 2 } -export enum COMMANDS { - STOP = 1, - CONTINUE = 2, - THREADS = 3, - STACKTRACE = 4, - VARIABLES = 5, - STEP = 6, // Since protocol 1.1 - ADD_BREAKPOINTS = 7, // since protocol 1.2 - LIST_BREAKPOINTS = 8, // since protocol 1.2 - REMOVE_BREAKPOINTS = 9, // since protocol 1.2 - EXECUTE = 10, // since protocol 2.1 - ADD_CONDITIONAL_BREAKPOINTS = 11, // since protocol 3.1 - EXIT_CHANNEL = 122 +/** + * The name of commands that can be sent from the client to the server. Think of these like "requests". + */ +export enum Command { + /** + * Stop all threads in application. Enter into debugger. + * + * Individual threads can not be stopped/started. + */ + Stop = 'Stop', + /** + * Exit from debugger and continue execution of all threads. + */ + Continue = 'Continue', + /** + * Application threads info + */ + Threads = 'Threads', + /** + * Get the stack trace of a specific thread. + */ + StackTrace = 'StackTrace', + /** + * Listing of variables accessible from selected thread and stack frame. + */ + Variables = 'Variables', + /** + * Execute one step on a specified thread. + * + */ + Step = 'Step', + /** + * Add a dynamic breakpoint. + * + * @since protocol 2.0.0 (Roku OS 9.3) + */ + AddBreakpoints = 'AddBreakpoints', + /** + * Lists existing dynamic and conditional breakpoints and their status. + * + * @since protocol 2.0.0 (Roku OS 9.3) + */ + ListBreakpoints = 'ListBreakpoints', + /** + * Removes dynamic breakpoints. + * + * @since protocol 2.0.0 (Roku OS 9.3) + */ + RemoveBreakpoints = 'RemoveBreakpoints', + /** + * Executes code in a specific stack frame. + * + * @since protocol 2.1 (Roku OS 10.5) + */ + Execute = 'Execute', + /** + * Adds a conditional breakpoint. + * + * @since protocol 3.1.0 (Roku OS 11.5) + */ + AddConditionalBreakpoints = 'AddConditionalBreakpoints', + /** + * + */ + ExitChannel = 'ExitChannel' +} +/** + * Only used for serializing/deserializing over the debug protocol. Use `Command` in your code. + */ +export enum CommandCode { + Stop = 1, + Continue = 2, + Threads = 3, + StackTrace = 4, + Variables = 5, + Step = 6, + AddBreakpoints = 7, + ListBreakpoints = 8, + RemoveBreakpoints = 9, + Execute = 10, + AddConditionalBreakpoints = 11, + ExitChannel = 122 } -export enum STEP_TYPE { - STEP_TYPE_NONE = 0, - STEP_TYPE_LINE = 1, - STEP_TYPE_OUT = 2, - STEP_TYPE_OVER = 3 +/** + * Contains an a StepType enum, indicating the type of step action to be executed. + */ +export enum StepType { + None = 'None', + Line = 'Line', + Out = 'Out', + Over = 'Over' +} +/** + * Only used for serializing/deserializing over the debug protocol. Use `StepType` in your code. + */ +export enum StepTypeCode { + None = 0, + Line = 1, + Out = 2, + Over = 3 } -//#region RESPONSE CONSTS export enum ErrorCode { OK = 0, OTHER_ERR = 1, @@ -36,7 +116,35 @@ export enum ErrorCode { INVALID_ARGS = 5 } -export type StopReason = 'Undefined' | 'NotStopped' | 'NormalExit' | 'StopStatement' | 'Break' | 'RuntimeError'; +export enum StopReason { + /** + * Uninitialized stopReason. + */ + Undefined = 'Undefined', + /** + * Thread is running. + */ + NotStopped = 'NotStopped', + /** + * Thread exited. + */ + NormalExit = 'NormalExit', + /** + * Stop statement executed. + */ + StopStatement = 'StopStatement', + /** + * Another thread in the group encountered an error, this thread completed a step operation, or other reason outside this thread. + */ + Break = 'Break', + /** + * Thread stopped because of an error during execution. + */ + RuntimeError = 'RuntimeError' +} +/** + * Only used for serializing/deserializing over the debug protocol. Use `StopReason` in your code. + */ export enum StopReasonCode { Undefined = 0, NotStopped = 1, @@ -46,51 +154,55 @@ export enum StopReasonCode { RuntimeError = 5 } -export enum UPDATE_TYPES { - UNDEF = 0, - IO_PORT_OPENED = 1, // client needs to connect to port to retrieve channel output - ALL_THREADS_STOPPED = 2, - THREAD_ATTACHED = 3, +/** + * Human-readable UpdateType values. To get the codes, use the `UpdateTypeCode` enum + */ +export enum UpdateType { + Undefined = 'Undefined', + /** + * The remote debugging client should connect to the port included in the data field to retrieve the running script's output. Only reads are allowed on the I/O connection. + */ + IOPortOpened = 'IOPortOpened', + /** + * All threads are stopped and an ALL_THREADS_STOPPED message is sent to the debugging client. + * + * The data field includes information on why the threads were stopped. + */ + AllThreadsStopped = 'AllThreadsStopped', + /** + * A new thread attempts to execute a script when all threads have already been stopped. The new thread is immediately stopped and is "attached" to the + * debugger so that the debugger can inspect the thread, its stack frames, and local variables. + * + * Additionally, when a thread executes a step operation, that thread detaches from the debugger temporarily, + * and a THREAD_ATTACHED message is sent to the debugging client when the thread has completed its step operation and has re-attached to the debugger. + * + * The data field includes information on why the threads were stopped + */ + ThreadAttached = 'ThreadAttached', /** * A compilation or runtime error occurred when evaluating the cond_expr of a conditional breakpoint * @since protocol 3.1 */ - BREAKPOINT_ERROR = 4, + BreakpointError = 'BreakpointError', /** * A compilation error occurred * @since protocol 3.1 */ - COMPILE_ERROR = 5 + CompileError = 'CompileError' +} +/** + * The integer values for `UPDATE_TYPE`. Only used for serializing/deserializing over the debug protocol. Use `UpdateType` in your code. + */ +export enum UpdateTypeCode { + Undefined = 0, + IOPortOpened = 1, + AllThreadsStopped = 2, + ThreadAttached = 3, + BreakpointError = 4, + CompileError = 5 } export enum VARIABLE_REQUEST_FLAGS { GET_CHILD_KEYS = 0x01, CASE_SENSITIVITY_OPTIONS = 0x02 } - - -//#endregion - -export function getUpdateType(value: number): UPDATE_TYPES { - switch (value) { - case UPDATE_TYPES.ALL_THREADS_STOPPED: - return UPDATE_TYPES.ALL_THREADS_STOPPED; - case UPDATE_TYPES.IO_PORT_OPENED: - return UPDATE_TYPES.IO_PORT_OPENED; - case UPDATE_TYPES.THREAD_ATTACHED: - return UPDATE_TYPES.THREAD_ATTACHED; - case UPDATE_TYPES.UNDEF: - return UPDATE_TYPES.UNDEF; - default: - return UPDATE_TYPES.UNDEF; - } -} - -/** - * Common properties found on all Command `.data` objects - */ -export interface RequestData { - packetLength: number; - requestId: number; - commandCode: number; -} diff --git a/src/debugProtocol/MockDebugProtocolServer.spec.ts b/src/debugProtocol/MockDebugProtocolServer.spec.ts deleted file mode 100644 index 11866bc6..00000000 --- a/src/debugProtocol/MockDebugProtocolServer.spec.ts +++ /dev/null @@ -1,174 +0,0 @@ -import * as net from 'net'; -import type { Subscription } from 'rxjs'; -import { ReplaySubject } from 'rxjs'; -import { SmartBuffer } from 'smart-buffer'; -import type { Deferred } from '../util'; -import { util, defer } from '../util'; -import { protocolUtils } from './ProtocolUtil'; - -export class MockDebugProtocolServer { - /** - * The net server that will be listening for incoming socket connections from clients - */ - public server: net.Server; - /** - * The list of server sockets created in response to clients connecting. - * There should be one for every client - */ - public client: Client; - - /** - * The port that the client should use to send commands - */ - public controllerPort: number; - - public actions = [] as Action[]; - - private clientLoadedPromise: Promise; - - private processActionsSubscription: Subscription; - - public async initialize() { - const clientDeferred = defer(); - this.clientLoadedPromise = clientDeferred.promise; - void new Promise((resolve) => { - this.server = net.createServer((s) => { - this.client = new Client(s); - clientDeferred.resolve(); - }); - }); - this.server.listen(0); - //wait for the server to start listening - await new Promise((resolve) => { - this.server.on('listening', () => { - this.controllerPort = (this.server.address() as net.AddressInfo).port; - resolve(); - }); - }); - } - - /** - * After queueing up actions, this method starts processing those actions. - * If an action cannot be processed yet, it will wait until the client sends the corresponding - * request. If that request never comes, this server will wait indefinitely - */ - public async processActions() { - //wait for a client to connect - await this.clientLoadedPromise; - - //listen to all events sent to the client - console.log('subscription being created'); - // eslint-disable-next-line @typescript-eslint/no-misused-promises - this.processActionsSubscription = this.client.subject.subscribe(async () => { - console.log('subscription handler fired'); - //process events until one of them returns false. - //when an event returns false, we will wait for more data to come back and try again - while (await this.actions[0]?.process(this.client) === true) { - this.actions.splice(0, 1); - } - }); - } - - public waitForMagic() { - const action = new WaitForMagicAction(); - this.actions.push(action); - return action; - } - - public sendHandshakeResponse(magic: Promise | string) { - const action = new SendHandshakeResponseAction(magic); - this.actions.push(action); - return action; - } - - public reset() { - this.client?.destroy(); - this.client = undefined; - this.processActionsSubscription.unsubscribe(); - this.actions = []; - } - - public destroy() { - this.server?.close(); - this.server = undefined; - } -} - -class Client { - constructor( - public socket: net.Socket - ) { - const handler = (data) => { - this.buffer = Buffer.concat([this.buffer, data]); - this.subject.next(undefined); - }; - socket.on('data', handler); - this.disconnectSocket = () => { - this.socket.off('data', handler); - }; - } - public subject = new ReplaySubject(); - public buffer = Buffer.alloc(0); - public disconnectSocket: () => void; - - public destroy() { - this.disconnectSocket(); - this.subject.complete(); - this.socket.destroy(); - } -} - -abstract class Action { - constructor() { - this.deferred = defer(); - } - protected deferred: Deferred; - public get promise() { - return this.deferred.promise; - } - /** - * - * @param ref - an object that has a property named "buffer". This is so that, if new data comes in, - * the client can update the reference to the buffer, and the actions can alter that new buffer directly - */ - public abstract process(client: Client): Promise; -} - -class WaitForMagicAction extends Action { - public process(client: Client) { - const b = SmartBuffer.fromBuffer(client.buffer); - try { - const str = protocolUtils.readStringNT(b); - this.deferred.resolve(str); - client.buffer = client.buffer.slice(b.readOffset); - return Promise.resolve(true); - } catch (e) { - console.error('WaitForMagicAction failed', e); - return Promise.resolve(false); - } - } -} - -class SendHandshakeResponseAction extends Action { - constructor( - private magic: Promise | string - ) { - super(); - } - - public async process(client: Client) { - console.log('processing handshake response'); - const magic = await Promise.resolve(this.magic); - const b = new SmartBuffer(); - b.writeStringNT(magic); - b.writeInt32LE(2); - b.writeInt32LE(0); - b.writeInt32LE(0); - const buffer = b.toBuffer(); - - client.socket.write(buffer); - this.deferred.resolve(); - console.log('sent handshake response'); - return true; - } -} diff --git a/src/debugProtocol/ProtocolUtil.ts b/src/debugProtocol/ProtocolUtil.ts index 11c5f8b2..e0538fc6 100644 --- a/src/debugProtocol/ProtocolUtil.ts +++ b/src/debugProtocol/ProtocolUtil.ts @@ -1,5 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; -import type { UPDATE_TYPES } from './Constants'; +import type { Command, UpdateType } from './Constants'; +import { CommandCode, UpdateTypeCode } from './Constants'; import type { ProtocolRequest, ProtocolResponse, ProtocolUpdate } from './events/ProtocolEvent'; export class ProtocolUtils { @@ -49,7 +50,7 @@ export class ProtocolUtils { public loadCommonRequestFields(request: ProtocolRequest, smartBuffer: SmartBuffer) { request.data.packetLength = smartBuffer.readUInt32LE(); // packet_length request.data.requestId = smartBuffer.readUInt32LE(); // request_id - request.data.commandCode = smartBuffer.readUInt32LE(); // command_code + request.data.command = CommandCode[smartBuffer.readUInt32LE()] as Command; // command_code } /** @@ -61,13 +62,13 @@ export class ProtocolUtils { request.data.errorCode = smartBuffer.readUInt32LE(); // error_code } - public loadCommonUpdateFields(update: ProtocolUpdate, smartBuffer: SmartBuffer, updateType: UPDATE_TYPES) { + public loadCommonUpdateFields(update: ProtocolUpdate, smartBuffer: SmartBuffer, updateType: UpdateType) { update.data.packetLength = smartBuffer.readUInt32LE(); // packet_length update.data.requestId = smartBuffer.readUInt32LE(); // request_id update.data.errorCode = smartBuffer.readUInt32LE(); // error_code // requestId 0 means this is an update. if (update.data.requestId === 0) { - update.data.updateType = smartBuffer.readUInt32LE(); + update.data.updateType = UpdateTypeCode[smartBuffer.readUInt32LE()] as UpdateType; //if this is not the update type we want, return false if (update.data.updateType !== updateType) { @@ -85,7 +86,7 @@ export class ProtocolUtils { * the correct `packet_length` value. */ public insertCommonRequestFields(request: ProtocolRequest, smartBuffer: SmartBuffer) { - smartBuffer.insertUInt32LE(request.data.commandCode, 0); // command_code - An enum representing the debugging command being sent. See the COMMANDS enum + smartBuffer.insertUInt32LE(CommandCode[request.data.command], 0); // command_code - An enum representing the debugging command being sent. See the COMMANDS enum smartBuffer.insertUInt32LE(request.data.requestId, 0); // request_id - The ID of the debugger request (must be >=1). This ID is included in the debugger response. smartBuffer.insertUInt32LE(smartBuffer.writeOffset + 4, 0); // packet_length - The size of the packet to be sent. request.data.packetLength = smartBuffer.writeOffset; @@ -106,7 +107,7 @@ export class ProtocolUtils { * the correct `packet_length` value. */ public insertCommonUpdateFields(update: ProtocolUpdate, smartBuffer: SmartBuffer) { - smartBuffer.insertUInt32LE(update.data.updateType, 0); // update_type + smartBuffer.insertUInt32LE(UpdateTypeCode[update.data.updateType], 0); // update_type smartBuffer.insertUInt32LE(update.data.errorCode, 0); // error_code smartBuffer.insertUInt32LE(update.data.requestId, 0); // request_id smartBuffer.insertUInt32LE(smartBuffer.writeOffset + 4, 0); // packet_length diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index 98db4f94..504dbc16 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -1,9 +1,8 @@ import { DebugProtocolClient } from './DebugProtocolClient'; import { expect } from 'chai'; import type { SmartBuffer } from 'smart-buffer'; -import { MockDebugProtocolServer } from '../MockDebugProtocolServer.spec'; import { createSandbox } from 'sinon'; -import { StopReasonCode, VARIABLE_REQUEST_FLAGS } from '../Constants'; +import { ErrorCode, StopReasonCode, VARIABLE_REQUEST_FLAGS } from '../Constants'; import { DebugProtocolServer } from '../server/DebugProtocolServer'; import * as portfinder from 'portfinder'; import { util } from '../../util'; @@ -17,48 +16,113 @@ import { AllThreadsStoppedUpdate } from '../events/updates/AllThreadsStoppedUpda const sinon = createSandbox(); describe('DebugProtocolClient', () => { - let bsDebugger: DebugProtocolClient; - let roku: MockDebugProtocolServer; + let server: DebugProtocolServer; + let client: DebugProtocolClient; + let plugin: TestPlugin; beforeEach(async () => { sinon.stub(console, 'log').callsFake((...args) => { }); - roku = new MockDebugProtocolServer(); - await roku.initialize(); - bsDebugger = new DebugProtocolClient({ - host: 'localhost', - controllerPort: roku.controllerPort - }); + const options = { + controllerPort: undefined as number, + host: '127.0.0.1' + }; + + if (!options.controllerPort) { + options.controllerPort = await portfinder.getPortPromise(); + } + server = new DebugProtocolServer(options); + plugin = server.plugins.add(new TestPlugin()); + await server.start(); + + client = new DebugProtocolClient(options); + //disable logging for tests because they clutter the test output + client['logger'].logLevel = 'off'; }); - afterEach(() => { - bsDebugger?.destroy(); - bsDebugger = undefined; + afterEach(async () => { + client?.destroy(); + //shut down and destroy the server after each test + await server?.stop(); + await util.sleep(10); sinon.restore(); - roku.destroy(); }); - describe('connect', () => { - it('sends magic to server on connect', async () => { - let action = roku.waitForMagic(); - void bsDebugger.connect(); - void roku.processActions(); - let magic = await action.promise; - expect(magic).to.equal(DebugProtocolClient.DEBUGGER_MAGIC); - }); + it('handles v3 handshake', async () => { + //these are false by default + expect(client.watchPacketLength).to.be.equal(false); + expect(client.isHandshakeComplete).to.be.equal(false); - it('validates magic from server on connect', async () => { - const magicAction = roku.waitForMagic(); - roku.sendHandshakeResponse(magicAction.promise); + await client.connect(); + expect(plugin.responses[0].data).to.eql({ + packetLength: undefined, + requestId: HandshakeRequest.REQUEST_ID, + errorCode: ErrorCode.OK, - void bsDebugger.connect(); + magic: 'bsdebug', + protocolVersion: '3.1.0', + revisionTimestamp: new Date(2022, 1, 1) + } as HandshakeV3Response['data']); - void roku.processActions(); + //version 3.0 includes packet length, so these should be true now + expect(client.watchPacketLength).to.be.equal(true); + expect(client.isHandshakeComplete).to.be.equal(true); + }); - //wait for the debugger to finish verifying the handshake - expect( - await bsDebugger.once('handshake-verified') - ).to.be.true; + it('throws on magic mismatch', async () => { + plugin.pushResponse( + HandshakeV3Response.fromJson({ + magic: 'not correct magic', + protocolVersion: '3.1.0', + revisionTimestamp: new Date(2022, 1, 1) + }) + ); + + const verifyHandshakePromise = client.once('handshake-verified'); + + await client.connect(); + + //wait for the debugger to finish verifying the handshake + expect(await verifyHandshakePromise).to.be.false; + }); + + it('handles legacy handshake', async () => { + + expect(client.watchPacketLength).to.be.equal(false); + expect(client.isHandshakeComplete).to.be.equal(false); + + plugin.pushResponse( + HandshakeResponse.fromJson({ + magic: DebugProtocolClient.DEBUGGER_MAGIC, + protocolVersion: '1.0.0' + }) + ); + + await client.connect(); + + expect(client.watchPacketLength).to.be.equal(false); + expect(client.isHandshakeComplete).to.be.equal(true); + }); + + it.only('handles AllThreadsStoppedUpdate after handshake', async () => { + await client.connect(); + + const [, event] = await Promise.all([ + //wait for the client to suspend + client.once('suspend'), + //send an update which should cause the client to suspend + server.sendUpdate( + AllThreadsStoppedUpdate.fromJson({ + threadIndex: 1, + stopReason: StopReasonCode.Break, + stopReasonDetail: 'test' + }) + ) + ]); + expect(event.data).include({ + threadIndex: 1, + stopReason: StopReasonCode.Break, + stopReasonDetail: 'test' }); }); @@ -94,10 +158,21 @@ describe('DebugProtocolClient', () => { } it('skips case sensitivity info on lower protocol versions', async () => { - bsDebugger.protocolVersion = '2.0.0'; - bsDebugger['stopped'] = true; - const stub = sinon.stub(bsDebugger as any, 'makeRequest').callsFake(() => { }); - await bsDebugger.getVariables(['m', 'top'], false, 1, 2); + await client.connect(); + //send the AllThreadsStopped event, and also wait for the client to suspend + await Promise.all([ + server.sendUpdate(AllThreadsStoppedUpdate.fromJson({ + threadIndex: 2, + stopReason: StopReasonCode.Break, + stopReasonDetail: 'because' + })), + await client.once('suspend') + ]); + + client.protocolVersion = '2.0.0'; + client['stopped'] = true; + const stub = sinon.stub(client as any, 'makeRequest').callsFake(() => { }); + await client.getVariables(['m', 'top'], false, 1, 2); expect( getVariablesRequestBufferToJson(stub.getCalls()[0].args[0]) ).to.eql({ @@ -111,10 +186,10 @@ describe('DebugProtocolClient', () => { }); it('marks strings as case-sensitive', async () => { - bsDebugger.protocolVersion = '3.1.0'; - bsDebugger['stopped'] = true; - const stub = sinon.stub(bsDebugger as any, 'makeRequest').callsFake(() => { }); - await bsDebugger.getVariables(['m', 'top', '"someKey"', '""someKeyWithInternalQuotes""'], true, 1, 2); + client.protocolVersion = '3.1.0'; + client['stopped'] = true; + const stub = sinon.stub(client as any, 'makeRequest').callsFake(() => { }); + await client.getVariables(['m', 'top', '"someKey"', '""someKeyWithInternalQuotes""'], true, 1, 2); expect( getVariablesRequestBufferToJson(stub.getCalls()[0].args[0]) ).to.eql({ @@ -177,130 +252,3 @@ class TestPlugin implements ProtocolPlugin { (this.responses as Array).push(event.response); } } - -describe.skip('Debugger new tests', () => { - let server: DebugProtocolServer; - let client: DebugProtocolClient; - let plugin: TestPlugin; - const options = { - controllerPort: undefined as number, - host: '127.0.0.1' - }; - - beforeEach(async () => { - if (!options.controllerPort) { - options.controllerPort = await portfinder.getPortPromise(); - } - server = new DebugProtocolServer(options); - plugin = server.plugins.add(new TestPlugin()); - await server.start(); - - client = new DebugProtocolClient(options); - //disable logging for tests because they clutter the test output - client['logger'].logLevel = 'off'; - }); - - afterEach(async () => { - client?.destroy(); - //shut down and destroy the server after each test - await server?.stop(); - await util.sleep(10); - }); - - it('handles v3 handshake', async () => { - //these are false by default - expect(client.watchPacketLength).to.be.equal(false); - expect(client.isHandshakeComplete).to.be.equal(false); - - await client.connect(); - expect(plugin.responses[0].data).to.eql({ - magic: 'bsdebug', - protocolVersion: '3.1.0', - revisionTimestamp: new Date(2022, 1, 1) - } as HandshakeV3Response['data']); - - //version 3.0 includes packet length, so these should be true now - expect(client.watchPacketLength).to.be.equal(true); - expect(client.isHandshakeComplete).to.be.equal(true); - }); - - it('throws on magic mismatch', async () => { - plugin.pushResponse( - HandshakeV3Response.fromJson({ - magic: 'not correct magic', - protocolVersion: '3.1.0', - revisionTimestamp: new Date(2022, 1, 1) - }) - ); - - const verifyHandshakePromise = client.once('handshake-verified'); - - await client.connect(); - - //wait for the debugger to finish verifying the handshake - expect(await verifyHandshakePromise).to.be.false; - }); - - it('handles legacy handshake', async () => { - - expect(client.watchPacketLength).to.be.equal(false); - expect(client.isHandshakeComplete).to.be.equal(false); - - plugin.pushResponse( - HandshakeResponse.fromJson({ - magic: DebugProtocolClient.DEBUGGER_MAGIC, - protocolVersion: '1.0.0' - }) - ); - - await client.connect(); - - expect(client.watchPacketLength).to.be.equal(false); - expect(client.isHandshakeComplete).to.be.equal(true); - }); - - it('handles events after handshake', async () => { - await client.connect(); - - await server.sendUpdate( - AllThreadsStoppedUpdate.fromJson({ - primaryThreadIndex: 1, - stopReason: StopReasonCode.Break, - stopReasonDetail: 'test' - }) - ); - const event = await client.once('suspend'); - expect(event.data).include({ - primaryThreadIndex: 1, - stopReason: StopReasonCode.Break, - stopReasonDetail: 'test' - }); - // let protocolEvent = createProtocolEventV3({ - // requestId: 0, - // errorCode: ERROR_CODES.CANT_CONTINUE, - // updateType: UPDATE_TYPES.ALL_THREADS_STOPPED - // }); - - // let mockResponse = new SmartBuffer(); - // mockResponse.writeBuffer(handshake.toBuffer()); - // mockResponse.writeBuffer(protocolEvent.toBuffer()); - - // bsDebugger['unhandledData'] = mockResponse.toBuffer(); - - // const stub = sinon.stub(bsDebugger as any, 'removedProcessedBytes').callThrough(); - - // expect(bsDebugger.watchPacketLength).to.be.equal(false); - // expect(bsDebugger.handshakeComplete).to.be.equal(false); - - // expect(bsDebugger['parseUnhandledData'](bsDebugger['unhandledData'])).to.be.equal(true); - - // expect(bsDebugger.watchPacketLength).to.be.equal(true); - // expect(bsDebugger.handshakeComplete).to.be.equal(true); - // expect(bsDebugger['unhandledData'].byteLength).to.be.equal(0); - - // let calls = stub.getCalls(); - // expect(calls[0].args[0]).instanceOf(HandshakeResponseV3); - // expect(calls[1].args[0]).instanceOf(ProtocolEventV3); - }); - -}); diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index beece53e..864a697a 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -1,7 +1,7 @@ import * as Net from 'net'; import * as EventEmitter from 'eventemitter3'; import * as semver from 'semver'; -import { PROTOCOL_ERROR_CODES, COMMANDS, STEP_TYPE, StopReasonCode, ErrorCode, UPDATE_TYPES } from '../Constants'; +import { PROTOCOL_ERROR_CODES, Command, StepType, StopReasonCode, ErrorCode, UpdateType, UpdateTypeCode, StepTypeCode } from '../Constants'; import { SmartBuffer } from 'smart-buffer'; import { logger } from '../../logging'; import { ExecuteV3Response } from '../events/responses/ExecuteV3Response'; @@ -77,8 +77,8 @@ export class DebugProtocolClient { private ioClient: Net.Socket; private buffer = Buffer.alloc(0); private stopped = false; - private totalRequests = 0; - private activeRequests1 = new Map(); + private requestIdSequence = 0; + private activeRequests = new Map(); private options: ConstructorOptions; /** @@ -234,7 +234,7 @@ export class DebugProtocolClient { this.stopped = false; return this.sendRequest( ContinueRequest.fromJson({ - requestId: this.totalRequests++ + requestId: this.requestIdSequence++ }) ); } @@ -244,7 +244,7 @@ export class DebugProtocolClient { if (!this.stopped || force) { return this.sendRequest( StopRequest.fromJson({ - requestId: this.totalRequests++ + requestId: this.requestIdSequence++ }) ); } @@ -253,34 +253,31 @@ export class DebugProtocolClient { public async exitChannel() { return this.sendRequest( ExitChannelRequest.fromJson({ - requestId: this.totalRequests++ + requestId: this.requestIdSequence++ }) ); } public async stepIn(threadIndex: number = this.primaryThread) { - return this.step(STEP_TYPE.STEP_TYPE_LINE, threadIndex); + return this.step(StepType.Line, threadIndex); } public async stepOver(threadIndex: number = this.primaryThread) { - return this.step(STEP_TYPE.STEP_TYPE_OVER, threadIndex); + return this.step(StepType.Over, threadIndex); } public async stepOut(threadIndex: number = this.primaryThread) { - return this.step(STEP_TYPE.STEP_TYPE_OUT, threadIndex); + return this.step(StepType.Out, threadIndex); } - private async step(stepType: STEP_TYPE, threadIndex: number): Promise { - this.logger.log('[step]', { stepType: STEP_TYPE[stepType], threadId: threadIndex, stopped: this.stopped }); + private async step(stepType: StepType, threadIndex: number): Promise { + this.logger.log('[step]', { stepType: stepType, threadId: threadIndex, stopped: this.stopped }); - let buffer = new SmartBuffer({ size: 17 }); - buffer.writeUInt32LE(threadIndex); // thread_index - buffer.writeUInt8(stepType); // step_type if (this.stopped) { this.stopped = false; let stepResult = await this.sendRequest( StepRequest.fromJson({ - requestId: this.totalRequests++, + requestId: this.requestIdSequence++, stepType: stepType, threadIndex: threadIndex }) @@ -300,7 +297,7 @@ export class DebugProtocolClient { if (this.stopped) { let result = await this.sendRequest( ThreadsRequest.fromJson({ - requestId: this.totalRequests++ + requestId: this.requestIdSequence++ }) ); @@ -330,7 +327,7 @@ export class DebugProtocolClient { if (this.stopped && threadIndex > -1) { return this.sendRequest( StackTraceRequest.fromJson({ - requestId: this.totalRequests++, + requestId: this.requestIdSequence++, threadIndex: threadIndex }) ); @@ -351,7 +348,7 @@ export class DebugProtocolClient { public async getVariables(variablePathEntries: Array = [], getChildKeys = true, stackFrameIndex: number = this.stackFrameIndex, threadIndex: number = this.primaryThread) { if (this.stopped && threadIndex > -1) { const request = VariablesRequest.fromJson({ - requestId: this.totalRequests++, + requestId: this.requestIdSequence++, threadIndex: threadIndex, stackFrameIndex: stackFrameIndex, getChildKeys: getChildKeys, @@ -371,7 +368,7 @@ export class DebugProtocolClient { if (this.stopped && threadIndex > -1) { return this.sendRequest( ExecuteRequest.fromJson({ - requestId: this.totalRequests++, + requestId: this.requestIdSequence++, threadIndex: threadIndex, stackFrameIndex: stackFrameIndex, sourceCode: sourceCode @@ -384,11 +381,17 @@ export class DebugProtocolClient { const { enableComponentLibrarySpecificBreakpoints } = this; if (breakpoints?.length > 0) { const json = { - requestId: this.totalRequests++, - breakpoints: breakpoints.map(x => ({ - ...x, - ignoreCount: x.hitCount - })) + requestId: this.requestIdSequence++, + breakpoints: breakpoints.map(x => { + let breakpoint = { + ...x, + ignoreCount: x.hitCount + }; + if (enableComponentLibrarySpecificBreakpoints && breakpoint.componentLibraryName) { + breakpoint.filePath = breakpoint.filePath.replace(/^pkg:\//i, `lib:/${breakpoint.componentLibraryName}/`); + } + return breakpoint; + }) }; if (this.supportsConditionalBreakpoints) { @@ -407,7 +410,7 @@ export class DebugProtocolClient { public async listBreakpoints(): Promise { return this.sendRequest( ListBreakpointsRequest.fromJson({ - requestId: this.totalRequests++ + requestId: this.requestIdSequence++ }) ); } @@ -415,7 +418,7 @@ export class DebugProtocolClient { public async removeBreakpoints(breakpointIds: number[]): Promise { if (breakpointIds?.length > 0) { const command = RemoveBreakpointsRequest.fromJson({ - requestId: this.totalRequests++, + requestId: this.requestIdSequence++, breakpointIds: breakpointIds }); return this.sendRequest(command); @@ -427,25 +430,22 @@ export class DebugProtocolClient { * Send a request to the roku device, and get a promise that resolves once we have received the response */ private async sendRequest(request: ProtocolRequest) { - this.totalRequests++; - let requestId = this.totalRequests; + this.activeRequests.set(request.data.requestId, request); - this.activeRequests1.set(requestId, request); - - return new Promise((resolve, reject) => { + return new Promise((resolve) => { let unsubscribe = this.on('response', (event) => { - if (event.data.requestId === requestId) { + if (event.data.requestId === request.data.requestId) { unsubscribe(); - this.activeRequests1.delete(requestId); + this.activeRequests.delete(request.data.requestId); resolve(event as unknown as T); } }); - this.logger.debug('makeRequest', `requestId=${requestId}`, request); + this.logger.debug('makeRequest', `requestId=${request.data.requestId}`, request); if (this.controllerClient) { this.controllerClient.write(request.toBuffer()); } else { - throw new Error(`Controller connection was closed - Command: ${COMMANDS[request.data.commandCode]}`); + throw new Error(`Controller connection was closed - Command: ${Command[request.data.command]}`); } }); } @@ -516,58 +516,62 @@ export class DebugProtocolClient { //requestId 0 means this is an update return this.getResponse(genericResponse); } else { - return this.getUpdate(genericResponse); + return this.getUpdate(); } } private getResponse(genericResponse: GenericV3Response): ProtocolResponse { - const request = this.activeRequests1.get(genericResponse.data.requestId); + const request = this.activeRequests.get(genericResponse.data.requestId); if (!request) { return; } - switch (request.data.commandCode) { - case COMMANDS.STOP: - case COMMANDS.CONTINUE: - case COMMANDS.STEP: - case COMMANDS.EXIT_CHANNEL: + switch (request.data.command) { + case Command.Stop: + case Command.Continue: + case Command.Step: + case Command.ExitChannel: return genericResponse; - case COMMANDS.EXECUTE: + case Command.Execute: return ExecuteV3Response.fromBuffer(this.buffer); - case COMMANDS.ADD_BREAKPOINTS: - case COMMANDS.ADD_CONDITIONAL_BREAKPOINTS: + case Command.AddBreakpoints: + case Command.AddConditionalBreakpoints: return AddBreakpointsResponse.fromBuffer(this.buffer); - case COMMANDS.LIST_BREAKPOINTS: + case Command.ListBreakpoints: return ListBreakpointsResponse.fromBuffer(this.buffer); - case COMMANDS.REMOVE_BREAKPOINTS: + case Command.RemoveBreakpoints: return RemoveBreakpointsResponse.fromBuffer(this.buffer); - case COMMANDS.VARIABLES: + case Command.Variables: return VariablesResponse.fromBuffer(this.buffer); - case COMMANDS.STACKTRACE: + case Command.StackTrace: return this.watchPacketLength ? StackTraceV3Response.fromBuffer(this.buffer) : StackTraceResponse.fromBuffer(this.buffer); - case COMMANDS.THREADS: + case Command.Threads: return ThreadsResponse.fromBuffer(this.buffer); default: return undefined; } } - private getUpdate(genericResponse: GenericV3Response): ProtocolUpdate { + private getUpdate(): ProtocolUpdate { //read the update_type from the buffer (save some buffer parsing time by narrowing to the exact update type) - const updateType = this.buffer.readUInt32LE(genericResponse.readOffset) as UPDATE_TYPES; + const updateTypeCode = this.buffer.readUInt32LE( + // if the protocol supports packet length, then update_type is bytes 12-16. Otherwise, it's bytes 8-12 + this.watchPacketLength ? 12 : 8 + ); + const updateType = UpdateTypeCode[updateTypeCode] as UpdateType; - this.logger.log('Update Type:', updateType, UPDATE_TYPES[updateType]); + this.logger.log('Update Type:', updateType, updateType); switch (updateType) { - case UPDATE_TYPES.IO_PORT_OPENED: + case UpdateType.IOPortOpened: //TODO handle this return IOPortOpenedUpdate.fromBuffer(this.buffer); - case UPDATE_TYPES.ALL_THREADS_STOPPED: + case UpdateType.AllThreadsStopped: return AllThreadsStoppedUpdate.fromBuffer(this.buffer); - case UPDATE_TYPES.THREAD_ATTACHED: + case UpdateType.ThreadAttached: return ThreadAttachedUpdate.fromBuffer(this.buffer); - case UPDATE_TYPES.BREAKPOINT_ERROR: + case UpdateType.BreakpointError: //we do nothing with breakpoint errors at this time. return BreakpointErrorUpdate.fromBuffer(this.buffer); - case UPDATE_TYPES.COMPILE_ERROR: + case UpdateType.CompileError: return CompileErrorUpdate.fromBuffer(this.buffer); default: return undefined; diff --git a/src/debugProtocol/events/ProtocolEvent.ts b/src/debugProtocol/events/ProtocolEvent.ts index 24113bc4..01a245e7 100644 --- a/src/debugProtocol/events/ProtocolEvent.ts +++ b/src/debugProtocol/events/ProtocolEvent.ts @@ -1,4 +1,4 @@ -import type { COMMANDS, UPDATE_TYPES } from '../Constants'; +import type { Command, UpdateType } from '../Constants'; export interface ProtocolEvent { /** @@ -30,7 +30,7 @@ export interface ProtocolRequestData { //common props packetLength: number; requestId: number; - commandCode: COMMANDS; + command: Command; } export type ProtocolRequest = ProtocolEvent; @@ -41,7 +41,7 @@ export interface ProtocolUpdateData { packetLength: number; requestId: number; errorCode: number; - updateType: UPDATE_TYPES; + updateType: UpdateType; } export type ProtocolUpdate = ProtocolEvent; diff --git a/src/debugProtocol/events/requests/AddBreakpointsRequest.spec.ts b/src/debugProtocol/events/requests/AddBreakpointsRequest.spec.ts index fe218564..0134dd5d 100644 --- a/src/debugProtocol/events/requests/AddBreakpointsRequest.spec.ts +++ b/src/debugProtocol/events/requests/AddBreakpointsRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS } from '../../Constants'; +import { Command } from '../../Constants'; import { AddBreakpointsRequest } from './AddBreakpointsRequest'; describe('AddBreakpointsRequest', () => { @@ -12,7 +12,7 @@ describe('AddBreakpointsRequest', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 3, - commandCode: COMMANDS.ADD_BREAKPOINTS, + command: Command.AddBreakpoints, breakpoints: [] }); @@ -22,7 +22,7 @@ describe('AddBreakpointsRequest', () => { ).to.eql({ packetLength: 16, // 4 bytes requestId: 3, // 4 bytes - commandCode: COMMANDS.ADD_BREAKPOINTS, // 4 bytes + command: Command.AddBreakpoints, // 4 bytes // num_breakpoints // 4 bytes breakpoints: [] @@ -47,7 +47,7 @@ describe('AddBreakpointsRequest', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 3, - commandCode: COMMANDS.CONTINUE, + command: Command.AddBreakpoints, breakpoints: [{ filePath: 'source/main.brs', @@ -66,7 +66,7 @@ describe('AddBreakpointsRequest', () => { ).to.eql({ packetLength: 64, // 4 bytes requestId: 3, // 4 bytes - commandCode: COMMANDS.CONTINUE, // 4 bytes + command: Command.AddBreakpoints, // 4 bytes // num_breakpoints // 4 bytes breakpoints: [{ diff --git a/src/debugProtocol/events/requests/AddBreakpointsRequest.ts b/src/debugProtocol/events/requests/AddBreakpointsRequest.ts index 2a912948..4ad98800 100644 --- a/src/debugProtocol/events/requests/AddBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/AddBreakpointsRequest.ts @@ -1,7 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import { util } from '../../../util'; -import type { RequestData } from '../../Constants'; -import { COMMANDS } from '../../Constants'; +import { Command } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; @@ -71,6 +69,6 @@ export class AddBreakpointsRequest implements ProtocolRequest { //common props packetLength: undefined as number, requestId: undefined as number, - commandCode: COMMANDS.ADD_BREAKPOINTS + command: Command.AddBreakpoints }; } diff --git a/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.spec.ts b/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.spec.ts index c8440d11..f8491513 100644 --- a/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.spec.ts +++ b/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS } from '../../Constants'; +import { Command } from '../../Constants'; import { AddConditionalBreakpointsRequest } from './AddConditionalBreakpointsRequest'; describe('AddConditionalBreakpointsRequest', () => { @@ -12,7 +12,7 @@ describe('AddConditionalBreakpointsRequest', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 3, - commandCode: COMMANDS.ADD_CONDITIONAL_BREAKPOINTS, + command: Command.AddConditionalBreakpoints, breakpoints: [] }); @@ -22,7 +22,7 @@ describe('AddConditionalBreakpointsRequest', () => { ).to.eql({ packetLength: 16, // 4 bytes requestId: 3, // 4 bytes - commandCode: COMMANDS.ADD_CONDITIONAL_BREAKPOINTS, // 4 bytes + command: Command.AddConditionalBreakpoints, // 4 bytes // num_breakpoints // 4 bytes breakpoints: [] @@ -48,7 +48,7 @@ describe('AddConditionalBreakpointsRequest', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 3, - commandCode: COMMANDS.ADD_CONDITIONAL_BREAKPOINTS, + command: Command.AddConditionalBreakpoints, breakpoints: [{ filePath: 'source/main.brs', @@ -69,7 +69,7 @@ describe('AddConditionalBreakpointsRequest', () => { ).to.eql({ packetLength: 73, // 4 bytes requestId: 3, // 4 bytes - commandCode: COMMANDS.ADD_CONDITIONAL_BREAKPOINTS, // 4 bytes + command: Command.AddConditionalBreakpoints, // 4 bytes // num_breakpoints // 4 bytes breakpoints: [{ diff --git a/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts b/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts index 12fef719..99252d49 100644 --- a/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; import { util } from '../../../util'; -import { COMMANDS } from '../../Constants'; +import { Command } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; @@ -94,6 +94,6 @@ export class AddConditionalBreakpointsRequest implements ProtocolRequest { //common props packetLength: undefined as number, requestId: undefined as number, - commandCode: COMMANDS.ADD_CONDITIONAL_BREAKPOINTS + command: Command.AddConditionalBreakpoints }; } diff --git a/src/debugProtocol/events/requests/ContinueRequest.spec.ts b/src/debugProtocol/events/requests/ContinueRequest.spec.ts index ea8fdd24..f0c4cde1 100644 --- a/src/debugProtocol/events/requests/ContinueRequest.spec.ts +++ b/src/debugProtocol/events/requests/ContinueRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS } from '../../Constants'; +import { Command } from '../../Constants'; import { ContinueRequest } from './ContinueRequest'; describe('ContinueRequest', () => { @@ -11,7 +11,7 @@ describe('ContinueRequest', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 3, - commandCode: COMMANDS.CONTINUE + command: Command.Continue }); expect( @@ -19,7 +19,7 @@ describe('ContinueRequest', () => { ).to.eql({ packetLength: 12, // 4 bytes requestId: 3, // 4 bytes - commandCode: COMMANDS.CONTINUE // 4 bytes + command: Command.Continue // 4 bytes }); }); }); diff --git a/src/debugProtocol/events/requests/ContinueRequest.ts b/src/debugProtocol/events/requests/ContinueRequest.ts index 55513e1c..5e977d92 100644 --- a/src/debugProtocol/events/requests/ContinueRequest.ts +++ b/src/debugProtocol/events/requests/ContinueRequest.ts @@ -1,6 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import type { RequestData } from '../../Constants'; -import { COMMANDS } from '../../Constants'; +import { Command } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; @@ -34,6 +33,6 @@ export class ContinueRequest implements ProtocolRequest { //common props packetLength: undefined as number, requestId: undefined as number, - commandCode: COMMANDS.CONTINUE + command: Command.Continue }; } diff --git a/src/debugProtocol/events/requests/ExecuteRequest.spec.ts b/src/debugProtocol/events/requests/ExecuteRequest.spec.ts index dcc423e7..8afca064 100644 --- a/src/debugProtocol/events/requests/ExecuteRequest.spec.ts +++ b/src/debugProtocol/events/requests/ExecuteRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS } from '../../Constants'; +import { Command } from '../../Constants'; import { ExecuteRequest } from './ExecuteRequest'; describe('ExecuteRequest', () => { @@ -14,7 +14,7 @@ describe('ExecuteRequest', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 3, - commandCode: COMMANDS.EXECUTE, + command: Command.Execute, sourceCode: 'print "text"', stackFrameIndex: 2, @@ -26,7 +26,7 @@ describe('ExecuteRequest', () => { ).to.eql({ packetLength: 33, // 4 bytes requestId: 3, // 4 bytes - commandCode: COMMANDS.EXECUTE, // 4 bytes + command: Command.Execute, // 4 bytes sourceCode: 'print "text"', // 13 bytes stackFrameIndex: 2, // 4 bytes diff --git a/src/debugProtocol/events/requests/ExecuteRequest.ts b/src/debugProtocol/events/requests/ExecuteRequest.ts index de5dbc09..7f8aafbb 100644 --- a/src/debugProtocol/events/requests/ExecuteRequest.ts +++ b/src/debugProtocol/events/requests/ExecuteRequest.ts @@ -1,6 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import type { RequestData } from '../../Constants'; -import { COMMANDS } from '../../Constants'; +import { Command } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; @@ -52,6 +51,6 @@ export class ExecuteRequest implements ProtocolRequest { //common props packetLength: undefined as number, requestId: undefined as number, - commandCode: COMMANDS.EXECUTE + command: Command.Execute }; } diff --git a/src/debugProtocol/events/requests/ExitChannelRequest.spec.ts b/src/debugProtocol/events/requests/ExitChannelRequest.spec.ts index 34452130..78982e56 100644 --- a/src/debugProtocol/events/requests/ExitChannelRequest.spec.ts +++ b/src/debugProtocol/events/requests/ExitChannelRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS } from '../../Constants'; +import { Command } from '../../Constants'; import { ExitChannelRequest } from './ExitChannelRequest'; describe('ExitChannelRequest', () => { @@ -11,7 +11,7 @@ describe('ExitChannelRequest', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 3, - commandCode: COMMANDS.EXIT_CHANNEL + command: Command.ExitChannel }); expect( @@ -19,7 +19,7 @@ describe('ExitChannelRequest', () => { ).to.eql({ packetLength: 12, // 4 bytes requestId: 3, // 4 bytes - commandCode: COMMANDS.EXIT_CHANNEL // 4 bytes + command: Command.ExitChannel // 4 bytes }); }); }); diff --git a/src/debugProtocol/events/requests/ExitChannelRequest.ts b/src/debugProtocol/events/requests/ExitChannelRequest.ts index 9813d098..592a718c 100644 --- a/src/debugProtocol/events/requests/ExitChannelRequest.ts +++ b/src/debugProtocol/events/requests/ExitChannelRequest.ts @@ -1,6 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import type { RequestData } from '../../Constants'; -import { COMMANDS } from '../../Constants'; +import { Command } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; @@ -34,6 +33,6 @@ export class ExitChannelRequest implements ProtocolRequest { //common props packetLength: undefined as number, requestId: undefined as number, - commandCode: COMMANDS.EXIT_CHANNEL + command: Command.ExitChannel }; } diff --git a/src/debugProtocol/events/requests/HandshakeRequest.ts b/src/debugProtocol/events/requests/HandshakeRequest.ts index 8cbd21b6..696efee8 100644 --- a/src/debugProtocol/events/requests/HandshakeRequest.ts +++ b/src/debugProtocol/events/requests/HandshakeRequest.ts @@ -2,7 +2,7 @@ import { SmartBuffer } from 'smart-buffer'; import { util } from '../../../util'; import { protocolUtils } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; -import type { COMMANDS } from '../../Constants'; +import type { Command } from '../../Constants'; /** * The initial handshake sent by the client. This is just the `magic` to initiate the debug protocol session @@ -46,6 +46,6 @@ export class HandshakeRequest implements ProtocolRequest { packetLength: undefined as number, //hardcode the max integer value. This must be the same value as the HandshakeResponse class requestId: HandshakeRequest.REQUEST_ID, - commandCode: undefined as COMMANDS + command: undefined as Command }; } diff --git a/src/debugProtocol/events/requests/ListBreakpointsRequest.spec.ts b/src/debugProtocol/events/requests/ListBreakpointsRequest.spec.ts index 794f2862..937c5c5e 100644 --- a/src/debugProtocol/events/requests/ListBreakpointsRequest.spec.ts +++ b/src/debugProtocol/events/requests/ListBreakpointsRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS } from '../../Constants'; +import { Command } from '../../Constants'; import { ListBreakpointsRequest } from './ListBreakpointsRequest'; describe('ListBreakpointsRequest', () => { @@ -11,7 +11,7 @@ describe('ListBreakpointsRequest', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 3, - commandCode: COMMANDS.LIST_BREAKPOINTS + command: Command.ListBreakpoints }); expect( @@ -19,7 +19,7 @@ describe('ListBreakpointsRequest', () => { ).to.eql({ packetLength: 12, // 4 bytes requestId: 3, // 4 bytes - commandCode: COMMANDS.LIST_BREAKPOINTS // 4 bytes + command: Command.ListBreakpoints // 4 bytes }); }); }); diff --git a/src/debugProtocol/events/requests/ListBreakpointsRequest.ts b/src/debugProtocol/events/requests/ListBreakpointsRequest.ts index d1c8f8d1..920e04ca 100644 --- a/src/debugProtocol/events/requests/ListBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/ListBreakpointsRequest.ts @@ -1,5 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import { COMMANDS } from '../../Constants'; +import { Command } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; @@ -33,6 +33,6 @@ export class ListBreakpointsRequest implements ProtocolRequest { //common props packetLength: undefined as number, requestId: undefined as number, - commandCode: COMMANDS.LIST_BREAKPOINTS + command: Command.ListBreakpoints }; } diff --git a/src/debugProtocol/events/requests/RemoveBreakpointsCommand.spec.ts b/src/debugProtocol/events/requests/RemoveBreakpointsCommand.spec.ts index 982fea63..76da8c1b 100644 --- a/src/debugProtocol/events/requests/RemoveBreakpointsCommand.spec.ts +++ b/src/debugProtocol/events/requests/RemoveBreakpointsCommand.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS } from '../../Constants'; +import { Command } from '../../Constants'; import { RemoveBreakpointsRequest } from './RemoveBreakpointsRequest'; describe('RemoveBreakpointsRequest', () => { @@ -12,7 +12,7 @@ describe('RemoveBreakpointsRequest', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 3, - commandCode: COMMANDS.REMOVE_BREAKPOINTS, + command: Command.RemoveBreakpoints, breakpointIds: [1, 2, 100], numBreakpoints: 3 @@ -23,7 +23,7 @@ describe('RemoveBreakpointsRequest', () => { ).to.eql({ packetLength: 28, // 4 bytes requestId: 3, // 4 bytes - commandCode: COMMANDS.REMOVE_BREAKPOINTS, // 4 bytes + command: Command.RemoveBreakpoints, // 4 bytes breakpointIds: [1, 2, 100], // 12 bytes numBreakpoints: 3 // 4 bytes diff --git a/src/debugProtocol/events/requests/RemoveBreakpointsRequest.ts b/src/debugProtocol/events/requests/RemoveBreakpointsRequest.ts index 0fc55114..5238e135 100644 --- a/src/debugProtocol/events/requests/RemoveBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/RemoveBreakpointsRequest.ts @@ -1,6 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import type { RequestData } from '../../Constants'; -import { COMMANDS } from '../../Constants'; +import { Command } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; @@ -56,6 +55,6 @@ export class RemoveBreakpointsRequest implements ProtocolRequest { //common props packetLength: undefined as number, requestId: undefined as number, - commandCode: COMMANDS.REMOVE_BREAKPOINTS + command: Command.RemoveBreakpoints }; } diff --git a/src/debugProtocol/events/requests/StackTraceRequest.spec.ts b/src/debugProtocol/events/requests/StackTraceRequest.spec.ts index 7fab9635..e7b6a2fc 100644 --- a/src/debugProtocol/events/requests/StackTraceRequest.spec.ts +++ b/src/debugProtocol/events/requests/StackTraceRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS, STEP_TYPE } from '../../Constants'; +import { Command, StepType } from '../../Constants'; import { StackTraceRequest } from './StackTraceRequest'; describe('StackTraceRequest', () => { @@ -12,7 +12,7 @@ describe('StackTraceRequest', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 3, - commandCode: COMMANDS.STACKTRACE, + command: Command.StackTrace, threadIndex: 2 }); @@ -22,7 +22,7 @@ describe('StackTraceRequest', () => { ).to.eql({ packetLength: 16, // 4 bytes requestId: 3, // 4 bytes - commandCode: COMMANDS.STACKTRACE, // 4 bytes + command: Command.StackTrace, // 4 bytes threadIndex: 2 //4 bytes }); diff --git a/src/debugProtocol/events/requests/StackTraceRequest.ts b/src/debugProtocol/events/requests/StackTraceRequest.ts index fec5f964..82906205 100644 --- a/src/debugProtocol/events/requests/StackTraceRequest.ts +++ b/src/debugProtocol/events/requests/StackTraceRequest.ts @@ -1,5 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import { COMMANDS } from '../../Constants'; +import { Command } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; @@ -39,7 +39,7 @@ export class StackTraceRequest implements ProtocolRequest { //common props packetLength: undefined as number, requestId: undefined as number, - commandCode: COMMANDS.STACKTRACE + command: Command.StackTrace }; } diff --git a/src/debugProtocol/events/requests/StepRequest.spec.ts b/src/debugProtocol/events/requests/StepRequest.spec.ts index d0f76ab6..c068bd69 100644 --- a/src/debugProtocol/events/requests/StepRequest.spec.ts +++ b/src/debugProtocol/events/requests/StepRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS, STEP_TYPE } from '../../Constants'; +import { Command, StepType } from '../../Constants'; import { StepRequest } from './StepRequest'; describe('StepRequest', () => { @@ -7,16 +7,16 @@ describe('StepRequest', () => { const command = StepRequest.fromJson({ requestId: 3, threadIndex: 2, - stepType: STEP_TYPE.STEP_TYPE_LINE + stepType: StepType.Line }); expect(command.data).to.eql({ packetLength: undefined, requestId: 3, - commandCode: COMMANDS.STEP, + command: Command.Step, threadIndex: 2, - stepType: STEP_TYPE.STEP_TYPE_LINE + stepType: StepType.Line }); expect( @@ -24,9 +24,9 @@ describe('StepRequest', () => { ).to.eql({ packetLength: 17, // 4 bytes requestId: 3, // 4 bytes - commandCode: COMMANDS.STEP, // 4 bytes + command: Command.Step, // 4 bytes - stepType: STEP_TYPE.STEP_TYPE_LINE, // 1 byte + stepType: StepType.Line, // 1 byte threadIndex: 2 // 4 bytes }); }); diff --git a/src/debugProtocol/events/requests/StepRequest.ts b/src/debugProtocol/events/requests/StepRequest.ts index 94cb4b4b..9ec6207d 100644 --- a/src/debugProtocol/events/requests/StepRequest.ts +++ b/src/debugProtocol/events/requests/StepRequest.ts @@ -1,12 +1,12 @@ import { SmartBuffer } from 'smart-buffer'; -import type { RequestData, STEP_TYPE } from '../../Constants'; -import { COMMANDS } from '../../Constants'; +import type { StepType } from '../../Constants'; +import { Command, StepTypeCode } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; export class StepRequest implements ProtocolRequest { - public static fromJson(data: { requestId: number; threadIndex: number; stepType: STEP_TYPE }) { + public static fromJson(data: { requestId: number; threadIndex: number; stepType: StepType }) { const request = new StepRequest(); protocolUtils.loadJson(request, data); return request; @@ -17,7 +17,7 @@ export class StepRequest implements ProtocolRequest { protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { protocolUtils.loadCommonRequestFields(request, smartBuffer); request.data.threadIndex = smartBuffer.readUInt32LE(); // thread_index - request.data.stepType = smartBuffer.readUInt8(); // step_type + request.data.stepType = StepTypeCode[smartBuffer.readUInt8()] as StepType; // step_type }); return request; } @@ -26,7 +26,7 @@ export class StepRequest implements ProtocolRequest { const smartBuffer = new SmartBuffer(); smartBuffer.writeUInt32LE(this.data.threadIndex); //thread_index - smartBuffer.writeUInt8(this.data.stepType); //step_type + smartBuffer.writeUInt8(StepTypeCode[this.data.stepType]); //step_type protocolUtils.insertCommonRequestFields(this, smartBuffer); return smartBuffer.toBuffer(); @@ -38,10 +38,10 @@ export class StepRequest implements ProtocolRequest { public data = { threadIndex: undefined as number, - stepType: undefined as STEP_TYPE, + stepType: undefined as StepType, //common props - commandCode: COMMANDS.STEP, + command: Command.Step, packetLength: undefined as number, requestId: undefined as number diff --git a/src/debugProtocol/events/requests/StopRequest.spec.ts b/src/debugProtocol/events/requests/StopRequest.spec.ts index 3696bc97..384841fb 100644 --- a/src/debugProtocol/events/requests/StopRequest.spec.ts +++ b/src/debugProtocol/events/requests/StopRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS, STEP_TYPE } from '../../Constants'; +import { Command, StepType } from '../../Constants'; import { StopRequest } from './StopRequest'; describe('StopRequest', () => { @@ -11,7 +11,7 @@ describe('StopRequest', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 3, - commandCode: COMMANDS.STOP + command: Command.Stop }); expect( @@ -19,7 +19,7 @@ describe('StopRequest', () => { ).to.eql({ packetLength: 12, // 4 bytes requestId: 3, // 4 bytes - commandCode: COMMANDS.STOP // 4 bytes + command: Command.Stop // 4 bytes }); }); }); diff --git a/src/debugProtocol/events/requests/StopRequest.ts b/src/debugProtocol/events/requests/StopRequest.ts index a1af7b63..5dc38758 100644 --- a/src/debugProtocol/events/requests/StopRequest.ts +++ b/src/debugProtocol/events/requests/StopRequest.ts @@ -1,6 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import type { RequestData } from '../../Constants'; -import { COMMANDS } from '../../Constants'; +import { Command } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; @@ -33,6 +32,6 @@ export class StopRequest implements ProtocolRequest { public data = { packetLength: undefined as number, requestId: undefined as number, - commandCode: COMMANDS.STOP + command: Command.Stop }; } diff --git a/src/debugProtocol/events/requests/ThreadsRequest.spec.ts b/src/debugProtocol/events/requests/ThreadsRequest.spec.ts index f71f98e4..d9c835c9 100644 --- a/src/debugProtocol/events/requests/ThreadsRequest.spec.ts +++ b/src/debugProtocol/events/requests/ThreadsRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS } from '../../Constants'; +import { Command } from '../../Constants'; import { ThreadsRequest } from './ThreadsRequest'; describe('ThreadsRequest', () => { @@ -11,7 +11,7 @@ describe('ThreadsRequest', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 3, - commandCode: COMMANDS.THREADS + command: Command.Threads }); expect( @@ -19,7 +19,7 @@ describe('ThreadsRequest', () => { ).to.eql({ packetLength: 12, // 4 bytes requestId: 3, // 4 bytes - commandCode: COMMANDS.THREADS // 4 bytes + command: Command.Threads // 4 bytes }); }); }); diff --git a/src/debugProtocol/events/requests/ThreadsRequest.ts b/src/debugProtocol/events/requests/ThreadsRequest.ts index 4a768700..acea5e56 100644 --- a/src/debugProtocol/events/requests/ThreadsRequest.ts +++ b/src/debugProtocol/events/requests/ThreadsRequest.ts @@ -1,6 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import type { RequestData } from '../../Constants'; -import { COMMANDS } from '../../Constants'; +import { Command } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; @@ -34,6 +33,6 @@ export class ThreadsRequest implements ProtocolRequest { //common props packetLength: undefined as number, requestId: undefined as number, - commandCode: COMMANDS.THREADS + command: Command.Threads }; } diff --git a/src/debugProtocol/events/requests/VariablesRequest.spec.ts b/src/debugProtocol/events/requests/VariablesRequest.spec.ts index 3ed54fd6..70639e6d 100644 --- a/src/debugProtocol/events/requests/VariablesRequest.spec.ts +++ b/src/debugProtocol/events/requests/VariablesRequest.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { COMMANDS } from '../../Constants'; +import { Command } from '../../Constants'; import { VariablesRequest } from './VariablesRequest'; describe('VariablesRequest', () => { @@ -20,7 +20,7 @@ describe('VariablesRequest', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 3, - commandCode: COMMANDS.VARIABLES, + command: Command.Variables, getChildKeys: true, enableCaseInsensitivityFlag: false, @@ -38,7 +38,7 @@ describe('VariablesRequest', () => { ).to.eql({ packetLength: 31, // 4 bytes requestId: 3, // 4 bytes - commandCode: COMMANDS.VARIABLES, // 4 bytes, + command: Command.Variables, // 4 bytes, //variable_request_flags // 1 byte getChildKeys: true, // 0 bytes @@ -71,7 +71,7 @@ describe('VariablesRequest', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 3, - commandCode: COMMANDS.VARIABLES, + command: Command.Variables, getChildKeys: false, enableCaseInsensitivityFlag: true, @@ -89,7 +89,7 @@ describe('VariablesRequest', () => { ).to.eql({ packetLength: 34, // 4 bytes requestId: 3, // 4 bytes - commandCode: COMMANDS.VARIABLES, // 4 bytes, + command: Command.Variables, // 4 bytes, //variable_request_flags // 1 byte getChildKeys: false, // 0 bytes @@ -127,7 +127,7 @@ describe('VariablesRequest', () => { expect(command.data).to.eql({ packetLength: undefined, requestId: 3, - commandCode: COMMANDS.VARIABLES, + command: Command.Variables, getChildKeys: false, enableCaseInsensitivityFlag: true, @@ -141,7 +141,7 @@ describe('VariablesRequest', () => { ).to.eql({ packetLength: 25, // 4 bytes requestId: 3, // 4 bytes - commandCode: COMMANDS.VARIABLES, // 4 bytes, + command: Command.Variables, // 4 bytes, //variable_request_flags // 1 byte getChildKeys: false, // 0 bytes diff --git a/src/debugProtocol/events/requests/VariablesRequest.ts b/src/debugProtocol/events/requests/VariablesRequest.ts index 26ceea08..9a3880db 100644 --- a/src/debugProtocol/events/requests/VariablesRequest.ts +++ b/src/debugProtocol/events/requests/VariablesRequest.ts @@ -1,6 +1,6 @@ /* eslint-disable no-bitwise */ import { SmartBuffer } from 'smart-buffer'; -import { COMMANDS, VARIABLE_REQUEST_FLAGS } from '../../Constants'; +import { Command, VARIABLE_REQUEST_FLAGS } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; import { util } from '../../../util'; @@ -129,6 +129,6 @@ export class VariablesRequest implements ProtocolRequest { //common props packetLength: undefined as number, requestId: undefined as number, - commandCode: COMMANDS.VARIABLES + command: Command.Variables }; } diff --git a/src/debugProtocol/events/responses/ExecuteV3Response.spec.ts b/src/debugProtocol/events/responses/ExecuteV3Response.spec.ts index b4ed0ec4..7f8854ca 100644 --- a/src/debugProtocol/events/responses/ExecuteV3Response.spec.ts +++ b/src/debugProtocol/events/responses/ExecuteV3Response.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { ErrorCode, StopReasonCode, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, StopReasonCode, UpdateType } from '../../Constants'; import { ExecuteV3Response } from './ExecuteV3Response'; describe('ExecuteV3Response', () => { diff --git a/src/debugProtocol/events/responses/ThreadsResponse.spec.ts b/src/debugProtocol/events/responses/ThreadsResponse.spec.ts index 5cf3a2dd..503da2f6 100644 --- a/src/debugProtocol/events/responses/ThreadsResponse.spec.ts +++ b/src/debugProtocol/events/responses/ThreadsResponse.spec.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { ThreadsResponse } from './ThreadsResponse'; -import { ErrorCode } from '../../Constants'; +import { ErrorCode, StopReason } from '../../Constants'; import { getRandomBuffer } from '../../../testHelpers.spec'; describe('ThreadsResponse', () => { @@ -9,7 +9,7 @@ describe('ThreadsResponse', () => { requestId: 3, threads: [{ isPrimary: true, - stopReason: 'Break', + stopReason: StopReason.Break, stopReasonDetail: 'because', lineNumber: 2, functionName: 'main', @@ -109,7 +109,7 @@ describe('ThreadsResponse', () => { requestId: 3, threads: [{ isPrimary: true, - stopReason: 'Break', + stopReason: StopReason.Break, stopReasonDetail: 'because', lineNumber: 2, functionName: 'main', @@ -143,7 +143,7 @@ describe('ThreadsResponse', () => { requestId: 3, threads: [{ isPrimary: true, - stopReason: 'Break', + stopReason: StopReason.Break, stopReasonDetail: 'because', lineNumber: 2, functionName: 'main', @@ -151,7 +151,7 @@ describe('ThreadsResponse', () => { codeSnippet: 'sub main()' }, { isPrimary: true, - stopReason: 'Break', + stopReason: StopReason.Break, stopReasonDetail: 'because', lineNumber: 3, functionName: 'main', diff --git a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts index 27b7bb86..8e8c9009 100644 --- a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts +++ b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts @@ -1,11 +1,11 @@ import { expect } from 'chai'; -import { ErrorCode, StopReasonCode, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, StopReasonCode, UpdateType } from '../../Constants'; import { AllThreadsStoppedUpdate } from './AllThreadsStoppedUpdate'; describe('AllThreadsStoppedUpdate', () => { it('serializes and deserializes properly', () => { const command = AllThreadsStoppedUpdate.fromJson({ - primaryThreadIndex: 1, + threadIndex: 1, stopReason: StopReasonCode.Break, stopReasonDetail: 'because' }); @@ -14,7 +14,7 @@ describe('AllThreadsStoppedUpdate', () => { packetLength: undefined, requestId: 0, errorCode: ErrorCode.OK, - updateType: UPDATE_TYPES.ALL_THREADS_STOPPED, + updateType: UpdateType.AllThreadsStopped, primaryThreadIndex: 1, stopReason: StopReasonCode.Break, @@ -27,7 +27,7 @@ describe('AllThreadsStoppedUpdate', () => { packetLength: 29, // 4 bytes requestId: 0, // 4 bytes errorCode: ErrorCode.OK, // 4 bytes - updateType: UPDATE_TYPES.ALL_THREADS_STOPPED, // 4 bytes + updateType: UpdateType.AllThreadsStopped, // 4 bytes primaryThreadIndex: 1, // 4 bytes stopReason: StopReasonCode.Break, // 1 bytes diff --git a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts index a71c3162..498fd47f 100644 --- a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts +++ b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; import type { StopReasonCode } from '../../Constants'; -import { ErrorCode, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, UpdateType } from '../../Constants'; import { util } from '../../../util'; import { protocolUtils } from '../../ProtocolUtil'; import type { ProtocolUpdate } from '../ProtocolEvent'; @@ -13,7 +13,7 @@ import type { ProtocolUpdate } from '../ProtocolEvent'; export class AllThreadsStoppedUpdate implements ProtocolUpdate { public static fromJson(data: { - primaryThreadIndex: number; + threadIndex: number; stopReason: number; stopReasonDetail: string; }) { @@ -61,6 +61,6 @@ export class AllThreadsStoppedUpdate implements ProtocolUpdate { packetLength: undefined as number, requestId: 0, //all updates have requestId === 0 errorCode: ErrorCode.OK, - updateType: UPDATE_TYPES.ALL_THREADS_STOPPED + updateType: UpdateType.AllThreadsStopped }; } diff --git a/src/debugProtocol/events/updates/BreakpointErrorUpdate.spec.ts b/src/debugProtocol/events/updates/BreakpointErrorUpdate.spec.ts index 5b5b183c..311d8597 100644 --- a/src/debugProtocol/events/updates/BreakpointErrorUpdate.spec.ts +++ b/src/debugProtocol/events/updates/BreakpointErrorUpdate.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { ErrorCode, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, UpdateType } from '../../Constants'; import { BreakpointErrorUpdate } from './BreakpointErrorUpdate'; describe('BreakpointErrorUpdate', () => { @@ -21,7 +21,7 @@ describe('BreakpointErrorUpdate', () => { packetLength: undefined, requestId: 0, errorCode: ErrorCode.OK, - updateType: UPDATE_TYPES.BREAKPOINT_ERROR, + updateType: UpdateType.BreakpointError, breakpointId: 3, compileErrors: [ @@ -41,7 +41,7 @@ describe('BreakpointErrorUpdate', () => { packetLength: 64, // 4 bytes requestId: 0, // 4 bytes errorCode: ErrorCode.OK, // 4 bytes - updateType: UPDATE_TYPES.BREAKPOINT_ERROR, // 4 bytes + updateType: UpdateType.BreakpointError, // 4 bytes //flags // 4 bytes @@ -73,7 +73,7 @@ describe('BreakpointErrorUpdate', () => { packetLength: undefined, requestId: 0, errorCode: ErrorCode.OK, - updateType: UPDATE_TYPES.BREAKPOINT_ERROR, + updateType: UpdateType.BreakpointError, breakpointId: 3, compileErrors: [], @@ -87,7 +87,7 @@ describe('BreakpointErrorUpdate', () => { packetLength: 36, // 4 bytes requestId: 0, // 4 bytes errorCode: ErrorCode.OK, // 4 bytes - updateType: UPDATE_TYPES.BREAKPOINT_ERROR, // 4 bytes + updateType: UpdateType.BreakpointError, // 4 bytes //flags // 4 bytes diff --git a/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts b/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts index 8536a385..5cb7ddd9 100644 --- a/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts +++ b/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts @@ -1,6 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import { util } from '../../../util'; -import { ErrorCode, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, UpdateType } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; /** @@ -109,6 +108,6 @@ export class BreakpointErrorUpdate { packetLength: undefined as number, requestId: 0, //all updates have requestId === 0 errorCode: ErrorCode.OK, - updateType: UPDATE_TYPES.BREAKPOINT_ERROR + updateType: UpdateType.BreakpointError }; } diff --git a/src/debugProtocol/events/updates/CompileErrorUpdate.spec.ts b/src/debugProtocol/events/updates/CompileErrorUpdate.spec.ts index 6f8a1727..d8e0eab9 100644 --- a/src/debugProtocol/events/updates/CompileErrorUpdate.spec.ts +++ b/src/debugProtocol/events/updates/CompileErrorUpdate.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { ErrorCode, StopReasonCode, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, UpdateType } from '../../Constants'; import { CompileErrorUpdate } from './CompileErrorUpdate'; describe('CompileErrorUpdate', () => { @@ -15,7 +15,7 @@ describe('CompileErrorUpdate', () => { packetLength: undefined, requestId: 0, errorCode: ErrorCode.OK, - updateType: UPDATE_TYPES.COMPILE_ERROR, + updateType: UpdateType.CompileError, errorMessage: 'crashed', filePath: 'pkg:/source/main.brs', @@ -29,7 +29,7 @@ describe('CompileErrorUpdate', () => { packetLength: 58, // 4 bytes requestId: 0, // 4 bytes errorCode: ErrorCode.OK, // 4 bytes - updateType: UPDATE_TYPES.COMPILE_ERROR, // 4 bytes + updateType: UpdateType.CompileError, // 4 bytes errorMessage: 'crashed', // 8 bytes filePath: 'pkg:/source/main.brs', // 21 bytes diff --git a/src/debugProtocol/events/updates/CompileErrorUpdate.ts b/src/debugProtocol/events/updates/CompileErrorUpdate.ts index af1a5bcb..4ac2b832 100644 --- a/src/debugProtocol/events/updates/CompileErrorUpdate.ts +++ b/src/debugProtocol/events/updates/CompileErrorUpdate.ts @@ -1,6 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import { util } from '../../../util'; -import { ErrorCode, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, UpdateType } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; /** @@ -88,6 +87,6 @@ export class CompileErrorUpdate { packetLength: undefined as number, requestId: 0, //all updates have requestId === 0 errorCode: ErrorCode.OK, - updateType: UPDATE_TYPES.COMPILE_ERROR + updateType: UpdateType.CompileError }; } diff --git a/src/debugProtocol/events/updates/IOPortOpenedUpdate.spec.ts b/src/debugProtocol/events/updates/IOPortOpenedUpdate.spec.ts index a34afc82..7e6feab1 100644 --- a/src/debugProtocol/events/updates/IOPortOpenedUpdate.spec.ts +++ b/src/debugProtocol/events/updates/IOPortOpenedUpdate.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { ErrorCode, StopReasonCode, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, StopReasonCode, UpdateType } from '../../Constants'; import { IOPortOpenedUpdate } from './IOPortOpenedUpdate'; describe('IOPortOpenedUpdate', () => { @@ -12,7 +12,7 @@ describe('IOPortOpenedUpdate', () => { packetLength: undefined, requestId: 0, errorCode: ErrorCode.OK, - updateType: UPDATE_TYPES.IO_PORT_OPENED, + updateType: UpdateType.IOPortOpened, port: 1234 }); @@ -23,7 +23,7 @@ describe('IOPortOpenedUpdate', () => { packetLength: 20, // 4 bytes requestId: 0, // 4 bytes errorCode: ErrorCode.OK, // 4 bytes - updateType: UPDATE_TYPES.IO_PORT_OPENED, // 4 bytes + updateType: UpdateType.IOPortOpened, // 4 bytes port: 1234 // 4 bytes }); diff --git a/src/debugProtocol/events/updates/IOPortOpenedUpdate.ts b/src/debugProtocol/events/updates/IOPortOpenedUpdate.ts index 346e5e22..6abf82ab 100644 --- a/src/debugProtocol/events/updates/IOPortOpenedUpdate.ts +++ b/src/debugProtocol/events/updates/IOPortOpenedUpdate.ts @@ -1,5 +1,5 @@ import { SmartBuffer } from 'smart-buffer'; -import { ErrorCode, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, UpdateType } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; export class IOPortOpenedUpdate { @@ -45,6 +45,6 @@ export class IOPortOpenedUpdate { packetLength: undefined as number, requestId: 0, //all updates have requestId === 0 errorCode: ErrorCode.OK, - updateType: UPDATE_TYPES.IO_PORT_OPENED + updateType: UpdateType.IOPortOpened }; } diff --git a/src/debugProtocol/events/updates/ThreadAttachedUpdate.spec.ts b/src/debugProtocol/events/updates/ThreadAttachedUpdate.spec.ts index c24e9c6e..59a1a940 100644 --- a/src/debugProtocol/events/updates/ThreadAttachedUpdate.spec.ts +++ b/src/debugProtocol/events/updates/ThreadAttachedUpdate.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { ErrorCode, StopReasonCode, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, StopReasonCode, UpdateType } from '../../Constants'; import { ThreadAttachedUpdate } from './ThreadAttachedUpdate'; describe('AllThreadsStoppedUpdate', () => { @@ -14,7 +14,7 @@ describe('AllThreadsStoppedUpdate', () => { packetLength: undefined, requestId: 0, errorCode: ErrorCode.OK, - updateType: UPDATE_TYPES.THREAD_ATTACHED, + updateType: UpdateType.ThreadAttached, threadIndex: 1, stopReason: StopReasonCode.Break, @@ -27,7 +27,7 @@ describe('AllThreadsStoppedUpdate', () => { packetLength: 29, // 4 bytes requestId: 0, // 4 bytes errorCode: ErrorCode.OK, // 4 bytes - updateType: UPDATE_TYPES.THREAD_ATTACHED, // 4 bytes + updateType: UpdateType.ThreadAttached, // 4 bytes threadIndex: 1, // 4 bytes stopReason: StopReasonCode.Break, // 1 bytes diff --git a/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts b/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts index 03170ce8..033e7d38 100644 --- a/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts +++ b/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; import type { StopReasonCode } from '../../Constants'; -import { ErrorCode, UPDATE_TYPES } from '../../Constants'; +import { ErrorCode, UpdateType } from '../../Constants'; import { util } from '../../../util'; import { protocolUtils } from '../../ProtocolUtil'; @@ -54,6 +54,6 @@ export class ThreadAttachedUpdate { packetLength: undefined as number, requestId: 0, //all updates have requestId === 0 errorCode: ErrorCode.OK, - updateType: UPDATE_TYPES.THREAD_ATTACHED + updateType: UpdateType.ThreadAttached }; } From 6580915c50d3b86f1f88dc9cebc785a1f8fa4423 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Thu, 13 Oct 2022 08:12:41 -0400 Subject: [PATCH 16/74] Fix forceCaseInsensitive variables command logic --- src/adapters/DebugProtocolAdapter.spec.ts | 4 +- src/adapters/DebugProtocolAdapter.ts | 6 +- src/debugProtocol/Constants.ts | 4 - .../client/DebugProtocolClient.spec.ts | 97 +++++++++++++------ .../client/DebugProtocolClient.ts | 41 ++++---- src/debugProtocol/events/ProtocolEvent.ts | 2 +- .../events/requests/AddBreakpointsRequest.ts | 2 +- .../AddConditionalBreakpointsRequest.ts | 2 +- .../events/requests/ContinueRequest.ts | 2 +- .../events/requests/ExecuteRequest.ts | 2 +- .../events/requests/ExitChannelRequest.ts | 2 +- .../events/requests/HandshakeRequest.spec.ts | 31 ++++++ .../events/requests/HandshakeRequest.ts | 2 +- .../events/requests/ListBreakpointsRequest.ts | 2 +- .../requests/RemoveBreakpointsRequest.ts | 2 +- .../events/requests/StackTraceRequest.ts | 2 +- .../events/requests/StepRequest.ts | 2 +- .../events/requests/StopRequest.ts | 2 +- .../events/requests/ThreadsRequest.ts | 2 +- .../events/requests/VariablesRequest.spec.ts | 56 +++++------ .../events/requests/VariablesRequest.ts | 53 +++++----- .../events/updates/AllThreadsStoppedUpdate.ts | 2 +- .../events/updates/BreakpointErrorUpdate.ts | 2 +- .../events/updates/CompileErrorUpdate.ts | 2 +- .../server/DebugProtocolServer.ts | 61 ++++++++++-- src/debugSession/BrightScriptDebugSession.ts | 4 +- 26 files changed, 254 insertions(+), 135 deletions(-) create mode 100644 src/debugProtocol/events/requests/HandshakeRequest.spec.ts diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index c6edee92..46515016 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -48,7 +48,7 @@ describe('DebugProtocolAdapter', () => { { name: 'person' }, { name: 'age' } ); - const vars = await adapter.getVariable('', 1, true); + const vars = await adapter.getVariable('', 1); expect( vars?.children.map(x => x.evaluateName) ).to.eql([ @@ -65,7 +65,7 @@ describe('DebugProtocolAdapter', () => { { name: 'age', isChildKey: true } as any ); - const vars = await adapter.getVariable('person', 1, true); + const vars = await adapter.getVariable('person', 1); expect( vars?.children.map(x => x.evaluateName) ).to.eql([ diff --git a/src/adapters/DebugProtocolAdapter.ts b/src/adapters/DebugProtocolAdapter.ts index f346a359..e60470d6 100644 --- a/src/adapters/DebugProtocolAdapter.ts +++ b/src/adapters/DebugProtocolAdapter.ts @@ -460,7 +460,7 @@ export class DebugProtocolAdapter { * Given an expression, evaluate that statement ON the roku * @param expression */ - public async getVariable(expression: string, frameId: number, withChildren = true) { + public async getVariable(expression: string, frameId: number) { const logger = this.logger.createLogger(' getVariable'); logger.info('begin', { expression }); if (!this.isAtDebuggerPrompt) { @@ -480,13 +480,13 @@ export class DebugProtocolAdapter { variablePath[0] = variablePath[0].toLowerCase(); } - let response = await this.socketDebugger.getVariables(variablePath, withChildren, frame.frameIndex, frame.threadIndex); + let response = await this.socketDebugger.getVariables(variablePath, frame.frameIndex, frame.threadIndex); if (this.enableVariablesLowerCaseRetry && response.data.errorCode !== ErrorCode.OK) { // Temporary workaround related to casing issues over the protocol logger.log(`Retrying expression as lower case:`, expression); variablePath = expression === '' ? [] : util.getVariablePath(expression?.toLowerCase()); - response = await this.socketDebugger.getVariables(variablePath, withChildren, frame.frameIndex, frame.threadIndex); + response = await this.socketDebugger.getVariables(variablePath, frame.frameIndex, frame.threadIndex); } diff --git a/src/debugProtocol/Constants.ts b/src/debugProtocol/Constants.ts index 3ee0a566..4dc6f975 100644 --- a/src/debugProtocol/Constants.ts +++ b/src/debugProtocol/Constants.ts @@ -202,7 +202,3 @@ export enum UpdateTypeCode { CompileError = 5 } -export enum VARIABLE_REQUEST_FLAGS { - GET_CHILD_KEYS = 0x01, - CASE_SENSITIVITY_OPTIONS = 0x02 -} diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index 504dbc16..c9713b3d 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -1,8 +1,9 @@ +/* eslint-disable no-bitwise */ import { DebugProtocolClient } from './DebugProtocolClient'; import { expect } from 'chai'; import type { SmartBuffer } from 'smart-buffer'; import { createSandbox } from 'sinon'; -import { ErrorCode, StopReasonCode, VARIABLE_REQUEST_FLAGS } from '../Constants'; +import { Command, ErrorCode, StopReasonCode } from '../Constants'; import { DebugProtocolServer } from '../server/DebugProtocolServer'; import * as portfinder from 'portfinder'; import { util } from '../../util'; @@ -12,6 +13,8 @@ import type { ProtocolResponse, ProtocolRequest } from '../events/ProtocolEvent' import { HandshakeResponse } from '../events/responses/HandshakeResponse'; import { HandshakeV3Response } from '../events/responses/HandshakeV3Response'; import { AllThreadsStoppedUpdate } from '../events/updates/AllThreadsStoppedUpdate'; +import { VariablesResponse } from '../events/responses/VariablesResponse'; +import { VariableRequestFlag, VariablesRequest } from '../events/requests/VariablesRequest'; const sinon = createSandbox(); @@ -104,7 +107,7 @@ describe('DebugProtocolClient', () => { expect(client.isHandshakeComplete).to.be.equal(true); }); - it.only('handles AllThreadsStoppedUpdate after handshake', async () => { + it('handles AllThreadsStoppedUpdate after handshake', async () => { await client.connect(); const [, event] = await Promise.all([ @@ -145,8 +148,7 @@ describe('DebugProtocolClient', () => { ); } } - // eslint-disable-next-line no-bitwise - if (result.flags & VARIABLE_REQUEST_FLAGS.CASE_SENSITIVITY_OPTIONS) { + if (result.flags & VariableRequestFlag.CaseSensitivityOptions) { result.pathForceCaseInsensitive = []; for (let i = 0; i < pathLength; i++) { result.pathForceCaseInsensitive.push( @@ -157,7 +159,7 @@ describe('DebugProtocolClient', () => { return result; } - it('skips case sensitivity info on lower protocol versions', async () => { + it('honors protocol version when deciding to send forceCaseInsensitive variable information', async () => { await client.connect(); //send the AllThreadsStopped event, and also wait for the client to suspend await Promise.all([ @@ -169,38 +171,61 @@ describe('DebugProtocolClient', () => { await client.once('suspend') ]); + // force the protocolVersion to 2.0.0 for this test client.protocolVersion = '2.0.0'; - client['stopped'] = true; - const stub = sinon.stub(client as any, 'makeRequest').callsFake(() => { }); - await client.getVariables(['m', 'top'], false, 1, 2); + + plugin.pushResponse(VariablesResponse.fromJson({ + requestId: -1, // overridden in the plugin + variables: [] + })); + + await client.getVariables(['m', '"top"'], 1, 2); expect( - getVariablesRequestBufferToJson(stub.getCalls()[0].args[0]) + VariablesRequest.fromBuffer(plugin.latestRequest.toBuffer()).data ).to.eql({ - flags: 0, + packetLength: 31, + requestId: 1, + command: Command.Variables, + enableForceCaseInsensitivity: false, + getChildKeys: true, stackFrameIndex: 1, threadIndex: 2, - variablePathEntries: ['m', 'top'], - //should be empty - pathForceCaseInsensitive: [] - }); - }); - - it('marks strings as case-sensitive', async () => { + variablePathEntries: [{ + name: 'm', + forceCaseInsensitive: false + }, { + name: 'top', + forceCaseInsensitive: false + }] + } as VariablesRequest['data']); + + // force the protocolVersion to 3.1.0 for this test client.protocolVersion = '3.1.0'; - client['stopped'] = true; - const stub = sinon.stub(client as any, 'makeRequest').callsFake(() => { }); - await client.getVariables(['m', 'top', '"someKey"', '""someKeyWithInternalQuotes""'], true, 1, 2); + + plugin.pushResponse(VariablesResponse.fromJson({ + requestId: -1, // overridden in the plugin + variables: [] + })); + + await client.getVariables(['m', '"top"'], 1, 2); expect( - getVariablesRequestBufferToJson(stub.getCalls()[0].args[0]) + VariablesRequest.fromBuffer(plugin.latestRequest.toBuffer()).data ).to.eql({ - // eslint-disable-next-line no-bitwise - flags: VARIABLE_REQUEST_FLAGS.GET_CHILD_KEYS | VARIABLE_REQUEST_FLAGS.CASE_SENSITIVITY_OPTIONS, + packetLength: 33, + requestId: 2, + command: Command.Variables, + enableForceCaseInsensitivity: true, + getChildKeys: true, stackFrameIndex: 1, threadIndex: 2, - variablePathEntries: ['m', 'top', 'someKey', '"someKeyWithInternalQuotes"'], - //should be empty - pathForceCaseInsensitive: [true, true, false, false] - }); + variablePathEntries: [{ + name: 'm', + forceCaseInsensitive: true + }, { + name: 'top', + forceCaseInsensitive: false + }] + } as VariablesRequest['data']); }); }); }); @@ -227,11 +252,25 @@ class TestPlugin implements ProtocolPlugin { */ public readonly requests: ReadonlyArray = []; + /** + * The most recent request received by the plugin + */ + public get latestRequest() { + return this.requests[this.requests.length - 1]; + } + /** * A running list of responses sent by the server during this test */ public readonly responses: ReadonlyArray = []; + /** + * The most recent response received by the plugin + */ + public get latestResponse() { + return this.responses[this.responses.length - 1]; + } + /** * Whenever the server receives a request, this event allows us to send back a response */ @@ -244,6 +283,10 @@ class TestPlugin implements ProtocolPlugin { if (!response && !(event.request instanceof HandshakeRequest)) { throw new Error('There was no response available to send back'); } + //force this response to have the current request's ID (for testing purposes + if (response) { + response.data.requestId = event.request.data.requestId; + } event.response = response; } diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 864a697a..4baa4a8f 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -41,10 +41,6 @@ export class DebugProtocolClient { private logger = logger.createLogger(`[${DebugProtocolClient.name}]`); - public get isStopped(): boolean { - return this.stopped; - } - // The highest tested version of the protocol we support. public supportedVersionRange = '<=3.0.0'; @@ -76,8 +72,11 @@ export class DebugProtocolClient { private controllerClient: Net.Socket; private ioClient: Net.Socket; private buffer = Buffer.alloc(0); - private stopped = false; - private requestIdSequence = 0; + /** + * Is the debugger currently stopped at a line of code in the program + */ + public isStopped = false; + private requestIdSequence = 1; private activeRequests = new Map(); private options: ConstructorOptions; @@ -230,8 +229,8 @@ export class DebugProtocolClient { } public async continue() { - if (this.stopped) { - this.stopped = false; + if (this.isStopped) { + this.isStopped = false; return this.sendRequest( ContinueRequest.fromJson({ requestId: this.requestIdSequence++ @@ -241,7 +240,7 @@ export class DebugProtocolClient { } public async pause(force = false) { - if (!this.stopped || force) { + if (!this.isStopped || force) { return this.sendRequest( StopRequest.fromJson({ requestId: this.requestIdSequence++ @@ -271,10 +270,10 @@ export class DebugProtocolClient { } private async step(stepType: StepType, threadIndex: number): Promise { - this.logger.log('[step]', { stepType: stepType, threadId: threadIndex, stopped: this.stopped }); + this.logger.log('[step]', { stepType: stepType, threadId: threadIndex, stopped: this.isStopped }); - if (this.stopped) { - this.stopped = false; + if (this.isStopped) { + this.isStopped = false; let stepResult = await this.sendRequest( StepRequest.fromJson({ requestId: this.requestIdSequence++, @@ -294,7 +293,7 @@ export class DebugProtocolClient { } public async threads() { - if (this.stopped) { + if (this.isStopped) { let result = await this.sendRequest( ThreadsRequest.fromJson({ requestId: this.requestIdSequence++ @@ -324,7 +323,7 @@ export class DebugProtocolClient { public async stackTrace(threadIndex: number = this.primaryThread) { let buffer = new SmartBuffer({ size: 16 }); buffer.writeUInt32LE(threadIndex); // thread_index - if (this.stopped && threadIndex > -1) { + if (this.isStopped && threadIndex > -1) { return this.sendRequest( StackTraceRequest.fromJson({ requestId: this.requestIdSequence++, @@ -345,27 +344,27 @@ export class DebugProtocolClient { * @param stackFrameIndex 0 = first function called, nframes-1 = last function. This indexing does not match the order of the frames returned from the STACKTRACE command * @param threadIndex the index (or perhaps ID?) of the thread to get variables for */ - public async getVariables(variablePathEntries: Array = [], getChildKeys = true, stackFrameIndex: number = this.stackFrameIndex, threadIndex: number = this.primaryThread) { - if (this.stopped && threadIndex > -1) { + public async getVariables(variablePathEntries: Array = [], stackFrameIndex: number = this.stackFrameIndex, threadIndex: number = this.primaryThread) { + if (this.isStopped && threadIndex > -1) { const request = VariablesRequest.fromJson({ requestId: this.requestIdSequence++, threadIndex: threadIndex, stackFrameIndex: stackFrameIndex, - getChildKeys: getChildKeys, + getChildKeys: true, variablePathEntries: variablePathEntries.map(x => ({ //remove leading and trailing quotes name: x.replace(/^"/, '').replace(/"$/, ''), - isCaseSensitive: x.startsWith('"') && x.endsWith('"') + forceCaseInsensitive: !x.startsWith('"') && !x.endsWith('"') })), //starting in protocol v3.1.0, it supports marking certain path items as case-insensitive (i.e. parts of DottedGet expressions) - enableCaseInsensitivityFlag: semver.satisfies(this.protocolVersion, '>=3.1.0') && variablePathEntries.length > 0 + enableForceCaseInsensitivity: semver.satisfies(this.protocolVersion, '>=3.1.0') && variablePathEntries.length > 0 }); return this.sendRequest(request); } } public async executeCommand(sourceCode: string, stackFrameIndex: number = this.stackFrameIndex, threadIndex: number = this.primaryThread) { - if (this.stopped && threadIndex > -1) { + if (this.isStopped && threadIndex > -1) { return this.sendRequest( ExecuteRequest.fromJson({ requestId: this.requestIdSequence++, @@ -583,7 +582,7 @@ export class DebugProtocolClient { */ private handleUpdate(update: ProtocolUpdate) { if (update instanceof AllThreadsStoppedUpdate || update instanceof ThreadAttachedUpdate) { - this.stopped = true; + this.isStopped = true; let eventName: 'runtime-error' | 'suspend' = (update.data.stopReason === StopReasonCode.RuntimeError ? 'runtime-error' : 'suspend'); const isValidStopReason = [StopReasonCode.RuntimeError, StopReasonCode.Break, StopReasonCode.StopStatement].includes(update.data.stopReason); diff --git a/src/debugProtocol/events/ProtocolEvent.ts b/src/debugProtocol/events/ProtocolEvent.ts index 01a245e7..abfd7cf9 100644 --- a/src/debugProtocol/events/ProtocolEvent.ts +++ b/src/debugProtocol/events/ProtocolEvent.ts @@ -12,7 +12,7 @@ export interface ProtocolEvent { readOffset: number; /** - * Serialize this event into Convert the current object into the debug protocol binary format, + * Serialize the current object into the debug protocol's binary format, * stored in a `Buffer` */ toBuffer(): Buffer; diff --git a/src/debugProtocol/events/requests/AddBreakpointsRequest.ts b/src/debugProtocol/events/requests/AddBreakpointsRequest.ts index 4ad98800..8db13c1a 100644 --- a/src/debugProtocol/events/requests/AddBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/AddBreakpointsRequest.ts @@ -57,7 +57,7 @@ export class AddBreakpointsRequest implements ProtocolRequest { public success = false; - public readOffset = -1; + public readOffset: number = undefined; public data = { breakpoints: undefined as Array<{ diff --git a/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts b/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts index 99252d49..25ae31c2 100644 --- a/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts @@ -65,7 +65,7 @@ export class AddConditionalBreakpointsRequest implements ProtocolRequest { public success = false; - public readOffset = -1; + public readOffset: number = undefined; public data = { breakpoints: undefined as Array<{ diff --git a/src/debugProtocol/events/requests/ContinueRequest.ts b/src/debugProtocol/events/requests/ContinueRequest.ts index 5e977d92..68f91385 100644 --- a/src/debugProtocol/events/requests/ContinueRequest.ts +++ b/src/debugProtocol/events/requests/ContinueRequest.ts @@ -27,7 +27,7 @@ export class ContinueRequest implements ProtocolRequest { public success = false; - public readOffset = -1; + public readOffset: number = undefined; public data = { //common props diff --git a/src/debugProtocol/events/requests/ExecuteRequest.ts b/src/debugProtocol/events/requests/ExecuteRequest.ts index 7f8aafbb..b5685912 100644 --- a/src/debugProtocol/events/requests/ExecuteRequest.ts +++ b/src/debugProtocol/events/requests/ExecuteRequest.ts @@ -41,7 +41,7 @@ export class ExecuteRequest implements ProtocolRequest { public success = false; - public readOffset = -1; + public readOffset: number = undefined; public data = { threadIndex: undefined as number, diff --git a/src/debugProtocol/events/requests/ExitChannelRequest.ts b/src/debugProtocol/events/requests/ExitChannelRequest.ts index 592a718c..39535dde 100644 --- a/src/debugProtocol/events/requests/ExitChannelRequest.ts +++ b/src/debugProtocol/events/requests/ExitChannelRequest.ts @@ -27,7 +27,7 @@ export class ExitChannelRequest implements ProtocolRequest { public success = false; - public readOffset = -1; + public readOffset: number = undefined; public data = { //common props diff --git a/src/debugProtocol/events/requests/HandshakeRequest.spec.ts b/src/debugProtocol/events/requests/HandshakeRequest.spec.ts new file mode 100644 index 00000000..cec6731f --- /dev/null +++ b/src/debugProtocol/events/requests/HandshakeRequest.spec.ts @@ -0,0 +1,31 @@ +import { expect } from 'chai'; +import { HandshakeRequest } from './HandshakeRequest'; + +describe('HandshakeRequest', () => { + it('serializes and deserializes properly', () => { + let request = HandshakeRequest.fromJson({ + magic: 'theMagic!' + }); + + expect(request.data).to.eql({ + packetLength: undefined, + requestId: HandshakeRequest.REQUEST_ID, + command: undefined, + + magic: 'theMagic!' + }); + + request = HandshakeRequest.fromBuffer(request.toBuffer()); + expect(request.readOffset).to.eql(10); + + expect( + request.data + ).to.eql({ + packetLength: undefined, + requestId: HandshakeRequest.REQUEST_ID, + command: undefined, + + magic: 'theMagic!' + }); + }); +}); diff --git a/src/debugProtocol/events/requests/HandshakeRequest.ts b/src/debugProtocol/events/requests/HandshakeRequest.ts index 696efee8..a53976ba 100644 --- a/src/debugProtocol/events/requests/HandshakeRequest.ts +++ b/src/debugProtocol/events/requests/HandshakeRequest.ts @@ -36,7 +36,7 @@ export class HandshakeRequest implements ProtocolRequest { public success = false; - public readOffset = -1; + public readOffset = undefined; public data = { magic: undefined as string, diff --git a/src/debugProtocol/events/requests/ListBreakpointsRequest.ts b/src/debugProtocol/events/requests/ListBreakpointsRequest.ts index 920e04ca..122c2758 100644 --- a/src/debugProtocol/events/requests/ListBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/ListBreakpointsRequest.ts @@ -27,7 +27,7 @@ export class ListBreakpointsRequest implements ProtocolRequest { public success = false; - public readOffset = -1; + public readOffset: number = undefined; public data = { //common props diff --git a/src/debugProtocol/events/requests/RemoveBreakpointsRequest.ts b/src/debugProtocol/events/requests/RemoveBreakpointsRequest.ts index 5238e135..dfb027b2 100644 --- a/src/debugProtocol/events/requests/RemoveBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/RemoveBreakpointsRequest.ts @@ -39,7 +39,7 @@ export class RemoveBreakpointsRequest implements ProtocolRequest { public success = false; - public readOffset = -1; + public readOffset: number = undefined; public data = { /** diff --git a/src/debugProtocol/events/requests/StackTraceRequest.ts b/src/debugProtocol/events/requests/StackTraceRequest.ts index 82906205..63ebb220 100644 --- a/src/debugProtocol/events/requests/StackTraceRequest.ts +++ b/src/debugProtocol/events/requests/StackTraceRequest.ts @@ -31,7 +31,7 @@ export class StackTraceRequest implements ProtocolRequest { public success = false; - public readOffset = -1; + public readOffset: number = undefined; public data = { threadIndex: undefined as number, diff --git a/src/debugProtocol/events/requests/StepRequest.ts b/src/debugProtocol/events/requests/StepRequest.ts index 9ec6207d..60c853bb 100644 --- a/src/debugProtocol/events/requests/StepRequest.ts +++ b/src/debugProtocol/events/requests/StepRequest.ts @@ -34,7 +34,7 @@ export class StepRequest implements ProtocolRequest { public success = false; - public readOffset = -1; + public readOffset: number = undefined; public data = { threadIndex: undefined as number, diff --git a/src/debugProtocol/events/requests/StopRequest.ts b/src/debugProtocol/events/requests/StopRequest.ts index 5dc38758..ed8ca409 100644 --- a/src/debugProtocol/events/requests/StopRequest.ts +++ b/src/debugProtocol/events/requests/StopRequest.ts @@ -27,7 +27,7 @@ export class StopRequest implements ProtocolRequest { public success = false; - public readOffset = -1; + public readOffset: number = undefined; public data = { packetLength: undefined as number, diff --git a/src/debugProtocol/events/requests/ThreadsRequest.ts b/src/debugProtocol/events/requests/ThreadsRequest.ts index acea5e56..f4e76117 100644 --- a/src/debugProtocol/events/requests/ThreadsRequest.ts +++ b/src/debugProtocol/events/requests/ThreadsRequest.ts @@ -27,7 +27,7 @@ export class ThreadsRequest implements ProtocolRequest { public success = false; - public readOffset = -1; + public readOffset: number = undefined; public data = { //common props diff --git a/src/debugProtocol/events/requests/VariablesRequest.spec.ts b/src/debugProtocol/events/requests/VariablesRequest.spec.ts index 70639e6d..a852bb6e 100644 --- a/src/debugProtocol/events/requests/VariablesRequest.spec.ts +++ b/src/debugProtocol/events/requests/VariablesRequest.spec.ts @@ -3,17 +3,17 @@ import { Command } from '../../Constants'; import { VariablesRequest } from './VariablesRequest'; describe('VariablesRequest', () => { - it('serializes and deserializes properly for case sensitive lookups', () => { + it('serializes and deserializes properly for unsupported forceCaseSensitivity lookups', () => { const command = VariablesRequest.fromJson({ requestId: 3, getChildKeys: true, - enableCaseInsensitivityFlag: false, + enableForceCaseInsensitivity: false, stackFrameIndex: 1, threadIndex: 2, variablePathEntries: [ - { name: 'a', isCaseSensitive: false }, - { name: 'b', isCaseSensitive: false }, - { name: 'c', isCaseSensitive: false } + { name: 'a', forceCaseInsensitive: true }, + { name: 'b', forceCaseInsensitive: true }, + { name: 'c', forceCaseInsensitive: true } ] }); @@ -23,13 +23,13 @@ describe('VariablesRequest', () => { command: Command.Variables, getChildKeys: true, - enableCaseInsensitivityFlag: false, + enableForceCaseInsensitivity: false, stackFrameIndex: 1, threadIndex: 2, variablePathEntries: [ - { name: 'a', isCaseSensitive: true }, - { name: 'b', isCaseSensitive: true }, - { name: 'c', isCaseSensitive: true } + { name: 'a', forceCaseInsensitive: false }, + { name: 'b', forceCaseInsensitive: false }, + { name: 'c', forceCaseInsensitive: false } ] }); @@ -42,14 +42,14 @@ describe('VariablesRequest', () => { //variable_request_flags // 1 byte getChildKeys: true, // 0 bytes - enableCaseInsensitivityFlag: false, // 0 bytes + enableForceCaseInsensitivity: false, // 0 bytes stackFrameIndex: 1, // 4 bytes threadIndex: 2, // 4 bytes // variable_path_len // 4 bytes variablePathEntries: [ - { name: 'a', isCaseSensitive: true }, // 2 bytes - { name: 'b', isCaseSensitive: true }, // 2 bytes - { name: 'c', isCaseSensitive: true } // 2 bytes + { name: 'a', forceCaseInsensitive: false }, // 2 bytes + { name: 'b', forceCaseInsensitive: false }, // 2 bytes + { name: 'c', forceCaseInsensitive: false } // 2 bytes ] }); }); @@ -58,13 +58,13 @@ describe('VariablesRequest', () => { const command = VariablesRequest.fromJson({ requestId: 3, getChildKeys: false, - enableCaseInsensitivityFlag: true, + enableForceCaseInsensitivity: true, stackFrameIndex: 1, threadIndex: 2, variablePathEntries: [ - { name: 'a', isCaseSensitive: false }, - { name: 'b', isCaseSensitive: true }, - { name: 'c', isCaseSensitive: false } + { name: 'a', forceCaseInsensitive: true }, + { name: 'b', forceCaseInsensitive: false }, + { name: 'c', forceCaseInsensitive: true } ] }); @@ -74,13 +74,13 @@ describe('VariablesRequest', () => { command: Command.Variables, getChildKeys: false, - enableCaseInsensitivityFlag: true, + enableForceCaseInsensitivity: true, stackFrameIndex: 1, threadIndex: 2, variablePathEntries: [ - { name: 'a', isCaseSensitive: false }, - { name: 'b', isCaseSensitive: true }, - { name: 'c', isCaseSensitive: false } + { name: 'a', forceCaseInsensitive: true }, + { name: 'b', forceCaseInsensitive: false }, + { name: 'c', forceCaseInsensitive: true } ] }); @@ -93,22 +93,22 @@ describe('VariablesRequest', () => { //variable_request_flags // 1 byte getChildKeys: false, // 0 bytes - enableCaseInsensitivityFlag: true, // 0 bytes + enableForceCaseInsensitivity: true, // 0 bytes stackFrameIndex: 1, // 4 bytes threadIndex: 2, // 4 bytes // variable_path_len // 4 bytes variablePathEntries: [ { name: 'a', // 2 bytes - isCaseSensitive: false // 1 byte + forceCaseInsensitive: true // 1 byte }, // ? { name: 'b', // 2 bytes - isCaseSensitive: true // 1 byte + forceCaseInsensitive: false // 1 byte }, // ? { name: 'c', // 2 bytes - isCaseSensitive: false // 1 byte + forceCaseInsensitive: true // 1 byte } // ? ] }); @@ -118,7 +118,7 @@ describe('VariablesRequest', () => { const command = VariablesRequest.fromJson({ requestId: 3, getChildKeys: false, - enableCaseInsensitivityFlag: true, + enableForceCaseInsensitivity: true, stackFrameIndex: 1, threadIndex: 2, variablePathEntries: [] @@ -130,7 +130,7 @@ describe('VariablesRequest', () => { command: Command.Variables, getChildKeys: false, - enableCaseInsensitivityFlag: true, + enableForceCaseInsensitivity: true, stackFrameIndex: 1, threadIndex: 2, variablePathEntries: [] @@ -145,7 +145,7 @@ describe('VariablesRequest', () => { //variable_request_flags // 1 byte getChildKeys: false, // 0 bytes - enableCaseInsensitivityFlag: true, // 0 bytes + enableForceCaseInsensitivity: true, // 0 bytes stackFrameIndex: 1, // 4 bytes threadIndex: 2, // 4 bytes // variable_path_len // 4 bytes diff --git a/src/debugProtocol/events/requests/VariablesRequest.ts b/src/debugProtocol/events/requests/VariablesRequest.ts index 9a3880db..63e7e857 100644 --- a/src/debugProtocol/events/requests/VariablesRequest.ts +++ b/src/debugProtocol/events/requests/VariablesRequest.ts @@ -1,31 +1,32 @@ /* eslint-disable no-bitwise */ import { SmartBuffer } from 'smart-buffer'; -import { Command, VARIABLE_REQUEST_FLAGS } from '../../Constants'; +import { Command } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; -import { util } from '../../../util'; export class VariablesRequest implements ProtocolRequest { public static fromJson(data: { requestId: number; getChildKeys: boolean; - enableCaseInsensitivityFlag: boolean; + enableForceCaseInsensitivity: boolean; threadIndex: number; stackFrameIndex: number; variablePathEntries: Array<{ name: string; - isCaseSensitive: boolean; + forceCaseInsensitive: boolean; }>; }) { const request = new VariablesRequest(); protocolUtils.loadJson(request, data); request.data.variablePathEntries ??= []; - // force all variables to case SENSITIVE if using the flag is disabled (just for consistency purposes), - // as it won't actually be sent - if (!request.data.enableCaseInsensitivityFlag) { - for (const entry of request.data.variablePathEntries) { - entry.isCaseSensitive = true; + // all variables will be case sensitive if the flag is disabled + for (const entry of request.data.variablePathEntries) { + if (request.data.enableForceCaseInsensitivity !== true) { + entry.forceCaseInsensitive = false; + } else { + //default any missing values to false + entry.forceCaseInsensitive ??= false; } } return request; @@ -38,8 +39,8 @@ export class VariablesRequest implements ProtocolRequest { const variableRequestFlags = smartBuffer.readUInt8(); // variable_request_flags - request.data.getChildKeys = !!(variableRequestFlags & VARIABLE_REQUEST_FLAGS.GET_CHILD_KEYS); - request.data.enableCaseInsensitivityFlag = !!(variableRequestFlags & VARIABLE_REQUEST_FLAGS.CASE_SENSITIVITY_OPTIONS); + request.data.getChildKeys = !!(variableRequestFlags & VariableRequestFlag.GetChildKeys); + request.data.enableForceCaseInsensitivity = !!(variableRequestFlags & VariableRequestFlag.CaseSensitivityOptions); request.data.threadIndex = smartBuffer.readUInt32LE(); // thread_index request.data.stackFrameIndex = smartBuffer.readUInt32LE(); // stack_frame_index const variablePathLength = smartBuffer.readUInt32LE(); // variable_path_len @@ -48,15 +49,16 @@ export class VariablesRequest implements ProtocolRequest { for (let i = 0; i < variablePathLength; i++) { request.data.variablePathEntries.push({ name: protocolUtils.readStringNT(smartBuffer), // variable_path_entries - optional - isCaseSensitive: true + //by default, all variable lookups are case SENSITIVE + forceCaseInsensitive: false }); } //get the case sensitive settings for each part of the path - if (request.data.enableCaseInsensitivityFlag) { + if (request.data.enableForceCaseInsensitivity) { for (let i = 0; i < variablePathLength; i++) { - //0 means case SENSITIVE lookup, 1 means case INsensitive lookup - request.data.variablePathEntries[i].isCaseSensitive = smartBuffer.readUInt8() === 0 ? true : false; + //0 means case SENSITIVE lookup, 1 means forced case INsensitive lookup + request.data.variablePathEntries[i].forceCaseInsensitive = smartBuffer.readUInt8() === 0 ? false : true; } } } @@ -69,8 +71,8 @@ export class VariablesRequest implements ProtocolRequest { //build the flags var let variableRequestFlags = 0; - variableRequestFlags |= this.data.getChildKeys ? VARIABLE_REQUEST_FLAGS.GET_CHILD_KEYS : 0; - variableRequestFlags |= this.data.enableCaseInsensitivityFlag ? VARIABLE_REQUEST_FLAGS.CASE_SENSITIVITY_OPTIONS : 0; + variableRequestFlags |= this.data.getChildKeys ? VariableRequestFlag.GetChildKeys : 0; + variableRequestFlags |= this.data.enableForceCaseInsensitivity ? VariableRequestFlag.CaseSensitivityOptions : 0; smartBuffer.writeUInt8(variableRequestFlags); // variable_request_flags smartBuffer.writeUInt32LE(this.data.threadIndex); // thread_index @@ -79,10 +81,10 @@ export class VariablesRequest implements ProtocolRequest { for (const entry of this.data.variablePathEntries) { smartBuffer.writeStringNT(entry.name); // variable_path_entries - optional } - if (this.data.enableCaseInsensitivityFlag) { + if (this.data.enableForceCaseInsensitivity) { for (const entry of this.data.variablePathEntries) { - //0 means case SENSITIVE lookup, 1 means case INsensitive lookup - smartBuffer.writeUInt8(entry.isCaseSensitive ? 0 : 1); + //0 means case SENSITIVE lookup, 1 means force case INsensitive lookup + smartBuffer.writeUInt8(entry.forceCaseInsensitive !== true ? 0 : 1); } } @@ -92,7 +94,7 @@ export class VariablesRequest implements ProtocolRequest { public success = false; - public readOffset = -1; + public readOffset: number = undefined; public data = { /** @@ -103,7 +105,7 @@ export class VariablesRequest implements ProtocolRequest { /** * Enables the client application to send path_force_case_insensitive data for each variable */ - enableCaseInsensitivityFlag: undefined as boolean, + enableForceCaseInsensitivity: undefined as boolean, /** * The index of the thread containing the variable. @@ -123,7 +125,7 @@ export class VariablesRequest implements ProtocolRequest { */ variablePathEntries: undefined as Array<{ name: string; - isCaseSensitive: boolean; + forceCaseInsensitive: boolean; }>, //common props @@ -132,3 +134,8 @@ export class VariablesRequest implements ProtocolRequest { command: Command.Variables }; } + +export enum VariableRequestFlag { + GetChildKeys = 1, + CaseSensitivityOptions = 2 +} diff --git a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts index 498fd47f..cf4b28ac 100644 --- a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts +++ b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts @@ -47,7 +47,7 @@ export class AllThreadsStoppedUpdate implements ProtocolUpdate { public success = false; - public readOffset = -1; + public readOffset: number = undefined; public data = { /** diff --git a/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts b/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts index 5cb7ddd9..43fe0ab6 100644 --- a/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts +++ b/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts @@ -96,7 +96,7 @@ export class BreakpointErrorUpdate { public success = false; - public readOffset = -1; + public readOffset: number = undefined; public data = { breakpointId: undefined as number, diff --git a/src/debugProtocol/events/updates/CompileErrorUpdate.ts b/src/debugProtocol/events/updates/CompileErrorUpdate.ts index 4ac2b832..d9f20f98 100644 --- a/src/debugProtocol/events/updates/CompileErrorUpdate.ts +++ b/src/debugProtocol/events/updates/CompileErrorUpdate.ts @@ -57,7 +57,7 @@ export class CompileErrorUpdate { public success = false; - public readOffset = -1; + public readOffset: number = undefined; public data = { /** diff --git a/src/debugProtocol/server/DebugProtocolServer.ts b/src/debugProtocol/server/DebugProtocolServer.ts index c83359a8..bafb14b3 100644 --- a/src/debugProtocol/server/DebugProtocolServer.ts +++ b/src/debugProtocol/server/DebugProtocolServer.ts @@ -1,8 +1,21 @@ import { EventEmitter } from 'eventemitter3'; import * as Net from 'net'; import { ActionQueue } from '../../managers/ActionQueue'; +import { Command, CommandCode } from '../Constants'; import type { ProtocolRequest, ProtocolResponse } from '../events/ProtocolEvent'; +import { AddBreakpointsRequest } from '../events/requests/AddBreakpointsRequest'; +import { AddConditionalBreakpointsRequest } from '../events/requests/AddConditionalBreakpointsRequest'; +import { ContinueRequest } from '../events/requests/ContinueRequest'; +import { ExecuteRequest } from '../events/requests/ExecuteRequest'; +import { ExitChannelRequest } from '../events/requests/ExitChannelRequest'; import { HandshakeRequest } from '../events/requests/HandshakeRequest'; +import { ListBreakpointsRequest } from '../events/requests/ListBreakpointsRequest'; +import { RemoveBreakpointsRequest } from '../events/requests/RemoveBreakpointsRequest'; +import { StackTraceRequest } from '../events/requests/StackTraceRequest'; +import { StepRequest } from '../events/requests/StepRequest'; +import { StopRequest } from '../events/requests/StopRequest'; +import { ThreadsRequest } from '../events/requests/ThreadsRequest'; +import { VariablesRequest } from '../events/requests/VariablesRequest'; import { HandshakeResponse } from '../events/responses/HandshakeResponse'; import { HandshakeV3Response } from '../events/responses/HandshakeV3Response'; import PluginInterface from './PluginInterface'; @@ -106,17 +119,39 @@ export class DebugProtocolServer { /** * Given a buffer, find the request that matches it */ - private getRequest(buffer: Buffer) { - let request: ProtocolRequest; + private getRequest(buffer: Buffer): ProtocolRequest { //if we haven't seen the handshake yet, look for the handshake first if (!this.isHandshakeComplete) { - request = HandshakeRequest.fromBuffer(buffer); - if (request.success) { - return request; - } + return HandshakeRequest.fromBuffer(buffer); + } + //we can only receive commands from the client, so pre-parse the command type + const command = CommandCode[buffer.readUInt32LE(8)] as Command; // command_code + switch (command) { + case Command.AddBreakpoints: + return AddBreakpointsRequest.fromBuffer(this.buffer); + case Command.Stop: + return StopRequest.fromBuffer(this.buffer); + case Command.Continue: + return ContinueRequest.fromBuffer(this.buffer); + case Command.Threads: + return ThreadsRequest.fromBuffer(this.buffer); + case Command.StackTrace: + return StackTraceRequest.fromBuffer(this.buffer); + case Command.Variables: + return VariablesRequest.fromBuffer(this.buffer); + case Command.Step: + return StepRequest.fromBuffer(this.buffer); + case Command.ListBreakpoints: + return ListBreakpointsRequest.fromBuffer(this.buffer); + case Command.RemoveBreakpoints: + return RemoveBreakpointsRequest.fromBuffer(this.buffer); + case Command.Execute: + return ExecuteRequest.fromBuffer(this.buffer); + case Command.AddConditionalBreakpoints: + return AddConditionalBreakpointsRequest.fromBuffer(this.buffer); + case Command.ExitChannel: + return ExitChannelRequest.fromBuffer(this.buffer); } - - //TODO handle all the other request types (variables, step, etc...) } private getResponse(request: ProtocolRequest) { @@ -143,6 +178,11 @@ export class DebugProtocolServer { request = this.getRequest(buffer); } + //if we couldn't construct a request this request, hard-fail + if (!request || !request.success) { + throw new Error(`Unable to parse request: ${JSON.stringify(this.buffer.toJSON().data)}`); + } + //trim the buffer now that the request has been processed this.buffer = buffer.slice(request.readOffset); @@ -155,7 +195,7 @@ export class DebugProtocolServer { //if the plugin didn't provide a response, we need to try our best to make one (we only support a few...plugins should provide most of them) - if (request instanceof HandshakeRequest && !response) { + if (!response) { response = this.getResponse(request); } @@ -163,6 +203,9 @@ export class DebugProtocolServer { if ((response instanceof HandshakeResponse || response instanceof HandshakeV3Response) && response.data.magic === this.magic) { this.isHandshakeComplete = true; } + if (!response) { + throw new Error(`Server was unable to provide a response for ${JSON.stringify(request.data)}`); + } //send the response to the client. (TODO handle when the response is missing) await this.sendResponse(response); diff --git a/src/debugSession/BrightScriptDebugSession.ts b/src/debugSession/BrightScriptDebugSession.ts index c3b39d39..7c156a8b 100644 --- a/src/debugSession/BrightScriptDebugSession.ts +++ b/src/debugSession/BrightScriptDebugSession.ts @@ -737,7 +737,7 @@ export class BrightScriptDebugSession extends BaseDebugSession { if (this.variables[refId]) { v = this.variables[refId]; } else { - let result = await this.rokuAdapter.getVariable('', args.frameId, true); + let result = await this.rokuAdapter.getVariable('', args.frameId); if (!result) { throw new Error(`Could not get scopes`); } @@ -921,7 +921,7 @@ export class BrightScriptDebugSession extends BaseDebugSession { if (this.variables[refId]) { v = this.variables[refId]; } else { - let result = await this.rokuAdapter.getVariable(args.expression, args.frameId, true); + let result = await this.rokuAdapter.getVariable(args.expression, args.frameId); if (!result) { throw new Error(`bad variable request "${args.expression}"`); } From 1021314853d147230b0c3b83f8ea323ba66f055b Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Thu, 13 Oct 2022 16:00:12 -0400 Subject: [PATCH 17/74] Fix unit tests and add better logging. --- src/adapters/DebugProtocolAdapter.spec.ts | 182 ++++++++++++++---- src/adapters/DebugProtocolAdapter.ts | 38 +++- .../DebugProtocolServerTestPlugin.spec.ts | 79 ++++++++ .../client/DebugProtocolClient.spec.ts | 71 +------ .../client/DebugProtocolClient.ts | 97 ++++++---- .../AddConditionalBreakpointsRequest.spec.ts | 8 +- .../AddConditionalBreakpointsRequest.ts | 2 + .../responses/HandshakeResponse.spec.ts | 10 + .../events/responses/ThreadsResponse.spec.ts | 4 +- .../events/responses/ThreadsResponse.ts | 8 +- .../events/responses/VariablesResponse.ts | 8 +- .../updates/AllThreadsStoppedUpdate.spec.ts | 12 +- .../events/updates/AllThreadsStoppedUpdate.ts | 13 +- .../updates/ThreadAttachedUpdate.spec.ts | 8 +- .../events/updates/ThreadAttachedUpdate.ts | 13 +- .../responseCreationHelpers.spec.ts | 1 - .../server/DebugProtocolServer.ts | 96 +++++---- src/debugProtocol/server/ProtocolPlugin.ts | 5 + src/testHelpers.spec.ts | 1 + 19 files changed, 427 insertions(+), 229 deletions(-) create mode 100644 src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts delete mode 100644 src/debugProtocol/responseCreationHelpers.spec.ts diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index 46515016..2fae106c 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -1,70 +1,175 @@ import { expect } from 'chai'; import { DebugProtocolClient } from '../debugProtocol/client/DebugProtocolClient'; +import * as portfinder from 'portfinder'; import { DebugProtocolAdapter } from './DebugProtocolAdapter'; import { createSandbox } from 'sinon'; import type { Variable } from '../debugProtocol/events/responses/VariablesResponse'; import { VariableType } from '../debugProtocol/events/responses/VariablesResponse'; // eslint-disable-next-line @typescript-eslint/no-duplicate-imports import { VariablesResponse } from '../debugProtocol/events/responses/VariablesResponse'; +import { DebugProtocolServer } from '../debugProtocol/server/DebugProtocolServer'; +import { util } from '../util'; +import { DebugProtocolServerTestPlugin } from '../debugProtocol/DebugProtocolServerTestPlugin.spec'; +import { AllThreadsStoppedUpdate } from '../debugProtocol/events/updates/AllThreadsStoppedUpdate'; +import { StopReason } from '../debugProtocol/Constants'; +import { ThreadsResponse } from '../debugProtocol/events/responses/ThreadsResponse'; +import { StackTraceV3Response } from '../debugProtocol/events/responses/StackTraceV3Response'; + const sinon = createSandbox(); describe('DebugProtocolAdapter', () => { let adapter: DebugProtocolAdapter; - let socketDebugger: DebugProtocolClient; - beforeEach(() => { + let server: DebugProtocolServer; + let client: DebugProtocolClient; + let plugin: DebugProtocolServerTestPlugin; - adapter = new DebugProtocolAdapter( - { - host: '127.0.0.1' - }, - undefined, - undefined - ); - socketDebugger = new DebugProtocolClient(undefined); - adapter['socketDebugger'] = socketDebugger; + beforeEach(async () => { + // sinon.stub(console, 'log').callsFake((...args) => { }); + const options = { + controllerPort: undefined as number, + host: '127.0.0.1' + }; + + adapter = new DebugProtocolAdapter(options, undefined, undefined); + + if (!options.controllerPort) { + options.controllerPort = await portfinder.getPortPromise(); + } + server = new DebugProtocolServer(options); + plugin = server.plugins.add(new DebugProtocolServerTestPlugin()); + await server.start(); + + client = new DebugProtocolClient(options); + //disable logging for tests because they clutter the test output + client['logger'].logLevel = 'off'; }); - describe('getVariable', () => { - let response: VariablesResponse; - let variables: Partial[]; + afterEach(async () => { + client?.destroy(); + //shut down and destroy the server after each test + await server?.stop(); + await util.sleep(10); + sinon.restore(); + }); - beforeEach(() => { - response = VariablesResponse.fromJson({ - requestId: 3, - variables: [] - }); - sinon.stub(adapter as any, 'getStackFrameById').returns({}); - sinon.stub(socketDebugger, 'getVariables').callsFake(() => { - response.data.variables = variables as any; - return Promise.resolve(response); - }); - socketDebugger['stopped'] = true; - }); + /** + * Handles the initial connection and the "stop at first byte code" flow + */ + async function initialize() { + await adapter.connect(); + await Promise.all([ + adapter.once('suspend'), + plugin.server.sendUpdate( + AllThreadsStoppedUpdate.fromJson({ + stopReason: StopReason.Break, + stopReasonDetail: 'initial stop', + threadIndex: 0 + }) + ) + ]); + //the stackTrace request first sends a threads request + plugin.pushResponse( + ThreadsResponse.fromJson({ + requestId: undefined, + threads: [{ + filePath: 'pkg:/source/main.brs', + lineNumber: 12, + functionName: 'main', + isPrimary: true, + codeSnippet: '', + stopReason: StopReason.Break, + stopReasonDetail: 'because' + }] + }) + ); + //then it sends the stacktrace request + plugin.pushResponse( + StackTraceV3Response.fromJson({ + requestId: undefined, + entries: [{ + filePath: 'pkg:/source/main.brs', + functionName: 'main', + lineNumber: 12 + }] + }) + ); + //load stack frames + await adapter.getStackTrace(0); + } + + describe('getVariable', () => { it('works for local vars', async () => { - variables.push( - { name: 'm' }, - { name: 'person' }, - { name: 'age' } + await initialize(); + + plugin.pushResponse( + VariablesResponse.fromJson({ + requestId: undefined, + variables: [ + { + isConst: false, + isContainer: true, + refCount: 1, + type: VariableType.AA, + value: undefined, + childCount: 4, + keyType: VariableType.String, + name: 'm' + }, + { + isConst: false, + isContainer: false, + refCount: 1, + type: VariableType.String, + value: '1.0.0', + name: 'apiVersion' + } + ] + }) ); const vars = await adapter.getVariable('', 1); expect( vars?.children.map(x => x.evaluateName) ).to.eql([ 'm', - 'person', - 'age' + 'apiVersion' ]); }); - it('works for object properties', async () => { - variables.push( - { isContainer: true, childCount: 2, variableType: VariableType.AA } as any, - { name: 'name', isChildKey: true } as any, - { name: 'age', isChildKey: true } as any - ); + it.only('works for object properties', async () => { + await initialize(); + plugin.pushResponse( + VariablesResponse.fromJson({ + requestId: undefined, + variables: [ + { + isConst: false, + isContainer: true, + refCount: 1, + type: VariableType.AA, + value: undefined, + keyType: VariableType.String, + children: [{ + isConst: false, + isContainer: false, + refCount: 1, + type: VariableType.String, + name: 'name', + value: 'bob' + }, { + isConst: false, + isContainer: false, + refCount: 1, + type: VariableType.Integer, + name: 'age', + value: 12 + }] + } + ] + }) + ); const vars = await adapter.getVariable('person', 1); expect( vars?.children.map(x => x.evaluateName) @@ -73,6 +178,5 @@ describe('DebugProtocolAdapter', () => { 'person["age"]' ]); }); - }); }); diff --git a/src/adapters/DebugProtocolAdapter.ts b/src/adapters/DebugProtocolAdapter.ts index e60470d6..b0cd67d7 100644 --- a/src/adapters/DebugProtocolAdapter.ts +++ b/src/adapters/DebugProtocolAdapter.ts @@ -79,6 +79,32 @@ export class DebugProtocolAdapter { public readonly supportsMultipleRuns = false; + /** + * Subscribe to an event exactly once + * @param eventName + */ + public once(eventName: 'cannot-continue'): Promise; + public once(eventname: 'chanperf'): Promise; + public once(eventName: 'close'): Promise; + public once(eventName: 'app-exit'): Promise; + public once(eventName: 'diagnostics'): Promise; + public once(eventName: 'connected'): Promise; + public once(eventname: 'console-output'): Promise; // TODO: might be able to remove this at some point + public once(eventname: 'protocol-version'): Promise; + public once(eventname: 'rendezvous'): Promise; + public once(eventName: 'runtime-error'): Promise; + public once(eventName: 'suspend'): Promise; + public once(eventName: 'start'): Promise; + public once(eventname: 'unhandled-console-output'): Promise; + public once(eventName: string) { + return new Promise((resolve) => { + const disconnect = this.on(eventName as Parameters[0], (...args) => { + disconnect(); + resolve(...args); + }); + }); + } + /** * Subscribe to various events * @param eventName @@ -243,7 +269,7 @@ export class DebugProtocolAdapter { console.debug('hasRuntimeError!!', data); this.emit('runtime-error', { message: data.data.stopReasonDetail, - errorCode: StopReasonCode[data.data.stopReason] + errorCode: data.data.stopReason }); }); @@ -415,21 +441,21 @@ export class DebugProtocolAdapter { } } - public async getStackTrace(threadId: number = this.socketDebugger.primaryThread) { + public async getStackTrace(threadIndex: number = this.socketDebugger.primaryThread) { if (!this.isAtDebuggerPrompt) { throw new Error('Cannot get stack trace: debugger is not paused'); } - return this.resolve(`stack trace for thread ${threadId}`, async () => { - let thread = await this.getThreadByThreadId(threadId); + return this.resolve(`stack trace for thread ${threadIndex}`, async () => { + let thread = await this.getThreadByThreadId(threadIndex); let frames: StackFrame[] = []; - let stackTraceData = await this.socketDebugger.stackTrace(threadId); + let stackTraceData = await this.socketDebugger.getStackTrace(threadIndex); for (let i = 0; i < stackTraceData.data.entries.length; i++) { let frameData = stackTraceData.data.entries[i]; let stackFrame: StackFrame = { frameId: this.nextFrameId++, // frame index is the reverse of the returned order. frameIndex: stackTraceData.data.entries.length - i - 1, - threadIndex: threadId, + threadIndex: threadIndex, filePath: frameData.filePath, lineNumber: frameData.lineNumber, // eslint-disable-next-line no-nested-ternary diff --git a/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts b/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts new file mode 100644 index 00000000..8d0f91c1 --- /dev/null +++ b/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts @@ -0,0 +1,79 @@ +import type { ProtocolResponse, ProtocolRequest } from './events/ProtocolEvent'; +import { HandshakeRequest } from './events/requests/HandshakeRequest'; +import type { DebugProtocolServer } from './server/DebugProtocolServer'; +import type { BeforeSendResponseEvent, OnServerStartEvent, ProtocolPlugin, ProvideResponseEvent } from './server/ProtocolPlugin'; + +/** + * A class that intercepts all debug server events and provides test data for them + */ +export class DebugProtocolServerTestPlugin implements ProtocolPlugin { + /** + * A list of responses to be sent by the server in this exact order. + * One of these will be sent for every `provideResponse` event received. + */ + private responseQueue: ProtocolResponse[] = []; + + /** + * Adds a response to the queue, which should be returned from the server in first-in-first-out order, one for each request received by the server + */ + public pushResponse(response: ProtocolResponse) { + this.responseQueue.push(response); + } + + /** + * A running list of requests received by the server during this test + */ + public readonly requests: ReadonlyArray = []; + + /** + * The most recent request received by the plugin + */ + public get latestRequest() { + return this.requests[this.requests.length - 1]; + } + + /** + * A running list of responses sent by the server during this test + */ + public readonly responses: ReadonlyArray = []; + + /** + * The most recent response received by the plugin + */ + public get latestResponse() { + return this.responses[this.responses.length - 1]; + } + + public server: DebugProtocolServer; + + /** + * Fired whenever the server starts up + */ + onServerStart({ server }: OnServerStartEvent) { + this.server = server; + } + + /** + * Whenever the server receives a request, this event allows us to send back a response + */ + provideResponse(event: ProvideResponseEvent) { + //store the request for testing purposes + (this.requests as Array).push(event.request); + + const response = this.responseQueue.shift(); + //if there's no response, AND this isn't the handshake, fail. (we want the protocol to handle the handshake most of the time) + if (!response && !(event.request instanceof HandshakeRequest)) { + throw new Error('There was no response available to send back'); + } + //force this response to have the current request's ID (for testing purposes + if (response) { + response.data.requestId = event.request.data.requestId; + } + event.response = response; + } + + beforeSendResponse(event: BeforeSendResponseEvent) { + //store the response for testing purposes + (this.responses as Array).push(event.response); + } +} diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index c9713b3d..1bf67f86 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -15,13 +15,14 @@ import { HandshakeV3Response } from '../events/responses/HandshakeV3Response'; import { AllThreadsStoppedUpdate } from '../events/updates/AllThreadsStoppedUpdate'; import { VariablesResponse } from '../events/responses/VariablesResponse'; import { VariableRequestFlag, VariablesRequest } from '../events/requests/VariablesRequest'; +import { DebugProtocolServerTestPlugin } from '../DebugProtocolServerTestPlugin.spec'; const sinon = createSandbox(); describe('DebugProtocolClient', () => { let server: DebugProtocolServer; let client: DebugProtocolClient; - let plugin: TestPlugin; + let plugin: DebugProtocolServerTestPlugin; beforeEach(async () => { sinon.stub(console, 'log').callsFake((...args) => { }); @@ -35,7 +36,7 @@ describe('DebugProtocolClient', () => { options.controllerPort = await portfinder.getPortPromise(); } server = new DebugProtocolServer(options); - plugin = server.plugins.add(new TestPlugin()); + plugin = server.plugins.add(new DebugProtocolServerTestPlugin()); await server.start(); client = new DebugProtocolClient(options); @@ -229,69 +230,3 @@ describe('DebugProtocolClient', () => { }); }); }); - -/** - * A class that intercepts all debug server events and provides test data for them - */ -class TestPlugin implements ProtocolPlugin { - /** - * A list of responses to be sent by the server in this exact order. - * One of these will be sent for every `provideResponse` event received. - */ - private responseQueue: ProtocolResponse[] = []; - - /** - * Adds a response to the queue, which should be returned from the server in first-in-first-out order, one for each request received by the server - */ - public pushResponse(response: ProtocolResponse) { - this.responseQueue.push(response); - } - - /** - * A running list of requests received by the server during this test - */ - public readonly requests: ReadonlyArray = []; - - /** - * The most recent request received by the plugin - */ - public get latestRequest() { - return this.requests[this.requests.length - 1]; - } - - /** - * A running list of responses sent by the server during this test - */ - public readonly responses: ReadonlyArray = []; - - /** - * The most recent response received by the plugin - */ - public get latestResponse() { - return this.responses[this.responses.length - 1]; - } - - /** - * Whenever the server receives a request, this event allows us to send back a response - */ - provideResponse(event: ProvideResponseEvent) { - //store the request for testing purposes - (this.requests as Array).push(event.request); - - const response = this.responseQueue.shift(); - //if there's no response, AND this isn't the handshake, fail. (we want the protocol to handle the handshake most of the time) - if (!response && !(event.request instanceof HandshakeRequest)) { - throw new Error('There was no response available to send back'); - } - //force this response to have the current request's ID (for testing purposes - if (response) { - response.data.requestId = event.request.data.requestId; - } - event.response = response; - } - - beforeSendResponse(event: BeforeSendResponseEvent) { - //store the response for testing purposes - (this.responses as Array).push(event.response); - } -} diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 4baa4a8f..6387aa47 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -1,8 +1,7 @@ import * as Net from 'net'; import * as EventEmitter from 'eventemitter3'; import * as semver from 'semver'; -import { PROTOCOL_ERROR_CODES, Command, StepType, StopReasonCode, ErrorCode, UpdateType, UpdateTypeCode, StepTypeCode } from '../Constants'; -import { SmartBuffer } from 'smart-buffer'; +import { PROTOCOL_ERROR_CODES, Command, StepType, StopReasonCode, ErrorCode, UpdateType, UpdateTypeCode, StopReason } from '../Constants'; import { logger } from '../../logging'; import { ExecuteV3Response } from '../events/responses/ExecuteV3Response'; import { ListBreakpointsResponse } from '../events/responses/ListBreakpointsResponse'; @@ -282,8 +281,9 @@ export class DebugProtocolClient { }) ); if (stepResult.data.errorCode === ErrorCode.OK) { - // this.stopped = true; - // this.emit('suspend'); + this.isStopped = true; + //TODO this is not correct. Do we get a new threads event after a step? Perhaps that should be what triggers the event instead of us? + this.emit('suspend', stepResult as AllThreadsStoppedUpdate); } else { // there is a CANT_CONTINUE error code but we can likely treat all errors like a CANT_CONTINUE this.emit('cannot-continue'); @@ -320,16 +320,20 @@ export class DebugProtocolClient { } } - public async stackTrace(threadIndex: number = this.primaryThread) { - let buffer = new SmartBuffer({ size: 16 }); - buffer.writeUInt32LE(threadIndex); // thread_index + /** + * Get the stackTrace from the device IF currently stopped + */ + public async getStackTrace(threadIndex: number = this.primaryThread) { if (this.isStopped && threadIndex > -1) { + this.logger.log('getStackTrace()', { threadIndex: threadIndex }); return this.sendRequest( StackTraceRequest.fromJson({ requestId: this.requestIdSequence++, threadIndex: threadIndex }) ); + } else { + this.logger.log('[getStackTrace] skipped. ', { isStopped: this.isStopped, threadIndex: threadIndex }); } } @@ -450,45 +454,56 @@ export class DebugProtocolClient { } private process(): void { - if (this.buffer.length < 1) { - // short circuit if the buffer is empty - return; - } + try { + if (this.buffer.length < 1) { + // short circuit if the buffer is empty + return; + } - const event = this.getResponseOrUpdate(this.buffer); + this.logger.log('process(): buffer=', this.buffer.toJSON()); - //if the event failed to parse, or the buffer doesn't have enough bytes to satisfy the packetLength, exit here (new data will re-trigger this function) - if (!event || !event.success || event.data.packetLength > this.buffer.length) { - //TODO do something about this - return; - } + const event = this.getResponseOrUpdate(this.buffer); + + //if the event failed to parse, or the buffer doesn't have enough bytes to satisfy the packetLength, exit here (new data will re-trigger this function) + if (!event) { + this.logger.log('Unable to convert buffer into anything meaningful'); + //TODO what should we do about this? + return; + } + if (!event.success || event.data.packetLength > this.buffer.length) { + this.logger.log(`event parse failed. ${event?.data?.packetLength} bytes required, ${this.buffer.length} bytes available`); + return; + } - //we have a valid event. Clear the buffer of this data - this.buffer = this.buffer.slice(event.readOffset); + //we have a valid event. Clear the buffer of this data + this.buffer = this.buffer.slice(event.readOffset); - //TODO why did we ever do this? Just to handle when we misread incoming data? I think this should be scrapped - // if (event.data.requestId > this.totalRequests) { - // this.removedProcessedBytes(genericResponse, slicedBuffer, packetLength); - // return true; - // } + //TODO why did we ever do this? Just to handle when we misread incoming data? I think this should be scrapped + // if (event.data.requestId > this.totalRequests) { + // this.removedProcessedBytes(genericResponse, slicedBuffer, packetLength); + // return true; + // } - if (event.data.errorCode !== ErrorCode.OK) { - this.logger.error(event.data.errorCode, event); - return; - } + if (event.data.errorCode !== ErrorCode.OK) { + this.logger.error(event.data.errorCode, event); + return; + } - //we got a response - if (event) { - //emit the corresponding event - if (isProtocolUpdate(event)) { - this.emit('update', event); - } else { - this.emit('response', event); + //we got a response + if (event) { + //emit the corresponding event + if (isProtocolUpdate(event)) { + this.emit('update', event); + } else { + this.emit('response', event); + } } - } - // process again (will run recursively until the buffer is empty) - this.process(); + // process again (will run recursively until the buffer is empty) + this.process(); + } catch (e) { + this.logger.error(`process() failed:`, e); + } } /** @@ -558,7 +573,7 @@ export class DebugProtocolClient { ); const updateType = UpdateTypeCode[updateTypeCode] as UpdateType; - this.logger.log('Update Type:', updateType, updateType); + this.logger.log('getUpdate(): update Type:', updateType); switch (updateType) { case UpdateType.IOPortOpened: //TODO handle this @@ -583,9 +598,9 @@ export class DebugProtocolClient { private handleUpdate(update: ProtocolUpdate) { if (update instanceof AllThreadsStoppedUpdate || update instanceof ThreadAttachedUpdate) { this.isStopped = true; - let eventName: 'runtime-error' | 'suspend' = (update.data.stopReason === StopReasonCode.RuntimeError ? 'runtime-error' : 'suspend'); + let eventName: 'runtime-error' | 'suspend' = (update.data.stopReason === StopReason.RuntimeError ? 'runtime-error' : 'suspend'); - const isValidStopReason = [StopReasonCode.RuntimeError, StopReasonCode.Break, StopReasonCode.StopStatement].includes(update.data.stopReason); + const isValidStopReason = [StopReason.RuntimeError, StopReason.Break, StopReason.StopStatement].includes(update.data.stopReason); if (update instanceof AllThreadsStoppedUpdate && isValidStopReason) { this.primaryThread = update.data.threadIndex; diff --git a/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.spec.ts b/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.spec.ts index f8491513..7509eeb3 100644 --- a/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.spec.ts +++ b/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.spec.ts @@ -20,10 +20,10 @@ describe('AddConditionalBreakpointsRequest', () => { expect( AddConditionalBreakpointsRequest.fromBuffer(command.toBuffer()).data ).to.eql({ - packetLength: 16, // 4 bytes + packetLength: 20, // 4 bytes requestId: 3, // 4 bytes command: Command.AddConditionalBreakpoints, // 4 bytes - + // flags // 4 bytes // num_breakpoints // 4 bytes breakpoints: [] }); @@ -67,10 +67,12 @@ describe('AddConditionalBreakpointsRequest', () => { expect( AddConditionalBreakpointsRequest.fromBuffer(command.toBuffer()).data ).to.eql({ - packetLength: 73, // 4 bytes + packetLength: 77, // 4 bytes requestId: 3, // 4 bytes command: Command.AddConditionalBreakpoints, // 4 bytes + //flags // 4 bytes + // num_breakpoints // 4 bytes breakpoints: [{ filePath: 'source/main.brs', // 16 bytes diff --git a/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts b/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts index 25ae31c2..48a36260 100644 --- a/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts @@ -32,6 +32,8 @@ export class AddConditionalBreakpointsRequest implements ProtocolRequest { protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { protocolUtils.loadCommonRequestFields(request, smartBuffer); + smartBuffer.readUInt32LE(); // flags - Should always be passed as 0. Unused, reserved for future use. + const numBreakpoints = smartBuffer.readUInt32LE(); // num_breakpoints request.data.breakpoints = []; for (let i = 0; i < numBreakpoints; i++) { diff --git a/src/debugProtocol/events/responses/HandshakeResponse.spec.ts b/src/debugProtocol/events/responses/HandshakeResponse.spec.ts index f4dff8f0..93dde4a6 100644 --- a/src/debugProtocol/events/responses/HandshakeResponse.spec.ts +++ b/src/debugProtocol/events/responses/HandshakeResponse.spec.ts @@ -1,6 +1,8 @@ import { HandshakeResponse } from './HandshakeResponse'; import { DebugProtocolClient } from '../../client/DebugProtocolClient'; import { expect } from 'chai'; +import { HandshakeRequest } from '../requests/HandshakeRequest'; +import { ErrorCode } from '../../Constants'; describe('HandshakeResponse', () => { it('Handles a handshake response', () => { @@ -10,6 +12,10 @@ describe('HandshakeResponse', () => { }); expect(response.data).to.eql({ + packetLength: undefined, + requestId: HandshakeRequest.REQUEST_ID, + errorCode: ErrorCode.OK, + magic: 'not bsdebug', protocolVersion: '1.0.0' }); @@ -17,6 +23,10 @@ describe('HandshakeResponse', () => { expect( HandshakeResponse.fromBuffer(response.toBuffer()).data ).to.eql({ + packetLength: undefined, + requestId: HandshakeRequest.REQUEST_ID, + errorCode: ErrorCode.OK, + magic: 'not bsdebug', // 12 bytes protocolVersion: '1.0.0' // 12 bytes (each number is sent as uint32) }); diff --git a/src/debugProtocol/events/responses/ThreadsResponse.spec.ts b/src/debugProtocol/events/responses/ThreadsResponse.spec.ts index 503da2f6..8b5eda32 100644 --- a/src/debugProtocol/events/responses/ThreadsResponse.spec.ts +++ b/src/debugProtocol/events/responses/ThreadsResponse.spec.ts @@ -151,7 +151,7 @@ describe('ThreadsResponse', () => { codeSnippet: 'sub main()' }, { isPrimary: true, - stopReason: StopReason.Break, + stopReason: StopReason.StopStatement, stopReasonDetail: 'because', lineNumber: 3, functionName: 'main', @@ -169,7 +169,7 @@ describe('ThreadsResponse', () => { expect(response.success).to.be.false; expect(response.data.threads).to.eql([{ isPrimary: true, - stopReason: 'Break', + stopReason: StopReason.Break, stopReasonDetail: 'because', lineNumber: 2, functionName: 'main', diff --git a/src/debugProtocol/events/responses/ThreadsResponse.ts b/src/debugProtocol/events/responses/ThreadsResponse.ts index ccd10e37..956eb173 100644 --- a/src/debugProtocol/events/responses/ThreadsResponse.ts +++ b/src/debugProtocol/events/responses/ThreadsResponse.ts @@ -29,7 +29,6 @@ export class ThreadsResponse { const thread = {} as ThreadInfo; const flags = smartBuffer.readUInt8(); thread.isPrimary = (flags & ThreadInfoFlags.isPrimary) > 0; - thread.stopReason = StopReasonCode[smartBuffer.readUInt32LE()] as StopReason; // stop_reason thread.stopReasonDetail = protocolUtils.readStringNT(smartBuffer); // stop_reason_detail thread.lineNumber = smartBuffer.readUInt32LE(); // line_number @@ -49,10 +48,9 @@ export class ThreadsResponse { for (const thread of this.data.threads ?? []) { let flags = 0; flags |= thread.isPrimary ? 1 : 0; - // NOTE: The docs say the flags should be both unit8 AND uint32. In testing it seems like they are sending uint32 but meant to send unit8. - smartBuffer.writeUInt32LE(flags); - - smartBuffer.writeUInt8(StopReasonCode[thread.stopReason]); // stop_reason + smartBuffer.writeUInt8(flags); //flags + //stop_reason is an 8-bit value (same as the other locations in this protocol); however, it is sent in this response as a 32bit value for historical purposes + smartBuffer.writeUInt32LE(StopReasonCode[thread.stopReason]); // stop_reason smartBuffer.writeStringNT(thread.stopReasonDetail); // stop_reason_detail smartBuffer.writeUInt32LE(thread.lineNumber); // line_number smartBuffer.writeStringNT(thread.functionName); // function_name diff --git a/src/debugProtocol/events/responses/VariablesResponse.ts b/src/debugProtocol/events/responses/VariablesResponse.ts index de4c5e19..639214ce 100644 --- a/src/debugProtocol/events/responses/VariablesResponse.ts +++ b/src/debugProtocol/events/responses/VariablesResponse.ts @@ -20,7 +20,7 @@ export class VariablesResponse { variable.isContainer = true; } if (hasChildrenArray) { - variable.childCount = variable.children.length; + delete variable.childCount; } if (util.isNullish(variable.isContainer)) { variable.isContainer = [VariableType.AA, VariableType.Array, VariableType.List, VariableType.Object, VariableType.SubtypedObject].includes(variable.type); @@ -385,11 +385,13 @@ export interface Variable { */ isContainer: boolean; /** - * If the variable is a container, it will have child elements. this is the number of those children. This field is ignored when serializing if `.children` is set + * If the variable is a container, it will have child elements. this is the number of those children. If `.children` is set, this field will be set to undefined + * (meaning it will be ignored during serialization) */ childCount?: number; /** - * The full list of children for this variable. This list may not be more than 2 total levels deep (i.e. `parent` -> `children`). Children may not have additional children. + * The full list of children for this variable. The entire Variable response may not be more than 2 total levels deep. + * (i.e. `parent` -> `children[]`). Children may not have additional children, those would need to be resolve using subsequent `variables` requests. */ children?: Variable[]; } diff --git a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts index 8e8c9009..26f8047e 100644 --- a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts +++ b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.spec.ts @@ -1,12 +1,12 @@ import { expect } from 'chai'; -import { ErrorCode, StopReasonCode, UpdateType } from '../../Constants'; +import { ErrorCode, StopReason, UpdateType } from '../../Constants'; import { AllThreadsStoppedUpdate } from './AllThreadsStoppedUpdate'; describe('AllThreadsStoppedUpdate', () => { it('serializes and deserializes properly', () => { const command = AllThreadsStoppedUpdate.fromJson({ threadIndex: 1, - stopReason: StopReasonCode.Break, + stopReason: StopReason.Break, stopReasonDetail: 'because' }); @@ -16,8 +16,8 @@ describe('AllThreadsStoppedUpdate', () => { errorCode: ErrorCode.OK, updateType: UpdateType.AllThreadsStopped, - primaryThreadIndex: 1, - stopReason: StopReasonCode.Break, + threadIndex: 1, + stopReason: StopReason.Break, stopReasonDetail: 'because' }); @@ -29,8 +29,8 @@ describe('AllThreadsStoppedUpdate', () => { errorCode: ErrorCode.OK, // 4 bytes updateType: UpdateType.AllThreadsStopped, // 4 bytes - primaryThreadIndex: 1, // 4 bytes - stopReason: StopReasonCode.Break, // 1 bytes + threadIndex: 1, // 4 bytes + stopReason: StopReason.Break, // 1 bytes stopReasonDetail: 'because' // 8 bytes }); }); diff --git a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts index cf4b28ac..09d28204 100644 --- a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts +++ b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts @@ -1,7 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; -import type { StopReasonCode } from '../../Constants'; -import { ErrorCode, UpdateType } from '../../Constants'; -import { util } from '../../../util'; +import type { StopReason } from '../../Constants'; +import { ErrorCode, StopReasonCode, UpdateType } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; import type { ProtocolUpdate } from '../ProtocolEvent'; @@ -14,7 +13,7 @@ export class AllThreadsStoppedUpdate implements ProtocolUpdate { public static fromJson(data: { threadIndex: number; - stopReason: number; + stopReason: StopReason; stopReasonDetail: string; }) { const update = new AllThreadsStoppedUpdate(); @@ -28,7 +27,7 @@ export class AllThreadsStoppedUpdate implements ProtocolUpdate { protocolUtils.loadCommonUpdateFields(update, smartBuffer, update.data.updateType); update.data.threadIndex = smartBuffer.readInt32LE(); - update.data.stopReason = smartBuffer.readUInt8(); + update.data.stopReason = StopReasonCode[smartBuffer.readUInt8()] as StopReason; update.data.stopReasonDetail = protocolUtils.readStringNT(smartBuffer); }); return update; @@ -38,7 +37,7 @@ export class AllThreadsStoppedUpdate implements ProtocolUpdate { let smartBuffer = new SmartBuffer(); smartBuffer.writeInt32LE(this.data.threadIndex); // primary_thread_index - smartBuffer.writeUInt8(this.data.stopReason); // stop_reason + smartBuffer.writeUInt8(StopReasonCode[this.data.stopReason]); // stop_reason smartBuffer.writeStringNT(this.data.stopReasonDetail); //stop_reason_detail protocolUtils.insertCommonUpdateFields(this, smartBuffer); @@ -54,7 +53,7 @@ export class AllThreadsStoppedUpdate implements ProtocolUpdate { * The index of the primary thread that triggered the stop */ threadIndex: undefined as number, - stopReason: undefined as StopReasonCode, + stopReason: undefined as StopReason, stopReasonDetail: undefined as string, //common props diff --git a/src/debugProtocol/events/updates/ThreadAttachedUpdate.spec.ts b/src/debugProtocol/events/updates/ThreadAttachedUpdate.spec.ts index 59a1a940..aa5d226f 100644 --- a/src/debugProtocol/events/updates/ThreadAttachedUpdate.spec.ts +++ b/src/debugProtocol/events/updates/ThreadAttachedUpdate.spec.ts @@ -1,12 +1,12 @@ import { expect } from 'chai'; -import { ErrorCode, StopReasonCode, UpdateType } from '../../Constants'; +import { ErrorCode, StopReason, UpdateType } from '../../Constants'; import { ThreadAttachedUpdate } from './ThreadAttachedUpdate'; describe('AllThreadsStoppedUpdate', () => { it('serializes and deserializes properly', () => { const update = ThreadAttachedUpdate.fromJson({ threadIndex: 1, - stopReason: StopReasonCode.Break, + stopReason: StopReason.Break, stopReasonDetail: 'because' }); @@ -17,7 +17,7 @@ describe('AllThreadsStoppedUpdate', () => { updateType: UpdateType.ThreadAttached, threadIndex: 1, - stopReason: StopReasonCode.Break, + stopReason: StopReason.Break, stopReasonDetail: 'because' }); @@ -30,7 +30,7 @@ describe('AllThreadsStoppedUpdate', () => { updateType: UpdateType.ThreadAttached, // 4 bytes threadIndex: 1, // 4 bytes - stopReason: StopReasonCode.Break, // 1 bytes + stopReason: StopReason.Break, // 1 bytes stopReasonDetail: 'because' // 8 bytes }); }); diff --git a/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts b/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts index 033e7d38..80b194c1 100644 --- a/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts +++ b/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts @@ -1,14 +1,13 @@ import { SmartBuffer } from 'smart-buffer'; -import type { StopReasonCode } from '../../Constants'; -import { ErrorCode, UpdateType } from '../../Constants'; -import { util } from '../../../util'; +import type { StopReason } from '../../Constants'; +import { ErrorCode, StopReasonCode, UpdateType } from '../../Constants'; import { protocolUtils } from '../../ProtocolUtil'; export class ThreadAttachedUpdate { public static fromJson(data: { threadIndex: number; - stopReason: number; + stopReason: StopReason; stopReasonDetail: string; }) { const update = new ThreadAttachedUpdate(); @@ -21,7 +20,7 @@ export class ThreadAttachedUpdate { protocolUtils.bufferLoaderHelper(update, buffer, 12, (smartBuffer) => { protocolUtils.loadCommonUpdateFields(update, smartBuffer, update.data.updateType); update.data.threadIndex = smartBuffer.readInt32LE(); - update.data.stopReason = smartBuffer.readUInt8(); + update.data.stopReason = StopReasonCode[smartBuffer.readUInt8()] as StopReason; update.data.stopReasonDetail = protocolUtils.readStringNT(smartBuffer); }); return update; @@ -31,7 +30,7 @@ export class ThreadAttachedUpdate { const smartBuffer = new SmartBuffer(); smartBuffer.writeInt32LE(this.data.threadIndex); - smartBuffer.writeUInt8(this.data.stopReason); + smartBuffer.writeUInt8(StopReasonCode[this.data.stopReason]); smartBuffer.writeStringNT(this.data.stopReasonDetail); protocolUtils.insertCommonUpdateFields(this, smartBuffer); @@ -47,7 +46,7 @@ export class ThreadAttachedUpdate { * The index of the thread that was just attached */ threadIndex: undefined as number, - stopReason: undefined as StopReasonCode, + stopReason: undefined as StopReason, stopReasonDetail: undefined as string, //common props diff --git a/src/debugProtocol/responseCreationHelpers.spec.ts b/src/debugProtocol/responseCreationHelpers.spec.ts deleted file mode 100644 index 957a632b..00000000 --- a/src/debugProtocol/responseCreationHelpers.spec.ts +++ /dev/null @@ -1 +0,0 @@ -import { SmartBuffer } from 'smart-buffer'; diff --git a/src/debugProtocol/server/DebugProtocolServer.ts b/src/debugProtocol/server/DebugProtocolServer.ts index bafb14b3..38f68139 100644 --- a/src/debugProtocol/server/DebugProtocolServer.ts +++ b/src/debugProtocol/server/DebugProtocolServer.ts @@ -20,6 +20,7 @@ import { HandshakeResponse } from '../events/responses/HandshakeResponse'; import { HandshakeV3Response } from '../events/responses/HandshakeV3Response'; import PluginInterface from './PluginInterface'; import type { ProtocolPlugin } from './ProtocolPlugin'; +import { logger } from '../../logging'; export const DEBUGGER_MAGIC = 'bsdebug'; @@ -34,6 +35,8 @@ export class DebugProtocolServer { } + private logger = logger.createLogger(`[${DebugProtocolServer.name}]`); + /** * Indicates whether the client has sent the magic string to kick off the debug session. */ @@ -65,7 +68,7 @@ export class DebugProtocolServer { * Run the server. This opens a socket and listens for a connection. * The promise resolves when the server has started listening. It does NOT wait for a client to connect */ - public start() { + public async start() { return new Promise((resolve) => { this.server = new Net.Server({}); //Roku only allows 1 connection, so we should too. @@ -94,7 +97,10 @@ export class DebugProtocolServer { this.server.listen({ port: this.options.controllerPort ?? 8081, hostName: this.options.host ?? '0.0.0.0' - }, resolve); + }, () => { + void this.plugins.emit('onServerStart', { server: this }); + resolve(); + }); }); } @@ -166,49 +172,64 @@ export class DebugProtocolServer { } private async process() { - //at this point, there is an active debug session. The plugin must provide us all the real-world data - let { buffer, request } = await this.plugins.emit('provideRequest', { - server: this, - buffer: this.buffer, - request: undefined - }); + try { + this.logger.log('process() start', { buffer: this.buffer.toJSON() }); + + //at this point, there is an active debug session. The plugin must provide us all the real-world data + let { buffer, request } = await this.plugins.emit('provideRequest', { + server: this, + buffer: this.buffer, + request: undefined + }); - //we must build the request if the plugin didn't supply one (most plugins won't provide a request...) - if (!request) { - request = this.getRequest(buffer); - } + //we must build the request if the plugin didn't supply one (most plugins won't provide a request...) + if (!request) { + request = this.getRequest(buffer); + } - //if we couldn't construct a request this request, hard-fail - if (!request || !request.success) { - throw new Error(`Unable to parse request: ${JSON.stringify(this.buffer.toJSON().data)}`); - } - //trim the buffer now that the request has been processed - this.buffer = buffer.slice(request.readOffset); + //if we couldn't construct a request this request, hard-fail + if (!request || !request.success) { + this.logger.error('process() invalid request', { request }); + throw new Error(`Unable to parse request: ${JSON.stringify(this.buffer.toJSON().data)}`); + } - //now ask the plugin to provide a response for the given request - let { response } = await this.plugins.emit('provideResponse', { - server: this, - request: request, - response: undefined - }); + this.logger.log('process() constructed request', { request }); + //trim the buffer now that the request has been processed + this.buffer = buffer.slice(request.readOffset); - //if the plugin didn't provide a response, we need to try our best to make one (we only support a few...plugins should provide most of them) - if (!response) { - response = this.getResponse(request); - } + this.logger.log('process() buffer sliced', { buffer: this.buffer.toJSON() }); - //the client should send a magic string to kick off the debugger - if ((response instanceof HandshakeResponse || response instanceof HandshakeV3Response) && response.data.magic === this.magic) { - this.isHandshakeComplete = true; - } - if (!response) { - throw new Error(`Server was unable to provide a response for ${JSON.stringify(request.data)}`); - } + //now ask the plugin to provide a response for the given request + let { response } = await this.plugins.emit('provideResponse', { + server: this, + request: request, + response: undefined + }); + + + //if the plugin didn't provide a response, we need to try our best to make one (we only support a few...plugins should provide most of them) + if (!response) { + response = this.getResponse(request); + } - //send the response to the client. (TODO handle when the response is missing) - await this.sendResponse(response); + if (!response) { + this.logger.error('process() invalid response', { request, response }); + throw new Error(`Server was unable to provide a response for ${JSON.stringify(request.data)}`); + } + + + //the client should send a magic string to kick off the debugger + if ((response instanceof HandshakeResponse || response instanceof HandshakeV3Response) && response.data.magic === this.magic) { + this.isHandshakeComplete = true; + } + + //send the response to the client. (TODO handle when the response is missing) + await this.sendResponse(response); + } catch (e) { + this.logger.error('process() error', e); + } } /** @@ -220,6 +241,7 @@ export class DebugProtocolServer { response: response }); + this.logger.log('sendResponse()', { response }); this.client.write(event.response.toBuffer()); await this.plugins.emit('afterSendResponse', { diff --git a/src/debugProtocol/server/ProtocolPlugin.ts b/src/debugProtocol/server/ProtocolPlugin.ts index ade8cad8..07c6f33f 100644 --- a/src/debugProtocol/server/ProtocolPlugin.ts +++ b/src/debugProtocol/server/ProtocolPlugin.ts @@ -3,6 +3,7 @@ import type { Socket } from 'net'; import type { ProtocolRequest, ProtocolResponse } from '../events/ProtocolEvent'; export interface ProtocolPlugin { + onServerStart: Handler; onClientConnected?: Handler; provideRequest?: Handler; @@ -12,6 +13,10 @@ export interface ProtocolPlugin { afterSendResponse?: Handler; } +export interface OnServerStartEvent { + server: DebugProtocolServer; +} + export interface OnClientConnectedEvent { server: DebugProtocolServer; client: Socket; diff --git a/src/testHelpers.spec.ts b/src/testHelpers.spec.ts index 26f594e5..9519fcf8 100644 --- a/src/testHelpers.spec.ts +++ b/src/testHelpers.spec.ts @@ -74,3 +74,4 @@ export function getRandomBuffer(byteCount: number) { } return result.toBuffer(); } + From 241a9db0fc3148027372fb592bc28f156ab39802 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 11 Jan 2023 08:19:01 -0500 Subject: [PATCH 18/74] Fix npm package issues --- package-lock.json | 77 +++++++++++-------- package.json | 7 +- src/adapters/DebugProtocolAdapter.spec.ts | 4 +- .../client/DebugProtocolClient.device.spec.ts | 5 ++ 4 files changed, 54 insertions(+), 39 deletions(-) create mode 100644 src/debugProtocol/client/DebugProtocolClient.device.spec.ts diff --git a/package-lock.json b/package-lock.json index cdb76144..c67caccf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "semver": "^7.3.5", "serialize-error": "^8.1.0", "smart-buffer": "^4.2.0", - "source-map": "^0.7.3", + "source-map": "^0.7.4", "telnet-client": "^1.4.9", "vscode-debugadapter": "^1.49.0", "vscode-debugprotocol": "^1.49.0", @@ -110,20 +110,6 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/json5": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.0", "dev": true, @@ -2875,6 +2861,18 @@ "version": "5.0.1", "license": "ISC" }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonc-parser": { "version": "2.3.1", "license": "MIT" @@ -3022,8 +3020,9 @@ } }, "node_modules/luxon": { - "version": "1.28.0", - "license": "MIT", + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz", + "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==", "engines": { "node": "*" } @@ -3091,8 +3090,9 @@ } }, "node_modules/minimatch": { - "version": "3.0.4", - "license": "ISC", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3766,8 +3766,9 @@ } }, "node_modules/qs": { - "version": "6.5.2", - "license": "BSD-3-Clause", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", "engines": { "node": ">=0.6" } @@ -4270,8 +4271,9 @@ } }, "node_modules/source-map": { - "version": "0.7.3", - "license": "BSD-3-Clause", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", "engines": { "node": ">= 8" } @@ -4906,13 +4908,6 @@ "source-map": "^0.5.0" }, "dependencies": { - "json5": { - "version": "2.2.0", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, "semver": { "version": "6.3.0", "dev": true @@ -6687,6 +6682,12 @@ "json-stringify-safe": { "version": "5.0.1" }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, "jsonc-parser": { "version": "2.3.1" }, @@ -6787,7 +6788,9 @@ } }, "luxon": { - "version": "1.28.0" + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz", + "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==" }, "make-dir": { "version": "3.1.0", @@ -6826,7 +6829,9 @@ } }, "minimatch": { - "version": "3.0.4", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "requires": { "brace-expansion": "^1.1.7" } @@ -7258,7 +7263,9 @@ "version": "1.5.1" }, "qs": { - "version": "6.5.2" + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" }, "queue-microtask": { "version": "1.2.3" @@ -7580,7 +7587,9 @@ "version": "4.2.0" }, "source-map": { - "version": "0.7.3" + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" }, "source-map-support": { "version": "0.5.20", diff --git a/package.json b/package.json index a685bb05..f4a49638 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,9 @@ "preversion": "npm run build && npm run lint && npm run test", "lint": "eslint \"src/**\"", "watch": "tsc --watch", - "test": "nyc mocha \"src/**/*spec.ts\"", - "test:nocover": "mocha \"src/**/*.spec.ts\"", + "test": "nyc mocha \"src/**/*spec.ts\" --exclude \"src/**/*.device.spec.ts\"", + "device-test": "mocha --spec \"src/**/*.device.spec.ts\"", + "test:nocover": "mocha \"src/**/*.spec.ts\" --exclude \"src/**/*.device.spec.ts\"", "publish-coverage": "nyc report --reporter=text-lcov | coveralls" }, "typings": "dist/index.d.ts", @@ -99,7 +100,7 @@ "semver": "^7.3.5", "serialize-error": "^8.1.0", "smart-buffer": "^4.2.0", - "source-map": "^0.7.3", + "source-map": "^0.7.4", "telnet-client": "^1.4.9", "vscode-debugadapter": "^1.49.0", "vscode-debugprotocol": "^1.49.0", diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index 2fae106c..7625b6b9 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -137,7 +137,7 @@ describe('DebugProtocolAdapter', () => { ]); }); - it.only('works for object properties', async () => { + it.skip('works for object properties', async () => { await initialize(); plugin.pushResponse( @@ -170,7 +170,7 @@ describe('DebugProtocolAdapter', () => { ] }) ); - const vars = await adapter.getVariable('person', 1); + const vars = await adapter.getVariable('person', 0); expect( vars?.children.map(x => x.evaluateName) ).to.eql([ diff --git a/src/debugProtocol/client/DebugProtocolClient.device.spec.ts b/src/debugProtocol/client/DebugProtocolClient.device.spec.ts new file mode 100644 index 00000000..826a31e9 --- /dev/null +++ b/src/debugProtocol/client/DebugProtocolClient.device.spec.ts @@ -0,0 +1,5 @@ +describe('DebugProtocolClient on-device tests', () => { + it('fails for no reason', () => { + throw new Error('Crash!'); + }); +}); From b52dcafd94f8dbf1ff1d4c837d38fd09bdc11603 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 11 Jan 2023 09:03:36 -0500 Subject: [PATCH 19/74] Rename ProtocolUtils to singular. Add testHelpers tests --- src/debugProtocol/ProtocolUtil.spec.ts | 55 +++++++++++++++++++ src/debugProtocol/ProtocolUtil.ts | 4 +- .../events/requests/AddBreakpointsRequest.ts | 12 ++-- .../AddConditionalBreakpointsRequest.ts | 14 ++--- .../events/requests/ContinueRequest.ts | 10 ++-- .../events/requests/ExecuteRequest.ts | 12 ++-- .../events/requests/ExitChannelRequest.ts | 10 ++-- .../events/requests/HandshakeRequest.ts | 8 +-- .../events/requests/ListBreakpointsRequest.ts | 10 ++-- .../requests/RemoveBreakpointsRequest.ts | 10 ++-- .../events/requests/StackTraceRequest.ts | 10 ++-- .../events/requests/StepRequest.ts | 10 ++-- .../events/requests/StopRequest.ts | 10 ++-- .../events/requests/ThreadsRequest.ts | 10 ++-- .../events/requests/VariablesRequest.ts | 12 ++-- .../events/responses/ExecuteV3Response.ts | 16 +++--- .../events/responses/GenericResponse.ts | 6 +- .../events/responses/GenericV3Response.ts | 8 +-- .../events/responses/HandshakeResponse.ts | 8 +-- .../events/responses/HandshakeV3Response.ts | 8 +-- .../responses/ListBreakpointsResponse.ts | 10 ++-- .../events/responses/StackTraceResponse.ts | 10 ++-- .../events/responses/StackTraceV3Response.ts | 14 ++--- .../events/responses/ThreadsResponse.ts | 18 +++--- .../events/responses/VariablesResponse.ts | 18 +++--- .../events/updates/AllThreadsStoppedUpdate.ts | 12 ++-- .../events/updates/BreakpointErrorUpdate.ts | 16 +++--- .../events/updates/CompileErrorUpdate.ts | 16 +++--- .../events/updates/IOPortOpenedUpdate.ts | 10 ++-- .../events/updates/ThreadAttachedUpdate.ts | 12 ++-- src/testHelpers.spec.ts | 14 +++++ 31 files changed, 231 insertions(+), 162 deletions(-) create mode 100644 src/debugProtocol/ProtocolUtil.spec.ts diff --git a/src/debugProtocol/ProtocolUtil.spec.ts b/src/debugProtocol/ProtocolUtil.spec.ts new file mode 100644 index 00000000..eab656f8 --- /dev/null +++ b/src/debugProtocol/ProtocolUtil.spec.ts @@ -0,0 +1,55 @@ +import { expect } from 'chai'; +import { protocolUtil } from './ProtocolUtil'; +import { ProtocolUpdate } from './events/ProtocolEvent'; +import { SmartBuffer } from 'smart-buffer'; +import { ErrorCode, UpdateType, UpdateTypeCode } from './Constants'; +import { expectThrows } from '../testHelpers.spec'; + +describe('ProtocolUtil', () => { + describe('loadJson', () => { + it('defaults to an empty object', () => { + protocolUtil.loadJson({} as any, undefined); + //test passes if there was no exception + }); + }); + + describe('bufferLoaderHelper', () => { + it('handles when no event success', () => { + expect( + protocolUtil.bufferLoaderHelper({ + readOffset: -1 + } as any, Buffer.alloc(1), 0, () => false).readOffset + ).to.eql(-1); + }); + }); + + describe('loadCommonUpdateFields', () => { + it('handles when the requestId is greater than 0', () => { + const update = { + data: {} + } as ProtocolUpdate; + const buffer = new SmartBuffer(); + buffer.writeUInt32LE(12); //packet_length + buffer.writeUInt32LE(999); //request_id + buffer.writeUInt32LE(ErrorCode.OK); //error_code + expectThrows( + () => protocolUtil.loadCommonUpdateFields(update, buffer, UpdateType.CompileError), + 'This is not an update' + ); + }); + + it('returns false if this is the wrong update type', () => { + const update = { + data: {} + } as ProtocolUpdate; + const buffer = new SmartBuffer(); + buffer.writeUInt32LE(12); //packet_length + buffer.writeUInt32LE(0); //request_id + buffer.writeUInt32LE(ErrorCode.OK); //error_code + buffer.writeUInt32LE(UpdateTypeCode.AllThreadsStopped); //update_type + expect( + protocolUtil.loadCommonUpdateFields(update, buffer, UpdateType.CompileError) + ).to.be.false; + }); + }); +}); diff --git a/src/debugProtocol/ProtocolUtil.ts b/src/debugProtocol/ProtocolUtil.ts index e0538fc6..b1f42a2f 100644 --- a/src/debugProtocol/ProtocolUtil.ts +++ b/src/debugProtocol/ProtocolUtil.ts @@ -3,7 +3,7 @@ import type { Command, UpdateType } from './Constants'; import { CommandCode, UpdateTypeCode } from './Constants'; import type { ProtocolRequest, ProtocolResponse, ProtocolUpdate } from './events/ProtocolEvent'; -export class ProtocolUtils { +export class ProtocolUtil { /** * Load json data onto an event, and mark it as successful @@ -137,5 +137,5 @@ export class ProtocolUtils { } } -export const protocolUtils = new ProtocolUtils(); +export const protocolUtil = new ProtocolUtil(); diff --git a/src/debugProtocol/events/requests/AddBreakpointsRequest.ts b/src/debugProtocol/events/requests/AddBreakpointsRequest.ts index 8db13c1a..3c5f6e7c 100644 --- a/src/debugProtocol/events/requests/AddBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/AddBreakpointsRequest.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; import { Command } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; export class AddBreakpointsRequest implements ProtocolRequest { @@ -14,7 +14,7 @@ export class AddBreakpointsRequest implements ProtocolRequest { }>; }) { const request = new AddBreakpointsRequest(); - protocolUtils.loadJson(request, data); + protocolUtil.loadJson(request, data); request.data.breakpoints ??= []; //default ignoreCount to 0 for consistency purposes for (const breakpoint of request.data.breakpoints) { @@ -25,14 +25,14 @@ export class AddBreakpointsRequest implements ProtocolRequest { public static fromBuffer(buffer: Buffer) { const request = new AddBreakpointsRequest(); - protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { - protocolUtils.loadCommonRequestFields(request, smartBuffer); + protocolUtil.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtil.loadCommonRequestFields(request, smartBuffer); const numBreakpoints = smartBuffer.readUInt32LE(); // num_breakpoints request.data.breakpoints = []; for (let i = 0; i < numBreakpoints; i++) { request.data.breakpoints.push({ - filePath: protocolUtils.readStringNT(smartBuffer), // file_path + filePath: protocolUtil.readStringNT(smartBuffer), // file_path lineNumber: smartBuffer.readUInt32LE(), // line_number ignoreCount: smartBuffer.readUInt32LE() // ignore_count }); @@ -51,7 +51,7 @@ export class AddBreakpointsRequest implements ProtocolRequest { smartBuffer.writeUInt32LE(breakpoint.ignoreCount); // ignore_count } - protocolUtils.insertCommonRequestFields(this, smartBuffer); + protocolUtil.insertCommonRequestFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts b/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts index 48a36260..0ca5bb8f 100644 --- a/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts @@ -1,7 +1,7 @@ import { SmartBuffer } from 'smart-buffer'; import { util } from '../../../util'; import { Command } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; export class AddConditionalBreakpointsRequest implements ProtocolRequest { @@ -16,7 +16,7 @@ export class AddConditionalBreakpointsRequest implements ProtocolRequest { }>; }) { const request = new AddConditionalBreakpointsRequest(); - protocolUtils.loadJson(request, data); + protocolUtil.loadJson(request, data); request.data.breakpoints ??= []; //default ignoreCount to 0 for consistency purposes for (const breakpoint of request.data.breakpoints) { @@ -29,8 +29,8 @@ export class AddConditionalBreakpointsRequest implements ProtocolRequest { public static fromBuffer(buffer: Buffer) { const request = new AddConditionalBreakpointsRequest(); - protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { - protocolUtils.loadCommonRequestFields(request, smartBuffer); + protocolUtil.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtil.loadCommonRequestFields(request, smartBuffer); smartBuffer.readUInt32LE(); // flags - Should always be passed as 0. Unused, reserved for future use. @@ -38,10 +38,10 @@ export class AddConditionalBreakpointsRequest implements ProtocolRequest { request.data.breakpoints = []; for (let i = 0; i < numBreakpoints; i++) { request.data.breakpoints.push({ - filePath: protocolUtils.readStringNT(smartBuffer), // file_path + filePath: protocolUtil.readStringNT(smartBuffer), // file_path lineNumber: smartBuffer.readUInt32LE(), // line_number ignoreCount: smartBuffer.readUInt32LE(), // ignore_count - conditionalExpression: protocolUtils.readStringNT(smartBuffer) // cond_expr + conditionalExpression: protocolUtil.readStringNT(smartBuffer) // cond_expr }); } }); @@ -61,7 +61,7 @@ export class AddConditionalBreakpointsRequest implements ProtocolRequest { smartBuffer.writeStringNT(breakpoint.conditionalExpression); // cond_expr } - protocolUtils.insertCommonRequestFields(this, smartBuffer); + protocolUtil.insertCommonRequestFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/debugProtocol/events/requests/ContinueRequest.ts b/src/debugProtocol/events/requests/ContinueRequest.ts index 68f91385..e6f2a773 100644 --- a/src/debugProtocol/events/requests/ContinueRequest.ts +++ b/src/debugProtocol/events/requests/ContinueRequest.ts @@ -1,27 +1,27 @@ import { SmartBuffer } from 'smart-buffer'; import { Command } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; export class ContinueRequest implements ProtocolRequest { public static fromJson(data: { requestId: number }) { const request = new ContinueRequest(); - protocolUtils.loadJson(request, data); + protocolUtil.loadJson(request, data); return request; } public static fromBuffer(buffer: Buffer) { const request = new ContinueRequest(); - protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { - protocolUtils.loadCommonRequestFields(request, smartBuffer); + protocolUtil.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtil.loadCommonRequestFields(request, smartBuffer); }); return request; } public toBuffer(): Buffer { const smartBuffer = new SmartBuffer(); - protocolUtils.insertCommonRequestFields(this, smartBuffer); + protocolUtil.insertCommonRequestFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/debugProtocol/events/requests/ExecuteRequest.ts b/src/debugProtocol/events/requests/ExecuteRequest.ts index b5685912..09342bb3 100644 --- a/src/debugProtocol/events/requests/ExecuteRequest.ts +++ b/src/debugProtocol/events/requests/ExecuteRequest.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; import { Command } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; export class ExecuteRequest implements ProtocolRequest { @@ -12,18 +12,18 @@ export class ExecuteRequest implements ProtocolRequest { sourceCode: string; }) { const request = new ExecuteRequest(); - protocolUtils.loadJson(request, data); + protocolUtil.loadJson(request, data); return request; } public static fromBuffer(buffer: Buffer) { const request = new ExecuteRequest(); - protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { - protocolUtils.loadCommonRequestFields(request, smartBuffer); + protocolUtil.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtil.loadCommonRequestFields(request, smartBuffer); request.data.threadIndex = smartBuffer.readUInt32LE(); // thread_index request.data.stackFrameIndex = smartBuffer.readUInt32LE(); // stack_frame_index - request.data.sourceCode = protocolUtils.readStringNT(smartBuffer); // source_code + request.data.sourceCode = protocolUtil.readStringNT(smartBuffer); // source_code }); return request; } @@ -35,7 +35,7 @@ export class ExecuteRequest implements ProtocolRequest { smartBuffer.writeUInt32LE(this.data.stackFrameIndex); // stack_frame_index smartBuffer.writeStringNT(this.data.sourceCode); // source_code - protocolUtils.insertCommonRequestFields(this, smartBuffer); + protocolUtil.insertCommonRequestFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/debugProtocol/events/requests/ExitChannelRequest.ts b/src/debugProtocol/events/requests/ExitChannelRequest.ts index 39535dde..60c81359 100644 --- a/src/debugProtocol/events/requests/ExitChannelRequest.ts +++ b/src/debugProtocol/events/requests/ExitChannelRequest.ts @@ -1,27 +1,27 @@ import { SmartBuffer } from 'smart-buffer'; import { Command } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; export class ExitChannelRequest implements ProtocolRequest { public static fromJson(data: { requestId: number }) { const request = new ExitChannelRequest(); - protocolUtils.loadJson(request, data); + protocolUtil.loadJson(request, data); return request; } public static fromBuffer(buffer: Buffer) { const request = new ExitChannelRequest(); - protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { - protocolUtils.loadCommonRequestFields(request, smartBuffer); + protocolUtil.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtil.loadCommonRequestFields(request, smartBuffer); }); return request; } public toBuffer(): Buffer { const smartBuffer = new SmartBuffer(); - protocolUtils.insertCommonRequestFields(this, smartBuffer); + protocolUtil.insertCommonRequestFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/debugProtocol/events/requests/HandshakeRequest.ts b/src/debugProtocol/events/requests/HandshakeRequest.ts index a53976ba..f80d2a41 100644 --- a/src/debugProtocol/events/requests/HandshakeRequest.ts +++ b/src/debugProtocol/events/requests/HandshakeRequest.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; import { util } from '../../../util'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; import type { Command } from '../../Constants'; @@ -16,14 +16,14 @@ export class HandshakeRequest implements ProtocolRequest { public static fromJson(data: { magic: string }) { const request = new HandshakeRequest(); - protocolUtils.loadJson(request, data); + protocolUtil.loadJson(request, data); return request; } public static fromBuffer(buffer: Buffer) { const request = new HandshakeRequest(); - protocolUtils.bufferLoaderHelper(request, buffer, 0, (smartBuffer) => { - request.data.magic = protocolUtils.readStringNT(smartBuffer); + protocolUtil.bufferLoaderHelper(request, buffer, 0, (smartBuffer) => { + request.data.magic = protocolUtil.readStringNT(smartBuffer); }); return request; } diff --git a/src/debugProtocol/events/requests/ListBreakpointsRequest.ts b/src/debugProtocol/events/requests/ListBreakpointsRequest.ts index 122c2758..cd55f96e 100644 --- a/src/debugProtocol/events/requests/ListBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/ListBreakpointsRequest.ts @@ -1,27 +1,27 @@ import { SmartBuffer } from 'smart-buffer'; import { Command } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; export class ListBreakpointsRequest implements ProtocolRequest { public static fromJson(data: { requestId: number }) { const request = new ListBreakpointsRequest(); - protocolUtils.loadJson(request, data); + protocolUtil.loadJson(request, data); return request; } public static fromBuffer(buffer: Buffer) { const request = new ListBreakpointsRequest(); - protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { - protocolUtils.loadCommonRequestFields(request, smartBuffer); + protocolUtil.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtil.loadCommonRequestFields(request, smartBuffer); }); return request; } public toBuffer(): Buffer { const smartBuffer = new SmartBuffer(); - protocolUtils.insertCommonRequestFields(this, smartBuffer); + protocolUtil.insertCommonRequestFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/debugProtocol/events/requests/RemoveBreakpointsRequest.ts b/src/debugProtocol/events/requests/RemoveBreakpointsRequest.ts index dfb027b2..57a6c53e 100644 --- a/src/debugProtocol/events/requests/RemoveBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/RemoveBreakpointsRequest.ts @@ -1,21 +1,21 @@ import { SmartBuffer } from 'smart-buffer'; import { Command } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; export class RemoveBreakpointsRequest implements ProtocolRequest { public static fromJson(data: { requestId: number; breakpointIds: number[] }) { const request = new RemoveBreakpointsRequest(); - protocolUtils.loadJson(request, data); + protocolUtil.loadJson(request, data); request.data.numBreakpoints = request.data.breakpointIds.length; return request; } public static fromBuffer(buffer: Buffer) { const command = new RemoveBreakpointsRequest(); - protocolUtils.bufferLoaderHelper(command, buffer, 16, (smartBuffer) => { - protocolUtils.loadCommonRequestFields(command, smartBuffer); + protocolUtil.bufferLoaderHelper(command, buffer, 16, (smartBuffer) => { + protocolUtil.loadCommonRequestFields(command, smartBuffer); command.data.numBreakpoints = smartBuffer.readUInt32LE(); command.data.breakpointIds = []; for (let i = 0; i < command.data.numBreakpoints; i++) { @@ -33,7 +33,7 @@ export class RemoveBreakpointsRequest implements ProtocolRequest { for (const breakpointId of this.data.breakpointIds ?? []) { smartBuffer.writeUInt32LE(breakpointId as number); // breakpoint_ids } - protocolUtils.insertCommonRequestFields(this, smartBuffer); + protocolUtil.insertCommonRequestFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/debugProtocol/events/requests/StackTraceRequest.ts b/src/debugProtocol/events/requests/StackTraceRequest.ts index 63ebb220..ae649c7b 100644 --- a/src/debugProtocol/events/requests/StackTraceRequest.ts +++ b/src/debugProtocol/events/requests/StackTraceRequest.ts @@ -1,20 +1,20 @@ import { SmartBuffer } from 'smart-buffer'; import { Command } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; export class StackTraceRequest implements ProtocolRequest { public static fromJson(data: { requestId: number; threadIndex: number }) { const request = new StackTraceRequest(); - protocolUtils.loadJson(request, data); + protocolUtil.loadJson(request, data); return request; } public static fromBuffer(buffer: Buffer) { const request = new StackTraceRequest(); - protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { - protocolUtils.loadCommonRequestFields(request, smartBuffer); + protocolUtil.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtil.loadCommonRequestFields(request, smartBuffer); request.data.threadIndex = smartBuffer.readUInt32LE(); //thread_index }); return request; @@ -25,7 +25,7 @@ export class StackTraceRequest implements ProtocolRequest { smartBuffer.writeUInt32LE(this.data.threadIndex); //thread_index - protocolUtils.insertCommonRequestFields(this, smartBuffer); + protocolUtil.insertCommonRequestFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/debugProtocol/events/requests/StepRequest.ts b/src/debugProtocol/events/requests/StepRequest.ts index 60c853bb..47bfd535 100644 --- a/src/debugProtocol/events/requests/StepRequest.ts +++ b/src/debugProtocol/events/requests/StepRequest.ts @@ -1,21 +1,21 @@ import { SmartBuffer } from 'smart-buffer'; import type { StepType } from '../../Constants'; import { Command, StepTypeCode } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; export class StepRequest implements ProtocolRequest { public static fromJson(data: { requestId: number; threadIndex: number; stepType: StepType }) { const request = new StepRequest(); - protocolUtils.loadJson(request, data); + protocolUtil.loadJson(request, data); return request; } public static fromBuffer(buffer: Buffer) { const request = new StepRequest(); - protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { - protocolUtils.loadCommonRequestFields(request, smartBuffer); + protocolUtil.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtil.loadCommonRequestFields(request, smartBuffer); request.data.threadIndex = smartBuffer.readUInt32LE(); // thread_index request.data.stepType = StepTypeCode[smartBuffer.readUInt8()] as StepType; // step_type }); @@ -28,7 +28,7 @@ export class StepRequest implements ProtocolRequest { smartBuffer.writeUInt32LE(this.data.threadIndex); //thread_index smartBuffer.writeUInt8(StepTypeCode[this.data.stepType]); //step_type - protocolUtils.insertCommonRequestFields(this, smartBuffer); + protocolUtil.insertCommonRequestFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/debugProtocol/events/requests/StopRequest.ts b/src/debugProtocol/events/requests/StopRequest.ts index ed8ca409..d99973e2 100644 --- a/src/debugProtocol/events/requests/StopRequest.ts +++ b/src/debugProtocol/events/requests/StopRequest.ts @@ -1,27 +1,27 @@ import { SmartBuffer } from 'smart-buffer'; import { Command } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; export class StopRequest implements ProtocolRequest { public static fromJson(data: { requestId: number }) { const request = new StopRequest(); - protocolUtils.loadJson(request, data); + protocolUtil.loadJson(request, data); return request; } public static fromBuffer(buffer: Buffer) { const request = new StopRequest(); - protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { - protocolUtils.loadCommonRequestFields(request, smartBuffer); + protocolUtil.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtil.loadCommonRequestFields(request, smartBuffer); }); return request; } public toBuffer(): Buffer { const smartBuffer = new SmartBuffer(); - protocolUtils.insertCommonRequestFields(this, smartBuffer); + protocolUtil.insertCommonRequestFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/debugProtocol/events/requests/ThreadsRequest.ts b/src/debugProtocol/events/requests/ThreadsRequest.ts index f4e76117..deb41928 100644 --- a/src/debugProtocol/events/requests/ThreadsRequest.ts +++ b/src/debugProtocol/events/requests/ThreadsRequest.ts @@ -1,27 +1,27 @@ import { SmartBuffer } from 'smart-buffer'; import { Command } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; export class ThreadsRequest implements ProtocolRequest { public static fromJson(data: { requestId: number }) { const request = new ThreadsRequest(); - protocolUtils.loadJson(request, data); + protocolUtil.loadJson(request, data); return request; } public static fromBuffer(buffer: Buffer) { const request = new ThreadsRequest(); - protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { - protocolUtils.loadCommonRequestFields(request, smartBuffer); + protocolUtil.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtil.loadCommonRequestFields(request, smartBuffer); }); return request; } public toBuffer(): Buffer { const smartBuffer = new SmartBuffer(); - protocolUtils.insertCommonRequestFields(this, smartBuffer); + protocolUtil.insertCommonRequestFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/debugProtocol/events/requests/VariablesRequest.ts b/src/debugProtocol/events/requests/VariablesRequest.ts index 63e7e857..e7b81b92 100644 --- a/src/debugProtocol/events/requests/VariablesRequest.ts +++ b/src/debugProtocol/events/requests/VariablesRequest.ts @@ -1,7 +1,7 @@ /* eslint-disable no-bitwise */ import { SmartBuffer } from 'smart-buffer'; import { Command } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; import type { ProtocolRequest } from '../ProtocolEvent'; export class VariablesRequest implements ProtocolRequest { @@ -18,7 +18,7 @@ export class VariablesRequest implements ProtocolRequest { }>; }) { const request = new VariablesRequest(); - protocolUtils.loadJson(request, data); + protocolUtil.loadJson(request, data); request.data.variablePathEntries ??= []; // all variables will be case sensitive if the flag is disabled for (const entry of request.data.variablePathEntries) { @@ -34,8 +34,8 @@ export class VariablesRequest implements ProtocolRequest { public static fromBuffer(buffer: Buffer) { const request = new VariablesRequest(); - protocolUtils.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { - protocolUtils.loadCommonRequestFields(request, smartBuffer); + protocolUtil.bufferLoaderHelper(request, buffer, 12, (smartBuffer) => { + protocolUtil.loadCommonRequestFields(request, smartBuffer); const variableRequestFlags = smartBuffer.readUInt8(); // variable_request_flags @@ -48,7 +48,7 @@ export class VariablesRequest implements ProtocolRequest { if (variablePathLength > 0) { for (let i = 0; i < variablePathLength; i++) { request.data.variablePathEntries.push({ - name: protocolUtils.readStringNT(smartBuffer), // variable_path_entries - optional + name: protocolUtil.readStringNT(smartBuffer), // variable_path_entries - optional //by default, all variable lookups are case SENSITIVE forceCaseInsensitive: false }); @@ -88,7 +88,7 @@ export class VariablesRequest implements ProtocolRequest { } } - protocolUtils.insertCommonRequestFields(this, smartBuffer); + protocolUtil.insertCommonRequestFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/debugProtocol/events/responses/ExecuteV3Response.ts b/src/debugProtocol/events/responses/ExecuteV3Response.ts index 4cd7360f..e1383278 100644 --- a/src/debugProtocol/events/responses/ExecuteV3Response.ts +++ b/src/debugProtocol/events/responses/ExecuteV3Response.ts @@ -1,7 +1,7 @@ import { SmartBuffer } from 'smart-buffer'; import type { StopReasonCode } from '../../Constants'; import { ErrorCode } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; export class ExecuteV3Response { public static fromJson(data: { @@ -13,7 +13,7 @@ export class ExecuteV3Response { otherErrors: string[]; }) { const response = new ExecuteV3Response(); - protocolUtils.loadJson(response, data); + protocolUtil.loadJson(response, data); response.data.compileErrors ??= []; response.data.runtimeErrors ??= []; response.data.otherErrors ??= []; @@ -22,8 +22,8 @@ export class ExecuteV3Response { public static fromBuffer(buffer: Buffer) { const response = new ExecuteV3Response(); - protocolUtils.bufferLoaderHelper(response, buffer, 8, (smartBuffer: SmartBuffer) => { - protocolUtils.loadCommonResponseFields(response, smartBuffer); + protocolUtil.bufferLoaderHelper(response, buffer, 8, (smartBuffer: SmartBuffer) => { + protocolUtil.loadCommonResponseFields(response, smartBuffer); response.data.executeSuccess = smartBuffer.readUInt8() !== 0; //execute_success response.data.runtimeStopCode = smartBuffer.readUInt8(); //runtime_stop_code @@ -32,7 +32,7 @@ export class ExecuteV3Response { response.data.compileErrors = []; for (let i = 0; i < compileErrorCount; i++) { response.data.compileErrors.push( - protocolUtils.readStringNT(smartBuffer) + protocolUtil.readStringNT(smartBuffer) ); } @@ -40,7 +40,7 @@ export class ExecuteV3Response { response.data.runtimeErrors = []; for (let i = 0; i < runtimeErrorCount; i++) { response.data.runtimeErrors.push( - protocolUtils.readStringNT(smartBuffer) + protocolUtil.readStringNT(smartBuffer) ); } @@ -48,7 +48,7 @@ export class ExecuteV3Response { response.data.otherErrors = []; for (let i = 0; i < otherErrorCount; i++) { response.data.otherErrors.push( - protocolUtils.readStringNT(smartBuffer) + protocolUtil.readStringNT(smartBuffer) ); } }); @@ -76,7 +76,7 @@ export class ExecuteV3Response { smartBuffer.writeStringNT(error); } - protocolUtils.insertCommonResponseFields(this, smartBuffer); + protocolUtil.insertCommonResponseFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/debugProtocol/events/responses/GenericResponse.ts b/src/debugProtocol/events/responses/GenericResponse.ts index 2cd2bbf5..66b59e1e 100644 --- a/src/debugProtocol/events/responses/GenericResponse.ts +++ b/src/debugProtocol/events/responses/GenericResponse.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; import type { ErrorCode } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; export class GenericResponse { public static fromJson(data: { @@ -8,13 +8,13 @@ export class GenericResponse { errorCode: ErrorCode; }) { const response = new GenericResponse(); - protocolUtils.loadJson(response, data); + protocolUtil.loadJson(response, data); return response; } public static fromBuffer(buffer: Buffer) { const response = new GenericResponse(); - protocolUtils.bufferLoaderHelper(response, buffer, 8, (smartBuffer: SmartBuffer) => { + protocolUtil.bufferLoaderHelper(response, buffer, 8, (smartBuffer: SmartBuffer) => { response.data.packetLength = 8; response.data.requestId = smartBuffer.readUInt32LE(); // request_id response.data.errorCode = smartBuffer.readUInt32LE(); // error_code diff --git a/src/debugProtocol/events/responses/GenericV3Response.ts b/src/debugProtocol/events/responses/GenericV3Response.ts index e3df8bf7..3b5891a7 100644 --- a/src/debugProtocol/events/responses/GenericV3Response.ts +++ b/src/debugProtocol/events/responses/GenericV3Response.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; import type { ErrorCode } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; export class GenericV3Response { public static fromJson(data: { @@ -8,13 +8,13 @@ export class GenericV3Response { errorCode: ErrorCode; }) { const response = new GenericV3Response(); - protocolUtils.loadJson(response, data); + protocolUtil.loadJson(response, data); return response; } public static fromBuffer(buffer: Buffer) { const response = new GenericV3Response(); - protocolUtils.bufferLoaderHelper(response, buffer, 12, (smartBuffer: SmartBuffer) => { + protocolUtil.bufferLoaderHelper(response, buffer, 12, (smartBuffer: SmartBuffer) => { response.data.packetLength = smartBuffer.readUInt32LE(); // packet_length response.data.requestId = smartBuffer.readUInt32LE(); // request_id response.data.errorCode = smartBuffer.readUInt32LE(); // error_code @@ -28,7 +28,7 @@ export class GenericV3Response { public toBuffer() { const smartBuffer = new SmartBuffer(); - protocolUtils.insertCommonResponseFields(this, smartBuffer); + protocolUtil.insertCommonResponseFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/debugProtocol/events/responses/HandshakeResponse.ts b/src/debugProtocol/events/responses/HandshakeResponse.ts index 0c6a69c6..4a82cfaf 100644 --- a/src/debugProtocol/events/responses/HandshakeResponse.ts +++ b/src/debugProtocol/events/responses/HandshakeResponse.ts @@ -2,7 +2,7 @@ import { SmartBuffer } from 'smart-buffer'; import * as semver from 'semver'; import { util } from '../../../util'; import type { ProtocolEvent, ProtocolResponse } from '../ProtocolEvent'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; import { ErrorCode } from '../../Constants'; import { HandshakeRequest } from '../requests/HandshakeRequest'; @@ -12,7 +12,7 @@ export class HandshakeResponse implements ProtocolResponse { protocolVersion: string; }) { const response = new HandshakeResponse(); - protocolUtils.loadJson(response, data); + protocolUtil.loadJson(response, data); // We only support version prior to v3 with this handshake if (!semver.satisfies(response.data.protocolVersion, '<3.0.0')) { response.success = false; @@ -22,8 +22,8 @@ export class HandshakeResponse implements ProtocolResponse { public static fromBuffer(buffer: Buffer) { const response = new HandshakeResponse(); - protocolUtils.bufferLoaderHelper(response, buffer, 20, (smartBuffer: SmartBuffer) => { - response.data.magic = protocolUtils.readStringNT(smartBuffer); // magic_number + protocolUtil.bufferLoaderHelper(response, buffer, 20, (smartBuffer: SmartBuffer) => { + response.data.magic = protocolUtil.readStringNT(smartBuffer); // magic_number response.data.protocolVersion = [ smartBuffer.readInt32LE(), // protocol_major_version diff --git a/src/debugProtocol/events/responses/HandshakeV3Response.ts b/src/debugProtocol/events/responses/HandshakeV3Response.ts index aae9f43d..01eca514 100644 --- a/src/debugProtocol/events/responses/HandshakeV3Response.ts +++ b/src/debugProtocol/events/responses/HandshakeV3Response.ts @@ -1,7 +1,7 @@ import { SmartBuffer } from 'smart-buffer'; import * as semver from 'semver'; import type { ProtocolResponse } from '../ProtocolEvent'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; import { ErrorCode } from '../../Constants'; import { HandshakeRequest } from '../requests/HandshakeRequest'; @@ -13,7 +13,7 @@ export class HandshakeV3Response implements ProtocolResponse { revisionTimestamp: Date; }) { const response = new HandshakeV3Response(); - protocolUtils.loadJson(response, data); + protocolUtil.loadJson(response, data); // We only support v3 or above with this handshake if (semver.satisfies(response.data.protocolVersion, '<3.0.0')) { response.success = false; @@ -23,8 +23,8 @@ export class HandshakeV3Response implements ProtocolResponse { public static fromBuffer(buffer: Buffer) { const response = new HandshakeV3Response(); - protocolUtils.bufferLoaderHelper(response, buffer, 20, (smartBuffer: SmartBuffer) => { - response.data.magic = protocolUtils.readStringNT(smartBuffer); // magic_number + protocolUtil.bufferLoaderHelper(response, buffer, 20, (smartBuffer: SmartBuffer) => { + response.data.magic = protocolUtil.readStringNT(smartBuffer); // magic_number response.data.protocolVersion = [ smartBuffer.readUInt32LE(), // protocol_major_version diff --git a/src/debugProtocol/events/responses/ListBreakpointsResponse.ts b/src/debugProtocol/events/responses/ListBreakpointsResponse.ts index 140d839b..f07772c3 100644 --- a/src/debugProtocol/events/responses/ListBreakpointsResponse.ts +++ b/src/debugProtocol/events/responses/ListBreakpointsResponse.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; import { ErrorCode } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; export class ListBreakpointsResponse { @@ -9,15 +9,15 @@ export class ListBreakpointsResponse { breakpoints: BreakpointInfo[]; }) { const response = new ListBreakpointsResponse(); - protocolUtils.loadJson(response, data); + protocolUtil.loadJson(response, data); response.data.breakpoints ??= []; return response; } public static fromBuffer(buffer: Buffer) { const response = new ListBreakpointsResponse(); - protocolUtils.bufferLoaderHelper(response, buffer, 12, (smartBuffer: SmartBuffer) => { - protocolUtils.loadCommonResponseFields(response, smartBuffer); + protocolUtil.bufferLoaderHelper(response, buffer, 12, (smartBuffer: SmartBuffer) => { + protocolUtil.loadCommonResponseFields(response, smartBuffer); const numBreakpoints = smartBuffer.readUInt32LE(); // num_breakpoints response.data.breakpoints = []; @@ -53,7 +53,7 @@ export class ListBreakpointsResponse { smartBuffer.writeUInt32LE(breakpoint.ignoreCount); // ignore_count } } - protocolUtils.insertCommonResponseFields(this, smartBuffer); + protocolUtil.insertCommonResponseFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/debugProtocol/events/responses/StackTraceResponse.ts b/src/debugProtocol/events/responses/StackTraceResponse.ts index d1cbd2bd..6a705b6d 100644 --- a/src/debugProtocol/events/responses/StackTraceResponse.ts +++ b/src/debugProtocol/events/responses/StackTraceResponse.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; import { ErrorCode } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; import type { StackEntry } from './StackTraceV3Response'; export class StackTraceResponse { @@ -10,14 +10,14 @@ export class StackTraceResponse { entries: StackEntry[]; }) { const response = new StackTraceResponse(); - protocolUtils.loadJson(response, data); + protocolUtil.loadJson(response, data); response.data.entries ??= []; return response; } public static fromBuffer(buffer: Buffer) { const response = new StackTraceResponse(); - protocolUtils.bufferLoaderHelper(response, buffer, 12, (smartBuffer: SmartBuffer) => { + protocolUtil.bufferLoaderHelper(response, buffer, 12, (smartBuffer: SmartBuffer) => { response.data.requestId = smartBuffer.readUInt32LE(); // request_id response.data.errorCode = smartBuffer.readUInt32LE(); // error_code @@ -30,8 +30,8 @@ export class StackTraceResponse { const entry = {} as StackEntry; entry.lineNumber = smartBuffer.readUInt32LE(); // NOTE: this is documented as being function name then file name but it is being returned by the device backwards. - entry.filePath = protocolUtils.readStringNT(smartBuffer); - entry.functionName = protocolUtils.readStringNT(smartBuffer); + entry.filePath = protocolUtil.readStringNT(smartBuffer); + entry.functionName = protocolUtil.readStringNT(smartBuffer); // TODO do we need this anymore? // let fileExtension = path.extname(this.fileName).toLowerCase(); diff --git a/src/debugProtocol/events/responses/StackTraceV3Response.ts b/src/debugProtocol/events/responses/StackTraceV3Response.ts index bb97e8e5..7a6bf7a7 100644 --- a/src/debugProtocol/events/responses/StackTraceV3Response.ts +++ b/src/debugProtocol/events/responses/StackTraceV3Response.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; import { ErrorCode } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; export class StackTraceV3Response { @@ -9,15 +9,15 @@ export class StackTraceV3Response { entries: StackEntry[]; }) { const response = new StackTraceV3Response(); - protocolUtils.loadJson(response, data); + protocolUtil.loadJson(response, data); response.data.entries ??= []; return response; } public static fromBuffer(buffer: Buffer) { const response = new StackTraceV3Response(); - protocolUtils.bufferLoaderHelper(response, buffer, 16, (smartBuffer: SmartBuffer) => { - protocolUtils.loadCommonResponseFields(response, smartBuffer); + protocolUtil.bufferLoaderHelper(response, buffer, 16, (smartBuffer: SmartBuffer) => { + protocolUtil.loadCommonResponseFields(response, smartBuffer); const stackSize = smartBuffer.readUInt32LE(); // stack_size @@ -27,8 +27,8 @@ export class StackTraceV3Response { for (let i = 0; i < stackSize; i++) { const entry = {} as StackEntry; entry.lineNumber = smartBuffer.readUInt32LE(); - entry.functionName = protocolUtils.readStringNT(smartBuffer); - entry.filePath = protocolUtils.readStringNT(smartBuffer); + entry.functionName = protocolUtil.readStringNT(smartBuffer); + entry.filePath = protocolUtil.readStringNT(smartBuffer); // TODO do we need this anymore? // let fileExtension = path.extname(this.fileName).toLowerCase(); @@ -48,7 +48,7 @@ export class StackTraceV3Response { smartBuffer.writeStringNT(entry.functionName); // function_name smartBuffer.writeStringNT(entry.filePath); // file_path } - protocolUtils.insertCommonResponseFields(this, smartBuffer); + protocolUtil.insertCommonResponseFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/debugProtocol/events/responses/ThreadsResponse.ts b/src/debugProtocol/events/responses/ThreadsResponse.ts index 956eb173..0e8977aa 100644 --- a/src/debugProtocol/events/responses/ThreadsResponse.ts +++ b/src/debugProtocol/events/responses/ThreadsResponse.ts @@ -2,7 +2,7 @@ import { SmartBuffer } from 'smart-buffer'; import type { StopReason } from '../../Constants'; import { ErrorCode, StopReasonCode } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; export class ThreadsResponse { public static fromJson(data: { @@ -10,15 +10,15 @@ export class ThreadsResponse { threads: ThreadInfo[]; }) { const response = new ThreadsResponse(); - protocolUtils.loadJson(response, data); + protocolUtil.loadJson(response, data); response.data.threads ??= []; return response; } public static fromBuffer(buffer: Buffer) { const response = new ThreadsResponse(); - protocolUtils.bufferLoaderHelper(response, buffer, 16, (smartBuffer: SmartBuffer) => { - protocolUtils.loadCommonResponseFields(response, smartBuffer); + protocolUtil.bufferLoaderHelper(response, buffer, 16, (smartBuffer: SmartBuffer) => { + protocolUtil.loadCommonResponseFields(response, smartBuffer); const threadsCount = smartBuffer.readUInt32LE(); // threads_count @@ -30,11 +30,11 @@ export class ThreadsResponse { const flags = smartBuffer.readUInt8(); thread.isPrimary = (flags & ThreadInfoFlags.isPrimary) > 0; thread.stopReason = StopReasonCode[smartBuffer.readUInt32LE()] as StopReason; // stop_reason - thread.stopReasonDetail = protocolUtils.readStringNT(smartBuffer); // stop_reason_detail + thread.stopReasonDetail = protocolUtil.readStringNT(smartBuffer); // stop_reason_detail thread.lineNumber = smartBuffer.readUInt32LE(); // line_number - thread.functionName = protocolUtils.readStringNT(smartBuffer); // function_name - thread.filePath = protocolUtils.readStringNT(smartBuffer); // file_path - thread.codeSnippet = protocolUtils.readStringNT(smartBuffer); // code_snippet + thread.functionName = protocolUtil.readStringNT(smartBuffer); // function_name + thread.filePath = protocolUtil.readStringNT(smartBuffer); // file_path + thread.codeSnippet = protocolUtil.readStringNT(smartBuffer); // code_snippet response.data.threads.push(thread); } @@ -57,7 +57,7 @@ export class ThreadsResponse { smartBuffer.writeStringNT(thread.filePath); // file_path smartBuffer.writeStringNT(thread.codeSnippet); // code_snippet } - protocolUtils.insertCommonResponseFields(this, smartBuffer); + protocolUtil.insertCommonResponseFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/debugProtocol/events/responses/VariablesResponse.ts b/src/debugProtocol/events/responses/VariablesResponse.ts index 639214ce..a4838491 100644 --- a/src/debugProtocol/events/responses/VariablesResponse.ts +++ b/src/debugProtocol/events/responses/VariablesResponse.ts @@ -2,7 +2,7 @@ import { SmartBuffer } from 'smart-buffer'; import { util } from '../../../util'; import { ErrorCode } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; export class VariablesResponse { @@ -11,7 +11,7 @@ export class VariablesResponse { variables: Variable[]; }) { const response = new VariablesResponse(); - protocolUtils.loadJson(response, data); + protocolUtil.loadJson(response, data); response.data.variables ??= []; //validate that any object marked as `isContainer` either has an array of children or has an element count for (const variable of response.flattenVariables(response.data.variables)) { @@ -34,8 +34,8 @@ export class VariablesResponse { public static fromBuffer(buffer: Buffer) { const response = new VariablesResponse(); - protocolUtils.bufferLoaderHelper(response, buffer, 12, (smartBuffer: SmartBuffer) => { - protocolUtils.loadCommonResponseFields(response, smartBuffer); + protocolUtil.bufferLoaderHelper(response, buffer, 12, (smartBuffer: SmartBuffer) => { + protocolUtil.loadCommonResponseFields(response, smartBuffer); const numVariables = smartBuffer.readUInt32LE(); // num_variables @@ -84,7 +84,7 @@ export class VariablesResponse { if (isNameHere) { // we have a name. Pull it out of the buffer. - variable.name = protocolUtils.readStringNT(smartBuffer); //name + variable.name = protocolUtil.readStringNT(smartBuffer); //name } if (isRefCounted) { @@ -114,11 +114,11 @@ export class VariablesResponse { case VariableType.String: case VariableType.Subroutine: case VariableType.Function: - return protocolUtils.readStringNT(smartBuffer); + return protocolUtil.readStringNT(smartBuffer); case VariableType.SubtypedObject: let names = []; for (let i = 0; i < 2; i++) { - names.push(protocolUtils.readStringNT(smartBuffer)); + names.push(protocolUtil.readStringNT(smartBuffer)); } if (names.length !== 2) { @@ -173,7 +173,7 @@ export class VariablesResponse { for (const variable of variables) { this.writeVariable(variable, smartBuffer); } - protocolUtils.insertCommonResponseFields(this, smartBuffer); + protocolUtil.insertCommonResponseFields(this, smartBuffer); return smartBuffer.toBuffer(); } @@ -232,7 +232,7 @@ export class VariablesResponse { case VariableType.SubtypedObject: let names = []; for (let i = 0; i < 2; i++) { - names.push(protocolUtils.readStringNT(smartBuffer)); + names.push(protocolUtil.readStringNT(smartBuffer)); } if (names.length !== 2) { diff --git a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts index 09d28204..ddedde3d 100644 --- a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts +++ b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts @@ -1,7 +1,7 @@ import { SmartBuffer } from 'smart-buffer'; import type { StopReason } from '../../Constants'; import { ErrorCode, StopReasonCode, UpdateType } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; import type { ProtocolUpdate } from '../ProtocolEvent'; /** @@ -17,18 +17,18 @@ export class AllThreadsStoppedUpdate implements ProtocolUpdate { stopReasonDetail: string; }) { const update = new AllThreadsStoppedUpdate(); - protocolUtils.loadJson(update, data); + protocolUtil.loadJson(update, data); return update; } public static fromBuffer(buffer: Buffer) { const update = new AllThreadsStoppedUpdate(); - protocolUtils.bufferLoaderHelper(update, buffer, 16, (smartBuffer) => { - protocolUtils.loadCommonUpdateFields(update, smartBuffer, update.data.updateType); + protocolUtil.bufferLoaderHelper(update, buffer, 16, (smartBuffer) => { + protocolUtil.loadCommonUpdateFields(update, smartBuffer, update.data.updateType); update.data.threadIndex = smartBuffer.readInt32LE(); update.data.stopReason = StopReasonCode[smartBuffer.readUInt8()] as StopReason; - update.data.stopReasonDetail = protocolUtils.readStringNT(smartBuffer); + update.data.stopReasonDetail = protocolUtil.readStringNT(smartBuffer); }); return update; } @@ -40,7 +40,7 @@ export class AllThreadsStoppedUpdate implements ProtocolUpdate { smartBuffer.writeUInt8(StopReasonCode[this.data.stopReason]); // stop_reason smartBuffer.writeStringNT(this.data.stopReasonDetail); //stop_reason_detail - protocolUtils.insertCommonUpdateFields(this, smartBuffer); + protocolUtil.insertCommonUpdateFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts b/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts index 43fe0ab6..91365490 100644 --- a/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts +++ b/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; import { ErrorCode, UpdateType } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; /** * Data sent as the data segment of message type: BREAKPOINT_ERROR @@ -26,7 +26,7 @@ export class BreakpointErrorUpdate { otherErrors: string[]; }) { const update = new BreakpointErrorUpdate(); - protocolUtils.loadJson(update, data); + protocolUtil.loadJson(update, data); update.data.compileErrors ??= []; update.data.runtimeErrors ??= []; update.data.otherErrors ??= []; @@ -35,8 +35,8 @@ export class BreakpointErrorUpdate { public static fromBuffer(buffer: Buffer) { const update = new BreakpointErrorUpdate(); - protocolUtils.bufferLoaderHelper(update, buffer, 20, (smartBuffer) => { - protocolUtils.loadCommonUpdateFields(update, smartBuffer, update.data.updateType); + protocolUtil.bufferLoaderHelper(update, buffer, 20, (smartBuffer) => { + protocolUtil.loadCommonUpdateFields(update, smartBuffer, update.data.updateType); smartBuffer.readUInt32LE(); // flags - always 0, reserved for future use update.data.breakpointId = smartBuffer.readUInt32LE(); // breakpoint_id @@ -45,7 +45,7 @@ export class BreakpointErrorUpdate { update.data.compileErrors = []; for (let i = 0; i < compileErrorCount; i++) { update.data.compileErrors.push( - protocolUtils.readStringNT(smartBuffer) + protocolUtil.readStringNT(smartBuffer) ); } @@ -53,7 +53,7 @@ export class BreakpointErrorUpdate { update.data.runtimeErrors = []; for (let i = 0; i < runtimeErrorCount; i++) { update.data.runtimeErrors.push( - protocolUtils.readStringNT(smartBuffer) + protocolUtil.readStringNT(smartBuffer) ); } @@ -61,7 +61,7 @@ export class BreakpointErrorUpdate { update.data.otherErrors = []; for (let i = 0; i < otherErrorCount; i++) { update.data.otherErrors.push( - protocolUtils.readStringNT(smartBuffer) + protocolUtil.readStringNT(smartBuffer) ); } }); @@ -89,7 +89,7 @@ export class BreakpointErrorUpdate { smartBuffer.writeStringNT(error); } - protocolUtils.insertCommonUpdateFields(this, smartBuffer); + protocolUtil.insertCommonUpdateFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/debugProtocol/events/updates/CompileErrorUpdate.ts b/src/debugProtocol/events/updates/CompileErrorUpdate.ts index d9f20f98..8d980802 100644 --- a/src/debugProtocol/events/updates/CompileErrorUpdate.ts +++ b/src/debugProtocol/events/updates/CompileErrorUpdate.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; import { ErrorCode, UpdateType } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; /** * A COMPILE_ERROR is sent if a compilation error occurs. In this case, the update_type field in a DebuggerUpdate message is set to @@ -25,19 +25,19 @@ export class CompileErrorUpdate { libraryName: string; }) { const update = new CompileErrorUpdate(); - protocolUtils.loadJson(update, data); + protocolUtil.loadJson(update, data); return update; } public static fromBuffer(buffer: Buffer) { const update = new CompileErrorUpdate(); - protocolUtils.bufferLoaderHelper(update, buffer, 20, (smartBuffer) => { - protocolUtils.loadCommonUpdateFields(update, smartBuffer, update.data.updateType); + protocolUtil.bufferLoaderHelper(update, buffer, 20, (smartBuffer) => { + protocolUtil.loadCommonUpdateFields(update, smartBuffer, update.data.updateType); - update.data.errorMessage = protocolUtils.readStringNT(smartBuffer); // error_string - update.data.filePath = protocolUtils.readStringNT(smartBuffer); // file_spec + update.data.errorMessage = protocolUtil.readStringNT(smartBuffer); // error_string + update.data.filePath = protocolUtil.readStringNT(smartBuffer); // file_spec update.data.lineNumber = smartBuffer.readUInt32LE(); // line_number - update.data.libraryName = protocolUtils.readStringNT(smartBuffer); // library_name + update.data.libraryName = protocolUtil.readStringNT(smartBuffer); // library_name }); return update; } @@ -50,7 +50,7 @@ export class CompileErrorUpdate { smartBuffer.writeUInt32LE(this.data.lineNumber); // line_number smartBuffer.writeStringNT(this.data.libraryName); // library_name - protocolUtils.insertCommonUpdateFields(this, smartBuffer); + protocolUtil.insertCommonUpdateFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/debugProtocol/events/updates/IOPortOpenedUpdate.ts b/src/debugProtocol/events/updates/IOPortOpenedUpdate.ts index 6abf82ab..a229f16b 100644 --- a/src/debugProtocol/events/updates/IOPortOpenedUpdate.ts +++ b/src/debugProtocol/events/updates/IOPortOpenedUpdate.ts @@ -1,6 +1,6 @@ import { SmartBuffer } from 'smart-buffer'; import { ErrorCode, UpdateType } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; export class IOPortOpenedUpdate { @@ -8,14 +8,14 @@ export class IOPortOpenedUpdate { port: number; }) { const update = new IOPortOpenedUpdate(); - protocolUtils.loadJson(update, data); + protocolUtil.loadJson(update, data); return update; } public static fromBuffer(buffer: Buffer) { const update = new IOPortOpenedUpdate(); - protocolUtils.bufferLoaderHelper(update, buffer, 16, (smartBuffer) => { - protocolUtils.loadCommonUpdateFields(update, smartBuffer, update.data.updateType); + protocolUtil.bufferLoaderHelper(update, buffer, 16, (smartBuffer) => { + protocolUtil.loadCommonUpdateFields(update, smartBuffer, update.data.updateType); update.data.port = smartBuffer.readInt32LE(); }); @@ -27,7 +27,7 @@ export class IOPortOpenedUpdate { smartBuffer.writeInt32LE(this.data.port); // primary_thread_index - protocolUtils.insertCommonUpdateFields(this, smartBuffer); + protocolUtil.insertCommonUpdateFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts b/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts index 80b194c1..9ee84b8a 100644 --- a/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts +++ b/src/debugProtocol/events/updates/ThreadAttachedUpdate.ts @@ -1,7 +1,7 @@ import { SmartBuffer } from 'smart-buffer'; import type { StopReason } from '../../Constants'; import { ErrorCode, StopReasonCode, UpdateType } from '../../Constants'; -import { protocolUtils } from '../../ProtocolUtil'; +import { protocolUtil } from '../../ProtocolUtil'; export class ThreadAttachedUpdate { @@ -11,17 +11,17 @@ export class ThreadAttachedUpdate { stopReasonDetail: string; }) { const update = new ThreadAttachedUpdate(); - protocolUtils.loadJson(update, data); + protocolUtil.loadJson(update, data); return update; } public static fromBuffer(buffer: Buffer) { const update = new ThreadAttachedUpdate(); - protocolUtils.bufferLoaderHelper(update, buffer, 12, (smartBuffer) => { - protocolUtils.loadCommonUpdateFields(update, smartBuffer, update.data.updateType); + protocolUtil.bufferLoaderHelper(update, buffer, 12, (smartBuffer) => { + protocolUtil.loadCommonUpdateFields(update, smartBuffer, update.data.updateType); update.data.threadIndex = smartBuffer.readInt32LE(); update.data.stopReason = StopReasonCode[smartBuffer.readUInt8()] as StopReason; - update.data.stopReasonDetail = protocolUtils.readStringNT(smartBuffer); + update.data.stopReasonDetail = protocolUtil.readStringNT(smartBuffer); }); return update; } @@ -33,7 +33,7 @@ export class ThreadAttachedUpdate { smartBuffer.writeUInt8(StopReasonCode[this.data.stopReason]); smartBuffer.writeStringNT(this.data.stopReasonDetail); - protocolUtils.insertCommonUpdateFields(this, smartBuffer); + protocolUtil.insertCommonUpdateFields(this, smartBuffer); return smartBuffer.toBuffer(); } diff --git a/src/testHelpers.spec.ts b/src/testHelpers.spec.ts index 9519fcf8..cba1e73d 100644 --- a/src/testHelpers.spec.ts +++ b/src/testHelpers.spec.ts @@ -75,3 +75,17 @@ export function getRandomBuffer(byteCount: number) { return result.toBuffer(); } +export function expectThrows(callback: () => any, expectedMessage = undefined, failedTestMessage = 'Expected to throw but did not') { + let wasExceptionThrown = false; + try { + callback(); + } catch (e) { + wasExceptionThrown = true; + if (expectedMessage) { + expect(e.message).to.eql(expectedMessage); + } + } + if (wasExceptionThrown === false) { + throw new Error(failedTestMessage); + } +} From c25a6816639c3ef8c84c0585ab52e814705fd001 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 11 Jan 2023 09:10:45 -0500 Subject: [PATCH 20/74] test enable/disable flags --- .../client/DebugProtocolClient.spec.ts | 69 +++++++++++++++++++ .../client/DebugProtocolClient.ts | 4 +- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index 1bf67f86..c1f63811 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -52,6 +52,75 @@ describe('DebugProtocolClient', () => { sinon.restore(); }); + it('knows when to enable the thread hopping workaround', () => { + //only supported below version 3.1.0 + client.protocolVersion = '1.0.0'; + expect( + client['enableThreadHoppingWorkaround'] + ).to.be.true; + + client.protocolVersion = '3.0.0'; + expect( + client['enableThreadHoppingWorkaround'] + ).to.be.true; + + client.protocolVersion = '3.1.0'; + expect( + client['enableThreadHoppingWorkaround'] + ).to.be.false; + + client.protocolVersion = '4.0.0'; + expect( + client['enableThreadHoppingWorkaround'] + ).to.be.false; + }); + + it('knows when to enable complib specific breakpoints', () => { + //only supported on version 3.1.0 and above + client.protocolVersion = '1.0.0'; + expect( + client['enableComponentLibrarySpecificBreakpoints'] + ).to.be.false; + + client.protocolVersion = '3.0.0'; + expect( + client['enableComponentLibrarySpecificBreakpoints'] + ).to.be.false; + + client.protocolVersion = '3.1.0'; + expect( + client['enableComponentLibrarySpecificBreakpoints'] + ).to.be.true; + + client.protocolVersion = '4.0.0'; + expect( + client['enableComponentLibrarySpecificBreakpoints'] + ).to.be.true; + }); + + it('knows when to enable conditional breakpoints', () => { + //only supported on version 3.1.0 and above + client.protocolVersion = '1.0.0'; + expect( + client['supportsConditionalBreakpoints'] + ).to.be.false; + + client.protocolVersion = '3.0.0'; + expect( + client['supportsConditionalBreakpoints'] + ).to.be.false; + + client.protocolVersion = '3.1.0'; + expect( + client['supportsConditionalBreakpoints'] + ).to.be.true; + + client.protocolVersion = '4.0.0'; + expect( + client['supportsConditionalBreakpoints'] + ).to.be.true; + }); + it('handles v3 handshake', async () => { //these are false by default expect(client.watchPacketLength).to.be.equal(false); diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 6387aa47..5022fcd2 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -131,7 +131,7 @@ export class DebugProtocolClient { public on(eventName: string, handler: (payload: any) => void) { this.emitter.on(eventName, handler); return () => { - this.emitter?.removeListener(eventName, handler); + this.emitter.removeListener(eventName, handler); }; } @@ -143,7 +143,7 @@ export class DebugProtocolClient { //emit these events on next tick, otherwise they will be processed immediately which could cause issues setTimeout(() => { //in rare cases, this event is fired after the debugger has closed, so make sure the event emitter still exists - this.emitter?.emit(eventName, data); + this.emitter.emit(eventName, data); }, 0); } From fee78486c73318485133396e1255816efbe865d2 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 11 Jan 2023 09:12:44 -0500 Subject: [PATCH 21/74] Fix broken tests --- src/debugProtocol/client/DebugProtocolClient.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index c1f63811..321f15f7 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -3,7 +3,7 @@ import { DebugProtocolClient } from './DebugProtocolClient'; import { expect } from 'chai'; import type { SmartBuffer } from 'smart-buffer'; import { createSandbox } from 'sinon'; -import { Command, ErrorCode, StopReasonCode } from '../Constants'; +import { Command, ErrorCode, StopReason, StopReasonCode } from '../Constants'; import { DebugProtocolServer } from '../server/DebugProtocolServer'; import * as portfinder from 'portfinder'; import { util } from '../../util'; @@ -187,14 +187,14 @@ describe('DebugProtocolClient', () => { server.sendUpdate( AllThreadsStoppedUpdate.fromJson({ threadIndex: 1, - stopReason: StopReasonCode.Break, + stopReason: StopReason.Break, stopReasonDetail: 'test' }) ) ]); expect(event.data).include({ threadIndex: 1, - stopReason: StopReasonCode.Break, + stopReason: StopReason.Break, stopReasonDetail: 'test' }); }); @@ -235,7 +235,7 @@ describe('DebugProtocolClient', () => { await Promise.all([ server.sendUpdate(AllThreadsStoppedUpdate.fromJson({ threadIndex: 2, - stopReason: StopReasonCode.Break, + stopReason: StopReason.Break, stopReasonDetail: 'because' })), await client.once('suspend') From 59d8be67365997a6e1925c333d448075ff5aa322 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 11 Jan 2023 09:22:59 -0500 Subject: [PATCH 22/74] test tcp server defaults --- .../server/DebugProtocolServer.spec.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/debugProtocol/server/DebugProtocolServer.spec.ts diff --git a/src/debugProtocol/server/DebugProtocolServer.spec.ts b/src/debugProtocol/server/DebugProtocolServer.spec.ts new file mode 100644 index 00000000..e4cdd0c3 --- /dev/null +++ b/src/debugProtocol/server/DebugProtocolServer.spec.ts @@ -0,0 +1,31 @@ +import { DebugProtocolServer } from './DebugProtocolServer'; +import * as Net from 'net'; +import { createSandbox } from 'sinon'; +import { expect } from 'chai'; +const sinon = createSandbox(); + +describe('DebugProtocolServer', () => { + afterEach(() => { + sinon.restore(); + }); + + describe('start', () => { + it('uses default port and host when not specified', async () => { + const tcpServer = { + on: () => { }, + listen: (options, callback) => { + callback(); + } + }; + sinon.stub(Net, 'Server').returns(tcpServer); + const stub = sinon.stub(tcpServer, 'listen').callThrough(); + + const protocolServer = new DebugProtocolServer({}); + await protocolServer.start(); + expect(stub.getCall(0).args[0]).to.eql({ + port: 8081, + hostName: '0.0.0.0' + }); + }); + }); +}); From 307cf274b29c231932d970290c93bdf2085c21f1 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 11 Jan 2023 09:26:23 -0500 Subject: [PATCH 23/74] Fix lint issue --- src/debugProtocol/ProtocolUtil.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debugProtocol/ProtocolUtil.spec.ts b/src/debugProtocol/ProtocolUtil.spec.ts index eab656f8..964fe563 100644 --- a/src/debugProtocol/ProtocolUtil.spec.ts +++ b/src/debugProtocol/ProtocolUtil.spec.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { protocolUtil } from './ProtocolUtil'; -import { ProtocolUpdate } from './events/ProtocolEvent'; +import type { ProtocolUpdate } from './events/ProtocolEvent'; import { SmartBuffer } from 'smart-buffer'; import { ErrorCode, UpdateType, UpdateTypeCode } from './Constants'; import { expectThrows } from '../testHelpers.spec'; From 59435d90c91bb83f303f13733ead605c00886c14 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 11 Jan 2023 10:53:23 -0500 Subject: [PATCH 24/74] better HandshakeResponse coverage --- .../responses/HandshakeResponse.spec.ts | 14 +++++++++ .../events/responses/HandshakeResponse.ts | 5 --- .../responses/HandshakeV3Response.spec.ts | 31 +++++++++++++++++++ 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/debugProtocol/events/responses/HandshakeResponse.spec.ts b/src/debugProtocol/events/responses/HandshakeResponse.spec.ts index 93dde4a6..76f8f2cc 100644 --- a/src/debugProtocol/events/responses/HandshakeResponse.spec.ts +++ b/src/debugProtocol/events/responses/HandshakeResponse.spec.ts @@ -3,6 +3,7 @@ import { DebugProtocolClient } from '../../client/DebugProtocolClient'; import { expect } from 'chai'; import { HandshakeRequest } from '../requests/HandshakeRequest'; import { ErrorCode } from '../../Constants'; +import { DEBUGGER_MAGIC } from '../../server/DebugProtocolServer'; describe('HandshakeResponse', () => { it('Handles a handshake response', () => { @@ -34,6 +35,19 @@ describe('HandshakeResponse', () => { expect(response.toBuffer().length).to.eql(24); }); + it('uses default version when missing', () => { + const handshake = HandshakeResponse.fromJson({ + magic: DEBUGGER_MAGIC, + protocolVersion: '1.2.3' + }); + handshake.data.protocolVersion = undefined; + expect( + HandshakeResponse.fromBuffer( + handshake.toBuffer() + ).data.protocolVersion + ).to.eql('0.0.0'); + }); + it('Fails when buffer is incomplete', () => { let handshake = HandshakeResponse.fromBuffer( //create a response diff --git a/src/debugProtocol/events/responses/HandshakeResponse.ts b/src/debugProtocol/events/responses/HandshakeResponse.ts index 4a82cfaf..86e78733 100644 --- a/src/debugProtocol/events/responses/HandshakeResponse.ts +++ b/src/debugProtocol/events/responses/HandshakeResponse.ts @@ -40,11 +40,6 @@ export class HandshakeResponse implements ProtocolResponse { return response; } - protected loadJson(data: HandshakeResponse['data']) { - this.data = data; - this.success = true; - } - public toBuffer() { let buffer = new SmartBuffer(); buffer.writeStringNT(this.data.magic); // magic_number diff --git a/src/debugProtocol/events/responses/HandshakeV3Response.spec.ts b/src/debugProtocol/events/responses/HandshakeV3Response.spec.ts index cf025d09..1c547183 100644 --- a/src/debugProtocol/events/responses/HandshakeV3Response.spec.ts +++ b/src/debugProtocol/events/responses/HandshakeV3Response.spec.ts @@ -4,6 +4,8 @@ import { expect } from 'chai'; import { SmartBuffer } from 'smart-buffer'; import { ErrorCode } from '../../Constants'; import { HandshakeRequest } from '../requests/HandshakeRequest'; +import { DEBUGGER_MAGIC } from '../../server/DebugProtocolServer'; +import { expectThrows } from '../../../testHelpers.spec'; describe('HandshakeV3Response', () => { const date = new Date(2022, 0, 0); @@ -69,6 +71,35 @@ describe('HandshakeV3Response', () => { expect(newResponse.readOffset).to.eql(32); }); + it('uses default version when missing', () => { + const handshake = HandshakeV3Response.fromJson({ + magic: DEBUGGER_MAGIC, + protocolVersion: '1.2.3', + revisionTimestamp: new Date() + }); + handshake.data.protocolVersion = undefined; + expect( + HandshakeV3Response.fromBuffer( + handshake.toBuffer() + ).data.protocolVersion + ).to.eql('0.0.0'); + }); + + it('rejects if there was not enough buffer data', () => { + const buffer = new SmartBuffer(); + buffer.writeStringNT(DEBUGGER_MAGIC); + buffer.writeUInt32LE(1); // protocol_major_version + buffer.writeUInt32LE(2); // protocol_minor_version + buffer.writeUInt32LE(3); // protocol_patch_version + buffer.writeUInt32LE(100); //remaining_packet_length. The exception is triggered because this is larger than the remaining buffer size + buffer.writeBigUInt64LE(BigInt(123)); + const response = HandshakeV3Response.fromBuffer(buffer.toBuffer()); + expect(response).to.include({ + readOffset: 0, + success: false + }); + }); + it('Fails when buffer is incomplete', () => { let handshake = HandshakeV3Response.fromBuffer( //create a response From 1d7a614b05c0f073fbe1554b9da592a12f6e8685 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Tue, 24 Jan 2023 13:23:11 -0500 Subject: [PATCH 25/74] Handle all variable types --- .../responses/VariablesResponse.spec.ts | 247 +++++++++++++++++- .../events/responses/VariablesResponse.ts | 78 +++--- 2 files changed, 288 insertions(+), 37 deletions(-) diff --git a/src/debugProtocol/events/responses/VariablesResponse.spec.ts b/src/debugProtocol/events/responses/VariablesResponse.spec.ts index 11ebbd24..e49c9a8b 100644 --- a/src/debugProtocol/events/responses/VariablesResponse.spec.ts +++ b/src/debugProtocol/events/responses/VariablesResponse.spec.ts @@ -1,9 +1,131 @@ -/* eslint-disable no-bitwise */ +import type { Variable } from './VariablesResponse'; import { VariablesResponse, VariableType } from './VariablesResponse'; import { expect } from 'chai'; import { ErrorCode } from '../../Constants'; +import { SmartBuffer } from 'smart-buffer'; +import { expectThrows } from '../../../testHelpers.spec'; describe('VariablesResponse', () => { + function v(name: string, type: VariableType, value: any, extra?: Record) { + return { + name: name, + type: type, + value: value, + refCount: 1, + isConst: false, + isContainer: false, + ...extra ?? {} + }; + } + + describe('fromJson', () => { + + it('defaults variables array to empty array', () => { + let response = VariablesResponse.fromJson({} as any); + expect(response.data.variables).to.eql([]); + }); + + it('computes isContainer based on variable type', () => { + let response = VariablesResponse.fromJson({ + requestId: 2, + variables: [{ + type: VariableType.AA, + children: [] + }, { + type: VariableType.Array, + children: [] + }, { + type: VariableType.List, + children: [] + }, { + type: VariableType.Object, + children: [] + }, { + type: VariableType.SubtypedObject, + children: [] + }] as any[] + }); + expect(response.data.variables.map(x => x.isContainer)).to.eql([ + true, true, true, true, true + ]); + }); + + it('throws if container has no children', () => { + expectThrows(() => { + VariablesResponse.fromJson({ + requestId: 2, + variables: [{ + type: VariableType.AA + }] as any[] + }); + }, 'Container variable must have one of these properties defined: childCount, children'); + }); + }); + + describe('readVariable', () => { + it('throws for too-small buffer', () => { + const response = VariablesResponse.fromJson({} as any); + expectThrows(() => { + response['readVariable'](new SmartBuffer()); + }, 'Not enough bytes to create a variable'); + }); + }); + + describe('readVariableValue', () => { + it('returns null for various types', () => { + expect(VariablesResponse.prototype['readVariableValue'](VariableType.Uninitialized, new SmartBuffer())).to.eql(null); + expect(VariablesResponse.prototype['readVariableValue'](VariableType.Unknown, new SmartBuffer())).to.eql(null); + expect(VariablesResponse.prototype['readVariableValue'](VariableType.Invalid, new SmartBuffer())).to.eql(null); + expect(VariablesResponse.prototype['readVariableValue'](VariableType.AA, new SmartBuffer())).to.eql(null); + expect(VariablesResponse.prototype['readVariableValue'](VariableType.Array, new SmartBuffer())).to.eql(null); + expect(VariablesResponse.prototype['readVariableValue'](VariableType.List, new SmartBuffer())).to.eql(null); + }); + + it('throws on unknown type', () => { + expectThrows(() => { + VariablesResponse.prototype['readVariableValue']('unknown type' as any, new SmartBuffer()); + }, 'Unable to determine the variable value'); + }); + }); + + describe('flattenVariables', () => { + + it('does not throw for undefined array', () => { + expect( + VariablesResponse.prototype['flattenVariables'](undefined) + ).to.eql([]); + }); + + it('throws for circular reference', () => { + const parent = { children: [] } as Variable; + const child = { children: [] } as Variable; + parent.children.push(child); + child.children.push(parent); + expectThrows(() => { + VariablesResponse.prototype['flattenVariables']([parent, child]); + }, `The variable at index 3 already exists at index 0. You have a circular reference in your variables that needs to be resolved`); + }); + }); + + it('skips var name if missing', () => { + const response = VariablesResponse.fromBuffer( + VariablesResponse.fromJson({ + requestId: 2, + variables: [{ + name: undefined, + refCount: 0, + isConst: false, + isContainer: true, + children: [], + type: VariableType.AA, + keyType: VariableType.String, + value: undefined + }] + }).toBuffer() + ); + expect(response.data.variables[0].name).not.to.exist; + expect(response.data.variables[0].refCount).to.eql(0); + }); it('handles parent var with children', () => { let response = VariablesResponse.fromJson({ @@ -104,6 +226,129 @@ describe('VariablesResponse', () => { }); }); + it('handles every variable type', () => { + let response = VariablesResponse.fromBuffer( + VariablesResponse.fromJson({ + requestId: 2, + variables: [ + v('a', VariableType.Interface, 'ifArray'), + v('b', VariableType.Object, 'SomeObj'), + v('c', VariableType.String, 'hello world'), + v('d', VariableType.Subroutine, 'main'), + v('e', VariableType.Function, 'test'), + v('f', VariableType.SubtypedObject, 'Parent; Child'), + v('gTrue', VariableType.Boolean, true), + v('gFalse', VariableType.Boolean, false), + v('h', VariableType.Integer, 987), + v('i1', VariableType.LongInteger, 123456789123), + v('i2', VariableType.LongInteger, BigInt(999999999999)), + // v('j', VariableType.Float, 1.987654), // handled in other test since this value is approximated + // v('k', VariableType.Float, 1.2345678912345) // handled in other test since this value is approximated + v('l', VariableType.Uninitialized, undefined), + v('m', VariableType.Unknown, undefined), + v('n', VariableType.Invalid, undefined), + v('o', VariableType.AA, undefined), + v('p', VariableType.Array, undefined), + v('q', VariableType.List, undefined) + ] + }).toBuffer() + ); + expect( + response.data.variables.map(x => ({ + name: x.name, + value: x.value, + type: x.type + })) + ).to.eql( + [ + ['a', VariableType.Interface, 'ifArray'], + ['b', VariableType.Object, 'SomeObj'], + ['c', VariableType.String, 'hello world'], + ['d', VariableType.Subroutine, 'main'], + ['e', VariableType.Function, 'test'], + ['f', VariableType.SubtypedObject, 'Parent; Child'], + ['gTrue', VariableType.Boolean, true], + ['gFalse', VariableType.Boolean, false], + ['h', VariableType.Integer, 987], + ['i1', VariableType.LongInteger, BigInt(123456789123)], + ['i2', VariableType.LongInteger, BigInt('999999999999')], + // ['j', VariableType.Float, 1.987654], // handled in other test since this value is approximated + // ['k', VariableType.Float, 1.2345678912345] // handled in other test since this value is approximated + ['l', VariableType.Uninitialized, undefined], + ['m', VariableType.Unknown, undefined], + ['n', VariableType.Invalid, undefined], + ['o', VariableType.AA, undefined], + ['p', VariableType.Array, undefined], + ['q', VariableType.List, undefined] + ].map(x => ({ + name: x.shift(), + type: x.shift(), + value: x.shift() + })) + ); + }); + + it('handles float and double', () => { + let response = VariablesResponse.fromBuffer( + VariablesResponse.fromJson({ + requestId: 2, + variables: [ + v('j', VariableType.Float, 1.234567), + v('k', VariableType.Double, 9.87654321987654) + ] + }).toBuffer() + ); + expect(response.data.variables[0].value).to.be.approximately(1.234567, 0.000001); + expect(response.data.variables[1].value).to.be.approximately(9.87654321987654, 0.0000000001); + }); + + it('writes nothing for data that has no value', () => { + const buffer = new SmartBuffer(); + VariablesResponse.prototype['writeVariableValue'](VariableType.Uninitialized, undefined, buffer); + VariablesResponse.prototype['writeVariableValue'](VariableType.Unknown, undefined, buffer); + VariablesResponse.prototype['writeVariableValue'](VariableType.Invalid, undefined, buffer); + VariablesResponse.prototype['writeVariableValue'](VariableType.AA, undefined, buffer); + VariablesResponse.prototype['writeVariableValue'](VariableType.Array, undefined, buffer); + VariablesResponse.prototype['writeVariableValue'](VariableType.List, undefined, buffer); + expect(buffer.length).to.eql(0); + }); + + it('writeVariableValue throws for unknown variable value', () => { + expectThrows( + () => VariablesResponse.prototype['writeVariableValue']('NotRealVariableType' as any, undefined, new SmartBuffer()), + 'Unable to determine the variable value' + ); + }); + + it('writeVariableValue throws for incorrectly formatted SubtypedObject', () => { + expectThrows( + () => VariablesResponse.prototype['writeVariableValue'](VariableType.SubtypedObject, 'IShouldHaveASemicolonAndAnotherThingAfterThat', new SmartBuffer()), + 'Expected two names for subtyped object' + ); + }); + + it('writeVariableValue throws for undefined SubtypedObject', () => { + expectThrows( + () => VariablesResponse.prototype['writeVariableValue'](VariableType.SubtypedObject, undefined, new SmartBuffer()), + 'Expected two names for subtyped object' + ); + }); + + it('writeVariable determines if variable is const', () => { + let response = VariablesResponse.fromBuffer( + VariablesResponse.fromJson({ + requestId: 2, + variables: [ + v('alpha', VariableType.List, undefined, { isConst: true }), + v('beta', VariableType.List, undefined, { isConst: false }) + + ] + }).toBuffer() + ); + expect(response.data.variables[0].isConst).to.be.true; + expect(response.data.variables[1].isConst).to.be.false; + }); + it('handles several root-level vars', () => { let response = VariablesResponse.fromJson({ requestId: 2, diff --git a/src/debugProtocol/events/responses/VariablesResponse.ts b/src/debugProtocol/events/responses/VariablesResponse.ts index a4838491..3ea0470a 100644 --- a/src/debugProtocol/events/responses/VariablesResponse.ts +++ b/src/debugProtocol/events/responses/VariablesResponse.ts @@ -26,7 +26,7 @@ export class VariablesResponse { variable.isContainer = [VariableType.AA, VariableType.Array, VariableType.List, VariableType.Object, VariableType.SubtypedObject].includes(variable.type); } if (variable.isContainer && util.isNullish(variable.childCount) && !hasChildrenArray) { - throw new Error('Container variable must either have one of these properties set: childCount, children'); + throw new Error('Container variable must have one of these properties defined: childCount, children'); } } return response; @@ -79,8 +79,8 @@ export class VariablesResponse { const isNameHere = (flags & VariableFlags.isNameHere) > 0; const isRefCounted = (flags & VariableFlags.isRefCounted) > 0; const isValueHere = (flags & VariableFlags.isValueHere) > 0; - - variable.type = VariableTypeCode[smartBuffer.readUInt8()] as VariableType; // variable_type + const variableTypeCode = smartBuffer.readUInt8(); + variable.type = VariableTypeCode[variableTypeCode] as VariableType; // variable_type if (isNameHere) { // we have a name. Pull it out of the buffer. @@ -90,6 +90,8 @@ export class VariablesResponse { if (isRefCounted) { // This variables reference counts are tracked and we can pull it from the buffer. variable.refCount = smartBuffer.readUInt32LE(); + } else { + variable.refCount = 0; } if (variable.isContainer) { @@ -120,27 +122,20 @@ export class VariablesResponse { for (let i = 0; i < 2; i++) { names.push(protocolUtil.readStringNT(smartBuffer)); } - - if (names.length !== 2) { - throw new Error('Expected two names for subtyped object'); - } return names.join('; '); case VariableType.Boolean: return smartBuffer.readUInt8() > 0; - case VariableType.Double: - return smartBuffer.readDoubleLE(); - case VariableType.Float: - return smartBuffer.readFloatLE(); case VariableType.Integer: return smartBuffer.readInt32LE(); case VariableType.LongInteger: return smartBuffer.readBigInt64LE(); + case VariableType.Float: + return smartBuffer.readFloatLE(); + case VariableType.Double: + return smartBuffer.readDoubleLE(); case VariableType.Uninitialized: - return ''; case VariableType.Unknown: - return 'Unknown'; case VariableType.Invalid: - return 'Invalid'; case VariableType.AA: case VariableType.Array: case VariableType.List: @@ -157,19 +152,23 @@ export class VariablesResponse { result.push(rootVariable); //add all child variables to the array for (const child of rootVariable.children ?? []) { - if (result.includes(child) && Array.isArray(child.childCount)) { - throw new Error('This variable already exists in the list. You have a circular reference in your variables that needs to be resolved'); - } result.push(child); } } + //catch duplicates and circular references + for (let i = 0; i < result.length; i++) { + const idx = result.indexOf(result[i], i + 1); + if (idx > -1) { + throw new Error(`The variable at index ${idx} already exists at index ${i}. You have a circular reference in your variables that needs to be resolved`); + } + } return result; } public toBuffer() { const smartBuffer = new SmartBuffer(); const variables = this.flattenVariables(this.data.variables); - smartBuffer.writeUInt32LE(variables.length ?? 0); // num_variables + smartBuffer.writeUInt32LE(variables.length); // num_variables for (const variable of variables) { this.writeVariable(variable, smartBuffer); } @@ -221,41 +220,48 @@ export class VariablesResponse { } - private writeVariableValue(variableType: VariableType, value: any, smartBuffer: SmartBuffer) { + private writeVariableValue(variableType: VariableType, value: any, smartBuffer: SmartBuffer): void { switch (variableType) { case VariableType.Interface: case VariableType.Object: case VariableType.String: case VariableType.Subroutine: case VariableType.Function: - return smartBuffer.writeStringNT(value as string); + smartBuffer.writeStringNT(value as string); + break; case VariableType.SubtypedObject: - let names = []; - for (let i = 0; i < 2; i++) { - names.push(protocolUtil.readStringNT(smartBuffer)); - } - + const names = (value as string ?? '').split('; '); if (names.length !== 2) { throw new Error('Expected two names for subtyped object'); } - return names.join('; '); + for (const name of names) { + smartBuffer.writeStringNT(name); + } + break; case VariableType.Boolean: - return smartBuffer.writeUInt8(value === true ? 1 : 0); - case VariableType.Double: - return smartBuffer.writeDoubleLE(value as number); - case VariableType.Float: - return smartBuffer.writeFloatLE(value as number); + smartBuffer.writeUInt8(value === true ? 1 : 0); + break; case VariableType.Integer: - return smartBuffer.writeInt32LE(value as number); + smartBuffer.writeInt32LE(value as number); + break; case VariableType.LongInteger: - return smartBuffer.writeBigInt64LE(value as bigint); + smartBuffer.writeBigInt64LE( + typeof value === 'bigint' ? value : BigInt(value as number) + ); + break; + case VariableType.Float: + smartBuffer.writeFloatLE(value as number); + break; + case VariableType.Double: + smartBuffer.writeDoubleLE(value as number); + break; case VariableType.Uninitialized: case VariableType.Unknown: case VariableType.Invalid: case VariableType.AA: case VariableType.Array: case VariableType.List: - return null; + break; default: throw new Error('Unable to determine the variable value'); } @@ -349,14 +355,14 @@ enum VariableTypeCode { Object = 12, String = 13, Subroutine = 14, - Subtyped_Object = 15, + SubtypedObject = 15, Uninitialized = 16, Unknown = 17 } export interface Variable { /** - * 0 means this var isn't refCountded, and will be omitted from reading and writing to buffer + * 0 means this var isn't refCounted, and thus `refCount` will be omitted from reading and writing to buffer */ refCount: number; /** From 8f2d10c5d7326f44215c8744b23355cc6a304620 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Tue, 24 Jan 2023 14:55:37 -0500 Subject: [PATCH 26/74] Responses 100% coverage --- .../responses/ExecuteV3Response.spec.ts | 19 +++++++++ .../responses/ListBreakpointsResponse.spec.ts | 34 ++++++++++++++++ .../responses/StackTraceResponse.spec.ts | 12 ++++++ .../responses/StackTraceV3Response.spec.ts | 12 ++++++ .../events/responses/ThreadsResponse.spec.ts | 40 +++++++++++++++++++ 5 files changed, 117 insertions(+) diff --git a/src/debugProtocol/events/responses/ExecuteV3Response.spec.ts b/src/debugProtocol/events/responses/ExecuteV3Response.spec.ts index 7f8854ca..467e5ff8 100644 --- a/src/debugProtocol/events/responses/ExecuteV3Response.spec.ts +++ b/src/debugProtocol/events/responses/ExecuteV3Response.spec.ts @@ -3,6 +3,25 @@ import { ErrorCode, StopReasonCode, UpdateType } from '../../Constants'; import { ExecuteV3Response } from './ExecuteV3Response'; describe('ExecuteV3Response', () => { + it('defaults empty arrays for missing error arrays', () => { + const response = ExecuteV3Response.fromJson({} as any); + expect(response.data.compileErrors).to.eql([]); + expect(response.data.runtimeErrors).to.eql([]); + expect(response.data.otherErrors).to.eql([]); + }); + + it('uses default values when data is missing', () => { + let response = ExecuteV3Response.fromJson({} as any); + response.data = {} as any; + response = ExecuteV3Response.fromBuffer( + response.toBuffer() + ); + expect(response.data.executeSuccess).to.eql(false); + expect(response.data.compileErrors).to.eql([]); + expect(response.data.runtimeErrors).to.eql([]); + expect(response.data.otherErrors).to.eql([]); + }); + it('serializes and deserializes properly', () => { const command = ExecuteV3Response.fromJson({ requestId: 3, diff --git a/src/debugProtocol/events/responses/ListBreakpointsResponse.spec.ts b/src/debugProtocol/events/responses/ListBreakpointsResponse.spec.ts index cfa4bd57..c43dee10 100644 --- a/src/debugProtocol/events/responses/ListBreakpointsResponse.spec.ts +++ b/src/debugProtocol/events/responses/ListBreakpointsResponse.spec.ts @@ -4,6 +4,40 @@ import { ErrorCode } from '../../Constants'; import { getRandomBuffer } from '../../../testHelpers.spec'; describe('ListBreakpointsResponse', () => { + it('defaults undefined breakpoint array to empty', () => { + let response = ListBreakpointsResponse.fromJson({} as any); + expect(response.data.breakpoints).to.eql([]); + }); + + it('skips ignoreCount for invalid breakpoints', () => { + const response = ListBreakpointsResponse.fromBuffer( + ListBreakpointsResponse.fromJson({ + requestId: 2, + breakpoints: [{ + id: 12, + errorCode: ErrorCode.OK, + ignoreCount: 10 + }, { + id: 0, + errorCode: ErrorCode.OK, + ignoreCount: 20 + }] + }).toBuffer() + ); + expect(response.data.breakpoints[0].ignoreCount).to.eql(10); + expect(response.data.breakpoints[1].ignoreCount).to.eql(undefined); + + }); + + it('defaults num_breakpoints to 0 if array is missing', () => { + let response = ListBreakpointsResponse.fromJson({} as any); + response.data = {} as any; + response = ListBreakpointsResponse.fromBuffer( + response.toBuffer() + ); + expect(response.data.breakpoints).to.eql([]); + }); + it('serializes and deserializes multiple breakpoints properly', () => { let response = ListBreakpointsResponse.fromJson({ requestId: 3, diff --git a/src/debugProtocol/events/responses/StackTraceResponse.spec.ts b/src/debugProtocol/events/responses/StackTraceResponse.spec.ts index 6c5b07cc..90564bd3 100644 --- a/src/debugProtocol/events/responses/StackTraceResponse.spec.ts +++ b/src/debugProtocol/events/responses/StackTraceResponse.spec.ts @@ -4,6 +4,18 @@ import { ErrorCode } from '../../Constants'; import { getRandomBuffer } from '../../../testHelpers.spec'; describe('StackTraceResponse', () => { + it('defaults data.entries to empty array when missing', () => { + let response = StackTraceResponse.fromJson({} as any); + expect(response.data.entries).to.eql([]); + }); + + it('does not crash when data is invalid', () => { + let response = StackTraceResponse.fromJson({} as any); + response.data = {} as any; + response = StackTraceResponse.fromBuffer(response.toBuffer()); + expect(response.data.entries).to.eql([]); + }); + it('serializes and deserializes multiple breakpoints properly', () => { let response = StackTraceResponse.fromJson({ requestId: 3, diff --git a/src/debugProtocol/events/responses/StackTraceV3Response.spec.ts b/src/debugProtocol/events/responses/StackTraceV3Response.spec.ts index f2e130dd..95abddcf 100644 --- a/src/debugProtocol/events/responses/StackTraceV3Response.spec.ts +++ b/src/debugProtocol/events/responses/StackTraceV3Response.spec.ts @@ -4,6 +4,18 @@ import { ErrorCode } from '../../Constants'; import { getRandomBuffer } from '../../../testHelpers.spec'; describe('StackTraceV3Response', () => { + it('defaults data.entries to empty array when missing', () => { + let response = StackTraceV3Response.fromJson({} as any); + expect(response.data.entries).to.eql([]); + }); + + it('does not crash when data is invalid', () => { + let response = StackTraceV3Response.fromJson({} as any); + response.data = {} as any; + response = StackTraceV3Response.fromBuffer(response.toBuffer()); + expect(response.data.entries).to.eql([]); + }); + it('serializes and deserializes multiple breakpoints properly', () => { let response = StackTraceV3Response.fromJson({ requestId: 3, diff --git a/src/debugProtocol/events/responses/ThreadsResponse.spec.ts b/src/debugProtocol/events/responses/ThreadsResponse.spec.ts index 8b5eda32..fbda3a7e 100644 --- a/src/debugProtocol/events/responses/ThreadsResponse.spec.ts +++ b/src/debugProtocol/events/responses/ThreadsResponse.spec.ts @@ -4,6 +4,46 @@ import { ErrorCode, StopReason } from '../../Constants'; import { getRandomBuffer } from '../../../testHelpers.spec'; describe('ThreadsResponse', () => { + it('defaults data.entries to empty array when missing', () => { + let response = ThreadsResponse.fromJson({} as any); + expect(response.data.threads).to.eql([]); + }); + + it('does not crash when data is invalid', () => { + let response = ThreadsResponse.fromJson({} as any); + response.data = {} as any; + response = ThreadsResponse.fromBuffer(response.toBuffer()); + expect(response.data.threads).to.eql([]); + }); + + function t(extra?: Record) { + return { + isPrimary: true, + stopReason: StopReason.Break, + stopReasonDetail: 'because', + lineNumber: 2, + functionName: 'main', + filePath: 'pkg:/source/main.brs', + codeSnippet: 'sub main()', + ...extra ?? {} + }; + } + + it('defaults unspecified threads as not primary', () => { + const response = ThreadsResponse.fromBuffer( + ThreadsResponse.fromJson({ + threads: [t({ + isPrimary: undefined + }), t({ + isPrimary: true + }), t({ + isPrimary: false + })] + } as any).toBuffer() + ); + expect(response.data.threads.map(x => x.isPrimary)).to.eql([false, true, false]); + }); + it('serializes and deserializes multiple breakpoints properly', () => { let response = ThreadsResponse.fromJson({ requestId: 3, From 8260f7a2b717fd7eae455ad18cf8d76492bd87c9 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Tue, 24 Jan 2023 16:05:02 -0500 Subject: [PATCH 27/74] More DebugProtocolClient tests --- .../DebugProtocolServerTestPlugin.spec.ts | 11 +- .../client/DebugProtocolClient.spec.ts | 203 +++++++++++++++--- .../client/DebugProtocolClient.ts | 10 +- .../server/DebugProtocolServer.ts | 2 +- 4 files changed, 187 insertions(+), 39 deletions(-) diff --git a/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts b/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts index 8d0f91c1..cbe1bde3 100644 --- a/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts +++ b/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts @@ -1,3 +1,5 @@ +import { ConsoleTransport, Logger } from '@rokucommunity/logger'; +import { createLogger } from '../logging'; import type { ProtocolResponse, ProtocolRequest } from './events/ProtocolEvent'; import { HandshakeRequest } from './events/requests/HandshakeRequest'; import type { DebugProtocolServer } from './server/DebugProtocolServer'; @@ -7,6 +9,7 @@ import type { BeforeSendResponseEvent, OnServerStartEvent, ProtocolPlugin, Provi * A class that intercepts all debug server events and provides test data for them */ export class DebugProtocolServerTestPlugin implements ProtocolPlugin { + /** * A list of responses to be sent by the server in this exact order. * One of these will be sent for every `provideResponse` event received. @@ -32,6 +35,10 @@ export class DebugProtocolServerTestPlugin implements ProtocolPlugin { return this.requests[this.requests.length - 1]; } + public getLatestRequest() { + return this.latestRequest as T; + } + /** * A running list of responses sent by the server during this test */ @@ -63,9 +70,9 @@ export class DebugProtocolServerTestPlugin implements ProtocolPlugin { const response = this.responseQueue.shift(); //if there's no response, AND this isn't the handshake, fail. (we want the protocol to handle the handshake most of the time) if (!response && !(event.request instanceof HandshakeRequest)) { - throw new Error('There was no response available to send back'); + throw new Error(`There was no response available to send back for ${event.request.constructor.name}`); } - //force this response to have the current request's ID (for testing purposes + //force this response to have the current request's ID (for testing purposes) if (response) { response.data.requestId = event.request.data.requestId; } diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index 321f15f7..a71c69f8 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -1,21 +1,24 @@ /* eslint-disable no-bitwise */ import { DebugProtocolClient } from './DebugProtocolClient'; import { expect } from 'chai'; -import type { SmartBuffer } from 'smart-buffer'; import { createSandbox } from 'sinon'; -import { Command, ErrorCode, StopReason, StopReasonCode } from '../Constants'; +import { Command, ErrorCode, StepType, StopReason } from '../Constants'; import { DebugProtocolServer } from '../server/DebugProtocolServer'; import * as portfinder from 'portfinder'; import { util } from '../../util'; -import type { BeforeSendResponseEvent, ProtocolPlugin, ProvideResponseEvent } from '../server/ProtocolPlugin'; import { HandshakeRequest } from '../events/requests/HandshakeRequest'; -import type { ProtocolResponse, ProtocolRequest } from '../events/ProtocolEvent'; import { HandshakeResponse } from '../events/responses/HandshakeResponse'; import { HandshakeV3Response } from '../events/responses/HandshakeV3Response'; import { AllThreadsStoppedUpdate } from '../events/updates/AllThreadsStoppedUpdate'; import { VariablesResponse } from '../events/responses/VariablesResponse'; -import { VariableRequestFlag, VariablesRequest } from '../events/requests/VariablesRequest'; +import { VariablesRequest } from '../events/requests/VariablesRequest'; import { DebugProtocolServerTestPlugin } from '../DebugProtocolServerTestPlugin.spec'; +import { ContinueRequest } from '../events/requests/ContinueRequest'; +import { GenericV3Response } from '../events/responses/GenericV3Response'; +import { StopRequest } from '../events/requests/StopRequest'; +import { ExitChannelRequest } from '../events/requests/ExitChannelRequest'; +import { StepRequest } from '../events/requests/StepRequest'; +import { ThreadsResponse } from '../events/responses/ThreadsResponse'; const sinon = createSandbox(); @@ -24,6 +27,22 @@ describe('DebugProtocolClient', () => { let client: DebugProtocolClient; let plugin: DebugProtocolServerTestPlugin; + /** + * Helper function to simplify the initial connect flow + */ + async function connect() { + await client.connect(); + //send the AllThreadsStopped event, and also wait for the client to suspend + await Promise.all([ + server.sendUpdate(AllThreadsStoppedUpdate.fromJson({ + threadIndex: 2, + stopReason: StopReason.Break, + stopReasonDetail: 'because' + })), + await client.once('suspend') + ]); + } + beforeEach(async () => { sinon.stub(console, 'log').callsFake((...args) => { }); @@ -75,6 +94,151 @@ describe('DebugProtocolClient', () => { ).to.be.false; }); + it('does not crash on unspecified options', () => { + const client = new DebugProtocolClient(undefined); + //no exception means it passed + }); + + it('only sends the continue command when stopped', async () => { + await connect(); + + client.isStopped = false; + await client.continue(); + expect(plugin.latestRequest).not.to.be.instanceof(ContinueRequest); + + plugin.pushResponse(GenericV3Response.fromJson({} as any)); + client.isStopped = true; + await client.continue(); + expect(plugin.latestRequest).to.be.instanceOf(ContinueRequest); + }); + + it('sends the pause command when forced', async () => { + await connect(); + + client.isStopped = true; + await client.pause(); //should do nothing + expect(plugin.latestRequest).not.to.be.instanceof(StopRequest); + + plugin.pushResponse(GenericV3Response.fromJson({} as any)); + client.isStopped = false; + await client.pause(); + expect(plugin.latestRequest).to.be.instanceOf(StopRequest); + }); + + it('sends the pause command when forced', async () => { + await connect(); + + plugin.pushResponse(GenericV3Response.fromJson({} as any)); + client.isStopped = true; + await client.pause(true); //true means force + expect(plugin.latestRequest).to.be.instanceOf(StopRequest); + }); + + it('sends the exitChannel command', async () => { + await connect(); + + plugin.pushResponse(GenericV3Response.fromJson({} as any)); + + await client.exitChannel(); + + expect(plugin.latestRequest).to.be.instanceOf(ExitChannelRequest); + }); + + it('stepIn defaults to client.primaryThread and can be overridden', async () => { + await connect(); + client.primaryThread = 9; + + plugin.pushResponse(GenericV3Response.fromJson({} as any)); + await client.stepIn(); + expect(plugin.getLatestRequest().data.threadIndex).to.eql(9); + expect(plugin.getLatestRequest().data.stepType).to.eql(StepType.Line); + + plugin.pushResponse(GenericV3Response.fromJson({} as any)); + await client.stepIn(5); + expect(plugin.getLatestRequest().data.threadIndex).to.eql(5); + expect(plugin.getLatestRequest().data.stepType).to.eql(StepType.Line); + }); + + it('stepOver defaults to client.primaryThread and can be overridden', async () => { + await connect(); + client.primaryThread = 9; + + plugin.pushResponse(GenericV3Response.fromJson({} as any)); + await client.stepOver(); + expect(plugin.getLatestRequest().data.threadIndex).to.eql(9); + expect(plugin.getLatestRequest().data.stepType).to.eql(StepType.Over); + + plugin.pushResponse(GenericV3Response.fromJson({} as any)); + await client.stepOver(5); + expect(plugin.getLatestRequest().data.threadIndex).to.eql(5); + expect(plugin.getLatestRequest().data.stepType).to.eql(StepType.Over); + }); + + it('stepOut defaults to client.primaryThread and can be overridden', async () => { + await connect(); + client.primaryThread = 9; + + plugin.pushResponse(GenericV3Response.fromJson({} as any)); + await client.stepOut(); + expect(plugin.getLatestRequest().data.threadIndex).to.eql(9); + expect(plugin.getLatestRequest().data.stepType).to.eql(StepType.Out); + + plugin.pushResponse(GenericV3Response.fromJson({} as any)); + await client.stepOut(5); + expect(plugin.getLatestRequest().data.threadIndex).to.eql(5); + expect(plugin.getLatestRequest().data.stepType).to.eql(StepType.Out); + }); + + it('stepOut defaults to client.primaryThread and can be overridden', async () => { + await connect(); + + plugin.pushResponse(GenericV3Response.fromJson({} as any)); + + //does not send command because we're not stopped + client.isStopped = false; + await client.stepOut(); + expect(plugin.latestRequest).not.to.be.instanceof(StepRequest); + }); + + it('handles step cannot-continue response', async () => { + await connect(); + + plugin.pushResponse(GenericV3Response.fromJson({ + errorCode: ErrorCode.CANT_CONTINUE, + requestId: 12 + })); + + let cannotContinuePromise = client.once('cannot-continue'); + + client.isStopped = true; + await client.stepOut(); + + //if the cannot-continue event resolved, this test passed + await cannotContinuePromise; + }); + + describe('threads()', () => { + it('skips sending command when not stopped', async () => { + await connect(); + + client.isStopped = false; + await client.threads(); + expect(plugin.latestRequest).not.to.be.instanceof(ThreadsResponse); + }); + + it('returns response even when error code is not ok', async () => { + await connect(); + + plugin.pushResponse(GenericV3Response.fromJson({ + errorCode: ErrorCode.CANT_CONTINUE, + requestId: 12 + })); + + const response = await client.threads(); + expect(response.data.errorCode).to.eql(ErrorCode.CANT_CONTINUE); + }); + }); + it('knows when to enable complib specific breakpoints', () => { //only supported on version 3.1.0 and above client.protocolVersion = '1.0.0'; @@ -200,35 +364,6 @@ describe('DebugProtocolClient', () => { }); describe('getVariables', () => { - function getVariablesRequestBufferToJson(buffer: SmartBuffer) { - const result = { - flags: buffer.readUInt8(), - threadIndex: buffer.readUInt32LE(), - stackFrameIndex: buffer.readUInt32LE(), - variablePathEntries: [], - pathForceCaseInsensitive: [] - }; - - const pathLength = buffer.readUInt32LE(); - if (pathLength > 0) { - result.variablePathEntries = []; - for (let i = 0; i < pathLength; i++) { - result.variablePathEntries.push( - buffer.readBufferNT().toString() - ); - } - } - if (result.flags & VariableRequestFlag.CaseSensitivityOptions) { - result.pathForceCaseInsensitive = []; - for (let i = 0; i < pathLength; i++) { - result.pathForceCaseInsensitive.push( - buffer.readUInt8() === 0 ? false : true - ); - } - } - return result; - } - it('honors protocol version when deciding to send forceCaseInsensitive variable information', async () => { await client.connect(); //send the AllThreadsStopped event, and also wait for the client to suspend diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 5022fcd2..e25c3617 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -239,7 +239,7 @@ export class DebugProtocolClient { } public async pause(force = false) { - if (!this.isStopped || force) { + if (this.isStopped === false || force) { return this.sendRequest( StopRequest.fromJson({ requestId: this.requestIdSequence++ @@ -486,7 +486,7 @@ export class DebugProtocolClient { if (event.data.errorCode !== ErrorCode.OK) { this.logger.error(event.data.errorCode, event); - return; + // return; } //we got a response @@ -525,6 +525,12 @@ export class DebugProtocolClient { } let genericResponse = this.watchPacketLength ? GenericV3Response.fromBuffer(buffer) : GenericResponse.fromBuffer(buffer); + + //if the response has a non-OK error code, we won't receive the expected response type, + //so return the generic response + if (genericResponse.success && genericResponse.data.errorCode !== ErrorCode.OK) { + return genericResponse; + } // a nonzero requestId means this is a response to a request that we sent if (genericResponse.data.requestId !== 0) { //requestId 0 means this is an update diff --git a/src/debugProtocol/server/DebugProtocolServer.ts b/src/debugProtocol/server/DebugProtocolServer.ts index 38f68139..0cf91a87 100644 --- a/src/debugProtocol/server/DebugProtocolServer.ts +++ b/src/debugProtocol/server/DebugProtocolServer.ts @@ -220,7 +220,7 @@ export class DebugProtocolServer { } - //the client should send a magic string to kick off the debugger + //if this is part of the handshake flow, the client should have sent a magic string to kick off the debugger. If it matches, set `isHandshakeComplete = true` if ((response instanceof HandshakeResponse || response instanceof HandshakeV3Response) && response.data.magic === this.magic) { this.isHandshakeComplete = true; } From a78f8354b50e2ef8c6d8902cccb39a924ac984ac Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 25 Jan 2023 11:23:04 -0500 Subject: [PATCH 28/74] fix server support mulitple requests in single buffer --- package-lock.json | 249 ++++++++++++++++ package.json | 5 + .../DebugProtocolServerTestPlugin.spec.ts | 38 ++- .../client/DebugProtocolClient.spec.ts | 265 +++++++++++++++++- .../client/DebugProtocolClient.ts | 13 +- .../events/requests/HandshakeRequest.ts | 2 +- .../server/DebugProtocolServer.ts | 12 +- src/testHelpers.spec.ts | 14 + 8 files changed, 580 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index c67caccf..8fd2bc84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,6 +50,7 @@ "eslint-plugin-no-only-tests": "^2.6.0", "get-port": "^5.1.1", "mocha": "^9.1.3", + "nodemon": "^2.0.20", "nyc": "^15.1.0", "p-defer": "^4.0.0", "portfinder": "^1.0.32", @@ -926,6 +927,12 @@ "regexp-to-ast": "0.5.0" } }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, "node_modules/acorn": { "version": "8.5.0", "dev": true, @@ -2549,6 +2556,12 @@ "node": ">= 4" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, "node_modules/immediate": { "version": "3.0.6", "license": "MIT" @@ -3398,6 +3411,88 @@ "dev": true, "license": "MIT" }, + "node_modules/nodemon": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", + "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "license": "MIT", @@ -3750,6 +3845,12 @@ "version": "1.8.0", "license": "MIT" }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, "node_modules/punycode": { "version": "2.1.1", "license": "MIT", @@ -4237,6 +4338,27 @@ "dev": true, "license": "ISC" }, + "node_modules/simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dev": true, + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/sinon": { "version": "11.1.2", "dev": true, @@ -4439,6 +4561,18 @@ "node": ">=8.0" } }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tough-cookie": { "version": "2.5.0", "license": "BSD-3-Clause", @@ -4592,6 +4726,12 @@ "node": ">=4.2.0" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, "node_modules/universalify": { "version": "0.1.2", "license": "MIT", @@ -5459,6 +5599,12 @@ } } }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, "acorn": { "version": "8.5.0", "dev": true @@ -6485,6 +6631,12 @@ "version": "4.0.6", "dev": true }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, "immediate": { "version": "3.0.6" }, @@ -7026,6 +7178,65 @@ "version": "2.0.1", "dev": true }, + "nodemon": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", + "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", + "dev": true, + "requires": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "requires": { + "abbrev": "1" + } + }, "normalize-path": { "version": "3.0.0" }, @@ -7256,6 +7467,12 @@ "psl": { "version": "1.8.0" }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, "punycode": { "version": "2.1.1" }, @@ -7567,6 +7784,23 @@ "version": "3.0.5", "dev": true }, + "simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dev": true, + "requires": { + "semver": "~7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, "sinon": { "version": "11.1.2", "dev": true, @@ -7699,6 +7933,15 @@ "is-number": "^7.0.0" } }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "requires": { + "nopt": "~1.0.10" + } + }, "tough-cookie": { "version": "2.5.0", "requires": { @@ -7782,6 +8025,12 @@ "version": "4.4.4", "dev": true }, + "undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, "universalify": { "version": "0.1.2" }, diff --git a/package.json b/package.json index f4a49638..b62a8c3c 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "lint": "eslint \"src/**\"", "watch": "tsc --watch", "test": "nyc mocha \"src/**/*spec.ts\" --exclude \"src/**/*.device.spec.ts\"", + "test-watch": "npx nodemon --watch src --ext \"ts\" --exec \"npm run test\"", "device-test": "mocha --spec \"src/**/*.device.spec.ts\"", "test:nocover": "mocha \"src/**/*.spec.ts\" --exclude \"src/**/*.device.spec.ts\"", "publish-coverage": "nyc report --reporter=text-lcov | coveralls" @@ -25,6 +26,9 @@ "source-map-support/register", "ts-node/register" ], + "watchFiles": [ + "src/**/*" + ], "fullTrace": true, "watchExtensions": [ "ts" @@ -74,6 +78,7 @@ "eslint-plugin-no-only-tests": "^2.6.0", "get-port": "^5.1.1", "mocha": "^9.1.3", + "nodemon": "^2.0.20", "nyc": "^15.1.0", "p-defer": "^4.0.0", "portfinder": "^1.0.32", diff --git a/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts b/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts index cbe1bde3..3a066dc2 100644 --- a/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts +++ b/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts @@ -1,6 +1,7 @@ import { ConsoleTransport, Logger } from '@rokucommunity/logger'; import { createLogger } from '../logging'; -import type { ProtocolResponse, ProtocolRequest } from './events/ProtocolEvent'; +import { isProtocolUpdate } from './client/DebugProtocolClient'; +import type { ProtocolResponse, ProtocolRequest, ProtocolUpdate } from './events/ProtocolEvent'; import { HandshakeRequest } from './events/requests/HandshakeRequest'; import type { DebugProtocolServer } from './server/DebugProtocolServer'; import type { BeforeSendResponseEvent, OnServerStartEvent, ProtocolPlugin, ProvideResponseEvent } from './server/ProtocolPlugin'; @@ -11,16 +12,25 @@ import type { BeforeSendResponseEvent, OnServerStartEvent, ProtocolPlugin, Provi export class DebugProtocolServerTestPlugin implements ProtocolPlugin { /** - * A list of responses to be sent by the server in this exact order. - * One of these will be sent for every `provideResponse` event received. + * A list of responses or updates to be sent by the server in this exact order. + * One of these will be sent for every `provideResponse` event received. Any leading ProtocolUpdate entries will be sent as soon as seen. + * For example, if the array is `[Update1, Update2, Response1, Update3]`, when the `provideResponse` event is triggered, we will first send + * `Update1` and `Update2`, then provide `Response1`. `Update3` will be triggered when the next `provideResponse` is requested, or if `.flush()` is called */ - private responseQueue: ProtocolResponse[] = []; + private responseUpdateQueue: Array = []; /** * Adds a response to the queue, which should be returned from the server in first-in-first-out order, one for each request received by the server */ - public pushResponse(response: ProtocolResponse) { - this.responseQueue.push(response); + public pushResponse(event: ProtocolResponse) { + this.responseUpdateQueue.push(event); + } + + /** + * Adds a ProtocolUpdate to the queue. Any leading updates are send to the client anytime `provideResponse` is triggered, or when `.flush()` is called + */ + public pushUpdate(event: ProtocolUpdate) { + this.responseUpdateQueue.push(event); } /** @@ -60,14 +70,26 @@ export class DebugProtocolServerTestPlugin implements ProtocolPlugin { this.server = server; } + /** + * Flush all leading updates in the queue + */ + public async flush() { + while (isProtocolUpdate(this.responseUpdateQueue[0])) { + await this.server.sendUpdate(this.responseUpdateQueue.shift()); + } + } + /** * Whenever the server receives a request, this event allows us to send back a response */ - provideResponse(event: ProvideResponseEvent) { + async provideResponse(event: ProvideResponseEvent) { //store the request for testing purposes (this.requests as Array).push(event.request); - const response = this.responseQueue.shift(); + //flush leading updates + await this.flush(); + + const response = this.responseUpdateQueue.shift(); //if there's no response, AND this isn't the handshake, fail. (we want the protocol to handle the handshake most of the time) if (!response && !(event.request instanceof HandshakeRequest)) { throw new Error(`There was no response available to send back for ${event.request.constructor.name}`); diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index a71c69f8..16157d1e 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -18,7 +18,21 @@ import { GenericV3Response } from '../events/responses/GenericV3Response'; import { StopRequest } from '../events/requests/StopRequest'; import { ExitChannelRequest } from '../events/requests/ExitChannelRequest'; import { StepRequest } from '../events/requests/StepRequest'; +import type { ThreadInfo } from '../events/responses/ThreadsResponse'; import { ThreadsResponse } from '../events/responses/ThreadsResponse'; +import { StackTraceResponse } from '../events/responses/StackTraceResponse'; +import { ExecuteRequest } from '../events/requests/ExecuteRequest'; +import { ExecuteV3Response } from '../events/responses/ExecuteV3Response'; +import { AddBreakpointsResponse } from '../events/responses/AddBreakpointsResponse'; +import { AddBreakpointsRequest } from '../events/requests/AddBreakpointsRequest'; +import { AddConditionalBreakpointsRequest } from '../events/requests/AddConditionalBreakpointsRequest'; +import { AddConditionalBreakpointsResponse } from '../events/responses/AddConditionalBreakpointsResponse'; +import { ListBreakpointsRequest } from '../events/requests/ListBreakpointsRequest'; +import { ListBreakpointsResponse } from '../events/responses/ListBreakpointsResponse'; +import { RemoveBreakpointsResponse } from '../events/responses/RemoveBreakpointsResponse'; +import { RemoveBreakpointsRequest } from '../events/requests/RemoveBreakpointsRequest'; +import { expectThrows, expectThrowsAsync } from '../../testHelpers.spec'; +import { StackTraceV3Response } from '../events/responses/StackTraceV3Response'; const sinon = createSandbox(); @@ -44,7 +58,7 @@ describe('DebugProtocolClient', () => { } beforeEach(async () => { - sinon.stub(console, 'log').callsFake((...args) => { }); + // sinon.stub(console, 'log').callsFake((...args) => { }); const options = { controllerPort: undefined as number, @@ -218,6 +232,19 @@ describe('DebugProtocolClient', () => { }); describe('threads()', () => { + function thread(extra?: Partial) { + return { + isPrimary: true, + stopReason: StopReason.Break, + stopReasonDetail: 'because', + lineNumber: 2, + functionName: 'main', + filePath: 'pkg:/source/main.brs', + codeSnippet: 'sub main()', + ...extra ?? {} + }; + } + it('skips sending command when not stopped', async () => { await connect(); @@ -237,6 +264,192 @@ describe('DebugProtocolClient', () => { const response = await client.threads(); expect(response.data.errorCode).to.eql(ErrorCode.CANT_CONTINUE); }); + + it('ignores the `isPrimary` flag when threadHoppingWorkaround is enabled', async () => { + await connect(); + client.protocolVersion = '2.0.0'; + client.primaryThread = 0; + plugin.pushResponse(ThreadsResponse.fromJson({ + requestId: 1, + threads: [ + thread({ + isPrimary: false + }), + thread({ + isPrimary: true + }) + ] + })); + + await client.threads(); + expect(client.primaryThread).to.eql(0); + }); + + it('honors the `isPrimary` flag when threadHoppingWorkaround is disabled', async () => { + await connect(); + client.protocolVersion = '3.1.0'; + client.primaryThread = 0; + plugin.pushResponse(ThreadsResponse.fromJson({ + requestId: 1, + threads: [ + thread({ + isPrimary: false + }), + thread({ + isPrimary: true + }) + ] + })); + + await client.threads(); + expect(client.primaryThread).to.eql(1); + }); + }); + + describe('getStackTrace', () => { + it('skips request if not stopped', async () => { + await connect(); + client.isStopped = false; + + await client.getStackTrace(); + expect(plugin.latestRequest).not.to.be.instanceof(StackTraceResponse); + }); + }); + + describe('executeCommand', () => { + it('skips sending command if not stopped', async () => { + await connect(); + client.isStopped = false; + await client.executeCommand('code'); + expect(plugin.latestRequest).not.instanceof(ExecuteRequest); + }); + + it('sends command when client is stopped', async () => { + await connect(); + + //the response structure doesn't matter, this test is to verify the request was properly built + plugin.pushResponse(ExecuteV3Response.fromJson({} as any)); + + const response = await client.executeCommand('print 123', 1, 2); + expect(plugin.getLatestRequest().data).to.include({ + requestId: plugin.latestRequest.data.requestId, + stackFrameIndex: 1, + threadIndex: 2, + sourceCode: 'print 123' + }); + }); + }); + + describe('addBreakpoints', () => { + it('skips sending command on empty breakpoints array', async () => { + await connect(); + await client.addBreakpoints(undefined); + expect(plugin.latestRequest).not.instanceof(AddBreakpointsResponse); + + await client.addBreakpoints([]); + expect(plugin.latestRequest).not.instanceof(AddBreakpointsRequest); + }); + + it('sends AddBreakpointsRequest when conditional breakpoints are NOT supported', async () => { + await connect(); + client.protocolVersion = '2.0.0'; + + //response structure doesn't matter, we're verifying that the request was properly built + plugin.pushResponse(AddBreakpointsResponse.fromJson({} as any)); + await client.addBreakpoints([{ + filePath: 'pkg:/source/main.brs', + lineNumber: 12, + conditionalExpression: 'true or true' + }]); + + expect(plugin.getLatestRequest()).instanceof(AddBreakpointsRequest); + expect(plugin.getLatestRequest().data.breakpoints[0]).not.haveOwnProperty('conditionalExpression'); + }); + + it('sends AddConditionalBreakpointsRequest when conditional breakpoints ARE supported', async () => { + await connect(); + client.protocolVersion = '3.1.0'; + + //response structure doesn't matter, we're verifying that the request was properly built + plugin.pushResponse(AddConditionalBreakpointsResponse.fromJson({} as any)); + await client.addBreakpoints([{ + filePath: 'pkg:/source/main.brs', + lineNumber: 12, + conditionalExpression: 'true or true' + }]); + + expect(plugin.getLatestRequest()).instanceof(AddConditionalBreakpointsRequest); + expect(plugin.getLatestRequest().data.breakpoints[0].conditionalExpression).to.eql('true or true'); + }); + + it('includes complib prefix when supported', async () => { + await connect(); + client.protocolVersion = '3.1.0'; + + //response structure doesn't matter, we're verifying that the request was properly built + plugin.pushResponse(AddConditionalBreakpointsResponse.fromJson({} as any)); + await client.addBreakpoints([{ + filePath: 'pkg:/source/main.brs', + lineNumber: 12, + componentLibraryName: 'myapp' + }]); + + expect(plugin.getLatestRequest().data.breakpoints[0].filePath).to.eql('lib:/myapp/source/main.brs'); + }); + + it('excludes complib prefix when not supported', async () => { + await connect(); + client.protocolVersion = '2.0.0'; + + //response structure doesn't matter, we're verifying that the request was properly built + plugin.pushResponse(AddConditionalBreakpointsResponse.fromJson({} as any)); + await client.addBreakpoints([{ + filePath: 'pkg:/source/main.brs', + lineNumber: 12, + componentLibraryName: 'myapp' + }]); + + expect(plugin.getLatestRequest().data.breakpoints[0].filePath).to.eql('pkg:/source/main.brs'); + }); + }); + + describe('listBreakpoints', () => { + it('sends request when stopped', async () => { + await connect(); + client.isStopped = true; + + plugin.pushResponse(ListBreakpointsResponse.fromBuffer(null)); + await client.listBreakpoints(); + expect(plugin.latestRequest).instanceof(ListBreakpointsRequest); + }); + + it('sends request when running', async () => { + await connect(); + client.isStopped = false; + + plugin.pushResponse(ListBreakpointsResponse.fromBuffer(null)); + await client.listBreakpoints(); + expect(plugin.latestRequest).instanceof(ListBreakpointsRequest); + }); + }); + + describe('removeBreakpoints', () => { + it('sends breakpoint ids', async () => { + await connect(); + + //response structure doesn't matter, we're verifying that the request was properly built + plugin.pushResponse(RemoveBreakpointsResponse.fromJson({} as any)); + await client.removeBreakpoints([1, 2, 3]); + + expect(plugin.getLatestRequest().data.breakpointIds).to.eql([1, 2, 3]); + }); + + it('skips sending command if no breakpoints were provided', async () => { + await connect(); + + await client.removeBreakpoints(undefined); + expect(plugin.latestRequest).not.instanceof(RemoveBreakpointsRequest); + }); }); it('knows when to enable complib specific breakpoints', () => { @@ -364,6 +577,16 @@ describe('DebugProtocolClient', () => { }); describe('getVariables', () => { + + it('skips sending the request if not stopped', async () => { + await connect(); + + client.isStopped = false; + + await client.getVariables(); + expect(plugin.latestRequest).not.instanceof(VariablesRequest); + }); + it('honors protocol version when deciding to send forceCaseInsensitive variable information', async () => { await client.connect(); //send the AllThreadsStopped event, and also wait for the client to suspend @@ -433,4 +656,44 @@ describe('DebugProtocolClient', () => { } as VariablesRequest['data']); }); }); + + describe('sendRequest', () => { + it('throws when controller is missing', async () => { + await connect(); + + delete client['controllerClient']; + await expectThrowsAsync(async () => { + await client.listBreakpoints(); + }, 'Controller connection was closed - Command: ListBreakpoints'); + }); + + it('resolves only for matching requestId', async () => { + await connect(); + + plugin.pushResponse(ListBreakpointsResponse.fromJson({ + requestId: 10, + breakpoints: [{ + id: 123, + errorCode: 0, + ignoreCount: 2 + }] + })); + plugin.pushResponse(StackTraceV3Response.fromJson({ + requestId: 12, + entries: [] + })); + + //run both requests in quick succession so they both are listening to both responses + const [listBreakpointsResponse, getStackTraceResponse] = await Promise.all([ + client.listBreakpoints(), + client.getStackTrace() + ]); + expect(listBreakpointsResponse.data.breakpoints[0]).to.include({ + id: 123, + errorCode: 0, + ignoreCount: 2 + }); + expect(getStackTraceResponse.data.entries).to.eql([]); + }); + }); }); diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index e25c3617..54705a06 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -380,7 +380,7 @@ export class DebugProtocolClient { } } - public async addBreakpoints(breakpoints: Array): Promise { + public async addBreakpoints(breakpoints: Array): Promise { const { enableComponentLibrarySpecificBreakpoints } = this; if (breakpoints?.length > 0) { const json = { @@ -399,11 +399,11 @@ export class DebugProtocolClient { if (this.supportsConditionalBreakpoints) { return this.sendRequest( - AddBreakpointsRequest.fromJson(json) + AddConditionalBreakpointsRequest.fromJson(json) ); } else { return this.sendRequest( - AddConditionalBreakpointsRequest.fromJson(json) + AddBreakpointsRequest.fromJson(json) ); } } @@ -446,7 +446,8 @@ export class DebugProtocolClient { this.logger.debug('makeRequest', `requestId=${request.data.requestId}`, request); if (this.controllerClient) { - this.controllerClient.write(request.toBuffer()); + const buffer = request.toBuffer(); + this.controllerClient.write(buffer); } else { throw new Error(`Controller connection was closed - Command: ${Command[request.data.command]}`); } @@ -796,11 +797,11 @@ export interface ConstructorOptions { * Is the event a ProtocolUpdate update */ export function isProtocolUpdate(event: ProtocolUpdate | ProtocolResponse): event is ProtocolUpdate { - return event.data.requestId === 0; + return event?.constructor?.name.endsWith('Update') && event?.data?.requestId === 0; } /** * Is the event a ProtocolResponse */ export function isProtocolResponse(event: ProtocolUpdate | ProtocolResponse): event is ProtocolResponse { - return event.data.requestId !== 0; + return event?.constructor?.name.endsWith('Response') && event?.data?.requestId !== 0; } diff --git a/src/debugProtocol/events/requests/HandshakeRequest.ts b/src/debugProtocol/events/requests/HandshakeRequest.ts index f80d2a41..2335b5d1 100644 --- a/src/debugProtocol/events/requests/HandshakeRequest.ts +++ b/src/debugProtocol/events/requests/HandshakeRequest.ts @@ -10,7 +10,7 @@ import type { Command } from '../../Constants'; */ export class HandshakeRequest implements ProtocolRequest { /** - * A hardcoded id for the handshake classes to help them flow through the request/response flow even though they don't look the same + * A hardcoded id for the handshake classes to help them flow through the request/response flow even though Handshake events don't look the same as other protocol events */ public static REQUEST_ID = 4294967295; diff --git a/src/debugProtocol/server/DebugProtocolServer.ts b/src/debugProtocol/server/DebugProtocolServer.ts index 0cf91a87..ee22faaf 100644 --- a/src/debugProtocol/server/DebugProtocolServer.ts +++ b/src/debugProtocol/server/DebugProtocolServer.ts @@ -86,9 +86,11 @@ export class DebugProtocolServer { //anytime we receive incoming data from the client this.client.on('data', (data) => { //queue up processing the new data, chunk by chunk - void this.bufferQueue.run(() => { + void this.bufferQueue.run(async () => { this.buffer = Buffer.concat([this.buffer, data]); - void this.process(); + while (await this.process()) { + //the loop condition is the actual work + } return true; }); }); @@ -171,6 +173,10 @@ export class DebugProtocolServer { } } + /** + * Process a single request. + * @returns true if successfully processed a request, and false if not + */ private async process() { try { this.logger.log('process() start', { buffer: this.buffer.toJSON() }); @@ -227,9 +233,11 @@ export class DebugProtocolServer { //send the response to the client. (TODO handle when the response is missing) await this.sendResponse(response); + return true; } catch (e) { this.logger.error('process() error', e); } + return false; } /** diff --git a/src/testHelpers.spec.ts b/src/testHelpers.spec.ts index cba1e73d..512a3fef 100644 --- a/src/testHelpers.spec.ts +++ b/src/testHelpers.spec.ts @@ -89,3 +89,17 @@ export function expectThrows(callback: () => any, expectedMessage = undefined, f throw new Error(failedTestMessage); } } +export async function expectThrowsAsync(callback: () => any, expectedMessage = undefined, failedTestMessage = 'Expected to throw but did not') { + let wasExceptionThrown = false; + try { + await Promise.resolve(callback()); + } catch (e) { + wasExceptionThrown = true; + if (expectedMessage) { + expect(e.message).to.eql(expectedMessage); + } + } + if (wasExceptionThrown === false) { + throw new Error(failedTestMessage); + } +} From 61c77d86496e05a3cd6bc5d0a544713e8179ab15 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 1 Feb 2023 06:11:58 -0500 Subject: [PATCH 29/74] Support for DebugProtocolReplaySession --- src/adapters/DebugProtocolAdapter.spec.ts | 6 +- src/debugProtocol/Constants.ts | 14 +- .../DebugProtocolReplaySession.spec.ts | 103 +++++ .../DebugProtocolReplaySession.ts | 234 +++++++++++ .../client/DebugProtocolClient.spec.ts | 158 +++++++- .../client/DebugProtocolClient.ts | 379 ++++++++++++------ .../events/requests/AddBreakpointsRequest.ts | 4 +- .../AddConditionalBreakpointsRequest.ts | 4 +- .../events/requests/ContinueRequest.ts | 4 +- .../events/requests/ExecuteRequest.ts | 4 +- .../events/requests/ExitChannelRequest.ts | 4 +- .../events/requests/ListBreakpointsRequest.ts | 4 +- .../requests/RemoveBreakpointsRequest.ts | 6 +- .../events/requests/StackTraceRequest.ts | 4 +- .../events/requests/StepRequest.ts | 4 +- .../events/requests/StopRequest.ts | 3 + .../events/requests/ThreadsRequest.ts | 4 +- .../events/requests/VariablesRequest.ts | 4 +- .../responses/ListBreakpointsResponse.ts | 3 +- .../events/responses/ThreadsResponse.spec.ts | 1 + .../events/updates/AllThreadsStoppedUpdate.ts | 4 +- .../events/updates/BreakpointErrorUpdate.ts | 4 +- .../events/updates/CompileErrorUpdate.ts | 4 +- .../server/DebugProtocolServer.ts | 73 ++-- src/debugProtocol/server/ProtocolPlugin.ts | 2 +- src/managers/ActionQueue.ts | 4 + 26 files changed, 848 insertions(+), 190 deletions(-) create mode 100644 src/debugProtocol/DebugProtocolReplaySession.spec.ts create mode 100644 src/debugProtocol/DebugProtocolReplaySession.ts diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index 7625b6b9..efdff0b9 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -27,14 +27,14 @@ describe('DebugProtocolAdapter', () => { beforeEach(async () => { // sinon.stub(console, 'log').callsFake((...args) => { }); const options = { - controllerPort: undefined as number, + controlPort: undefined as number, host: '127.0.0.1' }; adapter = new DebugProtocolAdapter(options, undefined, undefined); - if (!options.controllerPort) { - options.controllerPort = await portfinder.getPortPromise(); + if (!options.controlPort) { + options.controlPort = await portfinder.getPortPromise(); } server = new DebugProtocolServer(options); plugin = server.plugins.add(new DebugProtocolServerTestPlugin()); diff --git a/src/debugProtocol/Constants.ts b/src/debugProtocol/Constants.ts index 4dc6f975..a53f1ff9 100644 --- a/src/debugProtocol/Constants.ts +++ b/src/debugProtocol/Constants.ts @@ -188,7 +188,12 @@ export enum UpdateType { * A compilation error occurred * @since protocol 3.1 */ - CompileError = 'CompileError' + CompileError = 'CompileError', + /** + * Breakpoints were successfully verified + * @since protocol 3.2 + */ + // BreakpointVerified = 'BreakpointVerified' } /** * The integer values for `UPDATE_TYPE`. Only used for serializing/deserializing over the debug protocol. Use `UpdateType` in your code. @@ -199,6 +204,11 @@ export enum UpdateTypeCode { AllThreadsStopped = 2, ThreadAttached = 3, BreakpointError = 4, - CompileError = 5 + CompileError = 5, + // /** + // * Breakpoints were successfully verified + // * @since protocol 3.2 + // */ + // BreakpointVerified = 6 } diff --git a/src/debugProtocol/DebugProtocolReplaySession.spec.ts b/src/debugProtocol/DebugProtocolReplaySession.spec.ts new file mode 100644 index 00000000..edbafce2 --- /dev/null +++ b/src/debugProtocol/DebugProtocolReplaySession.spec.ts @@ -0,0 +1,103 @@ +/* eslint-disable prefer-arrow-callback */ +import { DebugProtocolReplaySession } from './DebugProtocolReplaySession'; + +describe('DebugProtocolReplaySession', () => { + it.skip('works for this specific thing', async function test() { + this.timeout(10000000000); + const text = ` + {"type":"client-to-server","timestamp":"2023-01-27T19:48:25.607Z","buffer":{"type":"Buffer","data":[98,115,100,101,98,117,103,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:48:25.609Z","buffer":{"type":"Buffer","data":[98,115,100,101,98,117,103,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:48:25.666Z","buffer":{"type":"Buffer","data":[3,0,0,0,2,0,0,0,0,0,0,0,12,0,0,0,131,224,122,24,131,1,0,0,20,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,146,31,0,0,27,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,4,66,82,69,65,75,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:48:25.676Z","buffer":{"type":"Buffer","data":[12,0,0,0,1,0,0,0,3,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:48:25.680Z","buffer":{"type":"Buffer","data":[91,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:48:25.728Z","buffer":{"type":"Buffer","data":[1,0,0,0,0,0,0,0,1,0,0,0,1,4,0,0,0,66,82,69,65,75,0,1,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,115,117,98,77,97,105,110,40,105,110,112,117,116,65,114,103,117,109,101,110,116,115,32,97,115,32,111,98,106,101,99,116,41,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:48:25.731Z","buffer":{"type":"Buffer","data":[16,0,0,0,2,0,0,0,4,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:48:25.734Z","buffer":{"type":"Buffer","data":[46,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:48:25.790Z","buffer":{"type":"Buffer","data":[2,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:48:25.803Z","buffer":{"type":"Buffer","data":[12,0,0,0,3,0,0,0,2,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:48:25.806Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:48:25.851Z","buffer":{"type":"Buffer","data":[3,0,0,0,0,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:06.310Z","buffer":{"type":"Buffer","data":[98,0,0,0,4,0,0,0,7,0,0,0,1,0,0,0,108,105,98,58,47,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,186,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:06.312Z","buffer":{"type":"Buffer","data":[28,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:06.359Z","buffer":{"type":"Buffer","data":[4,0,0,0,0,0,0,0,1,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:06.405Z","buffer":{"type":"Buffer","data":[1,0,0,0,0,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:06.454Z","buffer":{"type":"Buffer","data":[28,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:06.499Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:15.274Z","buffer":{"type":"Buffer","data":[27,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:15.322Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,2,0,0,0,5,0,0,0,4,66,82,69,65,75,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:15.414Z","buffer":{"type":"Buffer","data":[12,0,0,0,5,0,0,0,3,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:15.435Z","buffer":{"type":"Buffer","data":[129,3,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:15.476Z","buffer":{"type":"Buffer","data":[5,0,0,0,0,0,0,0,7,0,0,0,0,4,0,0,0,66,82,69,65,75,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,22,0,0,0,36,97,110,111,110,95,50,97,54,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,119,105,100,116,104,32,61,32,101,118,101,110,116,46,103,101,116,68,97,116,97,40,41,0,0,4,0,0,0,66,82,69,65,75,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,9,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,49,48,48,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,49,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,186,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,105,102,32,99,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,34,99,97,114,111,117,115,101,108,34,32,116,104,101,110,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,50,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:15.494Z","buffer":{"type":"Buffer","data":[16,0,0,0,6,0,0,0,4,0,0,0,5,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:15.494Z","buffer":{"type":"Buffer","data":[16,0,0,0,7,0,0,0,4,0,0,0,0,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:15.495Z","buffer":{"type":"Buffer","data":[16,0,0,0,8,0,0,0,4,0,0,0,1,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:15.495Z","buffer":{"type":"Buffer","data":[16,0,0,0,9,0,0,0,4,0,0,0,2,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:15.495Z","buffer":{"type":"Buffer","data":[16,0,0,0,10,0,0,0,4,0,0,0,3,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:15.496Z","buffer":{"type":"Buffer","data":[16,0,0,0,11,0,0,0,4,0,0,0,4,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:15.496Z","buffer":{"type":"Buffer","data":[16,0,0,0,12,0,0,0,4,0,0,0,6,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:15.508Z","buffer":{"type":"Buffer","data":[166,1,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:15.514Z","buffer":{"type":"Buffer","data":[6,0,0,0,0,0,0,0,5,0,0,0,186,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,14,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,99,114,101,101,110,115,47,67,97,116,97,108,111,103,83,99,114,101,101,110,47,67,97,116,97,108,111,103,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,67,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,80,97,114,115,101,114,115,95,95,108,105,98,48,46,98,114,115,0,16,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,239,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,66,97,115,101,95,95,108,105,98,48,46,98,114,115,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:15.569Z","buffer":{"type":"Buffer","data":[46,0,0,0,7,0,0,0,0,0,0,0,1,0,0,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,129,0,0,0,8,0,0,0,0,0,0,0,1,0,0,0,22,0,0,0,36,97,110,111,110,95,50,97,54,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,80,0,0,0,9,0,0,0,0,0,0,0,1,0,0,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,104,0,0,0,10,0,0,0,0,0,0,0,1,0,0,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,110,0,0,0,11,0,0,0,0,0,0,0,1,0,0,0,19,0,0,0,32,50,49,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,116,0,0,0,12,0,0,0,0,0,0,0,1,0,0,0,21,0,0,0,32,50,50,50,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:16.018Z","buffer":{"type":"Buffer","data":[25,0,0,0,13,0,0,0,5,0,0,0,1,5,0,0,0,4,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:16.021Z","buffer":{"type":"Buffer","data":[222,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:16.065Z","buffer":{"type":"Buffer","data":[13,0,0,0,0,0,0,0,14,0,0,0,93,1,99,111,110,116,101,110,116,0,2,0,0,0,13,3,0,0,0,41,8,103,108,111,98,97,108,0,105,102,71,108,111,98,97,108,0,29,1,109,0,5,0,0,0,13,15,0,0,0,29,2,115,101,99,116,105,111,110,115,0,1,0,0,0,7,0,0,0,0,93,1,115,101,99,116,105,111,110,0,2,0,0,0,13,8,0,0,0,57,13,99,111,109,112,111,110,101,110,116,116,121,112,101,0,2,0,0,0,99,97,114,111,117,115,101,108,0,9,16,99,97,114,111,117,115,101,108,99,111,109,112,111,110,101,110,116,116,121,112,101,0,9,16,116,97,98,108,105,115,116,0,9,16,99,104,105,112,108,105,115,116,0,9,16,99,97,114,111,117,115,101,108,0,9,16,103,114,105,100,0,9,16,104,101,114,111,0,9,16,108,105,115,116,0,9,16,102,97,108,108,98,97,99,107,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:21.256Z","buffer":{"type":"Buffer","data":[12,0,0,0,14,0,0,0,2,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:21.257Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:21.299Z","buffer":{"type":"Buffer","data":[14,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:22.293Z","buffer":{"type":"Buffer","data":[27,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:22.341Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,2,0,0,0,5,0,0,0,4,66,82,69,65,75,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:22.432Z","buffer":{"type":"Buffer","data":[12,0,0,0,15,0,0,0,3,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:22.453Z","buffer":{"type":"Buffer","data":[112,3,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:22.494Z","buffer":{"type":"Buffer","data":[15,0,0,0,0,0,0,0,7,0,0,0,0,4,0,0,0,66,82,69,65,75,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,123,1,0,0,117,116,105,108,115,95,99,114,101,97,116,101,99,108,105,112,112,105,110,103,114,101,99,116,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,115,111,117,114,99,101,47,117,116,105,108,115,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,103,101,116,95,110,117,109,98,101,114,40,99,108,105,112,112,105,110,103,67,111,110,102,105,103,117,114,97,116,105,111,110,44,32,34,119,105,100,116,104,34,41,0,0,4,0,0,0,66,82,69,65,75,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,9,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,49,48,48,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,49,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,186,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,105,102,32,99,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,34,99,97,114,111,117,115,101,108,34,32,116,104,101,110,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,50,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:22.511Z","buffer":{"type":"Buffer","data":[16,0,0,0,16,0,0,0,4,0,0,0,5,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:22.512Z","buffer":{"type":"Buffer","data":[16,0,0,0,17,0,0,0,4,0,0,0,0,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:22.512Z","buffer":{"type":"Buffer","data":[16,0,0,0,18,0,0,0,4,0,0,0,1,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:22.512Z","buffer":{"type":"Buffer","data":[16,0,0,0,19,0,0,0,4,0,0,0,2,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:22.513Z","buffer":{"type":"Buffer","data":[16,0,0,0,20,0,0,0,4,0,0,0,3,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:22.513Z","buffer":{"type":"Buffer","data":[16,0,0,0,21,0,0,0,4,0,0,0,4,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:22.513Z","buffer":{"type":"Buffer","data":[16,0,0,0,22,0,0,0,4,0,0,0,6,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:22.526Z","buffer":{"type":"Buffer","data":[166,1,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:22.527Z","buffer":{"type":"Buffer","data":[16,0,0,0,0,0,0,0,5,0,0,0,186,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,14,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,99,114,101,101,110,115,47,67,97,116,97,108,111,103,83,99,114,101,101,110,47,67,97,116,97,108,111,103,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,67,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,80,97,114,115,101,114,115,95,95,108,105,98,48,46,98,114,115,0,16,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,239,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,66,97,115,101,95,95,108,105,98,48,46,98,114,115,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:22.571Z","buffer":{"type":"Buffer","data":[46,0,0,0,17,0,0,0,0,0,0,0,1,0,0,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,217,0,0,0,18,0,0,0,0,0,0,0,2,0,0,0,123,1,0,0,117,116,105,108,115,95,99,114,101,97,116,101,99,108,105,112,112,105,110,103,114,101,99,116,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,115,111,117,114,99,101,47,117,116,105,108,115,95,95,108,105,98,48,46,98,114,115,0,26,0,0,0,117,116,105,108,115,95,99,114,101,97,116,101,99,108,105,112,112,105,110,103,114,101,99,116,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,80,0,0,0,19,0,0,0,0,0,0,0,1,0,0,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,104,0,0,0,20,0,0,0,0,0,0,0,1,0,0,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,110,0,0,0,21,0,0,0,0,0,0,0,1,0,0,0,19,0,0,0,32,50,49,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,116,0,0,0,22,0,0,0,0,0,0,0,1,0,0,0,21,0,0,0,32,50,50,50,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:23.015Z","buffer":{"type":"Buffer","data":[25,0,0,0,23,0,0,0,5,0,0,0,1,5,0,0,0,4,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:23.019Z","buffer":{"type":"Buffer","data":[246,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:23.064Z","buffer":{"type":"Buffer","data":[23,0,0,0,0,0,0,0,14,0,0,0,93,1,99,111,110,116,101,110,116,0,2,0,0,0,13,3,0,0,0,41,8,103,108,111,98,97,108,0,105,102,71,108,111,98,97,108,0,29,1,109,0,5,0,0,0,13,15,0,0,0,29,2,115,101,99,116,105,111,110,115,0,1,0,0,0,7,1,0,0,0,93,1,115,101,99,116,105,111,110,0,2,0,0,0,13,7,0,0,0,57,13,99,111,109,112,111,110,101,110,116,116,121,112,101,0,2,0,0,0,99,104,105,112,45,108,105,115,116,0,57,13,99,97,114,111,117,115,101,108,99,111,109,112,111,110,101,110,116,116,121,112,101,0,3,0,0,0,99,97,114,100,45,119,105,100,101,0,9,16,116,97,98,108,105,115,116,0,9,16,99,104,105,112,108,105,115,116,0,29,1,99,97,114,111,117,115,101,108,0,2,0,0,0,13,11,0,0,0,9,16,103,114,105,100,0,9,16,104,101,114,111,0,9,16,108,105,115,116,0,9,16,102,97,108,108,98,97,99,107,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:23.169Z","buffer":{"type":"Buffer","data":[12,0,0,0,24,0,0,0,2,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:23.171Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:23.220Z","buffer":{"type":"Buffer","data":[24,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:24.319Z","buffer":{"type":"Buffer","data":[27,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:24.373Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,2,0,0,0,5,0,0,0,4,66,82,69,65,75,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:24.461Z","buffer":{"type":"Buffer","data":[12,0,0,0,25,0,0,0,3,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:24.485Z","buffer":{"type":"Buffer","data":[129,3,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:24.527Z","buffer":{"type":"Buffer","data":[25,0,0,0,0,0,0,0,7,0,0,0,0,4,0,0,0,66,82,69,65,75,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,22,0,0,0,36,97,110,111,110,95,50,97,54,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,119,105,100,116,104,32,61,32,101,118,101,110,116,46,103,101,116,68,97,116,97,40,41,0,0,4,0,0,0,66,82,69,65,75,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,9,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,49,48,48,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,49,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,186,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,105,102,32,99,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,34,99,97,114,111,117,115,101,108,34,32,116,104,101,110,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,50,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:24.544Z","buffer":{"type":"Buffer","data":[16,0,0,0,26,0,0,0,4,0,0,0,5,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:24.545Z","buffer":{"type":"Buffer","data":[16,0,0,0,27,0,0,0,4,0,0,0,0,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:24.545Z","buffer":{"type":"Buffer","data":[16,0,0,0,28,0,0,0,4,0,0,0,1,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:24.545Z","buffer":{"type":"Buffer","data":[16,0,0,0,29,0,0,0,4,0,0,0,2,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:24.545Z","buffer":{"type":"Buffer","data":[16,0,0,0,30,0,0,0,4,0,0,0,3,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:24.546Z","buffer":{"type":"Buffer","data":[16,0,0,0,31,0,0,0,4,0,0,0,4,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:24.546Z","buffer":{"type":"Buffer","data":[16,0,0,0,32,0,0,0,4,0,0,0,6,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:24.559Z","buffer":{"type":"Buffer","data":[166,1,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:24.560Z","buffer":{"type":"Buffer","data":[26,0,0,0,0,0,0,0,5,0,0,0,186,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,14,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,99,114,101,101,110,115,47,67,97,116,97,108,111,103,83,99,114,101,101,110,47,67,97,116,97,108,111,103,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,67,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,80,97,114,115,101,114,115,95,95,108,105,98,48,46,98,114,115,0,16,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,239,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,66,97,115,101,95,95,108,105,98,48,46,98,114,115,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:24.605Z","buffer":{"type":"Buffer","data":[46,0,0,0,27,0,0,0,0,0,0,0,1,0,0,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,129,0,0,0,28,0,0,0,0,0,0,0,1,0,0,0,22,0,0,0,36,97,110,111,110,95,50,97,54,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,80,0,0,0,29,0,0,0,0,0,0,0,1,0,0,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,104,0,0,0,30,0,0,0,0,0,0,0,1,0,0,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,110,0,0,0,31,0,0,0,0,0,0,0,1,0,0,0,19,0,0,0,32,50,49,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,116,0,0,0,32,0,0,0,0,0,0,0,1,0,0,0,21,0,0,0,32,50,50,50,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:25.035Z","buffer":{"type":"Buffer","data":[25,0,0,0,33,0,0,0,5,0,0,0,1,5,0,0,0,4,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:25.038Z","buffer":{"type":"Buffer","data":[254,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:25.088Z","buffer":{"type":"Buffer","data":[33,0,0,0,0,0,0,0,14,0,0,0,93,1,99,111,110,116,101,110,116,0,2,0,0,0,13,3,0,0,0,41,8,103,108,111,98,97,108,0,105,102,71,108,111,98,97,108,0,29,1,109,0,5,0,0,0,13,15,0,0,0,29,2,115,101,99,116,105,111,110,115,0,1,0,0,0,7,2,0,0,0,93,1,115,101,99,116,105,111,110,0,2,0,0,0,13,9,0,0,0,57,13,99,111,109,112,111,110,101,110,116,116,121,112,101,0,2,0,0,0,99,97,114,111,117,115,101,108,0,57,13,99,97,114,111,117,115,101,108,99,111,109,112,111,110,101,110,116,116,121,112,101,0,3,0,0,0,99,97,114,100,45,119,105,100,101,0,9,16,116,97,98,108,105,115,116,0,29,1,99,104,105,112,108,105,115,116,0,2,0,0,0,13,8,0,0,0,29,1,99,97,114,111,117,115,101,108,0,2,0,0,0,13,11,0,0,0,9,16,103,114,105,100,0,9,16,104,101,114,111,0,9,16,108,105,115,116,0,9,16,102,97,108,108,98,97,99,107,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:25.091Z","buffer":{"type":"Buffer","data":[12,0,0,0,34,0,0,0,2,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:25.093Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:25.135Z","buffer":{"type":"Buffer","data":[34,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:26.341Z","buffer":{"type":"Buffer","data":[27,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:26.393Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,2,0,0,0,4,0,0,0,4,66,82,69,65,75,0,27,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,1,0,0,0,4,66,82,69,65,75,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:49:26.467Z","buffer":{"type":"Buffer","data":[12,0,0,0,35,0,0,0,3,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:26.490Z","buffer":{"type":"Buffer","data":[129,3,0,0]}} + {"type":"server-to-client","timestamp":"2023-01-27T19:49:26.532Z","buffer":{"type":"Buffer","data":[35,0,0,0,0,0,0,0,7,0,0,0,0,4,0,0,0,66,82,69,65,75,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,22,0,0,0,36,97,110,111,110,95,50,97,54,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,119,105,100,116,104,32,61,32,101,118,101,110,116,46,103,101,116,68,97,116,97,40,41,0,0,4,0,0,0,66,82,69,65,75,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,9,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,49,48,48,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,49,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,186,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,105,102,32,99,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,34,99,97,114,111,117,115,101,108,34,32,116,104,101,110,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,50,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} + {"type":"client-to-server","timestamp":"2023-01-27T19:57:18.397Z","buffer":{"type":"Buffer","data":[12,0,0,0,36,0,0,0,122,0,0,0]}} + `; + const session = new DebugProtocolReplaySession({ + bufferLog: text + }); + + await session.run(); + console.log(session); + }); +}); diff --git a/src/debugProtocol/DebugProtocolReplaySession.ts b/src/debugProtocol/DebugProtocolReplaySession.ts new file mode 100644 index 00000000..018e6225 --- /dev/null +++ b/src/debugProtocol/DebugProtocolReplaySession.ts @@ -0,0 +1,234 @@ +import { DebugProtocolClient } from './client/DebugProtocolClient'; +import { defer, util } from '../util'; +import * as portfinder from 'portfinder'; +import type { ProtocolRequest, ProtocolResponse, ProtocolUpdate } from './events/ProtocolEvent'; +import { DebugProtocolServer } from './server/DebugProtocolServer'; +import * as Net from 'net'; +import { ActionQueue } from '../managers/ActionQueue'; +import { IOPortOpenedUpdate } from './events/updates/IOPortOpenedUpdate'; + +export class DebugProtocolReplaySession { + constructor(options: { + bufferLog: string; + }) { + this.parseBufferLog(options?.bufferLog); + } + + /** + * A dumb tcp server that will simply spit back the server buffer data when needed + */ + private server: Net.Socket; + + private client: DebugProtocolClient; + + private entries: Array; + + private parseBufferLog(bufferLog: string) { + this.entries = bufferLog + .split(/\r?\n/g) + .map(x => x.trim()) + .filter(x => !!x) + .map(line => { + const entry = JSON.parse(line); + entry.timestamp = new Date(entry.timestamp as string); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + entry.buffer = Buffer.from(entry.buffer); + return entry; + }); + } + + public result: Array = []; + private finished = defer(); + private controlPort: number; + private ioPort: number; + + public async run() { + this.controlPort = await portfinder.getPortPromise(); + this.ioPort = await portfinder.getPortPromise(); + + await this.createServer(this.controlPort); + this.createClient(this.controlPort); + + await this.client.connect(); + + void this.clientProcess(); + await this.finished.promise; + } + + private createClient(controlPort: number) { + this.client = new DebugProtocolClient({ + controlPort: controlPort, + host: 'localhost' + }); + + //store the responses in the result + this.client.on('response', (response) => { + this.result.push(response); + }); + this.client.on('update', (update) => { + this.result.push(update); + if (update instanceof IOPortOpenedUpdate) { + void this.openIOPort(update); + } + }); + + //anytime the client receives buffer data, we should try and process it + this.client.on('data', (data) => { + this.clientSync.pushActual(data); + void this.clientProcess(); + }); + } + + private openIOPort(update: IOPortOpenedUpdate) { + return new Promise((resolve) => { + const server = new Net.Server({}); + + //whenever a client makes a connection + // eslint-disable-next-line @typescript-eslint/no-misused-promises + server.on('connection', (client: Net.Socket) => { + this.server = client; + //anytime we receive incoming data from the client + client.on('data', (data) => { + //TODO send IO data + }); + }); + server.listen({ + port: this.ioPort, + hostName: 'localhost' + }, () => { + resolve(); + }); + }); + } + + private clientSync = new BufferSync(); + + private clientActionQueue = new ActionQueue(); + + private async clientProcess() { + await this.clientActionQueue.run(async () => { + let clientBuffer = Buffer.alloc(0); + //build a single buffer of client data + while (this.entries[0]?.type === 'client-to-server') { + const entry = this.entries.shift(); + clientBuffer = Buffer.concat([clientBuffer, entry.buffer]); + } + //build and send requests + while (clientBuffer.length > 0) { + const request = DebugProtocolServer.getRequest(clientBuffer, true); + //remove the processed bytes + clientBuffer = clientBuffer.slice(request.readOffset); + + //store this client data for our mock server to recognize and track + this.serverSync.pushExpected(request.toBuffer()); + + //store the request in the result + this.result.push(request); + + //send the request + void this.client.processRequest(request); + + //wait small timeout before sending the next request + await util.sleep(10); + } + this.finalizeIfDone(); + return true; + }); + } + + private finalizeIfDone() { + if (this.clientSync.areInSync && this.serverSync.areInSync && this.entries.length === 0) { + this.finished.resolve(); + } + } + + private createServer(controlPort: number) { + return new Promise((resolve) => { + + const server = new Net.Server({}); + //Roku only allows 1 connection, so we should too. + server.maxConnections = 1; + + //whenever a client makes a connection + // eslint-disable-next-line @typescript-eslint/no-misused-promises + server.on('connection', (client: Net.Socket) => { + this.server = client; + //anytime we receive incoming data from the client + client.on('data', (data) => { + void this.serverProcess(data); + }); + }); + server.listen({ + port: controlPort, + hostName: 'localhost' + }, () => { + resolve(); + }); + }); + } + + private serverActionQueue = new ActionQueue(); + + private serverSync = new BufferSync(); + private async serverProcess(data: Buffer) { + await this.serverActionQueue.run(async () => { + this.serverSync.pushActual(data); + if (this.serverSync.areInSync) { + this.serverSync.clear(); + //send all the server messages, each delayed slightly to simulate the chunked buffer flushing that roku causes + while (this.entries[0]?.type === 'server-to-client') { + const entry = this.entries.shift(); + this.server.write(entry.buffer); + this.clientSync.pushExpected(entry.buffer); + await util.sleep(10); + } + } + this.finalizeIfDone(); + return true; + }); + } +} + +class BufferSync { + private expected = Buffer.alloc(0); + public pushExpected(buffer: Buffer) { + this.expected = Buffer.concat([this.expected, buffer]); + } + + private actual = Buffer.alloc(0); + public pushActual(buffer: Buffer) { + this.actual = Buffer.concat([this.actual, buffer]); + } + + /** + * Are the two buffers in sync? + */ + public get areInSync() { + return JSON.stringify(this.expected) === JSON.stringify(this.actual); + } + + public clear() { + this.expected = Buffer.alloc(0); + this.actual = Buffer.alloc(0); + } +} + +function bufferStartsWith(subject: Buffer, search: Buffer) { + const subjectData = subject.toJSON().data; + const searchData = search.toJSON().data; + for (let i = 0; i < searchData.length; i++) { + if (subjectData[i] !== searchData[i]) { + return false; + } + } + //if we made it to the end of the search, then the subject fully starts with search + return true; +} + +export interface BufferLogEntry { + type: 'client-to-server' | 'server-to-client'; + timestamp: Date; + buffer: Buffer; +} + + diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index 16157d1e..da54dcb1 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -5,7 +5,7 @@ import { createSandbox } from 'sinon'; import { Command, ErrorCode, StepType, StopReason } from '../Constants'; import { DebugProtocolServer } from '../server/DebugProtocolServer'; import * as portfinder from 'portfinder'; -import { util } from '../../util'; +import { defer, util } from '../../util'; import { HandshakeRequest } from '../events/requests/HandshakeRequest'; import { HandshakeResponse } from '../events/responses/HandshakeResponse'; import { HandshakeV3Response } from '../events/responses/HandshakeV3Response'; @@ -31,8 +31,11 @@ import { ListBreakpointsRequest } from '../events/requests/ListBreakpointsReques import { ListBreakpointsResponse } from '../events/responses/ListBreakpointsResponse'; import { RemoveBreakpointsResponse } from '../events/responses/RemoveBreakpointsResponse'; import { RemoveBreakpointsRequest } from '../events/requests/RemoveBreakpointsRequest'; -import { expectThrows, expectThrowsAsync } from '../../testHelpers.spec'; +import { expectThrowsAsync } from '../../testHelpers.spec'; import { StackTraceV3Response } from '../events/responses/StackTraceV3Response'; +import { IOPortOpenedUpdate } from '../events/updates/IOPortOpenedUpdate'; +import * as Net from 'net'; +import { ThreadAttachedUpdate } from '../events/updates/ThreadAttachedUpdate'; const sinon = createSandbox(); @@ -58,15 +61,15 @@ describe('DebugProtocolClient', () => { } beforeEach(async () => { - // sinon.stub(console, 'log').callsFake((...args) => { }); + sinon.stub(console, 'log').callsFake((...args) => { }); const options = { - controllerPort: undefined as number, + controlPort: undefined as number, host: '127.0.0.1' }; - if (!options.controllerPort) { - options.controllerPort = await portfinder.getPortPromise(); + if (!options.controlPort) { + options.controlPort = await portfinder.getPortPromise(); } server = new DebugProtocolServer(options); plugin = server.plugins.add(new DebugProtocolServerTestPlugin()); @@ -126,7 +129,7 @@ describe('DebugProtocolClient', () => { expect(plugin.latestRequest).to.be.instanceOf(ContinueRequest); }); - it('sends the pause command when forced', async () => { + it('sends the pause command', async () => { await connect(); client.isStopped = true; @@ -139,15 +142,6 @@ describe('DebugProtocolClient', () => { expect(plugin.latestRequest).to.be.instanceOf(StopRequest); }); - it('sends the pause command when forced', async () => { - await connect(); - - plugin.pushResponse(GenericV3Response.fromJson({} as any)); - client.isStopped = true; - await client.pause(true); //true means force - expect(plugin.latestRequest).to.be.instanceOf(StopRequest); - }); - it('sends the exitChannel command', async () => { await connect(); @@ -504,7 +498,7 @@ describe('DebugProtocolClient', () => { expect(client.isHandshakeComplete).to.be.equal(false); await client.connect(); - expect(plugin.responses[0].data).to.eql({ + expect(plugin.responses[0]?.data).to.eql({ packetLength: undefined, requestId: HandshakeRequest.REQUEST_ID, errorCode: ErrorCode.OK, @@ -661,10 +655,10 @@ describe('DebugProtocolClient', () => { it('throws when controller is missing', async () => { await connect(); - delete client['controllerClient']; + delete client['controlSocket']; await expectThrowsAsync(async () => { await client.listBreakpoints(); - }, 'Controller connection was closed - Command: ListBreakpoints'); + }, 'Control socket was closed - Command: ListBreakpoints'); }); it('resolves only for matching requestId', async () => { @@ -695,5 +689,131 @@ describe('DebugProtocolClient', () => { }); expect(getStackTraceResponse.data.entries).to.eql([]); }); + + it('recovers on incomplete buffer', async () => { + await connect(); + + const buffer = AllThreadsStoppedUpdate.fromJson({ + stopReason: StopReason.Break, + stopReasonDetail: 'because', + threadIndex: 0 + }).toBuffer(); + + const dataReceivedPromise = client.once('data'); + const promise = client.once('suspend'); + + //write half the buffer + plugin.server['client'].write(buffer.slice(0, 5)); + //wait until we receive that data + await dataReceivedPromise; + //write the rest of the buffer + plugin.server['client'].write(buffer.slice(5)); + + //wait until the update shows up + const update = await promise; + expect(update.data.stopReasonDetail).to.eql('because'); + }); + }); + describe('connectToIoPort', () => { + let ioServer: Net.Server; + let port: number; + let socketPromise: Promise; + + beforeEach(async () => { + port = await portfinder.getPortPromise(); + ioServer = new Net.Server(); + const deferred = defer(); + socketPromise = deferred.promise; + ioServer.listen({ + port: port, + hostName: '0.0.0.0' + }, () => { }); + ioServer.on('connection', (socket) => { + deferred.resolve(socket); + }); + }); + + afterEach(() => { + ioServer?.close(); + }); + + it('supports the IOPortOpened update', async () => { + await connect(); + + const ioOutputPromise = client.once('io-output'); + + await plugin.server.sendUpdate(IOPortOpenedUpdate.fromJson({ + port: port + })); + + const socket = await socketPromise; + socket.write('hello\nworld\n'); + + const output = await ioOutputPromise; + expect(output).to.eql('hello\nworld'); + }); + + it('handles partial lines', async () => { + await connect(); + + await plugin.server.sendUpdate(IOPortOpenedUpdate.fromJson({ + port: port + })); + + const socket = await socketPromise; + const outputMonitors = [ + defer(), + defer(), + defer() + ]; + const output = []; + + const outputPromise = client.once('io-output'); + + client['ioSocket'].on('data', (data) => { + outputMonitors[output.length].resolve(); + output.push(data.toString()); + }); + + socket.write('hello '); + await outputMonitors[0].promise; + socket.write('world\n'); + await outputMonitors[1].promise; + expect(await outputPromise).to.eql('hello world'); + }); + + it('handles failed update', async () => { + await connect(); + const update = IOPortOpenedUpdate.fromJson({ + port: port + }); + update.success = false; + expect( + client['connectToIoPort'](update) + ).to.be.false; + }); + + it('terminates the ioClient on "end"', async () => { + await connect(); + await plugin.server.sendUpdate(IOPortOpenedUpdate.fromJson({ + port: port + })); + await socketPromise; + ioServer.close(); + }); + }); + + it('handles ThreadAttachedUpdate type', async () => { + await connect(); + + const promise = client.once('suspend'); + client.primaryThread = 1; + await plugin.server.sendUpdate(ThreadAttachedUpdate.fromJson({ + stopReason: StopReason.Break, + stopReasonDetail: 'because', + threadIndex: 2 + })); + await promise; + expect(client.primaryThread).to.eql(2); }); }); diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 54705a06..856f395d 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -29,12 +29,13 @@ import { GenericV3Response } from '../events/responses/GenericV3Response'; import { AllThreadsStoppedUpdate } from '../events/updates/AllThreadsStoppedUpdate'; import { CompileErrorUpdate } from '../events/updates/CompileErrorUpdate'; import { GenericResponse } from '../events/responses/GenericResponse'; -import { StackTraceResponse } from '../events/responses/StackTraceResponse'; +import type { StackTraceResponse } from '../events/responses/StackTraceResponse'; import { ThreadsResponse } from '../events/responses/ThreadsResponse'; import { VariablesResponse } from '../events/responses/VariablesResponse'; import { IOPortOpenedUpdate } from '../events/updates/IOPortOpenedUpdate'; import { ThreadAttachedUpdate } from '../events/updates/ThreadAttachedUpdate'; import { StackTraceV3Response } from '../events/responses/StackTraceV3Response'; +import { ActionQueue } from '../../managers/ActionQueue'; export class DebugProtocolClient { @@ -44,10 +45,10 @@ export class DebugProtocolClient { public supportedVersionRange = '<=3.0.0'; constructor( - options: ConstructorOptions + options?: ConstructorOptions ) { this.options = { - controllerPort: 8081, + controlPort: 8081, host: undefined, //override the defaults with the options from parameters ...options ?? {} @@ -68,8 +69,17 @@ export class DebugProtocolClient { public stackFrameIndex: number; private emitter = new EventEmitter(); - private controllerClient: Net.Socket; - private ioClient: Net.Socket; + /** + * The primary socket for this session. It's used to communicate with the debugger by sending commands and receives responses or updates + */ + private controlSocket: Net.Socket; + /** + * A socket where the debug server will send stdio + */ + private ioSocket: Net.Socket; + /** + * The buffer where all unhandled data will be stored until successfully consumed + */ private buffer = Buffer.alloc(0); /** * Is the debugger currently stopped at a line of code in the program @@ -106,8 +116,9 @@ export class DebugProtocolClient { * Get a promise that resolves after an event occurs exactly once */ public once(eventName: 'app-exit' | 'cannot-continue' | 'close' | 'start'): Promise; - public once(eventName: 'runtime-error' | 'suspend'): Promise; + public once(eventName: 'runtime-error' | 'suspend'): Promise; public once(eventName: 'io-output'): Promise; + public once(eventName: 'data'): Promise; public once(eventName: 'protocol-version'): Promise; public once(eventName: 'handshake-verified'): Promise; public once(eventName: string) { @@ -120,9 +131,13 @@ export class DebugProtocolClient { } public on(eventName: 'app-exit' | 'cannot-continue' | 'close' | 'start', handler: () => void); - public on(eventName: 'response', handler: (update: ProtocolResponse) => void); + public on(eventName: 'response', handler: (response: ProtocolResponse) => void); public on(eventName: 'update', handler: (update: ProtocolUpdate) => void); - public on(eventName: 'runtime-error' | 'suspend', handler: (data: AllThreadsStoppedUpdate | ThreadAttachedUpdate) => void); + /** + * The raw data from the server socket. You probably don't need this... + */ + public on(eventName: 'data', handler: (data: Buffer) => void); + public on(eventName: 'runtime-error' | 'suspend', handler: (data: T) => void); public on(eventName: 'io-output', handler: (output: string) => void); public on(eventName: 'protocol-version', handler: (data: ProtocolVersionDetails) => void); public on(eventName: 'handshake-verified', handler: (data: HandshakeResponse) => void); @@ -137,6 +152,7 @@ export class DebugProtocolClient { private emit(eventName: 'response', response: ProtocolResponse); private emit(eventName: 'update', update: ProtocolUpdate); + private emit(eventName: 'data', update: Buffer); private emit(eventName: 'suspend' | 'runtime-error', data: AllThreadsStoppedUpdate | ThreadAttachedUpdate); private emit(eventName: 'app-exit' | 'cannot-continue' | 'close' | 'handshake-verified' | 'io-output' | 'protocol-version' | 'start', data?); private emit(eventName: string, data?) { @@ -147,7 +163,7 @@ export class DebugProtocolClient { }, 0); } - private async establishControllerConnection() { + private async establishControlConnection() { const pendingSockets = new Set(); const connection = await new Promise((resolve) => { util.setInterval((cancelInterval) => { @@ -156,10 +172,10 @@ export class DebugProtocolClient { socket.on('error', (error) => { console.debug(Date.now(), 'Encountered an error connecting to the debug protocol socket. Ignoring and will try again soon', error); }); - socket.connect({ port: this.options.controllerPort, host: this.options.host }, () => { + socket.connect({ port: this.options.controlPort, host: this.options.host }, () => { cancelInterval(); - this.logger.debug(`Connected to debug protocol controller port. Socket ${[...pendingSockets].indexOf(socket)} of ${pendingSockets.size} was the winner`); + this.logger.debug(`Connected to debug protocol control port. Socket ${[...pendingSockets].indexOf(socket)} of ${pendingSockets.size} was the winner`); //clean up all remaining pending sockets for (const pendingSocket of pendingSockets) { pendingSocket.removeAllListeners(); @@ -172,18 +188,23 @@ export class DebugProtocolClient { pendingSockets.clear(); resolve(socket); }); - }, this.options.controllerConnectInterval ?? 250); + }, this.options.controlConnectInterval ?? 250); }); return connection; } - public async connect(): Promise { + /** + * Connect to the debug server. + * @param sendHandshake should the handshake be sent as part of this connect process. If false, `.sendHandshake()` will need to be called before a session can begin + */ + public async connect(sendHandshake = true): Promise { this.logger.log('connect', this.options); // If there is no error, the server has accepted the request and created a new dedicated control socket - this.controllerClient = await this.establishControllerConnection(); + this.controlSocket = await this.establishControlConnection(); - this.controllerClient.on('data', (data) => { + this.controlSocket.on('data', (data) => { + this.emit('data', data); this.buffer = Buffer.concat([this.buffer, data]); this.logger.debug(`on('data'): incoming bytes`, data.length); @@ -195,13 +216,13 @@ export class DebugProtocolClient { this.logger.debug(`buffer size before:`, startBufferSize, ', buffer size after:', endBufferSize, ', bytes consumed:', startBufferSize - endBufferSize); }); - this.controllerClient.on('end', () => { + this.controlSocket.on('end', () => { this.logger.log('TCP connection closed'); this.shutdown('app-exit'); }); // Don't forget to catch error, for your own sake. - this.controllerClient.once('error', (error) => { + this.controlSocket.once('error', (error) => { //the Roku closed the connection for some unknown reason... console.error(`TCP connection error on control port`, error); this.shutdown('close'); @@ -211,23 +232,40 @@ export class DebugProtocolClient { this.on('update', (update) => { this.handleUpdate(update); }); + if (sendHandshake) { + await this.sendHandshake(); + } + return true; + } + + /** + * Send the initial handshake request, and wait for the handshake response + */ + public async sendHandshake() { + return this.processHandshakeRequest( + HandshakeRequest.fromJson({ + magic: DebugProtocolClient.DEBUGGER_MAGIC + }) + ); + } + private async processHandshakeRequest(request: HandshakeRequest) { //send the magic, which triggers the debug session this.logger.log('Sending magic to server'); //send the handshake request, and wait for the handshake response from the device - const response = await this.sendRequest( - HandshakeRequest.fromJson({ - magic: DebugProtocolClient.DEBUGGER_MAGIC + return this.sendRequest(request); + } + + public continue() { + return this.processContinueRequest( + ContinueRequest.fromJson({ + requestId: this.requestIdSequence++ }) ); - - this.verifyHandshake(response); - this.isHandshakeComplete = true; - return response.success; } - public async continue() { + private async processContinueRequest(request: ContinueRequest) { if (this.isStopped) { this.isStopped = false; return this.sendRequest( @@ -238,13 +276,17 @@ export class DebugProtocolClient { } } - public async pause(force = false) { - if (this.isStopped === false || force) { - return this.sendRequest( - StopRequest.fromJson({ - requestId: this.requestIdSequence++ - }) - ); + public pause() { + return this.processStopRequest( + StopRequest.fromJson({ + requestId: this.requestIdSequence++ + }) + ); + } + + private async processStopRequest(request: StopRequest) { + if (this.isStopped === false) { + return this.sendRequest(request); } } @@ -269,17 +311,19 @@ export class DebugProtocolClient { } private async step(stepType: StepType, threadIndex: number): Promise { - this.logger.log('[step]', { stepType: stepType, threadId: threadIndex, stopped: this.isStopped }); + return this.processStepRequest( + StepRequest.fromJson({ + requestId: this.requestIdSequence++, + stepType: stepType, + threadIndex: threadIndex + }) + ); + } + private async processStepRequest(request: StepRequest) { if (this.isStopped) { this.isStopped = false; - let stepResult = await this.sendRequest( - StepRequest.fromJson({ - requestId: this.requestIdSequence++, - stepType: stepType, - threadIndex: threadIndex - }) - ); + let stepResult = await this.sendRequest(request); if (stepResult.data.errorCode === ErrorCode.OK) { this.isStopped = true; //TODO this is not correct. Do we get a new threads event after a step? Perhaps that should be what triggers the event instead of us? @@ -289,10 +333,19 @@ export class DebugProtocolClient { this.emit('cannot-continue'); } return stepResult; + } else { + this.logger.log('[processStepRequest] skipped because not stopped', request); } } public async threads() { + return this.processThreadsRequest( + ThreadsRequest.fromJson({ + requestId: this.requestIdSequence++ + }) + ); + } + public async processThreadsRequest(request: ThreadsRequest) { if (this.isStopped) { let result = await this.sendRequest( ThreadsRequest.fromJson({ @@ -317,6 +370,8 @@ export class DebugProtocolClient { } } return result; + } else { + this.logger.log('[processThreadsRequest] skipped because not stopped', request); } } @@ -324,16 +379,19 @@ export class DebugProtocolClient { * Get the stackTrace from the device IF currently stopped */ public async getStackTrace(threadIndex: number = this.primaryThread) { - if (this.isStopped && threadIndex > -1) { - this.logger.log('getStackTrace()', { threadIndex: threadIndex }); - return this.sendRequest( - StackTraceRequest.fromJson({ - requestId: this.requestIdSequence++, - threadIndex: threadIndex - }) - ); + return this.processStackTraceRequest( + StackTraceRequest.fromJson({ + requestId: this.requestIdSequence++, + threadIndex: threadIndex + }) + ); + } + + private async processStackTraceRequest(request: StackTraceRequest) { + if (this.isStopped && request.data.threadIndex > -1) { + return this.sendRequest(request); } else { - this.logger.log('[getStackTrace] skipped. ', { isStopped: this.isStopped, threadIndex: threadIndex }); + this.logger.log('[getStackTrace] skipped. ', request); } } @@ -349,8 +407,8 @@ export class DebugProtocolClient { * @param threadIndex the index (or perhaps ID?) of the thread to get variables for */ public async getVariables(variablePathEntries: Array = [], stackFrameIndex: number = this.stackFrameIndex, threadIndex: number = this.primaryThread) { - if (this.isStopped && threadIndex > -1) { - const request = VariablesRequest.fromJson({ + return this.processVariablesRequest( + VariablesRequest.fromJson({ requestId: this.requestIdSequence++, threadIndex: threadIndex, stackFrameIndex: stackFrameIndex, @@ -362,21 +420,30 @@ export class DebugProtocolClient { })), //starting in protocol v3.1.0, it supports marking certain path items as case-insensitive (i.e. parts of DottedGet expressions) enableForceCaseInsensitivity: semver.satisfies(this.protocolVersion, '>=3.1.0') && variablePathEntries.length > 0 - }); + }) + ); + } + + private async processVariablesRequest(request: VariablesRequest) { + if (this.isStopped && request.data.threadIndex > -1) { return this.sendRequest(request); } } public async executeCommand(sourceCode: string, stackFrameIndex: number = this.stackFrameIndex, threadIndex: number = this.primaryThread) { - if (this.isStopped && threadIndex > -1) { - return this.sendRequest( - ExecuteRequest.fromJson({ - requestId: this.requestIdSequence++, - threadIndex: threadIndex, - stackFrameIndex: stackFrameIndex, - sourceCode: sourceCode - }) - ); + return this.processExecuteRequest( + ExecuteRequest.fromJson({ + requestId: this.requestIdSequence++, + threadIndex: threadIndex, + stackFrameIndex: stackFrameIndex, + sourceCode: sourceCode + }) + ); + } + + private async processExecuteRequest(request: ExecuteRequest) { + if (this.isStopped && request.data.threadIndex > -1) { + return this.sendRequest(request); } } @@ -411,24 +478,78 @@ export class DebugProtocolClient { } public async listBreakpoints(): Promise { - return this.sendRequest( + return this.processRequest( ListBreakpointsRequest.fromJson({ requestId: this.requestIdSequence++ }) ); } - public async removeBreakpoints(breakpointIds: number[]): Promise { - if (breakpointIds?.length > 0) { - const command = RemoveBreakpointsRequest.fromJson({ + /** + * Remove breakpoints having the specified IDs + */ + public async removeBreakpoints(breakpointIds: number[]) { + return this.processRemoveBreakpointsRequest( + RemoveBreakpointsRequest.fromJson({ requestId: this.requestIdSequence++, breakpointIds: breakpointIds - }); - return this.sendRequest(command); + }) + ); + } + + private async processRemoveBreakpointsRequest(request: RemoveBreakpointsRequest) { + if (request.data.breakpointIds?.length > 0) { + return this.sendRequest(request); } return RemoveBreakpointsResponse.fromJson(null); } + /** + * Given a request, process it in the proper fashion. This is mostly used for external mocking/testing of + * this client, but it should force the client to flow in the same fashion as a live debug session + */ + public async processRequest(request: ProtocolRequest): Promise { + switch (request?.constructor.name) { + case ContinueRequest.name: + return this.processContinueRequest(request as ContinueRequest) as any; + + case ExecuteRequest.name: + return this.processExecuteRequest(request as ExecuteRequest) as any; + + case HandshakeRequest.name: + return this.processHandshakeRequest(request as HandshakeRequest) as any; + + case RemoveBreakpointsRequest.name: + return this.processRemoveBreakpointsRequest(request as RemoveBreakpointsRequest) as any; + + case StackTraceRequest.name: + return this.processStackTraceRequest(request as StackTraceRequest) as any; + + case StepRequest.name: + return this.processStepRequest(request as StepRequest) as any; + + case StopRequest.name: + return this.processStopRequest(request as StopRequest) as any; + + case ThreadsRequest.name: + return this.processThreadsRequest(request as ThreadsRequest) as any; + + case VariablesRequest.name: + return this.processVariablesRequest(request as VariablesRequest) as any; + + //for all other request types, there's no custom business logic, so just pipe them through manually + case AddBreakpointsRequest.name: + case AddConditionalBreakpointsRequest.name: + case ExitChannelRequest.name: + case ListBreakpointsRequest.name: + return this.sendRequest(request); + default: + this.logger.log('Unknown request type. Sending anyway...', request); + //unknown request type. try sending it as-is + return this.sendRequest(request); + } + } + /** * Send a request to the roku device, and get a promise that resolves once we have received the response */ @@ -444,24 +565,28 @@ export class DebugProtocolClient { } }); - this.logger.debug('makeRequest', `requestId=${request.data.requestId}`, request); - if (this.controllerClient) { + this.logger.debug('sendRequest', `requestId=${request.data.requestId}`, request); + if (this.controlSocket) { const buffer = request.toBuffer(); - this.controllerClient.write(buffer); + this.controlSocket.write(buffer); } else { - throw new Error(`Controller connection was closed - Command: ${Command[request.data.command]}`); + throw new Error(`Control socket was closed - Command: ${Command[request.data.command]}`); } }); } - private process(): void { + private process() { + if (this.buffer.length < 1) { + // short circuit if the buffer is empty + return; + } try { if (this.buffer.length < 1) { // short circuit if the buffer is empty return; } - this.logger.log('process(): buffer=', this.buffer.toJSON()); + this.logger.info('[process()]: buffer=', JSON.stringify(this.buffer.toJSON().data)); const event = this.getResponseOrUpdate(this.buffer); @@ -510,19 +635,21 @@ export class DebugProtocolClient { /** * Given a buffer, try to parse into a specific ProtocolResponse or ProtocolUpdate */ - private getResponseOrUpdate(buffer: Buffer): ProtocolResponse | ProtocolUpdate { + public getResponseOrUpdate(buffer: Buffer): ProtocolResponse | ProtocolUpdate { //if we haven't seen a handshake yet, try to convert the buffer into a handshake if (!this.isHandshakeComplete) { + let handshake: HandshakeV3Response | HandshakeResponse; //try building the v3 handshake response first - let handshakev3 = HandshakeV3Response.fromBuffer(buffer); - if (handshakev3.success) { - return handshakev3; - } + handshake = HandshakeV3Response.fromBuffer(buffer); //we didn't get a v3 handshake. try building an older handshake response - let handshake = HandshakeResponse.fromBuffer(buffer); + if (!handshake.success) { + handshake = HandshakeResponse.fromBuffer(buffer); + } if (handshake.success) { + this.verifyHandshake(handshake); return handshake; } + return; } let genericResponse = this.watchPacketLength ? GenericV3Response.fromBuffer(buffer) : GenericResponse.fromBuffer(buffer); @@ -535,65 +662,64 @@ export class DebugProtocolClient { // a nonzero requestId means this is a response to a request that we sent if (genericResponse.data.requestId !== 0) { //requestId 0 means this is an update - return this.getResponse(genericResponse); + const request = this.activeRequests.get(genericResponse.data.requestId); + if (request) { + return DebugProtocolClient.getResponse(this.buffer, request.data.command); + } } else { - return this.getUpdate(); + return this.getUpdate(this.buffer); } } - private getResponse(genericResponse: GenericV3Response): ProtocolResponse { - const request = this.activeRequests.get(genericResponse.data.requestId); - if (!request) { - return; - } - switch (request.data.command) { + public static getResponse(buffer: Buffer, command: Command) { + switch (command) { case Command.Stop: case Command.Continue: case Command.Step: case Command.ExitChannel: - return genericResponse; + return GenericV3Response.fromBuffer(buffer); case Command.Execute: - return ExecuteV3Response.fromBuffer(this.buffer); + return ExecuteV3Response.fromBuffer(buffer); case Command.AddBreakpoints: case Command.AddConditionalBreakpoints: - return AddBreakpointsResponse.fromBuffer(this.buffer); + return AddBreakpointsResponse.fromBuffer(buffer); case Command.ListBreakpoints: - return ListBreakpointsResponse.fromBuffer(this.buffer); + return ListBreakpointsResponse.fromBuffer(buffer); case Command.RemoveBreakpoints: - return RemoveBreakpointsResponse.fromBuffer(this.buffer); + return RemoveBreakpointsResponse.fromBuffer(buffer); case Command.Variables: - return VariablesResponse.fromBuffer(this.buffer); + return VariablesResponse.fromBuffer(buffer); case Command.StackTrace: - return this.watchPacketLength ? StackTraceV3Response.fromBuffer(this.buffer) : StackTraceResponse.fromBuffer(this.buffer); + return StackTraceV3Response.fromBuffer(buffer); case Command.Threads: - return ThreadsResponse.fromBuffer(this.buffer); + return ThreadsResponse.fromBuffer(buffer); default: return undefined; } } - private getUpdate(): ProtocolUpdate { + public getUpdate(buffer: Buffer): ProtocolUpdate { //read the update_type from the buffer (save some buffer parsing time by narrowing to the exact update type) - const updateTypeCode = this.buffer.readUInt32LE( + const updateTypeCode = buffer.readUInt32LE( // if the protocol supports packet length, then update_type is bytes 12-16. Otherwise, it's bytes 8-12 this.watchPacketLength ? 12 : 8 ); const updateType = UpdateTypeCode[updateTypeCode] as UpdateType; - this.logger.log('getUpdate(): update Type:', updateType); + this.logger?.log('getUpdate(): update Type:', updateType); switch (updateType) { case UpdateType.IOPortOpened: //TODO handle this - return IOPortOpenedUpdate.fromBuffer(this.buffer); + return IOPortOpenedUpdate.fromBuffer(buffer); case UpdateType.AllThreadsStopped: - return AllThreadsStoppedUpdate.fromBuffer(this.buffer); + return AllThreadsStoppedUpdate.fromBuffer(buffer); case UpdateType.ThreadAttached: - return ThreadAttachedUpdate.fromBuffer(this.buffer); + return ThreadAttachedUpdate.fromBuffer(buffer); case UpdateType.BreakpointError: //we do nothing with breakpoint errors at this time. - return BreakpointErrorUpdate.fromBuffer(this.buffer); + return BreakpointErrorUpdate.fromBuffer(buffer); case UpdateType.CompileError: - return CompileErrorUpdate.fromBuffer(this.buffer); + return CompileErrorUpdate.fromBuffer(buffer); default: return undefined; } @@ -605,7 +731,13 @@ export class DebugProtocolClient { private handleUpdate(update: ProtocolUpdate) { if (update instanceof AllThreadsStoppedUpdate || update instanceof ThreadAttachedUpdate) { this.isStopped = true; - let eventName: 'runtime-error' | 'suspend' = (update.data.stopReason === StopReason.RuntimeError ? 'runtime-error' : 'suspend'); + + let eventName: 'runtime-error' | 'suspend'; + if (update.data.stopReason === StopReason.RuntimeError) { + eventName = 'runtime-error'; + } else { + eventName = 'suspend'; + } const isValidStopReason = [StopReason.RuntimeError, StopReason.Break, StopReason.StopStatement].includes(update.data.stopReason); @@ -634,6 +766,7 @@ export class DebugProtocolClient { this.logger.log('Protocol Version:', this.protocolVersion); this.watchPacketLength = semver.satisfies(this.protocolVersion, '>=3.0.0'); + this.isHandshakeComplete = true; let handshakeVerified = true; @@ -676,10 +809,10 @@ export class DebugProtocolClient { this.logger.log('Connecting to IO port. response status success =', update.success); if (update.success) { // Create a new TCP client. - this.ioClient = new Net.Socket(); + this.ioSocket = new Net.Socket(); // Send a connection request to the server. this.logger.log('Connect to IO Port: port', update.data, 'host', this.options.host); - this.ioClient.connect({ + this.ioSocket.connect({ port: update.data.port, host: this.options.host }, () => { @@ -688,7 +821,7 @@ export class DebugProtocolClient { this.connectedToIoPort = true; let lastPartialLine = ''; - this.ioClient.on('data', (buffer) => { + this.ioSocket.on('data', (buffer) => { let responseText = buffer.toString(); if (!responseText.endsWith('\n')) { // buffer was split, save the partial line @@ -704,14 +837,14 @@ export class DebugProtocolClient { } }); - this.ioClient.on('end', () => { - this.ioClient.end(); + this.ioSocket.on('end', () => { + this.ioSocket.end(); this.logger.log('Requested an end to the IO connection'); }); // Don't forget to catch error, for your own sake. - this.ioClient.once('error', (err) => { - this.ioClient.end(); + this.ioSocket.once('error', (err) => { + this.ioSocket.end(); this.logger.error(err); }); }); @@ -725,16 +858,16 @@ export class DebugProtocolClient { } private shutdown(eventName: 'app-exit' | 'close') { - if (this.controllerClient) { - this.controllerClient.removeAllListeners(); - this.controllerClient.destroy(); - this.controllerClient = undefined; + if (this.controlSocket) { + this.controlSocket.removeAllListeners(); + this.controlSocket.destroy(); + this.controlSocket = undefined; } - if (this.ioClient) { - this.ioClient.removeAllListeners(); - this.ioClient.destroy(); - this.ioClient = undefined; + if (this.ioSocket) { + this.ioSocket.removeAllListeners(); + this.ioSocket.destroy(); + this.ioSocket = undefined; } this.emit(eventName); @@ -777,20 +910,20 @@ export interface ConstructorOptions { * The port number used to send all debugger commands. This is static/unchanging for Roku devices, * but is configurable here to support unit testing or alternate runtimes (i.e. https://www.npmjs.com/package/brs) */ - controllerPort?: number; + controlPort?: number; /** * The interval (in milliseconds) for how frequently the `connect` - * call should retry connecting to the controller port. At the start of a debug session, + * call should retry connecting to the control port. At the start of a debug session, * the protocol debugger will start trying to connect the moment the channel is sideloaded, * and keep trying until a successful connection is established or the debug session is terminated * @default 250 */ - controllerConnectInterval?: number; + controlConnectInterval?: number; /** * The maximum time (in milliseconds) the debugger will keep retrying connections. * This is here to prevent infinitely pinging the Roku device. */ - controllerConnectMaxTime?: number; + controlConnectMaxTime?: number; } /** diff --git a/src/debugProtocol/events/requests/AddBreakpointsRequest.ts b/src/debugProtocol/events/requests/AddBreakpointsRequest.ts index 3c5f6e7c..b2bda891 100644 --- a/src/debugProtocol/events/requests/AddBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/AddBreakpointsRequest.ts @@ -56,7 +56,9 @@ export class AddBreakpointsRequest implements ProtocolRequest { } public success = false; - + /** + * How many bytes were read by the `fromBuffer` method. Only populated when constructed by `fromBuffer` + */ public readOffset: number = undefined; public data = { diff --git a/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts b/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts index 0ca5bb8f..5854bbef 100644 --- a/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/AddConditionalBreakpointsRequest.ts @@ -66,7 +66,9 @@ export class AddConditionalBreakpointsRequest implements ProtocolRequest { } public success = false; - + /** + * How many bytes were read by the `fromBuffer` method. Only populated when constructed by `fromBuffer` + */ public readOffset: number = undefined; public data = { diff --git a/src/debugProtocol/events/requests/ContinueRequest.ts b/src/debugProtocol/events/requests/ContinueRequest.ts index e6f2a773..6ea31745 100644 --- a/src/debugProtocol/events/requests/ContinueRequest.ts +++ b/src/debugProtocol/events/requests/ContinueRequest.ts @@ -26,7 +26,9 @@ export class ContinueRequest implements ProtocolRequest { } public success = false; - + /** + * How many bytes were read by the `fromBuffer` method. Only populated when constructed by `fromBuffer` + */ public readOffset: number = undefined; public data = { diff --git a/src/debugProtocol/events/requests/ExecuteRequest.ts b/src/debugProtocol/events/requests/ExecuteRequest.ts index 09342bb3..55abc68a 100644 --- a/src/debugProtocol/events/requests/ExecuteRequest.ts +++ b/src/debugProtocol/events/requests/ExecuteRequest.ts @@ -40,7 +40,9 @@ export class ExecuteRequest implements ProtocolRequest { } public success = false; - + /** + * How many bytes were read by the `fromBuffer` method. Only populated when constructed by `fromBuffer` + */ public readOffset: number = undefined; public data = { diff --git a/src/debugProtocol/events/requests/ExitChannelRequest.ts b/src/debugProtocol/events/requests/ExitChannelRequest.ts index 60c81359..3c5b0abd 100644 --- a/src/debugProtocol/events/requests/ExitChannelRequest.ts +++ b/src/debugProtocol/events/requests/ExitChannelRequest.ts @@ -26,7 +26,9 @@ export class ExitChannelRequest implements ProtocolRequest { } public success = false; - + /** + * How many bytes were read by the `fromBuffer` method. Only populated when constructed by `fromBuffer` + */ public readOffset: number = undefined; public data = { diff --git a/src/debugProtocol/events/requests/ListBreakpointsRequest.ts b/src/debugProtocol/events/requests/ListBreakpointsRequest.ts index cd55f96e..fc6458b5 100644 --- a/src/debugProtocol/events/requests/ListBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/ListBreakpointsRequest.ts @@ -26,7 +26,9 @@ export class ListBreakpointsRequest implements ProtocolRequest { } public success = false; - + /** + * How many bytes were read by the `fromBuffer` method. Only populated when constructed by `fromBuffer` + */ public readOffset: number = undefined; public data = { diff --git a/src/debugProtocol/events/requests/RemoveBreakpointsRequest.ts b/src/debugProtocol/events/requests/RemoveBreakpointsRequest.ts index 57a6c53e..2cb5604a 100644 --- a/src/debugProtocol/events/requests/RemoveBreakpointsRequest.ts +++ b/src/debugProtocol/events/requests/RemoveBreakpointsRequest.ts @@ -8,7 +8,7 @@ export class RemoveBreakpointsRequest implements ProtocolRequest { public static fromJson(data: { requestId: number; breakpointIds: number[] }) { const request = new RemoveBreakpointsRequest(); protocolUtil.loadJson(request, data); - request.data.numBreakpoints = request.data.breakpointIds.length; + request.data.numBreakpoints = request.data.breakpointIds?.length ?? 0; return request; } @@ -38,7 +38,9 @@ export class RemoveBreakpointsRequest implements ProtocolRequest { } public success = false; - + /** + * How many bytes were read by the `fromBuffer` method. Only populated when constructed by `fromBuffer` + */ public readOffset: number = undefined; public data = { diff --git a/src/debugProtocol/events/requests/StackTraceRequest.ts b/src/debugProtocol/events/requests/StackTraceRequest.ts index ae649c7b..8e4d1380 100644 --- a/src/debugProtocol/events/requests/StackTraceRequest.ts +++ b/src/debugProtocol/events/requests/StackTraceRequest.ts @@ -30,7 +30,9 @@ export class StackTraceRequest implements ProtocolRequest { } public success = false; - + /** + * How many bytes were read by the `fromBuffer` method. Only populated when constructed by `fromBuffer` + */ public readOffset: number = undefined; public data = { diff --git a/src/debugProtocol/events/requests/StepRequest.ts b/src/debugProtocol/events/requests/StepRequest.ts index 47bfd535..89d5916d 100644 --- a/src/debugProtocol/events/requests/StepRequest.ts +++ b/src/debugProtocol/events/requests/StepRequest.ts @@ -33,7 +33,9 @@ export class StepRequest implements ProtocolRequest { } public success = false; - + /** + * How many bytes were read by the `fromBuffer` method. Only populated when constructed by `fromBuffer` + */ public readOffset: number = undefined; public data = { diff --git a/src/debugProtocol/events/requests/StopRequest.ts b/src/debugProtocol/events/requests/StopRequest.ts index d99973e2..ef8551f9 100644 --- a/src/debugProtocol/events/requests/StopRequest.ts +++ b/src/debugProtocol/events/requests/StopRequest.ts @@ -27,6 +27,9 @@ export class StopRequest implements ProtocolRequest { public success = false; + /** + * How many bytes were read by the `fromBuffer` method. Only populated when constructed by `fromBuffer` + */ public readOffset: number = undefined; public data = { diff --git a/src/debugProtocol/events/requests/ThreadsRequest.ts b/src/debugProtocol/events/requests/ThreadsRequest.ts index deb41928..f5f577d2 100644 --- a/src/debugProtocol/events/requests/ThreadsRequest.ts +++ b/src/debugProtocol/events/requests/ThreadsRequest.ts @@ -26,7 +26,9 @@ export class ThreadsRequest implements ProtocolRequest { } public success = false; - + /** + * How many bytes were read by the `fromBuffer` method. Only populated when constructed by `fromBuffer` + */ public readOffset: number = undefined; public data = { diff --git a/src/debugProtocol/events/requests/VariablesRequest.ts b/src/debugProtocol/events/requests/VariablesRequest.ts index e7b81b92..e5081432 100644 --- a/src/debugProtocol/events/requests/VariablesRequest.ts +++ b/src/debugProtocol/events/requests/VariablesRequest.ts @@ -93,7 +93,9 @@ export class VariablesRequest implements ProtocolRequest { } public success = false; - + /** + * How many bytes were read by the `fromBuffer` method. Only populated when constructed by `fromBuffer` + */ public readOffset: number = undefined; public data = { diff --git a/src/debugProtocol/events/responses/ListBreakpointsResponse.ts b/src/debugProtocol/events/responses/ListBreakpointsResponse.ts index f07772c3..03fc74ee 100644 --- a/src/debugProtocol/events/responses/ListBreakpointsResponse.ts +++ b/src/debugProtocol/events/responses/ListBreakpointsResponse.ts @@ -1,8 +1,9 @@ import { SmartBuffer } from 'smart-buffer'; import { ErrorCode } from '../../Constants'; import { protocolUtil } from '../../ProtocolUtil'; +import type { ProtocolResponse } from '../ProtocolEvent'; -export class ListBreakpointsResponse { +export class ListBreakpointsResponse implements ProtocolResponse { public static fromJson(data: { requestId: number; diff --git a/src/debugProtocol/events/responses/ThreadsResponse.spec.ts b/src/debugProtocol/events/responses/ThreadsResponse.spec.ts index fbda3a7e..a16f9049 100644 --- a/src/debugProtocol/events/responses/ThreadsResponse.spec.ts +++ b/src/debugProtocol/events/responses/ThreadsResponse.spec.ts @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { ThreadsResponse } from './ThreadsResponse'; import { ErrorCode, StopReason } from '../../Constants'; import { getRandomBuffer } from '../../../testHelpers.spec'; +import { StackTraceV3Response } from './StackTraceV3Response'; describe('ThreadsResponse', () => { it('defaults data.entries to empty array when missing', () => { diff --git a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts index ddedde3d..969cf9ad 100644 --- a/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts +++ b/src/debugProtocol/events/updates/AllThreadsStoppedUpdate.ts @@ -45,7 +45,9 @@ export class AllThreadsStoppedUpdate implements ProtocolUpdate { } public success = false; - + /** + * How many bytes were read by the `fromBuffer` method. Only populated when constructed by `fromBuffer` + */ public readOffset: number = undefined; public data = { diff --git a/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts b/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts index 91365490..e135c7c3 100644 --- a/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts +++ b/src/debugProtocol/events/updates/BreakpointErrorUpdate.ts @@ -95,7 +95,9 @@ export class BreakpointErrorUpdate { } public success = false; - + /** + * How many bytes were read by the `fromBuffer` method. Only populated when constructed by `fromBuffer` + */ public readOffset: number = undefined; public data = { diff --git a/src/debugProtocol/events/updates/CompileErrorUpdate.ts b/src/debugProtocol/events/updates/CompileErrorUpdate.ts index 8d980802..294824db 100644 --- a/src/debugProtocol/events/updates/CompileErrorUpdate.ts +++ b/src/debugProtocol/events/updates/CompileErrorUpdate.ts @@ -56,7 +56,9 @@ export class CompileErrorUpdate { } public success = false; - + /** + * How many bytes were read by the `fromBuffer` method. Only populated when constructed by `fromBuffer` + */ public readOffset: number = undefined; public data = { diff --git a/src/debugProtocol/server/DebugProtocolServer.ts b/src/debugProtocol/server/DebugProtocolServer.ts index ee22faaf..0faea82a 100644 --- a/src/debugProtocol/server/DebugProtocolServer.ts +++ b/src/debugProtocol/server/DebugProtocolServer.ts @@ -21,6 +21,10 @@ import { HandshakeV3Response } from '../events/responses/HandshakeV3Response'; import PluginInterface from './PluginInterface'; import type { ProtocolPlugin } from './ProtocolPlugin'; import { logger } from '../../logging'; +import * as portfinder from 'portfinder'; +import { defer } from '../../util'; +import { protocolUtil } from '../ProtocolUtil'; +import { SmartBuffer } from 'smart-buffer'; export const DEBUGGER_MAGIC = 'bsdebug'; @@ -30,7 +34,7 @@ export const DEBUGGER_MAGIC = 'bsdebug'; */ export class DebugProtocolServer { constructor( - public options: DebugProtocolServerOptions + public options?: DebugProtocolServerOptions ) { } @@ -63,13 +67,18 @@ export class DebugProtocolServer { */ private bufferQueue = new ActionQueue(); + public get controlPort() { + return this.options.controlPort ?? this._port; + } + private _port: number; /** * Run the server. This opens a socket and listens for a connection. * The promise resolves when the server has started listening. It does NOT wait for a client to connect */ public async start() { - return new Promise((resolve) => { + const deferred = defer(); + try { this.server = new Net.Server({}); //Roku only allows 1 connection, so we should too. this.server.maxConnections = 1; @@ -88,22 +97,25 @@ export class DebugProtocolServer { //queue up processing the new data, chunk by chunk void this.bufferQueue.run(async () => { this.buffer = Buffer.concat([this.buffer, data]); - while (await this.process()) { + while (this.buffer.length > 0 && await this.process()) { //the loop condition is the actual work } return true; }); }); }); - + this._port = this.controlPort ?? await portfinder.getPortPromise(); this.server.listen({ - port: this.options.controllerPort ?? 8081, + port: this.options.controlPort ?? 8081, hostName: this.options.host ?? '0.0.0.0' }, () => { void this.plugins.emit('onServerStart', { server: this }); - resolve(); + deferred.resolve(); }); - }); + } catch (e) { + deferred.reject(e); + } + return deferred.promise; } public async stop() { @@ -127,38 +139,43 @@ export class DebugProtocolServer { /** * Given a buffer, find the request that matches it */ - private getRequest(buffer: Buffer): ProtocolRequest { - //if we haven't seen the handshake yet, look for the handshake first - if (!this.isHandshakeComplete) { + public static getRequest(buffer: Buffer, allowHandshake: boolean): ProtocolRequest { + //when enabled, look at the start of the buffer for the exact DEBUGGER_MAGIC text. This is a boolean because + //there could be cases where binary data looks similar to this structure, so the caller must opt-in to this logic + if (allowHandshake && buffer.length >= 8 && protocolUtil.readStringNT(SmartBuffer.fromBuffer(buffer)) === DEBUGGER_MAGIC) { return HandshakeRequest.fromBuffer(buffer); } - //we can only receive commands from the client, so pre-parse the command type + // if we don't have enough buffer data, skip this + if (buffer.length < 12) { + return; + } + //the client may only send commands to the server, so extract the command type from the known byte position const command = CommandCode[buffer.readUInt32LE(8)] as Command; // command_code switch (command) { case Command.AddBreakpoints: - return AddBreakpointsRequest.fromBuffer(this.buffer); + return AddBreakpointsRequest.fromBuffer(buffer); case Command.Stop: - return StopRequest.fromBuffer(this.buffer); + return StopRequest.fromBuffer(buffer); case Command.Continue: - return ContinueRequest.fromBuffer(this.buffer); + return ContinueRequest.fromBuffer(buffer); case Command.Threads: - return ThreadsRequest.fromBuffer(this.buffer); + return ThreadsRequest.fromBuffer(buffer); case Command.StackTrace: - return StackTraceRequest.fromBuffer(this.buffer); + return StackTraceRequest.fromBuffer(buffer); case Command.Variables: - return VariablesRequest.fromBuffer(this.buffer); + return VariablesRequest.fromBuffer(buffer); case Command.Step: - return StepRequest.fromBuffer(this.buffer); + return StepRequest.fromBuffer(buffer); case Command.ListBreakpoints: - return ListBreakpointsRequest.fromBuffer(this.buffer); + return ListBreakpointsRequest.fromBuffer(buffer); case Command.RemoveBreakpoints: - return RemoveBreakpointsRequest.fromBuffer(this.buffer); + return RemoveBreakpointsRequest.fromBuffer(buffer); case Command.Execute: - return ExecuteRequest.fromBuffer(this.buffer); + return ExecuteRequest.fromBuffer(buffer); case Command.AddConditionalBreakpoints: - return AddConditionalBreakpointsRequest.fromBuffer(this.buffer); + return AddConditionalBreakpointsRequest.fromBuffer(buffer); case Command.ExitChannel: - return ExitChannelRequest.fromBuffer(this.buffer); + return ExitChannelRequest.fromBuffer(buffer); } } @@ -190,10 +207,14 @@ export class DebugProtocolServer { //we must build the request if the plugin didn't supply one (most plugins won't provide a request...) if (!request) { - request = this.getRequest(buffer); + //if we haven't seen the handshake yet, look for the handshake first + if (!this.isHandshakeComplete) { + request = HandshakeRequest.fromBuffer(buffer); + } else { + request = DebugProtocolServer.getRequest(buffer, false); + } } - //if we couldn't construct a request this request, hard-fail if (!request || !request.success) { this.logger.error('process() invalid request', { request }); @@ -320,7 +341,7 @@ export interface DebugProtocolServerOptions { /** * The port to use for the primary communication between this server and a client */ - controllerPort?: number; + controlPort?: number; /** * A specific host to listen on. If not specified, all hosts are used */ diff --git a/src/debugProtocol/server/ProtocolPlugin.ts b/src/debugProtocol/server/ProtocolPlugin.ts index 07c6f33f..047132ed 100644 --- a/src/debugProtocol/server/ProtocolPlugin.ts +++ b/src/debugProtocol/server/ProtocolPlugin.ts @@ -3,7 +3,7 @@ import type { Socket } from 'net'; import type { ProtocolRequest, ProtocolResponse } from '../events/ProtocolEvent'; export interface ProtocolPlugin { - onServerStart: Handler; + onServerStart?: Handler; onClientConnected?: Handler; provideRequest?: Handler; diff --git a/src/managers/ActionQueue.ts b/src/managers/ActionQueue.ts index 9952a7a7..f7cdb1f3 100644 --- a/src/managers/ActionQueue.ts +++ b/src/managers/ActionQueue.ts @@ -12,6 +12,10 @@ export class ActionQueue { deferred: Deferred; }> = []; + /** + * Run an action in the queue. + * @param action return true or Promise to mark the action as finished + */ public async run(action: () => boolean | Promise) { this.queueItems.push({ action: action, From 70db47d7d45452bf59357fee193e93c26b8064af Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 1 Feb 2023 11:44:06 -0500 Subject: [PATCH 30/74] Add basic ProtocolClient plugin support --- .../DebugProtocolReplaySession.ts | 3 +- .../DebugProtocolServerTestPlugin.spec.ts | 4 +- .../{server => }/PluginInterface.ts | 47 +++--- .../client/DebugProtocolClient.spec.ts | 6 +- .../client/DebugProtocolClient.ts | 136 ++++++++++++------ .../client/DebugProtocolClientPlugin.ts | 47 ++++++ .../server/DebugProtocolServer.ts | 6 +- ...Plugin.ts => DebugProtocolServerPlugin.ts} | 2 +- 8 files changed, 180 insertions(+), 71 deletions(-) rename src/debugProtocol/{server => }/PluginInterface.ts (53%) create mode 100644 src/debugProtocol/client/DebugProtocolClientPlugin.ts rename src/debugProtocol/server/{ProtocolPlugin.ts => DebugProtocolServerPlugin.ts} (97%) diff --git a/src/debugProtocol/DebugProtocolReplaySession.ts b/src/debugProtocol/DebugProtocolReplaySession.ts index 018e6225..309d5749 100644 --- a/src/debugProtocol/DebugProtocolReplaySession.ts +++ b/src/debugProtocol/DebugProtocolReplaySession.ts @@ -49,7 +49,8 @@ export class DebugProtocolReplaySession { await this.createServer(this.controlPort); this.createClient(this.controlPort); - await this.client.connect(); + //connect, but don't send the handshake. That'll be send through our first server-to-client entry (hopefully) + await this.client.connect(false); void this.clientProcess(); await this.finished.promise; diff --git a/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts b/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts index 3a066dc2..d22df575 100644 --- a/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts +++ b/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts @@ -4,12 +4,12 @@ import { isProtocolUpdate } from './client/DebugProtocolClient'; import type { ProtocolResponse, ProtocolRequest, ProtocolUpdate } from './events/ProtocolEvent'; import { HandshakeRequest } from './events/requests/HandshakeRequest'; import type { DebugProtocolServer } from './server/DebugProtocolServer'; -import type { BeforeSendResponseEvent, OnServerStartEvent, ProtocolPlugin, ProvideResponseEvent } from './server/ProtocolPlugin'; +import type { BeforeSendResponseEvent, OnServerStartEvent, ProtocolServerPlugin, ProvideResponseEvent } from './server/DebugProtocolServerPlugin'; /** * A class that intercepts all debug server events and provides test data for them */ -export class DebugProtocolServerTestPlugin implements ProtocolPlugin { +export class DebugProtocolServerTestPlugin implements ProtocolServerPlugin { /** * A list of responses or updates to be sent by the server in this exact order. diff --git a/src/debugProtocol/server/PluginInterface.ts b/src/debugProtocol/PluginInterface.ts similarity index 53% rename from src/debugProtocol/server/PluginInterface.ts rename to src/debugProtocol/PluginInterface.ts index 7a94e305..05226b29 100644 --- a/src/debugProtocol/server/PluginInterface.ts +++ b/src/debugProtocol/PluginInterface.ts @@ -5,14 +5,20 @@ export type Arguments = [T] extends [(...args: infer U) => any] export default class PluginInterface { constructor( - private plugins = [] as TPlugin[] - ) { } + plugins = [] as TPlugin[] + ) { + for (const plugin of plugins ?? []) { + this.add(plugin); + } + } + + private plugins: Array> = []; /** * Call `event` on plugins */ public async emit(eventName: K, event: Arguments[0]) { - for (let plugin of this.plugins) { + for (let { plugin } of this.plugins) { if ((plugin as any)[eventName]) { await Promise.resolve((plugin as any)[eventName](event)); } @@ -23,28 +29,30 @@ export default class PluginInterface { /** * Add a plugin to the end of the list of plugins */ - public add(plugin: T) { - if (!this.has(plugin)) { - this.plugins.push(plugin); - } - return plugin; - } + public add(plugin: T, priority = 1) { + const container = { + plugin: plugin, + priority: priority + }; + this.plugins.push(container); - /** - * Is the specified plugin present in the list - */ - public has(plugin: TPlugin) { - return this.plugins.includes(plugin); + //sort the plugins by priority + this.plugins.sort((a, b) => { + return a.priority - b.priority; + }); + + return plugin; } /** * Remove the specified plugin */ public remove(plugin: T) { - if (this.has(plugin)) { - this.plugins.splice(this.plugins.indexOf(plugin)); + for (let i = this.plugins.length - 1; i >= 0; i--) { + if (this.plugins[i] === plugin) { + this.plugins.splice(i, 1); + } } - return plugin; } /** @@ -54,3 +62,8 @@ export default class PluginInterface { this.plugins = []; } } + +interface PluginContainer { + plugin: TPlugin; + priority: number; +} diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index da54dcb1..f93b4f75 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -276,7 +276,7 @@ describe('DebugProtocolClient', () => { })); await client.threads(); - expect(client.primaryThread).to.eql(0); + expect(client?.primaryThread).to.eql(0); }); it('honors the `isPrimary` flag when threadHoppingWorkaround is disabled', async () => { @@ -296,7 +296,7 @@ describe('DebugProtocolClient', () => { })); await client.threads(); - expect(client.primaryThread).to.eql(1); + expect(client?.primaryThread).to.eql(1); }); }); @@ -682,7 +682,7 @@ describe('DebugProtocolClient', () => { client.listBreakpoints(), client.getStackTrace() ]); - expect(listBreakpointsResponse.data.breakpoints[0]).to.include({ + expect(listBreakpointsResponse?.data.breakpoints[0]).to.include({ id: 123, errorCode: 0, ignoreCount: 2 diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 856f395d..442476cf 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -1,7 +1,7 @@ import * as Net from 'net'; import * as EventEmitter from 'eventemitter3'; import * as semver from 'semver'; -import { PROTOCOL_ERROR_CODES, Command, StepType, StopReasonCode, ErrorCode, UpdateType, UpdateTypeCode, StopReason } from '../Constants'; +import { PROTOCOL_ERROR_CODES, Command, StepType, ErrorCode, UpdateType, UpdateTypeCode, StopReason } from '../Constants'; import { logger } from '../../logging'; import { ExecuteV3Response } from '../events/responses/ExecuteV3Response'; import { ListBreakpointsResponse } from '../events/responses/ListBreakpointsResponse'; @@ -36,10 +36,12 @@ import { IOPortOpenedUpdate } from '../events/updates/IOPortOpenedUpdate'; import { ThreadAttachedUpdate } from '../events/updates/ThreadAttachedUpdate'; import { StackTraceV3Response } from '../events/responses/StackTraceV3Response'; import { ActionQueue } from '../../managers/ActionQueue'; +import type { DebugProtocolClientPlugin } from './DebugProtocolClientPlugin'; +import PluginInterface from '../PluginInterface'; export class DebugProtocolClient { - private logger = logger.createLogger(`[${DebugProtocolClient.name}]`); + public logger = logger.createLogger(`[${DebugProtocolClient.name}]`); // The highest tested version of the protocol we support. public supportedVersionRange = '<=3.0.0'; @@ -53,7 +55,19 @@ export class DebugProtocolClient { //override the defaults with the options from parameters ...options ?? {} }; + + //add the internal plugin last, so it's the final plugin to handle the events + this.addCorePlugin(); + } + + private addCorePlugin() { + this.plugins.add({ + onUpdate: (event) => { + return this.handleUpdate(event.update); + } + }, 999); } + public static DEBUGGER_MAGIC = 'bsdebug'; // 64-bit = [b'bsdebug\0' little-endian] public scriptTitle: string; @@ -68,6 +82,11 @@ export class DebugProtocolClient { public primaryThread: number; public stackFrameIndex: number; + /** + * A collection of plugins that can interact with the client at lifecycle points + */ + public plugins = new PluginInterface(); + private emitter = new EventEmitter(); /** * The primary socket for this session. It's used to communicate with the debugger by sending commands and receives responses or updates @@ -119,6 +138,8 @@ export class DebugProtocolClient { public once(eventName: 'runtime-error' | 'suspend'): Promise; public once(eventName: 'io-output'): Promise; public once(eventName: 'data'): Promise; + public once(eventName: 'response'): Promise; + public once(eventName: 'update'): Promise; public once(eventName: 'protocol-version'): Promise; public once(eventName: 'handshake-verified'): Promise; public once(eventName: string) { @@ -190,9 +211,18 @@ export class DebugProtocolClient { }); }, this.options.controlConnectInterval ?? 250); }); + await this.plugins.emit('onServerConnected', { + client: this, + server: connection + }); return connection; } + /** + * A queue for processing the incoming buffer, every transmission at a time + */ + private bufferQueue = new ActionQueue(); + /** * Connect to the debug server. * @param sendHandshake should the handshake be sent as part of this connect process. If false, `.sendHandshake()` will need to be called before a session can begin @@ -205,15 +235,24 @@ export class DebugProtocolClient { this.controlSocket.on('data', (data) => { this.emit('data', data); - this.buffer = Buffer.concat([this.buffer, data]); + //queue up processing the new data, chunk by chunk + void this.bufferQueue.run(async () => { + this.buffer = Buffer.concat([this.buffer, data]); + while (this.buffer.length > 0 && await this.process()) { + //the loop condition is the actual work + } + return true; + }); + + // this.buffer = Buffer.concat([this.buffer, data]); - this.logger.debug(`on('data'): incoming bytes`, data.length); - const startBufferSize = this.buffer.length; + // this.logger.debug(`on('data'): incoming bytes`, data.length); + // const startBufferSize = this.buffer.length; - this.process(); + // this.process(); - const endBufferSize = this.buffer?.length ?? 0; - this.logger.debug(`buffer size before:`, startBufferSize, ', buffer size after:', endBufferSize, ', bytes consumed:', startBufferSize - endBufferSize); + // const endBufferSize = this.buffer?.length ?? 0; + // this.logger.debug(`buffer size before:`, startBufferSize, ', buffer size after:', endBufferSize, ', bytes consumed:', startBufferSize - endBufferSize); }); this.controlSocket.on('end', () => { @@ -228,10 +267,6 @@ export class DebugProtocolClient { this.shutdown('close'); }); - //subscribe to all unsolicited updates - this.on('update', (update) => { - this.handleUpdate(update); - }); if (sendHandshake) { await this.sendHandshake(); } @@ -553,15 +588,20 @@ export class DebugProtocolClient { /** * Send a request to the roku device, and get a promise that resolves once we have received the response */ - private async sendRequest(request: ProtocolRequest) { + private async sendRequest(request: ProtocolRequest) { + request = (await this.plugins.emit('beforeSendRequest', { + client: this, + request: request + })).request; + this.activeRequests.set(request.data.requestId, request); return new Promise((resolve) => { - let unsubscribe = this.on('response', (event) => { - if (event.data.requestId === request.data.requestId) { + let unsubscribe = this.on('response', (response) => { + if (response.data.requestId === request.data.requestId) { unsubscribe(); this.activeRequests.delete(request.data.requestId); - resolve(event as unknown as T); + resolve(response as T); } }); @@ -569,40 +609,43 @@ export class DebugProtocolClient { if (this.controlSocket) { const buffer = request.toBuffer(); this.controlSocket.write(buffer); + void this.plugins.emit('afterSendRequest', { + client: this, + request: request + }); } else { throw new Error(`Control socket was closed - Command: ${Command[request.data.command]}`); } }); } - private process() { - if (this.buffer.length < 1) { - // short circuit if the buffer is empty - return; - } + private async process(): Promise { try { - if (this.buffer.length < 1) { - // short circuit if the buffer is empty - return; - } + this.logger.log('[process()]: buffer=', JSON.stringify(this.buffer.toJSON().data)); - this.logger.info('[process()]: buffer=', JSON.stringify(this.buffer.toJSON().data)); + let { responseOrUpdate } = await this.plugins.emit('provideResponseOrUpdate', { + client: this, + activeRequests: this.activeRequests, + buffer: this.buffer + }); - const event = this.getResponseOrUpdate(this.buffer); + if (!responseOrUpdate) { + responseOrUpdate = this.getResponseOrUpdate(this.buffer); + } //if the event failed to parse, or the buffer doesn't have enough bytes to satisfy the packetLength, exit here (new data will re-trigger this function) - if (!event) { + if (!responseOrUpdate) { this.logger.log('Unable to convert buffer into anything meaningful'); //TODO what should we do about this? - return; + return false; } - if (!event.success || event.data.packetLength > this.buffer.length) { - this.logger.log(`event parse failed. ${event?.data?.packetLength} bytes required, ${this.buffer.length} bytes available`); - return; + if (!responseOrUpdate.success || responseOrUpdate.data.packetLength > this.buffer.length) { + this.logger.log(`event parse failed. ${responseOrUpdate?.data?.packetLength} bytes required, ${this.buffer.length} bytes available`); + return false; } //we have a valid event. Clear the buffer of this data - this.buffer = this.buffer.slice(event.readOffset); + this.buffer = this.buffer.slice(responseOrUpdate.readOffset); //TODO why did we ever do this? Just to handle when we misread incoming data? I think this should be scrapped // if (event.data.requestId > this.totalRequests) { @@ -610,23 +653,28 @@ export class DebugProtocolClient { // return true; // } - if (event.data.errorCode !== ErrorCode.OK) { - this.logger.error(event.data.errorCode, event); - // return; + if (responseOrUpdate.data.errorCode !== ErrorCode.OK) { + this.logger.error(responseOrUpdate.data.errorCode, responseOrUpdate); } - //we got a response - if (event) { + //we got a result + if (responseOrUpdate) { //emit the corresponding event - if (isProtocolUpdate(event)) { - this.emit('update', event); + if (isProtocolUpdate(responseOrUpdate)) { + this.emit('update', responseOrUpdate); + await this.plugins.emit('onUpdate', { + client: this, + update: responseOrUpdate + }); } else { - this.emit('response', event); + this.emit('response', responseOrUpdate); + await this.plugins.emit('onResponse', { + client: this, + response: responseOrUpdate as any + }); } + return true; } - - // process again (will run recursively until the buffer is empty) - this.process(); } catch (e) { this.logger.error(`process() failed:`, e); } diff --git a/src/debugProtocol/client/DebugProtocolClientPlugin.ts b/src/debugProtocol/client/DebugProtocolClientPlugin.ts new file mode 100644 index 00000000..717d10b1 --- /dev/null +++ b/src/debugProtocol/client/DebugProtocolClientPlugin.ts @@ -0,0 +1,47 @@ +import type { DebugProtocolClient } from './DebugProtocolClient'; +import type { Socket } from 'net'; +import type { ProtocolRequest, ProtocolResponse, ProtocolUpdate } from '../events/ProtocolEvent'; + +export interface DebugProtocolClientPlugin { + onServerConnected?(event: OnServerConnectedEvent): void | Promise; + + beforeSendRequest?(event: BeforeSendRequestEvent): void | Promise; + afterSendRequest?(event: AfterSendRequestEvent): void | Promise; + + provideResponseOrUpdate?(event: ProvideResponseOrUpdateEvent): void | Promise; + + onUpdate?(event: OnUpdateEvent): void | Promise; + onResponse?(event: OnResponseEvent): void | Promise; +} + +export interface OnServerConnectedEvent { + client: DebugProtocolClient; + server: Socket; +} + +export interface ProvideResponseOrUpdateEvent { + client: DebugProtocolClient; + activeRequests: Map; + buffer: Readonly; + /** + * The plugin should provide this property + */ + responseOrUpdate?: ProtocolResponse | ProtocolUpdate; +} + +export interface BeforeSendRequestEvent { + client: DebugProtocolClient; + request: ProtocolRequest; +} +export type AfterSendRequestEvent = BeforeSendRequestEvent; + +export interface OnUpdateEvent { + client: DebugProtocolClient; + update: ProtocolUpdate; +} +export interface OnResponseEvent { + client: DebugProtocolClient; + response: ProtocolResponse; +} +export type Handler = (event: T) => R; + diff --git a/src/debugProtocol/server/DebugProtocolServer.ts b/src/debugProtocol/server/DebugProtocolServer.ts index 0faea82a..833d67f5 100644 --- a/src/debugProtocol/server/DebugProtocolServer.ts +++ b/src/debugProtocol/server/DebugProtocolServer.ts @@ -18,8 +18,8 @@ import { ThreadsRequest } from '../events/requests/ThreadsRequest'; import { VariablesRequest } from '../events/requests/VariablesRequest'; import { HandshakeResponse } from '../events/responses/HandshakeResponse'; import { HandshakeV3Response } from '../events/responses/HandshakeV3Response'; -import PluginInterface from './PluginInterface'; -import type { ProtocolPlugin } from './ProtocolPlugin'; +import PluginInterface from '../PluginInterface'; +import type { ProtocolServerPlugin } from './DebugProtocolServerPlugin'; import { logger } from '../../logging'; import * as portfinder from 'portfinder'; import { defer } from '../../util'; @@ -60,7 +60,7 @@ export class DebugProtocolServer { /** * A collection of plugins that can interact with the server at lifecycle points */ - public plugins = new PluginInterface(); + public plugins = new PluginInterface(); /** * A queue for processing the incoming buffer, every transmission at a time diff --git a/src/debugProtocol/server/ProtocolPlugin.ts b/src/debugProtocol/server/DebugProtocolServerPlugin.ts similarity index 97% rename from src/debugProtocol/server/ProtocolPlugin.ts rename to src/debugProtocol/server/DebugProtocolServerPlugin.ts index 047132ed..5f7c2aca 100644 --- a/src/debugProtocol/server/ProtocolPlugin.ts +++ b/src/debugProtocol/server/DebugProtocolServerPlugin.ts @@ -2,7 +2,7 @@ import type { DebugProtocolServer } from './DebugProtocolServer'; import type { Socket } from 'net'; import type { ProtocolRequest, ProtocolResponse } from '../events/ProtocolEvent'; -export interface ProtocolPlugin { +export interface ProtocolServerPlugin { onServerStart?: Handler; onClientConnected?: Handler; From 8aa40bd15edbea2ac6e5f6c36deb0a4853908b24 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 1 Feb 2023 12:01:17 -0500 Subject: [PATCH 31/74] Better logging --- src/debugProtocol/client/DebugProtocolClient.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 442476cf..927e0dd4 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -369,7 +369,7 @@ export class DebugProtocolClient { } return stepResult; } else { - this.logger.log('[processStepRequest] skipped because not stopped', request); + this.logger.log('[processStepRequest] skipped because debugger is not paused'); } } @@ -406,7 +406,7 @@ export class DebugProtocolClient { } return result; } else { - this.logger.log('[processThreadsRequest] skipped because not stopped', request); + this.logger.log('[processThreadsRequest] skipped because not stopped'); } } @@ -423,10 +423,12 @@ export class DebugProtocolClient { } private async processStackTraceRequest(request: StackTraceRequest) { - if (this.isStopped && request.data.threadIndex > -1) { + if (!this.isStopped) { + this.logger.log('[getStackTrace] skipped because debugger is not paused'); + } else if (request?.data?.threadIndex > -1) { return this.sendRequest(request); } else { - this.logger.log('[getStackTrace] skipped. ', request); + this.logger.log(`[getStackTrace] skipped because ${request?.data?.threadIndex} is not valid threadIndex`); } } @@ -621,7 +623,7 @@ export class DebugProtocolClient { private async process(): Promise { try { - this.logger.log('[process()]: buffer=', JSON.stringify(this.buffer.toJSON().data)); + this.logger.info('[process()]: buffer=', this.buffer.toJSON()); let { responseOrUpdate } = await this.plugins.emit('provideResponseOrUpdate', { client: this, @@ -635,7 +637,7 @@ export class DebugProtocolClient { //if the event failed to parse, or the buffer doesn't have enough bytes to satisfy the packetLength, exit here (new data will re-trigger this function) if (!responseOrUpdate) { - this.logger.log('Unable to convert buffer into anything meaningful'); + this.logger.info('Unable to convert buffer into anything meaningful', this.buffer); //TODO what should we do about this? return false; } @@ -661,12 +663,14 @@ export class DebugProtocolClient { if (responseOrUpdate) { //emit the corresponding event if (isProtocolUpdate(responseOrUpdate)) { + this.logger.log('Update from server:', responseOrUpdate); this.emit('update', responseOrUpdate); await this.plugins.emit('onUpdate', { client: this, update: responseOrUpdate }); } else { + this.logger.log('Response from server:', responseOrUpdate); this.emit('response', responseOrUpdate); await this.plugins.emit('onResponse', { client: this, From df3afb90d5a2141fb585adda26f07016bcf8c491 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 1 Feb 2023 16:59:55 -0500 Subject: [PATCH 32/74] Better port handling in ReplaySession --- ... DebugProtocolClientReplaySession.spec.ts} | 8 +-- ...ts => DebugProtocolClientReplaySession.ts} | 29 +++++++--- .../client/DebugProtocolClient.ts | 54 +++++++++++-------- .../client/DebugProtocolClientPlugin.ts | 8 +++ .../events/updates/IOPortOpenedUpdate.ts | 5 ++ 5 files changed, 70 insertions(+), 34 deletions(-) rename src/debugProtocol/{DebugProtocolReplaySession.spec.ts => DebugProtocolClientReplaySession.spec.ts} (99%) rename src/debugProtocol/{DebugProtocolReplaySession.ts => DebugProtocolClientReplaySession.ts} (88%) diff --git a/src/debugProtocol/DebugProtocolReplaySession.spec.ts b/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts similarity index 99% rename from src/debugProtocol/DebugProtocolReplaySession.spec.ts rename to src/debugProtocol/DebugProtocolClientReplaySession.spec.ts index edbafce2..4b5941e3 100644 --- a/src/debugProtocol/DebugProtocolReplaySession.spec.ts +++ b/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts @@ -1,8 +1,8 @@ /* eslint-disable prefer-arrow-callback */ -import { DebugProtocolReplaySession } from './DebugProtocolReplaySession'; +import { DebugProtocolClientReplaySession } from './DebugProtocolClientReplaySession'; -describe('DebugProtocolReplaySession', () => { - it.skip('works for this specific thing', async function test() { +describe(DebugProtocolClientReplaySession.name, () => { + it.only('works for this specific thing', async function test() { this.timeout(10000000000); const text = ` {"type":"client-to-server","timestamp":"2023-01-27T19:48:25.607Z","buffer":{"type":"Buffer","data":[98,115,100,101,98,117,103,0]}} @@ -93,7 +93,7 @@ describe('DebugProtocolReplaySession', () => { {"type":"server-to-client","timestamp":"2023-01-27T19:49:26.532Z","buffer":{"type":"Buffer","data":[35,0,0,0,0,0,0,0,7,0,0,0,0,4,0,0,0,66,82,69,65,75,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,22,0,0,0,36,97,110,111,110,95,50,97,54,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,119,105,100,116,104,32,61,32,101,118,101,110,116,46,103,101,116,68,97,116,97,40,41,0,0,4,0,0,0,66,82,69,65,75,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,9,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,49,48,48,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,49,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,186,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,105,102,32,99,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,34,99,97,114,111,117,115,101,108,34,32,116,104,101,110,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,50,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} {"type":"client-to-server","timestamp":"2023-01-27T19:57:18.397Z","buffer":{"type":"Buffer","data":[12,0,0,0,36,0,0,0,122,0,0,0]}} `; - const session = new DebugProtocolReplaySession({ + const session = new DebugProtocolClientReplaySession({ bufferLog: text }); diff --git a/src/debugProtocol/DebugProtocolReplaySession.ts b/src/debugProtocol/DebugProtocolClientReplaySession.ts similarity index 88% rename from src/debugProtocol/DebugProtocolReplaySession.ts rename to src/debugProtocol/DebugProtocolClientReplaySession.ts index 309d5749..072efe46 100644 --- a/src/debugProtocol/DebugProtocolReplaySession.ts +++ b/src/debugProtocol/DebugProtocolClientReplaySession.ts @@ -5,9 +5,9 @@ import type { ProtocolRequest, ProtocolResponse, ProtocolUpdate } from './events import { DebugProtocolServer } from './server/DebugProtocolServer'; import * as Net from 'net'; import { ActionQueue } from '../managers/ActionQueue'; -import { IOPortOpenedUpdate } from './events/updates/IOPortOpenedUpdate'; +import { IOPortOpenedUpdate, isIOPortOpenedUpdate } from './events/updates/IOPortOpenedUpdate'; -export class DebugProtocolReplaySession { +export class DebugProtocolClientReplaySession { constructor(options: { bufferLog: string; }) { @@ -43,10 +43,11 @@ export class DebugProtocolReplaySession { private ioPort: number; public async run() { - this.controlPort = await portfinder.getPortPromise(); - this.ioPort = await portfinder.getPortPromise(); + this.controlPort = await portfinder.getPortPromise({ port: 8000, stopPort: 8999 }); + this.ioPort = await portfinder.getPortPromise({ port: 9000, stopPort: 9999 }); await this.createServer(this.controlPort); + this.createClient(this.controlPort); //connect, but don't send the handshake. That'll be send through our first server-to-client entry (hopefully) @@ -68,9 +69,6 @@ export class DebugProtocolReplaySession { }); this.client.on('update', (update) => { this.result.push(update); - if (update instanceof IOPortOpenedUpdate) { - void this.openIOPort(update); - } }); //anytime the client receives buffer data, we should try and process it @@ -78,9 +76,24 @@ export class DebugProtocolReplaySession { this.clientSync.pushActual(data); void this.clientProcess(); }); + + this.client.plugins.add({ + beforeHandleUpdate: async (event) => { + if (isIOPortOpenedUpdate(event.update)) { + //spin up an IO port before finishing this update + await this.openIOPort(); + + const update = IOPortOpenedUpdate.fromJson(event.update.data); + update.data.port = this.ioPort; + //if we get an IO update, change the port and host to the local stuff (for testing purposes) + event.update = update; + } + } + }); } - private openIOPort(update: IOPortOpenedUpdate) { + private openIOPort() { + console.log(`Spinning up mock IO socket on port ${this.ioPort}`); return new Promise((resolve) => { const server = new Net.Server({}); diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 927e0dd4..212ba4d9 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -32,7 +32,7 @@ import { GenericResponse } from '../events/responses/GenericResponse'; import type { StackTraceResponse } from '../events/responses/StackTraceResponse'; import { ThreadsResponse } from '../events/responses/ThreadsResponse'; import { VariablesResponse } from '../events/responses/VariablesResponse'; -import { IOPortOpenedUpdate } from '../events/updates/IOPortOpenedUpdate'; +import { IOPortOpenedUpdate, isIOPortOpenedUpdate } from '../events/updates/IOPortOpenedUpdate'; import { ThreadAttachedUpdate } from '../events/updates/ThreadAttachedUpdate'; import { StackTraceV3Response } from '../events/responses/StackTraceV3Response'; import { ActionQueue } from '../../managers/ActionQueue'; @@ -777,34 +777,44 @@ export class DebugProtocolClient { } } + private handleUpdateQueue = new ActionQueue(); + /** * Handle/process any received updates from the debug protocol */ - private handleUpdate(update: ProtocolUpdate) { - if (update instanceof AllThreadsStoppedUpdate || update instanceof ThreadAttachedUpdate) { - this.isStopped = true; + private async handleUpdate(update: ProtocolUpdate) { + return this.handleUpdateQueue.run(async () => { + update = (await this.plugins.emit('beforeHandleUpdate', { + client: this, + update: update + })).update; - let eventName: 'runtime-error' | 'suspend'; - if (update.data.stopReason === StopReason.RuntimeError) { - eventName = 'runtime-error'; - } else { - eventName = 'suspend'; - } + if (update instanceof AllThreadsStoppedUpdate || update instanceof ThreadAttachedUpdate) { + this.isStopped = true; + + let eventName: 'runtime-error' | 'suspend'; + if (update.data.stopReason === StopReason.RuntimeError) { + eventName = 'runtime-error'; + } else { + eventName = 'suspend'; + } - const isValidStopReason = [StopReason.RuntimeError, StopReason.Break, StopReason.StopStatement].includes(update.data.stopReason); + const isValidStopReason = [StopReason.RuntimeError, StopReason.Break, StopReason.StopStatement].includes(update.data.stopReason); - if (update instanceof AllThreadsStoppedUpdate && isValidStopReason) { - this.primaryThread = update.data.threadIndex; - this.stackFrameIndex = 0; - this.emit(eventName, update); - } else if (update instanceof ThreadAttachedUpdate && isValidStopReason) { - this.primaryThread = update.data.threadIndex; - this.emit(eventName, update); - } + if (update instanceof AllThreadsStoppedUpdate && isValidStopReason) { + this.primaryThread = update.data.threadIndex; + this.stackFrameIndex = 0; + this.emit(eventName, update); + } else if (update instanceof ThreadAttachedUpdate && isValidStopReason) { + this.primaryThread = update.data.threadIndex; + this.emit(eventName, update); + } - } else if (update instanceof IOPortOpenedUpdate) { - this.connectToIoPort(update); - } + } else if (isIOPortOpenedUpdate(update)) { + this.connectToIoPort(update); + } + return true; + }); } /** diff --git a/src/debugProtocol/client/DebugProtocolClientPlugin.ts b/src/debugProtocol/client/DebugProtocolClientPlugin.ts index 717d10b1..88b6a8eb 100644 --- a/src/debugProtocol/client/DebugProtocolClientPlugin.ts +++ b/src/debugProtocol/client/DebugProtocolClientPlugin.ts @@ -12,6 +12,8 @@ export interface DebugProtocolClientPlugin { onUpdate?(event: OnUpdateEvent): void | Promise; onResponse?(event: OnResponseEvent): void | Promise; + + beforeHandleUpdate?(event: BeforeHandleUpdateEvent): void | Promise; } export interface OnServerConnectedEvent { @@ -39,6 +41,12 @@ export interface OnUpdateEvent { client: DebugProtocolClient; update: ProtocolUpdate; } + +export interface BeforeHandleUpdateEvent { + client: DebugProtocolClient; + update: ProtocolUpdate; +} + export interface OnResponseEvent { client: DebugProtocolClient; response: ProtocolResponse; diff --git a/src/debugProtocol/events/updates/IOPortOpenedUpdate.ts b/src/debugProtocol/events/updates/IOPortOpenedUpdate.ts index a229f16b..963d3d31 100644 --- a/src/debugProtocol/events/updates/IOPortOpenedUpdate.ts +++ b/src/debugProtocol/events/updates/IOPortOpenedUpdate.ts @@ -1,6 +1,7 @@ import { SmartBuffer } from 'smart-buffer'; import { ErrorCode, UpdateType } from '../../Constants'; import { protocolUtil } from '../../ProtocolUtil'; +import type { ProtocolUpdate, ProtocolResponse, ProtocolRequest } from '../ProtocolEvent'; export class IOPortOpenedUpdate { @@ -48,3 +49,7 @@ export class IOPortOpenedUpdate { updateType: UpdateType.IOPortOpened }; } + +export function isIOPortOpenedUpdate(event: ProtocolRequest | ProtocolResponse | ProtocolUpdate): event is IOPortOpenedUpdate { + return event?.constructor?.name === IOPortOpenedUpdate.name; +} From 5a4c0936d5aece778cc3b53e7fba0445f920a808 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Thu, 2 Feb 2023 09:03:15 -0500 Subject: [PATCH 33/74] Fix io socket replacing server socket --- src/debugProtocol/DebugProtocolClientReplaySession.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/debugProtocol/DebugProtocolClientReplaySession.ts b/src/debugProtocol/DebugProtocolClientReplaySession.ts index 072efe46..703ff26c 100644 --- a/src/debugProtocol/DebugProtocolClientReplaySession.ts +++ b/src/debugProtocol/DebugProtocolClientReplaySession.ts @@ -19,6 +19,8 @@ export class DebugProtocolClientReplaySession { */ private server: Net.Socket; + private ioSocket: Net.Socket; + private client: DebugProtocolClient; private entries: Array; @@ -100,7 +102,7 @@ export class DebugProtocolClientReplaySession { //whenever a client makes a connection // eslint-disable-next-line @typescript-eslint/no-misused-promises server.on('connection', (client: Net.Socket) => { - this.server = client; + this.ioSocket = client; //anytime we receive incoming data from the client client.on('data', (data) => { //TODO send IO data From b8eb58a02738f9dbc6aa1eb6d5ccca51c9699c76 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 8 Feb 2023 10:36:38 -0500 Subject: [PATCH 34/74] Fix several bugs. --- .../DebugProtocolClientReplaySession.spec.ts | 191 ++++++++++++++++++ .../DebugProtocolClientReplaySession.ts | 78 ++++--- .../client/DebugProtocolClient.ts | 22 +- src/managers/ActionQueue.ts | 11 +- 4 files changed, 261 insertions(+), 41 deletions(-) diff --git a/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts b/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts index 4b5941e3..24f28936 100644 --- a/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts +++ b/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts @@ -1,4 +1,5 @@ /* eslint-disable prefer-arrow-callback */ +import { expect } from 'chai'; import { DebugProtocolClientReplaySession } from './DebugProtocolClientReplaySession'; describe(DebugProtocolClientReplaySession.name, () => { @@ -100,4 +101,194 @@ describe(DebugProtocolClientReplaySession.name, () => { await session.run(); console.log(session); }); + + it.skip('works for this specific thing', async function test() { + this.timeout(10000000000); + const text = ` + {"type":"client-to-server","timestamp":"2023-02-06T16:47:49.370Z","buffer":{"type":"Buffer","data":[98,115,100,101,98,117,103,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:47:49.371Z","buffer":{"type":"Buffer","data":[98,115,100,101,98,117,103,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:47:49.374Z","buffer":{"type":"Buffer","data":[3,0,0,0,2,0,0,0,0,0,0,0,12,0,0,0,131,224,122,24,131,1,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:47:49.428Z","buffer":{"type":"Buffer","data":[20,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,146,31,0,0,27,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,4,66,82,69,65,75,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:47:49.433Z","buffer":{"type":"Buffer","data":[12,0,0,0,1,0,0,0,3,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:47:49.437Z","buffer":{"type":"Buffer","data":[91,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:47:49.489Z","buffer":{"type":"Buffer","data":[1,0,0,0,0,0,0,0,1,0,0,0,1,4,0,0,0,66,82,69,65,75,0,1,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,115,117,98,77,97,105,110,40,105,110,112,117,116,65,114,103,117,109,101,110,116,115,32,97,115,32,111,98,106,101,99,116,41,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:47:49.504Z","buffer":{"type":"Buffer","data":[16,0,0,0,2,0,0,0,4,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:47:49.506Z","buffer":{"type":"Buffer","data":[46,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:47:49.550Z","buffer":{"type":"Buffer","data":[2,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:47:49.552Z","buffer":{"type":"Buffer","data":[12,0,0,0,3,0,0,0,2,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:47:49.554Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:47:49.597Z","buffer":{"type":"Buffer","data":[3,0,0,0,0,0,0,0]}} + {"type":"io","timestamp":"2023-02-06T16:47:50.721Z","buffer":{"type":"Buffer","data":[48]}} + {"type":"io","timestamp":"2023-02-06T16:47:50.773Z","buffer":{"type":"Buffer","data":[50,45,48,54,32,49,49,58,52,55,58,53,50,46,56,54,50,32,91,82,84,65,93,91,73,78,70,79,93,32,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,32,105,110,105,116,10]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:47:57.260Z","buffer":{"type":"Buffer","data":[154,0,0,0,4,0,0,0,7,0,0,0,2,0,0,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,46,98,114,115,0,178,0,0,0,0,0,0,0,108,105,98,58,47,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,178,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:47:57.262Z","buffer":{"type":"Buffer","data":[40,0,0,0]}} + {"type":"io","timestamp":"2023-02-06T16:47:57.284Z","buffer":{"type":"Buffer","data":[48]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:47:57.314Z","buffer":{"type":"Buffer","data":[4,0,0,0,0,0,0,0,2,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0]}} + {"type":"io","timestamp":"2023-02-06T16:47:57.329Z","buffer":{"type":"Buffer","data":[50,45,48,54,32,49,54,58,52,55,58,53,57,46,52,50,54,32,91,115,99,114,112,116,46,99,109,112,108,93,32,67,111,109,112,105,108,105,110,103,32,39,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,39,44,32,105,100,32,39,82,83,71,95,66,65,65,65,65,65,68,48,83,82,101,112,39,10]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:47:57.360Z","buffer":{"type":"Buffer","data":[2,0,0,0,0,0,0,0,0,0,0,0]}} + {"type":"io","timestamp":"2023-02-06T16:47:57.386Z","buffer":{"type":"Buffer","data":[48]}} + {"type":"io","timestamp":"2023-02-06T16:47:57.436Z","buffer":{"type":"Buffer","data":[50,45,48,54,32,49,54,58,52,55,58,53,57,46,53,50,56,32,91,115,99,114,112,116,46,108,111,97,100,46,109,107,117,112,93,32,76,111,97,100,105,110,103,32,109,97,114,107,117,112,32,82,83,71,95,66,65,65,65,65,65,68,48,83,82,101,112,32,39,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,39,10]}} + {"type":"io","timestamp":"2023-02-06T16:47:57.482Z","buffer":{"type":"Buffer","data":[48,50,45,48,54,32,49,54,58,52,55,58,53,57,46,53,57,51,32,91,115,99,114,112,116,46,117,110,108,111,97,100,46,109,107,117,112,93,32,85,110,108,111,97,100,105,110,103,32,109,97,114,107,117,112,32,82,83,71,95,66,65,65,65,65,65,68,48,83,82,101,112,32,39,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,39,10]}} + {"type":"io","timestamp":"2023-02-06T16:47:57.581Z","buffer":{"type":"Buffer","data":[48]}} + {"type":"io","timestamp":"2023-02-06T16:47:57.638Z","buffer":{"type":"Buffer","data":[50,45,48,54,32,49,54,58,52,55,58,53,57,46,55,50,51,32,91,115,99,114,112,116,46,112,97,114,115,101,46,109,107,117,112,46,116,105,109,101,93,32,80,97,114,115,101,100,32,109,97,114,107,117,112,32,82,83,71,95,66,65,65,65,65,65,68,48,83,82,101,112,32,39,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,39,32,105,110,32,49,57,53,32,109,105,108,108,105,115,101,99,111,110,100,115,10]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:47:58.000Z","buffer":{"type":"Buffer","data":[28,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:47:58.054Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0]}} + {"type":"io","timestamp":"2023-02-06T16:48:02.781Z","buffer":{"type":"Buffer","data":[48]}} + {"type":"io","timestamp":"2023-02-06T16:48:02.829Z","buffer":{"type":"Buffer","data":[50,45,48,54,32,49,54,58,52,56,58,48,52,46,57,50,51,32,91,115,99,114,112,116,46,112,114,111,99,46,109,107,117,112,46,116,105,109,101,93,32,80,114,111,99,101,115,115,101,100,32,109,97,114,107,117,112,32,82,83,71,95,66,65,65,65,65,65,68,48,83,82,101,112,32,39,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,39,32,105,110,32,49,32,109,105,108,108,105,115,101,99,111,110,100,115,10,73,110,105,116,105,97,108,105,122,97,116,105,111,110,32,32,65,108,108,32,108,105,98,114,97,114,105,101,115,32,97,110,100,32,100,101,112,101,110,100,101,110,99,105,101,115,32,97,114,101,32,108,111,97,100,101,100,32,97,110,100,32,119,101,39,114,101,32,114,101,97,100,121,32,116,111,32,103,111,46,10,73,110,105,116,105,97,108,105,122,97,116,105,111,110,32,32,67,111,109,112,111,110,101,110,116,32,108,105,98,114,97,114,121,32,115,104,111,117,108,100,32,110,111,119,32,104,97,118,101,32,99,111,110,116,114,111,108,46,32,32,32,32,32,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,32,61,10,123,10,32,32,32,32,108,111,97,100,116,105,109,101,58,32,49,49,53,55,53,10,32,32,32,32,115,116,97,116,117,115,58,32,34,115,117,99,99,101,115,115,34,10,32,32,32,32,117,114,105,58,32,34,104,116,116,112,58,47,47,49,57,50,46,49,54,56,46,49,46,50,50,58,56,48,56,48,47,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,46,122,105,112,34,10,125,10]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:02.882Z","buffer":{"type":"Buffer","data":[28,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:02.935Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0]}} + {"type":"io","timestamp":"2023-02-06T16:48:02.975Z","buffer":{"type":"Buffer","data":[49]}} + {"type":"io","timestamp":"2023-02-06T16:48:03.028Z","buffer":{"type":"Buffer","data":[54,58,52,56,32,124,32,91,76,79,71,93,32,32,32,124,32,80,108,97,121,101,114,32,83,101,116,116,105,110,103,32,85,112,32,82,65,70,32,10,49,54,58,52,56,32,124,32,91,76,79,71,93,32,32,32,124,32,77,97,105,110,83,99,101,110,101,32,62,62,62,32,77,65,73,78,32,83,67,69,78,69,32,40,67,79,82,69,41,32,72,65,83,32,66,69,69,78,32,67,82,69,65,84,69,68,33,32,67,76,73,69,78,84,32,86,69,82,83,73,79,78,32,53,46,48,46,48,32,10]}} + {"type":"io","timestamp":"2023-02-06T16:48:03.080Z","buffer":{"type":"Buffer","data":[49]}} + {"type":"io","timestamp":"2023-02-06T16:48:03.136Z","buffer":{"type":"Buffer","data":[54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,32,114,117,110,110,105,110,103,58,32,116,114,117,101,32,10,49,54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,78,101,116,119,111,114,107,83,101,114,118,105,99,101,32,114,117,110,110,105,110,103,58,32,116,114,117,101,32,10,10,91,82,65,70,93,32,115,101,116,68,101,98,117,103,79,117,116,112,117,116,40,116,114,117,101,41,10,10,91,82,65,70,93,32,115,101,116,68,101,98,117,103,79,117,116,112,117,116,40,116,114,117,101,41,10]}} + {"type":"io","timestamp":"2023-02-06T16:48:03.322Z","buffer":{"type":"Buffer","data":[10]}} + {"type":"io","timestamp":"2023-02-06T16:48:03.366Z","buffer":{"type":"Buffer","data":[91,82,65,70,93,32,82,111,107,117,95,65,100,115,32,70,114,97,109,101,119,111,114,107,32,118,101,114,115,105,111,110,32,51,46,48,50,51,52,10,10,91,82,65,70,93,32,115,101,116,68,101,98,117,103,79,117,116,112,117,116,40,102,97,108,115,101,41,10,10,91,82,65,70,93,32,82,111,107,117,95,65,100,115,32,70,114,97,109,101,119,111,114,107,32,118,101,114,115,105,111,110,32,51,46,48,50,51,52,10]}} + {"type":"io","timestamp":"2023-02-06T16:48:03.412Z","buffer":{"type":"Buffer","data":[10,91,82,65,70,93,32,115,101,116,68,101,98,117,103,79,117,116,112,117,116,40,102,97,108,115,101,41,10,49,54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,32,114,117,110,110,105,110,103,58,32,116,114,117,101,32,10,49,54,58,52,56,32,124,32,91,76,79,71,93,32,32,32,124,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,114,114,97,121,62,32,61,10,91,10,32,32,32,32,34,65,99,116,105,111,110,34,10,32,32,32,32,34,65,112,112,108,105,99,97,116,105,111,110,83,116,97,116,101,34,10,32,32,32,32,34,65,117,116,104,34,10,32,32,32,32,34,67,111,110,116,101,120,116,77,101,110,117,34,10,32,32,32,32,34,68,101,118,101,108,111,112,101,114,34,10,32,32,32,32,34,68,105,97,108,111,103,34,10,32,32,32,32,34,80,114,111,102,105,108,101,34,10,32,32,32,32,34,80,114,111,102,105,108,101,82,101,103,105,115,116,114,121,34,10,32,32,32,32,34,82,101,103,105,115,116,114,121,34,10,32,32,32,32,34,82,111,119,73,116,101,109,34,10,32,32,32,32,34,83,99,114,101,101,110,34,10,32,32,32,32,34,84,111,97,115,116,34,10,32,32,32,32,34,84,111,111,108,116,105,112,34,10,93,32,10]}} + {"type":"io","timestamp":"2023-02-06T16:48:03.893Z","buffer":{"type":"Buffer","data":[49]}} + {"type":"io","timestamp":"2023-02-06T16:48:03.937Z","buffer":{"type":"Buffer","data":[54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,115,97,118,101,65,99,99,101,115,115,84,111,107,101,110,32,84,111,107,101,110,32,114,101,102,114,101,115,104,105,110,103,32,105,110,58,32,32,50,56,48,32,32,32,115,101,99,111,110,100,115,32,10]}} + {"type":"io","timestamp":"2023-02-06T16:48:04.003Z","buffer":{"type":"Buffer","data":[49]}} + {"type":"io","timestamp":"2023-02-06T16:48:04.044Z","buffer":{"type":"Buffer","data":[54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,65,99,116,105,111,110,77,97,110,97,103,101,114,32,101,120,101,99,117,116,101,65,99,116,105,111,110,32,45,32,110,97,118,105,103,97,116,105,111,110,32,10]}} + {"type":"io","timestamp":"2023-02-06T16:48:04.142Z","buffer":{"type":"Buffer","data":[48]}} + {"type":"io","timestamp":"2023-02-06T16:48:04.183Z","buffer":{"type":"Buffer","data":[50,45,48,54,32,49,54,58,52,56,58,48,54,46,50,56,52,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,68,105,97,108,111,103,73,110,105,116,105,97,116,101,32,45,45,45,45,45,45,45,45,45,62,32,84,105,109,101,66,97,115,101,40,50,48,51,51,57,32,109,115,41,10,49,54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,80,114,111,102,105,108,101,83,99,114,101,101,110,95,53,55,98,52,97,48,98,97,45,52,102,53,98,45,52,51,102,55,45,98,50,50,101,45,97,98,55,56,55,99,100,56,52,100,49,49,32,111,110,83,99,114,101,101,110,83,104,111,119,110,32,10]}} + {"type":"io","timestamp":"2023-02-06T16:48:05.795Z","buffer":{"type":"Buffer","data":[49]}} + {"type":"io","timestamp":"2023-02-06T16:48:05.930Z","buffer":{"type":"Buffer","data":[54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,78,101,119,32,112,114,111,102,105,108,101,32,115,101,108,101,99,116,101,100,58,32,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,83,71,78,111,100,101,58,67,111,110,116,101,110,116,78,111,100,101,62,32,61,10,123,10,32,32,32,32,99,104,97,110,103,101,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,102,111,99,117,115,97,98,108,101,58,32,102,97,108,115,101,10,32,32,32,32,102,111,99,117,115,101,100,67,104,105,108,100,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,73,110,118,97,108,105,100,62,10,32,32,32,32,105,100,58,32,34,53,50,98,54,97,100,100,48,45,54,54,55,57,45,52,50,98,49,45,56,48,49,102,45,51,53,100,100,97,99,99,101,98,102,55,101,34,10,32,32,32,32,97,118,97,116,97,114,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,110,97,109,101,58,32,34,66,114,111,110,108,101,121,34,10,32,32,32,32,116,121,112,101,58,32,34,112,114,111,102,105,108,101,34,10,125,32,10,49,54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,65,99,116,105,111,110,77,97,110,97,103,101,114,32,101,120,101,99,117,116,101,65,99,116,105,111,110,32,45,32,110,97,116,105,118,101,95,112,114,111,102,105,108,101,95,115,101,108,101,99,116,32,10]}} + {"type":"io","timestamp":"2023-02-06T16:48:06.091Z","buffer":{"type":"Buffer","data":[49]}} + {"type":"io","timestamp":"2023-02-06T16:48:06.187Z","buffer":{"type":"Buffer","data":[54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,65,99,116,105,111,110,77,97,110,97,103,101,114,32,101,120,101,99,117,116,101,65,99,116,105,111,110,32,45,32,110,97,116,105,118,101,95,110,97,118,105,103,97,116,101,95,116,111,95,108,97,110,100,105,110,103,32,10,49,54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,65,99,116,105,111,110,77,97,110,97,103,101,114,32,101,120,101,99,117,116,101,65,99,116,105,111,110,32,45,32,110,97,118,105,103,97,116,105,111,110,32,10]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:07.630Z","buffer":{"type":"Buffer","data":[27,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:07.679Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,2,0,0,0,4,0,0,0,4,66,82,69,65,75,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:07.825Z","buffer":{"type":"Buffer","data":[12,0,0,0,5,0,0,0,3,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:07.847Z","buffer":{"type":"Buffer","data":[228,2,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:07.898Z","buffer":{"type":"Buffer","data":[5,0,0,0,0,0,0,0,6,0,0,0,0,4,0,0,0,66,82,69,65,75,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,9,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,49,48,48,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,50,52,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,178,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,102,111,114,32,101,97,99,104,32,115,101,99,116,105,111,110,32,105,110,32,103,101,116,95,100,101,101,112,95,97,114,114,97,121,40,91,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,55,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:07.914Z","buffer":{"type":"Buffer","data":[16,0,0,0,6,0,0,0,4,0,0,0,4,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:07.914Z","buffer":{"type":"Buffer","data":[16,0,0,0,7,0,0,0,4,0,0,0,0,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:07.914Z","buffer":{"type":"Buffer","data":[16,0,0,0,8,0,0,0,4,0,0,0,1,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:07.915Z","buffer":{"type":"Buffer","data":[16,0,0,0,9,0,0,0,4,0,0,0,2,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:07.915Z","buffer":{"type":"Buffer","data":[16,0,0,0,10,0,0,0,4,0,0,0,3,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:07.915Z","buffer":{"type":"Buffer","data":[16,0,0,0,11,0,0,0,4,0,0,0,5,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:07.949Z","buffer":{"type":"Buffer","data":[166,1,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:07.950Z","buffer":{"type":"Buffer","data":[6,0,0,0,0,0,0,0,5,0,0,0,178,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,14,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,99,114,101,101,110,115,47,67,97,116,97,108,111,103,83,99,114,101,101,110,47,67,97,116,97,108,111,103,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,71,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,80,97,114,115,101,114,115,95,95,108,105,98,48,46,98,114,115,0,16,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,239,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,66,97,115,101,95,95,108,105,98,48,46,98,114,115,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:08.005Z","buffer":{"type":"Buffer","data":[46,0,0,0,7,0,0,0,0,0,0,0,1,0,0,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,80,0,0,0,8,0,0,0,0,0,0,0,1,0,0,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,104,0,0,0,9,0,0,0,0,0,0,0,1,0,0,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,110,0,0,0,10,0,0,0,0,0,0,0,1,0,0,0,19,0,0,0,32,50,50,52,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,116,0,0,0,11,0,0,0,0,0,0,0,1,0,0,0,21,0,0,0,32,50,50,55,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:08.441Z","buffer":{"type":"Buffer","data":[25,0,0,0,12,0,0,0,5,0,0,0,1,4,0,0,0,4,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:08.445Z","buffer":{"type":"Buffer","data":[200,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:08.487Z","buffer":{"type":"Buffer","data":[12,0,0,0,0,0,0,0,14,0,0,0,93,1,99,111,110,116,101,110,116,0,2,0,0,0,13,3,0,0,0,41,8,103,108,111,98,97,108,0,105,102,71,108,111,98,97,108,0,29,1,109,0,5,0,0,0,13,15,0,0,0,29,2,115,101,99,116,105,111,110,115,0,1,0,0,0,7,0,0,0,0,9,16,115,101,99,116,105,111,110,0,9,16,99,111,109,112,111,110,101,110,116,116,121,112,101,0,9,16,99,97,114,111,117,115,101,108,99,111,109,112,111,110,101,110,116,116,121,112,101,0,9,16,116,97,98,108,105,115,116,0,9,16,99,104,105,112,108,105,115,116,0,9,16,99,97,114,111,117,115,101,108,0,9,16,103,114,105,100,0,9,16,104,101,114,111,0,9,16,108,105,115,116,0,9,16,102,97,108,108,98,97,99,107,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:11.039Z","buffer":{"type":"Buffer","data":[24,0,0,0,13,0,0,0,9,0,0,0,2,0,0,0,2,0,0,0,2,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:11.042Z","buffer":{"type":"Buffer","data":[40,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:11.096Z","buffer":{"type":"Buffer","data":[13,0,0,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:11.821Z","buffer":{"type":"Buffer","data":[12,0,0,0,14,0,0,0,2,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:11.824Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:11.869Z","buffer":{"type":"Buffer","data":[14,0,0,0,0,0,0,0]}} + {"type":"io","timestamp":"2023-02-06T16:48:12.746Z","buffer":{"type":"Buffer","data":[49]}} + {"type":"io","timestamp":"2023-02-06T16:48:12.791Z","buffer":{"type":"Buffer","data":[54,58,52,56,32,124,32,91,87,65,82,78,93,32,32,124,32,112,97,114,115,101,83,101,99,116,105,111,110,115,32,78,111,32,99,104,105,108,100,32,99,111,109,112,111,110,101,110,116,115,32,116,111,32,114,101,110,100,101,114,46,32,67,97,114,111,117,115,101,108,32,116,104,114,111,119,110,32,111,117,116,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,32,61,10,123,10,32,32,32,32,99,111,109,112,111,110,101,110,116,95,116,121,112,101,58,32,34,99,97,114,100,45,119,105,100,101,34,10,32,32,32,32,99,111,110,116,101,120,116,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,102,111,99,117,115,101,100,95,101,108,101,109,101,110,116,58,32,102,97,108,115,101,10,32,32,32,32,115,105,122,101,58,32,34,109,34,10,32,32,32,32,115,108,117,103,58,32,34,100,118,114,45,99,111,110,116,101,110,116,45,99,97,114,111,117,115,101,108,34,10,32,32,32,32,116,105,116,108,101,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,116,121,112,101,58,32,34,99,97,114,111,117,115,101,108,34,10,125,32,10,49,54,58,52,56,32,124,32,91,87,65,82,78,93,32,32,124,32,112,97,114,115,101,83,101,99,116,105,111,110,115,32,78,111,32,99,104,105,108,100,32,99,111,109,112,111,110,101,110,116,115,32,116,111,32,114,101,110,100,101,114,46,32,67,97,114,111,117,115,101,108,32,116,104,114,111,119,110,32,111,117,116,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,32,61,10,123,10,32,32,32,32,99,111,109,112,111,110,101,110,116,95,116,121,112,101,58,32,34,99,97,114,100,45,119,105,100,101,34,10,32,32,32,32,99,111,110,116,101,120,116,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,102,111,99,117,115,101,100,95,101,108,101,109,101,110,116,58,32,102,97,108,115,101,10,32,32,32,32,115,105,122,101,58,32,34,109,34,10,32,32,32,32,115,108,117,103,58,32,34,118,111,100,45,99,111,110,116,101,110,116,45,99,97,114,111,117,115,101,108,34,10,32,32,32,32,116,105,116,108,101,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,116,121,112,101,58,32,34,99,97,114,111,117,115,101,108,34,10,125,32,10]}} + {"type":"io","timestamp":"2023-02-06T16:48:12.864Z","buffer":{"type":"Buffer","data":[49]}} + {"type":"io","timestamp":"2023-02-06T16:48:12.916Z","buffer":{"type":"Buffer","data":[54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,80,114,111,102,105,108,101,83,99,114,101,101,110,95,53,55,98,52,97,48,98,97,45,52,102,53,98,45,52,51,102,55,45,98,50,50,101,45,97,98,55,56,55,99,100,56,52,100,49,49,32,111,110,83,99,114,101,101,110,72,105,100,100,101,110,32,10,48,50,45,48,54,32,49,54,58,52,56,58,49,53,46,48,48,55,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,68,105,97,108,111,103,67,111,109,112,108,101,116,101,32,45,45,45,45,45,45,45,45,45,62,32,68,117,114,97,116,105,111,110,40,56,55,50,50,32,109,115,41,10,48,50,45,48,54,32,49,54,58,52,56,58,49,53,46,48,49,49,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,68,105,97,108,111,103,73,110,105,116,105,97,116,101,32,45,45,45,45,45,45,45,45,45,62,32,84,105,109,101,66,97,115,101,40,50,57,48,54,53,32,109,115,41,10,49,54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,83,101,99,116,105,111,110,115,32,115,101,99,116,105,111,110,115,32,108,111,97,100,101,100,58,32,32,55,32,10]}} + {"type":"io","timestamp":"2023-02-06T16:48:13.301Z","buffer":{"type":"Buffer","data":[48]}} + {"type":"io","timestamp":"2023-02-06T16:48:13.350Z","buffer":{"type":"Buffer","data":[50,45,48,54,32,49,54,58,52,56,58,49,53,46,52,52,52,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,68,105,97,108,111,103,67,111,109,112,108,101,116,101,32,45,45,45,45,45,45,45,45,45,62,32,68,117,114,97,116,105,111,110,40,52,51,51,32,109,115,41,10,48,50,45,48,54,32,49,54,58,52,56,58,49,53,46,52,52,52,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,76,97,117,110,99,104,67,111,109,112,108,101,116,101,32,45,45,45,45,45,45,45,45,45,62,32,80,101,110,100,105,110,103,32,82,101,110,100,101,114,32,80,97,115,115,10]}} + {"type":"io","timestamp":"2023-02-06T16:48:13.420Z","buffer":{"type":"Buffer","data":[48]}} + {"type":"io","timestamp":"2023-02-06T16:48:13.472Z","buffer":{"type":"Buffer","data":[50,45,48,54,32,49,54,58,52,56,58,49,53,46,53,54,51,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,76,97,117,110,99,104,67,111,109,112,108,101,116,101,32,45,45,45,45,45,45,45,45,45,62,32,68,117,114,97,116,105,111,110,40,50,57,54,49,48,32,109,115,41,44,32,68,105,97,108,111,103,84,105,109,101,40,57,49,53,53,32,109,115,41,10]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:18.062Z","buffer":{"type":"Buffer","data":[154,0,0,0,15,0,0,0,7,0,0,0,2,0,0,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,46,98,114,115,0,185,0,0,0,0,0,0,0,108,105,98,58,47,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,185,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:18.065Z","buffer":{"type":"Buffer","data":[40,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:18.116Z","buffer":{"type":"Buffer","data":[15,0,0,0,0,0,0,0,2,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:18.195Z","buffer":{"type":"Buffer","data":[3,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:18.240Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:18.308Z","buffer":{"type":"Buffer","data":[4,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:18.362Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:18.440Z","buffer":{"type":"Buffer","data":[28,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:18.486Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,1,0,0,0,4,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:18.629Z","buffer":{"type":"Buffer","data":[28,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:18.671Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,1,0,0,0,3,0,0,0]}} + {"type":"io","timestamp":"2023-02-06T16:48:22.015Z","buffer":{"type":"Buffer","data":[49]}} + {"type":"io","timestamp":"2023-02-06T16:48:22.070Z","buffer":{"type":"Buffer","data":[54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,65,99,116,105,111,110,77,97,110,97,103,101,114,32,101,120,101,99,117,116,101,65,99,116,105,111,110,32,45,32,110,97,118,105,103,97,116,105,111,110,32,10]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:23.294Z","buffer":{"type":"Buffer","data":[27,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:23.337Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,2,0,0,0,5,0,0,0,4,66,82,69,65,75,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:23.466Z","buffer":{"type":"Buffer","data":[12,0,0,0,16,0,0,0,3,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:23.516Z","buffer":{"type":"Buffer","data":[138,3,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:23.569Z","buffer":{"type":"Buffer","data":[16,0,0,0,0,0,0,0,7,0,0,0,0,4,0,0,0,66,82,69,65,75,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,22,0,0,0,36,97,110,111,110,95,50,97,55,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,119,105,100,116,104,32,61,32,101,118,101,110,116,46,103,101,116,68,97,116,97,40,41,0,0,4,0,0,0,66,82,69,65,75,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,9,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,49,48,48,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,50,52,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,185,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,99,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,103,101,116,95,115,116,114,105,110,103,40,115,101,99,116,105,111,110,44,32,34,116,121,112,101,34,41,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,55,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:23.586Z","buffer":{"type":"Buffer","data":[16,0,0,0,17,0,0,0,4,0,0,0,5,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:23.586Z","buffer":{"type":"Buffer","data":[16,0,0,0,18,0,0,0,4,0,0,0,0,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:23.586Z","buffer":{"type":"Buffer","data":[16,0,0,0,19,0,0,0,4,0,0,0,1,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:23.586Z","buffer":{"type":"Buffer","data":[16,0,0,0,20,0,0,0,4,0,0,0,2,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:23.587Z","buffer":{"type":"Buffer","data":[16,0,0,0,21,0,0,0,4,0,0,0,3,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:23.587Z","buffer":{"type":"Buffer","data":[16,0,0,0,22,0,0,0,4,0,0,0,4,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:23.587Z","buffer":{"type":"Buffer","data":[16,0,0,0,23,0,0,0,4,0,0,0,6,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:23.601Z","buffer":{"type":"Buffer","data":[166,1,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:23.647Z","buffer":{"type":"Buffer","data":[17,0,0,0,0,0,0,0,5,0,0,0,185,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,14,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,99,114,101,101,110,115,47,67,97,116,97,108,111,103,83,99,114,101,101,110,47,67,97,116,97,108,111,103,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,71,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,80,97,114,115,101,114,115,95,95,108,105,98,48,46,98,114,115,0,16,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,239,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,66,97,115,101,95,95,108,105,98,48,46,98,114,115,0,46,0,0,0,18,0,0,0,0,0,0,0,1,0,0,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,129,0,0,0,19,0,0,0,0,0,0,0,1,0,0,0,22,0,0,0,36,97,110,111,110,95,50,97,55,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,80,0,0,0,20,0,0,0,0,0,0,0,1,0,0,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,104,0,0,0,21,0,0,0,0,0,0,0,1,0,0,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,110,0,0,0,22,0,0,0,0,0,0,0,1,0,0,0,19,0,0,0,32,50,50,52,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,116,0,0,0,23,0,0,0,0,0,0,0,1,0,0,0,21,0,0,0,32,50,50,55,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:24.096Z","buffer":{"type":"Buffer","data":[25,0,0,0,24,0,0,0,5,0,0,0,1,5,0,0,0,4,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:24.108Z","buffer":{"type":"Buffer","data":[209,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:24.158Z","buffer":{"type":"Buffer","data":[24,0,0,0,0,0,0,0,14,0,0,0,93,1,99,111,110,116,101,110,116,0,2,0,0,0,13,3,0,0,0,41,8,103,108,111,98,97,108,0,105,102,71,108,111,98,97,108,0,29,1,109,0,5,0,0,0,13,15,0,0,0,29,2,115,101,99,116,105,111,110,115,0,1,0,0,0,7,0,0,0,0,93,1,115,101,99,116,105,111,110,0,2,0,0,0,13,8,0,0,0,9,16,99,111,109,112,111,110,101,110,116,116,121,112,101,0,9,16,99,97,114,111,117,115,101,108,99,111,109,112,111,110,101,110,116,116,121,112,101,0,9,16,116,97,98,108,105,115,116,0,9,16,99,104,105,112,108,105,115,116,0,9,16,99,97,114,111,117,115,101,108,0,9,16,103,114,105,100,0,9,16,104,101,114,111,0,9,16,108,105,115,116,0,9,16,102,97,108,108,98,97,99,107,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:25.329Z","buffer":{"type":"Buffer","data":[12,0,0,0,25,0,0,0,2,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:25.331Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:25.380Z","buffer":{"type":"Buffer","data":[25,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:27.334Z","buffer":{"type":"Buffer","data":[27,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:27.387Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,2,0,0,0,5,0,0,0,4,66,82,69,65,75,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:27.524Z","buffer":{"type":"Buffer","data":[12,0,0,0,26,0,0,0,3,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:27.560Z","buffer":{"type":"Buffer","data":[117,3,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:27.602Z","buffer":{"type":"Buffer","data":[26,0,0,0,0,0,0,0,7,0,0,0,0,4,0,0,0,66,82,69,65,75,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,27,0,0,0,36,97,110,111,110,95,50,97,55,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,125,41,0,0,4,0,0,0,66,82,69,65,75,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,9,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,49,48,48,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,50,52,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,185,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,99,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,103,101,116,95,115,116,114,105,110,103,40,115,101,99,116,105,111,110,44,32,34,116,121,112,101,34,41,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,55,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:27.619Z","buffer":{"type":"Buffer","data":[16,0,0,0,27,0,0,0,4,0,0,0,5,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:27.619Z","buffer":{"type":"Buffer","data":[16,0,0,0,28,0,0,0,4,0,0,0,0,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:27.619Z","buffer":{"type":"Buffer","data":[16,0,0,0,29,0,0,0,4,0,0,0,1,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:27.619Z","buffer":{"type":"Buffer","data":[16,0,0,0,30,0,0,0,4,0,0,0,2,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:27.620Z","buffer":{"type":"Buffer","data":[16,0,0,0,31,0,0,0,4,0,0,0,3,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:27.620Z","buffer":{"type":"Buffer","data":[16,0,0,0,32,0,0,0,4,0,0,0,4,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:27.620Z","buffer":{"type":"Buffer","data":[16,0,0,0,33,0,0,0,4,0,0,0,6,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:27.634Z","buffer":{"type":"Buffer","data":[166,1,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:27.635Z","buffer":{"type":"Buffer","data":[27,0,0,0,0,0,0,0,5,0,0,0,185,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,14,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,99,114,101,101,110,115,47,67,97,116,97,108,111,103,83,99,114,101,101,110,47,67,97,116,97,108,111,103,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,71,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,80,97,114,115,101,114,115,95,95,108,105,98,48,46,98,114,115,0,16,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,239,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,66,97,115,101,95,95,108,105,98,48,46,98,114,115,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:27.680Z","buffer":{"type":"Buffer","data":[46,0,0,0,28,0,0,0,0,0,0,0,1,0,0,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,129,0,0,0,29,0,0,0,0,0,0,0,1,0,0,0,27,0,0,0,36,97,110,111,110,95,50,97,55,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,80,0,0,0,30,0,0,0,0,0,0,0,1,0,0,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,104,0,0,0,31,0,0,0,0,0,0,0,1,0,0,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,110,0,0,0,32,0,0,0,0,0,0,0,1,0,0,0,19,0,0,0,32,50,50,52,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,116,0,0,0,33,0,0,0,0,0,0,0,1,0,0,0,21,0,0,0,32,50,50,55,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:28.104Z","buffer":{"type":"Buffer","data":[25,0,0,0,34,0,0,0,5,0,0,0,1,5,0,0,0,4,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:28.116Z","buffer":{"type":"Buffer","data":[245,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:28.163Z","buffer":{"type":"Buffer","data":[34,0,0,0,0,0,0,0,14,0,0,0,93,1,99,111,110,116,101,110,116,0,2,0,0,0,13,3,0,0,0,41,8,103,108,111,98,97,108,0,105,102,71,108,111,98,97,108,0,29,1,109,0,5,0,0,0,13,15,0,0,0,29,2,115,101,99,116,105,111,110,115,0,1,0,0,0,7,1,0,0,0,93,1,115,101,99,116,105,111,110,0,2,0,0,0,13,7,0,0,0,57,13,99,111,109,112,111,110,101,110,116,116,121,112,101,0,2,0,0,0,99,97,114,111,117,115,101,108,0,57,13,99,97,114,111,117,115,101,108,99,111,109,112,111,110,101,110,116,116,121,112,101,0,3,0,0,0,99,97,114,100,45,119,105,100,101,0,9,16,116,97,98,108,105,115,116,0,9,16,99,104,105,112,108,105,115,116,0,29,1,99,97,114,111,117,115,101,108,0,2,0,0,0,13,11,0,0,0,9,16,103,114,105,100,0,9,16,104,101,114,111,0,9,16,108,105,115,116,0,9,16,102,97,108,108,98,97,99,107,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:28.841Z","buffer":{"type":"Buffer","data":[17,0,0,0,35,0,0,0,6,0,0,0,5,0,0,0,3]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:28.845Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:28.892Z","buffer":{"type":"Buffer","data":[35,0,0,0,0,0,0,0]}} + {"type":"io","timestamp":"2023-02-06T16:48:29.335Z","buffer":{"type":"Buffer","data":[49]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:29.343Z","buffer":{"type":"Buffer","data":[27,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:29.386Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,2,0,0,0,5,0,0,0,4,66,82,69,65,75,0]}} + {"type":"io","timestamp":"2023-02-06T16:48:29.387Z","buffer":{"type":"Buffer","data":[56,53,58,32,32,32,32,32,32,32,32,32,32,32,32,32,99,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,103,101,116,95,115,116,114,105,110,103,40,115,101,99,116,105,111,110,44,32,34,116,121,112,101,34,41,10]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:29.511Z","buffer":{"type":"Buffer","data":[12,0,0,0,36,0,0,0,3,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:29.546Z","buffer":{"type":"Buffer","data":[117,3,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:29.603Z","buffer":{"type":"Buffer","data":[36,0,0,0,0,0,0,0,7,0,0,0,0,4,0,0,0,66,82,69,65,75,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,27,0,0,0,36,97,110,111,110,95,50,97,55,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,125,41,0,0,4,0,0,0,66,82,69,65,75,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,9,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,49,48,48,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,50,52,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,185,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,99,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,103,101,116,95,115,116,114,105,110,103,40,115,101,99,116,105,111,110,44,32,34,116,121,112,101,34,41,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,55,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:29.620Z","buffer":{"type":"Buffer","data":[16,0,0,0,37,0,0,0,4,0,0,0,5,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:29.620Z","buffer":{"type":"Buffer","data":[16,0,0,0,38,0,0,0,4,0,0,0,0,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:29.620Z","buffer":{"type":"Buffer","data":[16,0,0,0,39,0,0,0,4,0,0,0,1,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:29.620Z","buffer":{"type":"Buffer","data":[16,0,0,0,40,0,0,0,4,0,0,0,2,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:29.621Z","buffer":{"type":"Buffer","data":[16,0,0,0,41,0,0,0,4,0,0,0,3,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:29.621Z","buffer":{"type":"Buffer","data":[16,0,0,0,42,0,0,0,4,0,0,0,4,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:29.621Z","buffer":{"type":"Buffer","data":[16,0,0,0,43,0,0,0,4,0,0,0,6,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:29.635Z","buffer":{"type":"Buffer","data":[166,1,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:29.680Z","buffer":{"type":"Buffer","data":[37,0,0,0,0,0,0,0,5,0,0,0,185,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,14,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,99,114,101,101,110,115,47,67,97,116,97,108,111,103,83,99,114,101,101,110,47,67,97,116,97,108,111,103,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,71,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,80,97,114,115,101,114,115,95,95,108,105,98,48,46,98,114,115,0,16,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,239,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,66,97,115,101,95,95,108,105,98,48,46,98,114,115,0,46,0,0,0,38,0,0,0,0,0,0,0,1,0,0,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,129,0,0,0,39,0,0,0,0,0,0,0,1,0,0,0,27,0,0,0,36,97,110,111,110,95,50,97,55,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,80,0,0,0,40,0,0,0,0,0,0,0,1,0,0,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,104,0,0,0,41,0,0,0,0,0,0,0,1,0,0,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,110,0,0,0,42,0,0,0,0,0,0,0,1,0,0,0,19,0,0,0,32,50,50,52,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,116,0,0,0,43,0,0,0,0,0,0,0,1,0,0,0,21,0,0,0,32,50,50,55,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:30.107Z","buffer":{"type":"Buffer","data":[25,0,0,0,44,0,0,0,5,0,0,0,1,5,0,0,0,4,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:30.119Z","buffer":{"type":"Buffer","data":[246,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:30.163Z","buffer":{"type":"Buffer","data":[44,0,0,0,0,0,0,0,14,0,0,0,93,1,99,111,110,116,101,110,116,0,2,0,0,0,13,3,0,0,0,41,8,103,108,111,98,97,108,0,105,102,71,108,111,98,97,108,0,29,1,109,0,5,0,0,0,13,15,0,0,0,29,2,115,101,99,116,105,111,110,115,0,1,0,0,0,7,1,0,0,0,93,1,115,101,99,116,105,111,110,0,2,0,0,0,13,7,0,0,0,57,13,99,111,109,112,111,110,101,110,116,116,121,112,101,0,2,0,0,0,99,104,105,112,45,108,105,115,116,0,57,13,99,97,114,111,117,115,101,108,99,111,109,112,111,110,101,110,116,116,121,112,101,0,3,0,0,0,99,97,114,100,45,119,105,100,101,0,9,16,116,97,98,108,105,115,116,0,9,16,99,104,105,112,108,105,115,116,0,29,1,99,97,114,111,117,115,101,108,0,2,0,0,0,13,11,0,0,0,9,16,103,114,105,100,0,9,16,104,101,114,111,0,9,16,108,105,115,116,0,9,16,102,97,108,108,98,97,99,107,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:30.906Z","buffer":{"type":"Buffer","data":[24,0,0,0,45,0,0,0,9,0,0,0,2,0,0,0,4,0,0,0,4,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:30.908Z","buffer":{"type":"Buffer","data":[40,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:30.956Z","buffer":{"type":"Buffer","data":[45,0,0,0,0,0,0,0,2,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:32.148Z","buffer":{"type":"Buffer","data":[12,0,0,0,46,0,0,0,2,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:32.151Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:32.196Z","buffer":{"type":"Buffer","data":[46,0,0,0,0,0,0,0]}} + {"type":"io","timestamp":"2023-02-06T16:48:32.419Z","buffer":{"type":"Buffer","data":[49]}} + {"type":"io","timestamp":"2023-02-06T16:48:32.474Z","buffer":{"type":"Buffer","data":[54,58,52,56,32,124,32,91,87,65,82,78,93,32,32,124,32,112,97,114,115,101,83,101,99,116,105,111,110,115,32,78,111,32,99,104,105,108,100,32,99,111,109,112,111,110,101,110,116,115,32,116,111,32,114,101,110,100,101,114,46,32,67,97,114,111,117,115,101,108,32,116,104,114,111,119,110,32,111,117,116,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,32,61,10,123,10,32,32,32,32,99,111,109,112,111,110,101,110,116,95,116,121,112,101,58,32,34,99,97,114,100,45,119,105,100,101,34,10,32,32,32,32,99,111,110,116,101,120,116,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,102,111,99,117,115,101,100,95,101,108,101,109,101,110,116,58,32,102,97,108,115,101,10,32,32,32,32,103,114,111,117,112,95,105,100,58,32,34,49,34,10,32,32,32,32,115,105,122,101,58,32,34,115,34,10,32,32,32,32,115,108,117,103,58,32,34,118,111,100,34,10,32,32,32,32,116,105,116,108,101,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,116,121,112,101,58,32,34,99,97,114,111,117,115,101,108,34,10,125,32,10,49,54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,47,104,111,109,101,32,111,110,83,99,114,101,101,110,72,105,100,100,101,110,32,10]}} + {"type":"io","timestamp":"2023-02-06T16:48:32.519Z","buffer":{"type":"Buffer","data":[49,54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,83,101,99,116,105,111,110,115,32,115,101,99,116,105,111,110,115,32,108,111,97,100,101,100,58,32,32,52,32,10]}} + {"type":"client-to-server","timestamp":"2023-02-06T16:48:36.170Z","buffer":{"type":"Buffer","data":[12,0,0,0,47,0,0,0,122,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:36.171Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-06T16:48:36.176Z","buffer":{"type":"Buffer","data":[47,0,0,0,0,0,0,0]}} + + `; + const session = new DebugProtocolClientReplaySession({ + bufferLog: text + }); + + await session.run(); + expectResult([], result); + console.log(session); + }); }); + +function expectResult(expected: any[], result: DebugProtocolClientReplaySession['result']) { + let sanitizedResult = result.map((x, i) => { + if (typeof expected[i] === 'function') { + return x?.constructor?.name; + } else { + return x; + } + }); + expect(sanitizedResult).to.eql(expected); +} diff --git a/src/debugProtocol/DebugProtocolClientReplaySession.ts b/src/debugProtocol/DebugProtocolClientReplaySession.ts index 703ff26c..20bea590 100644 --- a/src/debugProtocol/DebugProtocolClientReplaySession.ts +++ b/src/debugProtocol/DebugProtocolClientReplaySession.ts @@ -23,8 +23,16 @@ export class DebugProtocolClientReplaySession { private client: DebugProtocolClient; + private entryIndex = 0; private entries: Array; + private peekEntry() { + return this.entries[this.entryIndex]; + } + private advanceEntry() { + return this.entries[this.entryIndex++]; + } + private parseBufferLog(bufferLog: string) { this.entries = bufferLog .split(/\r?\n/g) @@ -73,6 +81,10 @@ export class DebugProtocolClientReplaySession { this.result.push(update); }); + this.client.on('io-output', (data) => { + console.log(data); + }); + //anytime the client receives buffer data, we should try and process it this.client.on('data', (data) => { this.clientSync.pushActual(data); @@ -123,17 +135,12 @@ export class DebugProtocolClientReplaySession { private async clientProcess() { await this.clientActionQueue.run(async () => { - let clientBuffer = Buffer.alloc(0); //build a single buffer of client data - while (this.entries[0]?.type === 'client-to-server') { - const entry = this.entries.shift(); - clientBuffer = Buffer.concat([clientBuffer, entry.buffer]); - } - //build and send requests - while (clientBuffer.length > 0) { - const request = DebugProtocolServer.getRequest(clientBuffer, true); - //remove the processed bytes - clientBuffer = clientBuffer.slice(request.readOffset); + while (this.peekEntry()?.type === 'client-to-server') { + //make sure it's been enough time since the last entry + await this.sleepForEntryGap(); + const entry = this.advanceEntry(); + const request = DebugProtocolServer.getRequest(entry.buffer, true); //store this client data for our mock server to recognize and track this.serverSync.pushExpected(request.toBuffer()); @@ -144,8 +151,6 @@ export class DebugProtocolClientReplaySession { //send the request void this.client.processRequest(request); - //wait small timeout before sending the next request - await util.sleep(10); } this.finalizeIfDone(); return true; @@ -153,7 +158,7 @@ export class DebugProtocolClientReplaySession { } private finalizeIfDone() { - if (this.clientSync.areInSync && this.serverSync.areInSync && this.entries.length === 0) { + if (this.clientSync.areInSync && this.serverSync.areInSync && this.entryIndex >= this.entries.length) { this.finished.resolve(); } } @@ -171,6 +176,7 @@ export class DebugProtocolClientReplaySession { this.server = client; //anytime we receive incoming data from the client client.on('data', (data) => { + console.log('server got:', JSON.stringify(data.toJSON().data)); void this.serverProcess(data); }); }); @@ -186,23 +192,47 @@ export class DebugProtocolClientReplaySession { private serverActionQueue = new ActionQueue(); private serverSync = new BufferSync(); + private serverProcessIdx = 0; private async serverProcess(data: Buffer) { + let serverProcesIdx = this.serverProcessIdx++; await this.serverActionQueue.run(async () => { - this.serverSync.pushActual(data); - if (this.serverSync.areInSync) { - this.serverSync.clear(); - //send all the server messages, each delayed slightly to simulate the chunked buffer flushing that roku causes - while (this.entries[0]?.type === 'server-to-client') { - const entry = this.entries.shift(); - this.server.write(entry.buffer); - this.clientSync.pushExpected(entry.buffer); - await util.sleep(10); + try { + console.log(serverProcesIdx); + this.serverSync.pushActual(data); + if (this.serverSync.areInSync) { + this.serverSync.clear(); + //send all the server messages, each delayed slightly to simulate the chunked buffer flushing that roku causes + while (this.peekEntry()?.type === 'server-to-client') { + //make sure enough time has passed since the last entry + await this.sleepForEntryGap(); + const entry = this.advanceEntry(); + this.server.write(entry.buffer); + this.clientSync.pushExpected(entry.buffer); + } } + this.finalizeIfDone(); + } catch (e) { + console.error('serverProcess failed to handle buffer', e); } - this.finalizeIfDone(); return true; }); } + + /** + * Sleep for the amount of time between the two specified entries + */ + private async sleepForEntryGap() { + const currentEntry = this.entries[this.entryIndex]; + const previousEntry = this.entries[this.entryIndex - 1]; + let gap = 0; + if (currentEntry && previousEntry) { + gap = currentEntry.timestamp.getTime() - previousEntry?.timestamp.getTime(); + //if the gap is negative, then the time has already passed. Just timeout at zero + gap = gap > 0 ? gap : 0; + } + console.log(`sleeping for ${gap}ms`); + await util.sleep(gap); + } } class BufferSync { @@ -246,5 +276,3 @@ export interface BufferLogEntry { timestamp: Date; buffer: Buffer; } - - diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 212ba4d9..92f9fb0e 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -303,11 +303,7 @@ export class DebugProtocolClient { private async processContinueRequest(request: ContinueRequest) { if (this.isStopped) { this.isStopped = false; - return this.sendRequest( - ContinueRequest.fromJson({ - requestId: this.requestIdSequence++ - }) - ); + return this.sendRequest(request); } } @@ -382,11 +378,7 @@ export class DebugProtocolClient { } public async processThreadsRequest(request: ThreadsRequest) { if (this.isStopped) { - let result = await this.sendRequest( - ThreadsRequest.fromJson({ - requestId: this.requestIdSequence++ - }) - ); + let result = await this.sendRequest(request); if (result.data.errorCode === ErrorCode.OK) { //older versions of the debug protocol had issues with maintaining the active thread, so our workaround is to keep track of it elsewhere @@ -607,9 +599,10 @@ export class DebugProtocolClient { } }); - this.logger.debug('sendRequest', `requestId=${request.data.requestId}`, request); + this.logger.log(`Request ${request?.data?.requestId}`, request); if (this.controlSocket) { const buffer = request.toBuffer(); + console.log('client sent', JSON.stringify(buffer.toJSON().data)); this.controlSocket.write(buffer); void this.plugins.emit('afterSendRequest', { client: this, @@ -663,14 +656,14 @@ export class DebugProtocolClient { if (responseOrUpdate) { //emit the corresponding event if (isProtocolUpdate(responseOrUpdate)) { - this.logger.log('Update from server:', responseOrUpdate); + this.logger.log(`Update:`, responseOrUpdate); this.emit('update', responseOrUpdate); await this.plugins.emit('onUpdate', { client: this, update: responseOrUpdate }); } else { - this.logger.log('Response from server:', responseOrUpdate); + this.logger.log(`Response ${responseOrUpdate?.data?.requestId}:`, responseOrUpdate); this.emit('response', responseOrUpdate); await this.plugins.emit('onResponse', { client: this, @@ -868,12 +861,11 @@ export class DebugProtocolClient { * When the debugger emits the IOPortOpenedUpdate, we need to immediately connect to the IO port to start receiving that data */ private connectToIoPort(update: IOPortOpenedUpdate) { - this.logger.log('Connecting to IO port. response status success =', update.success); if (update.success) { // Create a new TCP client. this.ioSocket = new Net.Socket(); // Send a connection request to the server. - this.logger.log('Connect to IO Port: port', update.data, 'host', this.options.host); + this.logger.log(`Connect to IO Port ${this.options.host}:${update.data.port}`); this.ioSocket.connect({ port: update.data.port, host: this.options.host diff --git a/src/managers/ActionQueue.ts b/src/managers/ActionQueue.ts index f7cdb1f3..2f188f66 100644 --- a/src/managers/ActionQueue.ts +++ b/src/managers/ActionQueue.ts @@ -24,11 +24,19 @@ export class ActionQueue { await this._runActions(); } + private isRunning = false; + private async _runActions() { + if (this.isRunning) { + return; + } + this.isRunning = true; while (this.queueItems.length > 0) { const queueItem = this.queueItems[0]; try { - const isFinished = await Promise.resolve(queueItem.action()); + const isFinished = await Promise.resolve( + queueItem.action() + ); if (isFinished) { this.queueItems.shift(); queueItem.deferred.resolve(); @@ -38,5 +46,6 @@ export class ActionQueue { queueItem.deferred.reject(error); } } + this.isRunning = false; } } From c336e327ca2ccee5b95cba3a4d48de0f8fec8f83 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 8 Feb 2023 10:41:09 -0500 Subject: [PATCH 35/74] Process more frequently --- src/debugProtocol/DebugProtocolClientReplaySession.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/debugProtocol/DebugProtocolClientReplaySession.ts b/src/debugProtocol/DebugProtocolClientReplaySession.ts index 20bea590..d7b7ee70 100644 --- a/src/debugProtocol/DebugProtocolClientReplaySession.ts +++ b/src/debugProtocol/DebugProtocolClientReplaySession.ts @@ -76,13 +76,16 @@ export class DebugProtocolClientReplaySession { //store the responses in the result this.client.on('response', (response) => { this.result.push(response); + void this.clientProcess(); }); this.client.on('update', (update) => { this.result.push(update); + void this.clientProcess(); }); this.client.on('io-output', (data) => { console.log(data); + void this.clientProcess(); }); //anytime the client receives buffer data, we should try and process it @@ -230,7 +233,9 @@ export class DebugProtocolClientReplaySession { //if the gap is negative, then the time has already passed. Just timeout at zero gap = gap > 0 ? gap : 0; } - console.log(`sleeping for ${gap}ms`); + //TODO should we even re-implement this? + //console.log(`sleeping for ${gap}ms`); + gap = 10; await util.sleep(gap); } } From 70ef80a7547c3b27f63687b8df972f595098f917 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 8 Feb 2023 13:58:18 -0500 Subject: [PATCH 36/74] Discard unrecognized updates/responses --- .../client/DebugProtocolClient.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 92f9fb0e..faa00554 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -614,6 +614,21 @@ export class DebugProtocolClient { }); } + /** + * Sometimes a request arrives that we don't understand. If that's the case, this function can be used + * to discard that entire response by discarding `packet_length` number of bytes + */ + private discardNextResponseOrUpdate() { + const response = GenericV3Response.fromBuffer(this.buffer); + if (response.success && response.data.packetLength > 0) { + this.logger.warn(`Unsupported response or updated encountered. Discarding ${response.data.packetLength} bytes:`, JSON.stringify( + this.buffer.slice(0, response.data.packetLength + 1).toJSON().data + )); + //we have a valid event. Clear the buffer of this data + this.buffer = this.buffer.slice(response.data.packetLength); + } + } + private async process(): Promise { try { this.logger.info('[process()]: buffer=', this.buffer.toJSON()); @@ -631,7 +646,8 @@ export class DebugProtocolClient { //if the event failed to parse, or the buffer doesn't have enough bytes to satisfy the packetLength, exit here (new data will re-trigger this function) if (!responseOrUpdate) { this.logger.info('Unable to convert buffer into anything meaningful', this.buffer); - //TODO what should we do about this? + //if we have packet length, and we have at least that many bytes, throw out this message so we can hopefully recover + this.discardNextResponseOrUpdate(); return false; } if (!responseOrUpdate.success || responseOrUpdate.data.packetLength > this.buffer.length) { From b2520eebc34df20cb1071609b1179e52dfa5ec8d Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 8 Feb 2023 14:52:11 -0500 Subject: [PATCH 37/74] Clean up sockets after session is complete --- .../DebugProtocolClientReplaySession.spec.ts | 19 ++++++--- .../DebugProtocolClientReplaySession.ts | 39 +++++++++++++++++-- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts b/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts index 24f28936..15f40411 100644 --- a/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts +++ b/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts @@ -3,6 +3,12 @@ import { expect } from 'chai'; import { DebugProtocolClientReplaySession } from './DebugProtocolClientReplaySession'; describe(DebugProtocolClientReplaySession.name, () => { + let session: DebugProtocolClientReplaySession; + + afterEach(() => { + session.destroy(); + }); + it.only('works for this specific thing', async function test() { this.timeout(10000000000); const text = ` @@ -94,11 +100,12 @@ describe(DebugProtocolClientReplaySession.name, () => { {"type":"server-to-client","timestamp":"2023-01-27T19:49:26.532Z","buffer":{"type":"Buffer","data":[35,0,0,0,0,0,0,0,7,0,0,0,0,4,0,0,0,66,82,69,65,75,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,22,0,0,0,36,97,110,111,110,95,50,97,54,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,119,105,100,116,104,32,61,32,101,118,101,110,116,46,103,101,116,68,97,116,97,40,41,0,0,4,0,0,0,66,82,69,65,75,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,9,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,49,48,48,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,49,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,186,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,105,102,32,99,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,34,99,97,114,111,117,115,101,108,34,32,116,104,101,110,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,50,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} {"type":"client-to-server","timestamp":"2023-01-27T19:57:18.397Z","buffer":{"type":"Buffer","data":[12,0,0,0,36,0,0,0,122,0,0,0]}} `; - const session = new DebugProtocolClientReplaySession({ + session = new DebugProtocolClientReplaySession({ bufferLog: text }); await session.run(); + expectClientReplayResult([], session.result); console.log(session); }); @@ -272,20 +279,22 @@ describe(DebugProtocolClientReplaySession.name, () => { {"type":"server-to-client","timestamp":"2023-02-06T16:48:36.176Z","buffer":{"type":"Buffer","data":[47,0,0,0,0,0,0,0]}} `; - const session = new DebugProtocolClientReplaySession({ + session = new DebugProtocolClientReplaySession({ bufferLog: text }); await session.run(); - expectResult([], result); + expectClientReplayResult([], session.result); console.log(session); }); }); -function expectResult(expected: any[], result: DebugProtocolClientReplaySession['result']) { +function expectClientReplayResult(expected: any[], result: DebugProtocolClientReplaySession['result']) { let sanitizedResult = result.map((x, i) => { - if (typeof expected[i] === 'function') { + //if there is no expected object for this entry, or it's a constructor, then we will compare the constructor name + if (expected[i] === undefined || typeof expected[i] === 'function') { return x?.constructor?.name; + //deep compare the actual object } else { return x; } diff --git a/src/debugProtocol/DebugProtocolClientReplaySession.ts b/src/debugProtocol/DebugProtocolClientReplaySession.ts index d7b7ee70..2190dd15 100644 --- a/src/debugProtocol/DebugProtocolClientReplaySession.ts +++ b/src/debugProtocol/DebugProtocolClientReplaySession.ts @@ -14,6 +14,8 @@ export class DebugProtocolClientReplaySession { this.parseBufferLog(options?.bufferLog); } + private disposables = Array<() => void>(); + /** * A dumb tcp server that will simply spit back the server buffer data when needed */ @@ -107,6 +109,11 @@ export class DebugProtocolClientReplaySession { } } }); + + //stuff to run when the session is disposed + this.disposables.push(() => { + this.client.destroy(); + }); } private openIOPort() { @@ -129,6 +136,14 @@ export class DebugProtocolClientReplaySession { }, () => { resolve(); }); + + //stuff to run when the session is disposed + this.disposables.push(() => { + server.close(); + }); + this.disposables.push(() => { + this.ioSocket?.destroy(); + }); }); } @@ -189,6 +204,14 @@ export class DebugProtocolClientReplaySession { }, () => { resolve(); }); + + //stuff to run when the session is disposed + this.disposables.push(() => { + server.close(); + }); + this.disposables.push(() => { + this.client?.destroy(); + }); }); } @@ -233,11 +256,21 @@ export class DebugProtocolClientReplaySession { //if the gap is negative, then the time has already passed. Just timeout at zero gap = gap > 0 ? gap : 0; } - //TODO should we even re-implement this? - //console.log(`sleeping for ${gap}ms`); - gap = 10; + //longer delays make the test run slower, but don't really make the test any more accurate, + //so cap the delay at 100ms + if (gap > 100) { + gap = 100; + } await util.sleep(gap); } + + public destroy() { + for (const dispose of this.disposables) { + try { + dispose(); + } catch { } + } + } } class BufferSync { From f0a9675bbeb13a38b9b3ccf07e21060c8022dfac Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Tue, 14 Feb 2023 15:03:14 -0500 Subject: [PATCH 38/74] Fixed socket issues during testing --- src/adapters/DebugProtocolAdapter.spec.ts | 3 +- src/debugProtocol/Constants.ts | 4 +- .../DebugProtocolClientReplaySession.spec.ts | 256 +++++++++++------- .../DebugProtocolClientReplaySession.ts | 16 +- .../DebugProtocolServerTestPlugin.spec.ts | 12 +- src/debugProtocol/PluginInterface.ts | 2 +- .../client/DebugProtocolClient.spec.ts | 55 +++- .../client/DebugProtocolClient.ts | 16 +- .../server/DebugProtocolServer.ts | 19 +- src/util.ts | 17 +- 10 files changed, 278 insertions(+), 122 deletions(-) diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index efdff0b9..28dfc75d 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -1,7 +1,6 @@ import { expect } from 'chai'; import { DebugProtocolClient } from '../debugProtocol/client/DebugProtocolClient'; -import * as portfinder from 'portfinder'; import { DebugProtocolAdapter } from './DebugProtocolAdapter'; import { createSandbox } from 'sinon'; import type { Variable } from '../debugProtocol/events/responses/VariablesResponse'; @@ -34,7 +33,7 @@ describe('DebugProtocolAdapter', () => { adapter = new DebugProtocolAdapter(options, undefined, undefined); if (!options.controlPort) { - options.controlPort = await portfinder.getPortPromise(); + options.controlPort = await util.getPort(); } server = new DebugProtocolServer(options); plugin = server.plugins.add(new DebugProtocolServerTestPlugin()); diff --git a/src/debugProtocol/Constants.ts b/src/debugProtocol/Constants.ts index a53f1ff9..349dfdc0 100644 --- a/src/debugProtocol/Constants.ts +++ b/src/debugProtocol/Constants.ts @@ -188,7 +188,7 @@ export enum UpdateType { * A compilation error occurred * @since protocol 3.1 */ - CompileError = 'CompileError', + CompileError = 'CompileError' /** * Breakpoints were successfully verified * @since protocol 3.2 @@ -204,7 +204,7 @@ export enum UpdateTypeCode { AllThreadsStopped = 2, ThreadAttached = 3, BreakpointError = 4, - CompileError = 5, + CompileError = 5 // /** // * Breakpoints were successfully verified // * @since protocol 3.2 diff --git a/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts b/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts index 15f40411..577fa8f6 100644 --- a/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts +++ b/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts @@ -1,111 +1,176 @@ /* eslint-disable prefer-arrow-callback */ import { expect } from 'chai'; import { DebugProtocolClientReplaySession } from './DebugProtocolClientReplaySession'; +import type { ProtocolRequest, ProtocolResponse, ProtocolUpdate } from './events/ProtocolEvent'; +import { AddBreakpointsRequest } from './events/requests/AddBreakpointsRequest'; +import { ContinueRequest } from './events/requests/ContinueRequest'; +import { HandshakeRequest } from './events/requests/HandshakeRequest'; +import { StackTraceRequest } from './events/requests/StackTraceRequest'; +import { ThreadsRequest } from './events/requests/ThreadsRequest'; +import { VariablesRequest } from './events/requests/VariablesRequest'; +import { GenericV3Response } from './events/responses/GenericV3Response'; +import { HandshakeV3Response } from './events/responses/HandshakeV3Response'; +import { ListBreakpointsResponse } from './events/responses/ListBreakpointsResponse'; +import { StackTraceV3Response } from './events/responses/StackTraceV3Response'; +import { ThreadsResponse } from './events/responses/ThreadsResponse'; +import { VariablesResponse } from './events/responses/VariablesResponse'; +import { AllThreadsStoppedUpdate } from './events/updates/AllThreadsStoppedUpdate'; +import { IOPortOpenedUpdate } from './events/updates/IOPortOpenedUpdate'; +import { ThreadAttachedUpdate } from './events/updates/ThreadAttachedUpdate'; -describe(DebugProtocolClientReplaySession.name, () => { +describe.skip(DebugProtocolClientReplaySession.name, () => { let session: DebugProtocolClientReplaySession; afterEach(() => { session.destroy(); }); - it.only('works for this specific thing', async function test() { + it.skip('works for the failing breakpoint flow in fubo', async function test() { this.timeout(10000000000); const text = ` - {"type":"client-to-server","timestamp":"2023-01-27T19:48:25.607Z","buffer":{"type":"Buffer","data":[98,115,100,101,98,117,103,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:48:25.609Z","buffer":{"type":"Buffer","data":[98,115,100,101,98,117,103,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:48:25.666Z","buffer":{"type":"Buffer","data":[3,0,0,0,2,0,0,0,0,0,0,0,12,0,0,0,131,224,122,24,131,1,0,0,20,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,146,31,0,0,27,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,4,66,82,69,65,75,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:48:25.676Z","buffer":{"type":"Buffer","data":[12,0,0,0,1,0,0,0,3,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:48:25.680Z","buffer":{"type":"Buffer","data":[91,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:48:25.728Z","buffer":{"type":"Buffer","data":[1,0,0,0,0,0,0,0,1,0,0,0,1,4,0,0,0,66,82,69,65,75,0,1,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,115,117,98,77,97,105,110,40,105,110,112,117,116,65,114,103,117,109,101,110,116,115,32,97,115,32,111,98,106,101,99,116,41,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:48:25.731Z","buffer":{"type":"Buffer","data":[16,0,0,0,2,0,0,0,4,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:48:25.734Z","buffer":{"type":"Buffer","data":[46,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:48:25.790Z","buffer":{"type":"Buffer","data":[2,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:48:25.803Z","buffer":{"type":"Buffer","data":[12,0,0,0,3,0,0,0,2,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:48:25.806Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:48:25.851Z","buffer":{"type":"Buffer","data":[3,0,0,0,0,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:06.310Z","buffer":{"type":"Buffer","data":[98,0,0,0,4,0,0,0,7,0,0,0,1,0,0,0,108,105,98,58,47,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,186,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:06.312Z","buffer":{"type":"Buffer","data":[28,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:06.359Z","buffer":{"type":"Buffer","data":[4,0,0,0,0,0,0,0,1,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:06.405Z","buffer":{"type":"Buffer","data":[1,0,0,0,0,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:06.454Z","buffer":{"type":"Buffer","data":[28,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:06.499Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:15.274Z","buffer":{"type":"Buffer","data":[27,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:15.322Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,2,0,0,0,5,0,0,0,4,66,82,69,65,75,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:15.414Z","buffer":{"type":"Buffer","data":[12,0,0,0,5,0,0,0,3,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:15.435Z","buffer":{"type":"Buffer","data":[129,3,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:15.476Z","buffer":{"type":"Buffer","data":[5,0,0,0,0,0,0,0,7,0,0,0,0,4,0,0,0,66,82,69,65,75,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,22,0,0,0,36,97,110,111,110,95,50,97,54,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,119,105,100,116,104,32,61,32,101,118,101,110,116,46,103,101,116,68,97,116,97,40,41,0,0,4,0,0,0,66,82,69,65,75,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,9,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,49,48,48,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,49,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,186,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,105,102,32,99,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,34,99,97,114,111,117,115,101,108,34,32,116,104,101,110,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,50,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:15.494Z","buffer":{"type":"Buffer","data":[16,0,0,0,6,0,0,0,4,0,0,0,5,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:15.494Z","buffer":{"type":"Buffer","data":[16,0,0,0,7,0,0,0,4,0,0,0,0,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:15.495Z","buffer":{"type":"Buffer","data":[16,0,0,0,8,0,0,0,4,0,0,0,1,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:15.495Z","buffer":{"type":"Buffer","data":[16,0,0,0,9,0,0,0,4,0,0,0,2,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:15.495Z","buffer":{"type":"Buffer","data":[16,0,0,0,10,0,0,0,4,0,0,0,3,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:15.496Z","buffer":{"type":"Buffer","data":[16,0,0,0,11,0,0,0,4,0,0,0,4,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:15.496Z","buffer":{"type":"Buffer","data":[16,0,0,0,12,0,0,0,4,0,0,0,6,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:15.508Z","buffer":{"type":"Buffer","data":[166,1,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:15.514Z","buffer":{"type":"Buffer","data":[6,0,0,0,0,0,0,0,5,0,0,0,186,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,14,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,99,114,101,101,110,115,47,67,97,116,97,108,111,103,83,99,114,101,101,110,47,67,97,116,97,108,111,103,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,67,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,80,97,114,115,101,114,115,95,95,108,105,98,48,46,98,114,115,0,16,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,239,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,66,97,115,101,95,95,108,105,98,48,46,98,114,115,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:15.569Z","buffer":{"type":"Buffer","data":[46,0,0,0,7,0,0,0,0,0,0,0,1,0,0,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,129,0,0,0,8,0,0,0,0,0,0,0,1,0,0,0,22,0,0,0,36,97,110,111,110,95,50,97,54,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,80,0,0,0,9,0,0,0,0,0,0,0,1,0,0,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,104,0,0,0,10,0,0,0,0,0,0,0,1,0,0,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,110,0,0,0,11,0,0,0,0,0,0,0,1,0,0,0,19,0,0,0,32,50,49,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,116,0,0,0,12,0,0,0,0,0,0,0,1,0,0,0,21,0,0,0,32,50,50,50,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:16.018Z","buffer":{"type":"Buffer","data":[25,0,0,0,13,0,0,0,5,0,0,0,1,5,0,0,0,4,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:16.021Z","buffer":{"type":"Buffer","data":[222,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:16.065Z","buffer":{"type":"Buffer","data":[13,0,0,0,0,0,0,0,14,0,0,0,93,1,99,111,110,116,101,110,116,0,2,0,0,0,13,3,0,0,0,41,8,103,108,111,98,97,108,0,105,102,71,108,111,98,97,108,0,29,1,109,0,5,0,0,0,13,15,0,0,0,29,2,115,101,99,116,105,111,110,115,0,1,0,0,0,7,0,0,0,0,93,1,115,101,99,116,105,111,110,0,2,0,0,0,13,8,0,0,0,57,13,99,111,109,112,111,110,101,110,116,116,121,112,101,0,2,0,0,0,99,97,114,111,117,115,101,108,0,9,16,99,97,114,111,117,115,101,108,99,111,109,112,111,110,101,110,116,116,121,112,101,0,9,16,116,97,98,108,105,115,116,0,9,16,99,104,105,112,108,105,115,116,0,9,16,99,97,114,111,117,115,101,108,0,9,16,103,114,105,100,0,9,16,104,101,114,111,0,9,16,108,105,115,116,0,9,16,102,97,108,108,98,97,99,107,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:21.256Z","buffer":{"type":"Buffer","data":[12,0,0,0,14,0,0,0,2,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:21.257Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:21.299Z","buffer":{"type":"Buffer","data":[14,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:22.293Z","buffer":{"type":"Buffer","data":[27,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:22.341Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,2,0,0,0,5,0,0,0,4,66,82,69,65,75,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:22.432Z","buffer":{"type":"Buffer","data":[12,0,0,0,15,0,0,0,3,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:22.453Z","buffer":{"type":"Buffer","data":[112,3,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:22.494Z","buffer":{"type":"Buffer","data":[15,0,0,0,0,0,0,0,7,0,0,0,0,4,0,0,0,66,82,69,65,75,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,123,1,0,0,117,116,105,108,115,95,99,114,101,97,116,101,99,108,105,112,112,105,110,103,114,101,99,116,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,115,111,117,114,99,101,47,117,116,105,108,115,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,103,101,116,95,110,117,109,98,101,114,40,99,108,105,112,112,105,110,103,67,111,110,102,105,103,117,114,97,116,105,111,110,44,32,34,119,105,100,116,104,34,41,0,0,4,0,0,0,66,82,69,65,75,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,9,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,49,48,48,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,49,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,186,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,105,102,32,99,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,34,99,97,114,111,117,115,101,108,34,32,116,104,101,110,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,50,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:22.511Z","buffer":{"type":"Buffer","data":[16,0,0,0,16,0,0,0,4,0,0,0,5,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:22.512Z","buffer":{"type":"Buffer","data":[16,0,0,0,17,0,0,0,4,0,0,0,0,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:22.512Z","buffer":{"type":"Buffer","data":[16,0,0,0,18,0,0,0,4,0,0,0,1,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:22.512Z","buffer":{"type":"Buffer","data":[16,0,0,0,19,0,0,0,4,0,0,0,2,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:22.513Z","buffer":{"type":"Buffer","data":[16,0,0,0,20,0,0,0,4,0,0,0,3,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:22.513Z","buffer":{"type":"Buffer","data":[16,0,0,0,21,0,0,0,4,0,0,0,4,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:22.513Z","buffer":{"type":"Buffer","data":[16,0,0,0,22,0,0,0,4,0,0,0,6,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:22.526Z","buffer":{"type":"Buffer","data":[166,1,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:22.527Z","buffer":{"type":"Buffer","data":[16,0,0,0,0,0,0,0,5,0,0,0,186,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,14,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,99,114,101,101,110,115,47,67,97,116,97,108,111,103,83,99,114,101,101,110,47,67,97,116,97,108,111,103,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,67,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,80,97,114,115,101,114,115,95,95,108,105,98,48,46,98,114,115,0,16,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,239,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,66,97,115,101,95,95,108,105,98,48,46,98,114,115,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:22.571Z","buffer":{"type":"Buffer","data":[46,0,0,0,17,0,0,0,0,0,0,0,1,0,0,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,217,0,0,0,18,0,0,0,0,0,0,0,2,0,0,0,123,1,0,0,117,116,105,108,115,95,99,114,101,97,116,101,99,108,105,112,112,105,110,103,114,101,99,116,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,115,111,117,114,99,101,47,117,116,105,108,115,95,95,108,105,98,48,46,98,114,115,0,26,0,0,0,117,116,105,108,115,95,99,114,101,97,116,101,99,108,105,112,112,105,110,103,114,101,99,116,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,80,0,0,0,19,0,0,0,0,0,0,0,1,0,0,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,104,0,0,0,20,0,0,0,0,0,0,0,1,0,0,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,110,0,0,0,21,0,0,0,0,0,0,0,1,0,0,0,19,0,0,0,32,50,49,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,116,0,0,0,22,0,0,0,0,0,0,0,1,0,0,0,21,0,0,0,32,50,50,50,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:23.015Z","buffer":{"type":"Buffer","data":[25,0,0,0,23,0,0,0,5,0,0,0,1,5,0,0,0,4,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:23.019Z","buffer":{"type":"Buffer","data":[246,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:23.064Z","buffer":{"type":"Buffer","data":[23,0,0,0,0,0,0,0,14,0,0,0,93,1,99,111,110,116,101,110,116,0,2,0,0,0,13,3,0,0,0,41,8,103,108,111,98,97,108,0,105,102,71,108,111,98,97,108,0,29,1,109,0,5,0,0,0,13,15,0,0,0,29,2,115,101,99,116,105,111,110,115,0,1,0,0,0,7,1,0,0,0,93,1,115,101,99,116,105,111,110,0,2,0,0,0,13,7,0,0,0,57,13,99,111,109,112,111,110,101,110,116,116,121,112,101,0,2,0,0,0,99,104,105,112,45,108,105,115,116,0,57,13,99,97,114,111,117,115,101,108,99,111,109,112,111,110,101,110,116,116,121,112,101,0,3,0,0,0,99,97,114,100,45,119,105,100,101,0,9,16,116,97,98,108,105,115,116,0,9,16,99,104,105,112,108,105,115,116,0,29,1,99,97,114,111,117,115,101,108,0,2,0,0,0,13,11,0,0,0,9,16,103,114,105,100,0,9,16,104,101,114,111,0,9,16,108,105,115,116,0,9,16,102,97,108,108,98,97,99,107,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:23.169Z","buffer":{"type":"Buffer","data":[12,0,0,0,24,0,0,0,2,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:23.171Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:23.220Z","buffer":{"type":"Buffer","data":[24,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:24.319Z","buffer":{"type":"Buffer","data":[27,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:24.373Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,2,0,0,0,5,0,0,0,4,66,82,69,65,75,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:24.461Z","buffer":{"type":"Buffer","data":[12,0,0,0,25,0,0,0,3,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:24.485Z","buffer":{"type":"Buffer","data":[129,3,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:24.527Z","buffer":{"type":"Buffer","data":[25,0,0,0,0,0,0,0,7,0,0,0,0,4,0,0,0,66,82,69,65,75,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,22,0,0,0,36,97,110,111,110,95,50,97,54,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,119,105,100,116,104,32,61,32,101,118,101,110,116,46,103,101,116,68,97,116,97,40,41,0,0,4,0,0,0,66,82,69,65,75,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,9,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,49,48,48,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,49,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,186,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,105,102,32,99,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,34,99,97,114,111,117,115,101,108,34,32,116,104,101,110,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,50,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:24.544Z","buffer":{"type":"Buffer","data":[16,0,0,0,26,0,0,0,4,0,0,0,5,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:24.545Z","buffer":{"type":"Buffer","data":[16,0,0,0,27,0,0,0,4,0,0,0,0,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:24.545Z","buffer":{"type":"Buffer","data":[16,0,0,0,28,0,0,0,4,0,0,0,1,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:24.545Z","buffer":{"type":"Buffer","data":[16,0,0,0,29,0,0,0,4,0,0,0,2,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:24.545Z","buffer":{"type":"Buffer","data":[16,0,0,0,30,0,0,0,4,0,0,0,3,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:24.546Z","buffer":{"type":"Buffer","data":[16,0,0,0,31,0,0,0,4,0,0,0,4,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:24.546Z","buffer":{"type":"Buffer","data":[16,0,0,0,32,0,0,0,4,0,0,0,6,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:24.559Z","buffer":{"type":"Buffer","data":[166,1,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:24.560Z","buffer":{"type":"Buffer","data":[26,0,0,0,0,0,0,0,5,0,0,0,186,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,14,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,99,114,101,101,110,115,47,67,97,116,97,108,111,103,83,99,114,101,101,110,47,67,97,116,97,108,111,103,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,67,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,80,97,114,115,101,114,115,95,95,108,105,98,48,46,98,114,115,0,16,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,239,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,66,97,115,101,95,95,108,105,98,48,46,98,114,115,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:24.605Z","buffer":{"type":"Buffer","data":[46,0,0,0,27,0,0,0,0,0,0,0,1,0,0,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,129,0,0,0,28,0,0,0,0,0,0,0,1,0,0,0,22,0,0,0,36,97,110,111,110,95,50,97,54,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,80,0,0,0,29,0,0,0,0,0,0,0,1,0,0,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,104,0,0,0,30,0,0,0,0,0,0,0,1,0,0,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,110,0,0,0,31,0,0,0,0,0,0,0,1,0,0,0,19,0,0,0,32,50,49,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,116,0,0,0,32,0,0,0,0,0,0,0,1,0,0,0,21,0,0,0,32,50,50,50,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:25.035Z","buffer":{"type":"Buffer","data":[25,0,0,0,33,0,0,0,5,0,0,0,1,5,0,0,0,4,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:25.038Z","buffer":{"type":"Buffer","data":[254,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:25.088Z","buffer":{"type":"Buffer","data":[33,0,0,0,0,0,0,0,14,0,0,0,93,1,99,111,110,116,101,110,116,0,2,0,0,0,13,3,0,0,0,41,8,103,108,111,98,97,108,0,105,102,71,108,111,98,97,108,0,29,1,109,0,5,0,0,0,13,15,0,0,0,29,2,115,101,99,116,105,111,110,115,0,1,0,0,0,7,2,0,0,0,93,1,115,101,99,116,105,111,110,0,2,0,0,0,13,9,0,0,0,57,13,99,111,109,112,111,110,101,110,116,116,121,112,101,0,2,0,0,0,99,97,114,111,117,115,101,108,0,57,13,99,97,114,111,117,115,101,108,99,111,109,112,111,110,101,110,116,116,121,112,101,0,3,0,0,0,99,97,114,100,45,119,105,100,101,0,9,16,116,97,98,108,105,115,116,0,29,1,99,104,105,112,108,105,115,116,0,2,0,0,0,13,8,0,0,0,29,1,99,97,114,111,117,115,101,108,0,2,0,0,0,13,11,0,0,0,9,16,103,114,105,100,0,9,16,104,101,114,111,0,9,16,108,105,115,116,0,9,16,102,97,108,108,98,97,99,107,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:25.091Z","buffer":{"type":"Buffer","data":[12,0,0,0,34,0,0,0,2,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:25.093Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:25.135Z","buffer":{"type":"Buffer","data":[34,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:26.341Z","buffer":{"type":"Buffer","data":[27,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:26.393Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,2,0,0,0,4,0,0,0,4,66,82,69,65,75,0,27,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,1,0,0,0,4,66,82,69,65,75,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:49:26.467Z","buffer":{"type":"Buffer","data":[12,0,0,0,35,0,0,0,3,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:26.490Z","buffer":{"type":"Buffer","data":[129,3,0,0]}} - {"type":"server-to-client","timestamp":"2023-01-27T19:49:26.532Z","buffer":{"type":"Buffer","data":[35,0,0,0,0,0,0,0,7,0,0,0,0,4,0,0,0,66,82,69,65,75,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,22,0,0,0,36,97,110,111,110,95,50,97,54,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,119,105,100,116,104,32,61,32,101,118,101,110,116,46,103,101,116,68,97,116,97,40,41,0,0,4,0,0,0,66,82,69,65,75,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,9,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,49,48,48,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,49,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,186,0,0,0,36,97,110,111,110,95,50,100,56,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,105,102,32,99,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,34,99,97,114,111,117,115,101,108,34,32,116,104,101,110,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,50,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} - {"type":"client-to-server","timestamp":"2023-01-27T19:57:18.397Z","buffer":{"type":"Buffer","data":[12,0,0,0,36,0,0,0,122,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-10T15:02:34.732Z","buffer":{"type":"Buffer","data":[98,115,100,101,98,117,103,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:02:34.733Z","buffer":{"type":"Buffer","data":[98,115,100,101,98,117,103,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:02:34.734Z","buffer":{"type":"Buffer","data":[3,0,0,0,2,0,0,0,0,0,0,0,12,0,0,0,131,224,122,24,131,1,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:02:34.796Z","buffer":{"type":"Buffer","data":[20,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,146,31,0,0,27,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,4,66,82,69,65,75,0]}} + {"type":"client-to-server","timestamp":"2023-02-10T15:02:35.164Z","buffer":{"type":"Buffer","data":[12,0,0,0,1,0,0,0,3,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:02:35.736Z","buffer":{"type":"Buffer","data":[91,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:02:35.791Z","buffer":{"type":"Buffer","data":[1,0,0,0,0,0,0,0,1,0,0,0,1,4,0,0,0,66,82,69,65,75,0,1,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,115,117,98,77,97,105,110,40,105,110,112,117,116,65,114,103,117,109,101,110,116,115,32,97,115,32,111,98,106,101,99,116,41,0]}} + {"type":"client-to-server","timestamp":"2023-02-10T15:02:35.806Z","buffer":{"type":"Buffer","data":[16,0,0,0,2,0,0,0,4,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:02:35.812Z","buffer":{"type":"Buffer","data":[46,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:02:35.868Z","buffer":{"type":"Buffer","data":[2,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0]}} + {"type":"client-to-server","timestamp":"2023-02-10T15:02:36.094Z","buffer":{"type":"Buffer","data":[12,0,0,0,3,0,0,0,2,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:02:36.101Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:02:36.147Z","buffer":{"type":"Buffer","data":[3,0,0,0,0,0,0,0]}} + {"type":"io","timestamp":"2023-02-10T15:02:37.058Z","buffer":{"type":"Buffer","data":[48]}} + {"type":"io","timestamp":"2023-02-10T15:02:37.111Z","buffer":{"type":"Buffer","data":[50,45,49,48,32,49,48,58,48,50,58,51,57,46,49,56,51,32,91,82,84,65,93,91,73,78,70,79,93,32,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,32,105,110,105,116,10]}} + {"type":"io","timestamp":"2023-02-10T15:02:43.339Z","buffer":{"type":"Buffer","data":[48]}} + {"type":"io","timestamp":"2023-02-10T15:02:43.380Z","buffer":{"type":"Buffer","data":[50,45,49,48,32,49,53,58,48,50,58,52,53,46,52,54,53,32,91,115,99,114,112,116,46,99,109,112,108,93,32,67,111,109,112,105,108,105,110,103,32,39,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,39,44,32,105,100,32,39,82,83,71,95,66,65,65,65,65,65,89,114,48,104,114,83,39,10]}} + {"type":"io","timestamp":"2023-02-10T15:02:43.435Z","buffer":{"type":"Buffer","data":[48]}} + {"type":"io","timestamp":"2023-02-10T15:02:43.489Z","buffer":{"type":"Buffer","data":[50,45,49,48,32,49,53,58,48,50,58,52,53,46,53,54,49,32,91,115,99,114,112,116,46,108,111,97,100,46,109,107,117,112,93,32,76,111,97,100,105,110,103,32,109,97,114,107,117,112,32,82,83,71,95,66,65,65,65,65,65,89,114,48,104,114,83,32,39,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,39,10]}} + {"type":"io","timestamp":"2023-02-10T15:02:43.536Z","buffer":{"type":"Buffer","data":[48,50,45,49,48,32,49,53,58,48,50,58,52,53,46,54,50,52,32,91,115,99,114,112,116,46,117,110,108,111,97,100,46,109,107,117,112,93,32,85,110,108,111,97,100,105,110,103,32,109,97,114,107,117,112,32,82,83,71,95,66,65,65,65,65,65,89,114,48,104,114,83,32,39,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,39,10]}} + {"type":"io","timestamp":"2023-02-10T15:02:43.627Z","buffer":{"type":"Buffer","data":[48]}} + {"type":"io","timestamp":"2023-02-10T15:02:43.676Z","buffer":{"type":"Buffer","data":[50,45,49,48,32,49,53,58,48,50,58,52,53,46,55,53,51,32,91,115,99,114,112,116,46,112,97,114,115,101,46,109,107,117,112,46,116,105,109,101,93,32,80,97,114,115,101,100,32,109,97,114,107,117,112,32,82,83,71,95,66,65,65,65,65,65,89,114,48,104,114,83,32,39,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,39,32,105,110,32,49,57,50,32,109,105,108,108,105,115,101,99,111,110,100,115,10]}} + {"type":"io","timestamp":"2023-02-10T15:02:48.681Z","buffer":{"type":"Buffer","data":[48]}} + {"type":"io","timestamp":"2023-02-10T15:02:48.722Z","buffer":{"type":"Buffer","data":[50,45,49,48,32,49,53,58,48,50,58,53,48,46,56,48,55,32,91,115,99,114,112,116,46,112,114,111,99,46,109,107,117,112,46,116,105,109,101,93,32,80,114,111,99,101,115,115,101,100,32,109,97,114,107,117,112,32,82,83,71,95,66,65,65,65,65,65,89,114,48,104,114,83,32,39,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,39,32,105,110,32,49,32,109,105,108,108,105,115,101,99,111,110,100,115,10,73,110,105,116,105,97,108,105,122,97,116,105,111,110,32,32,65,108,108,32,108,105,98,114,97,114,105,101,115,32,97,110,100,32,100,101,112,101,110,100,101,110,99,105,101,115,32,97,114,101,32,108,111,97,100,101,100,32,97,110,100,32,119,101,39,114,101,32,114,101,97,100,121,32,116,111,32,103,111,46,10,73,110,105,116,105,97,108,105,122,97,116,105,111,110,32,32,67,111,109,112,111,110,101,110,116,32,108,105,98,114,97,114,121,32,115,104,111,117,108,100,32,110,111,119,32,104,97,118,101,32,99,111,110,116,114,111,108,46,32,32,32,32,32,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,32,61,10,123,10,32,32,32,32,108,111,97,100,116,105,109,101,58,32,49,49,49,51,50,10,32,32,32,32,115,116,97,116,117,115,58,32,34,115,117,99,99,101,115,115,34,10,32,32,32,32,117,114,105,58,32,34,104,116,116,112,58,47,47,49,57,50,46,49,54,56,46,49,46,50,50,58,56,48,56,48,47,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,46,122,105,112,34,10,125,10,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,10]}} + {"type":"io","timestamp":"2023-02-10T15:02:48.865Z","buffer":{"type":"Buffer","data":[49]}} + {"type":"io","timestamp":"2023-02-10T15:02:48.907Z","buffer":{"type":"Buffer","data":[53,58,48,50,32,124,32,91,76,79,71,93,32,32,32,124,32,80,108,97,121,101,114,32,83,101,116,116,105,110,103,32,85,112,32,82,65,70,32,10,49,53,58,48,50,32,124,32,91,76,79,71,93,32,32,32,124,32,77,97,105,110,83,99,101,110,101,32,62,62,62,32,77,65,73,78,32,83,67,69,78,69,32,40,67,79,82,69,41,32,72,65,83,32,66,69,69,78,32,67,82,69,65,84,69,68,33,32,67,76,73,69,78,84,32,86,69,82,83,73,79,78,32,53,46,48,46,48,32,10]}} + {"type":"io","timestamp":"2023-02-10T15:02:48.954Z","buffer":{"type":"Buffer","data":[49,53,58,48,50,32,124,32,91,73,78,70,79,93,32,32,124,32,78,101,116,119,111,114,107,83,101,114,118,105,99,101,32,114,117,110,110,105,110,103,58,32,116,114,117,101,32,10,49,53,58,48,50,32,124,32,91,73,78,70,79,93,32,32,124,32,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,32,114,117,110,110,105,110,103,58,32,116,114,117,101,32,10,10,91,82,65,70,93,32,115,101,116,68,101,98,117,103,79,117,116,112,117,116,40,116,114,117,101,41,10,10,91,82,65,70,93,32,115,101,116,68,101,98,117,103,79,117,116,112,117,116,40,116,114,117,101,41,10]}} + {"type":"io","timestamp":"2023-02-10T15:02:49.135Z","buffer":{"type":"Buffer","data":[10]}} + {"type":"io","timestamp":"2023-02-10T15:02:49.185Z","buffer":{"type":"Buffer","data":[91,82,65,70,93,32,82,111,107,117,95,65,100,115,32,70,114,97,109,101,119,111,114,107,32,118,101,114,115,105,111,110,32,51,46,48,50,51,52,10,10,91,82,65,70,93,32,115,101,116,68,101,98,117,103,79,117,116,112,117,116,40,102,97,108,115,101,41,10,10,91,82,65,70,93,32,82,111,107,117,95,65,100,115,32,70,114,97,109,101,119,111,114,107,32,118,101,114,115,105,111,110,32,51,46,48,50,51,52,10]}} + {"type":"io","timestamp":"2023-02-10T15:02:49.231Z","buffer":{"type":"Buffer","data":[10,91,82,65,70,93,32,115,101,116,68,101,98,117,103,79,117,116,112,117,116,40,102,97,108,115,101,41,10,49,53,58,48,50,32,124,32,91,73,78,70,79,93,32,32,124,32,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,32,114,117,110,110,105,110,103,58,32,116,114,117,101,32,10,49,53,58,48,50,32,124,32,91,76,79,71,93,32,32,32,124,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,114,114,97,121,62,32,61,10,91,10,32,32,32,32,34,65,99,116,105,111,110,34,10,32,32,32,32,34,65,112,112,108,105,99,97,116,105,111,110,83,116,97,116,101,34,10,32,32,32,32,34,65,117,116,104,34,10,32,32,32,32,34,67,111,110,116,101,120,116,77,101,110,117,34,10,32,32,32,32,34,68,101,118,101,108,111,112,101,114,34,10,32,32,32,32,34,68,105,97,108,111,103,34,10,32,32,32,32,34,80,114,111,102,105,108,101,34,10,32,32,32,32,34,80,114,111,102,105,108,101,82,101,103,105,115,116,114,121,34,10,32,32,32,32,34,82,101,103,105,115,116,114,121,34,10,32,32,32,32,34,82,111,119,73,116,101,109,34,10,32,32,32,32,34,83,99,114,101,101,110,34,10,32,32,32,32,34,84,111,97,115,116,34,10,32,32,32,32,34,84,111,111,108,116,105,112,34,10,93,32,10]}} + {"type":"io","timestamp":"2023-02-10T15:02:50.071Z","buffer":{"type":"Buffer","data":[49]}} + {"type":"io","timestamp":"2023-02-10T15:02:50.113Z","buffer":{"type":"Buffer","data":[53,58,48,50,32,124,32,91,73,78,70,79,93,32,32,124,32,115,97,118,101,65,99,99,101,115,115,84,111,107,101,110,32,84,111,107,101,110,32,114,101,102,114,101,115,104,105,110,103,32,105,110,58,32,32,50,56,48,32,32,32,115,101,99,111,110,100,115,32,10]}} + {"type":"io","timestamp":"2023-02-10T15:02:50.181Z","buffer":{"type":"Buffer","data":[49]}} + {"type":"io","timestamp":"2023-02-10T15:02:50.236Z","buffer":{"type":"Buffer","data":[53,58,48,50,32,124,32,91,73,78,70,79,93,32,32,124,32,65,99,116,105,111,110,77,97,110,97,103,101,114,32,101,120,101,99,117,116,101,65,99,116,105,111,110,32,45,32,110,97,118,105,103,97,116,105,111,110,32,10]}} + {"type":"io","timestamp":"2023-02-10T15:02:50.337Z","buffer":{"type":"Buffer","data":[49]}} + {"type":"io","timestamp":"2023-02-10T15:02:50.391Z","buffer":{"type":"Buffer","data":[53,58,48,50,32,124,32,91,73,78,70,79,93,32,32,124,32,80,114,111,102,105,108,101,83,99,114,101,101,110,95,102,57,57,56,53,53,55,57,45,101,100,100,98,45,52,56,51,48,45,56,55,50,101,45,52,55,56,49,52,56,56,50,56,101,101,98,32,111,110,83,99,114,101,101,110,83,104,111,119,110,32,10,48,50,45,49,48,32,49,53,58,48,50,58,53,50,46,52,54,51,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,68,105,97,108,111,103,73,110,105,116,105,97,116,101,32,45,45,45,45,45,45,45,45,45,62,32,84,105,109,101,66,97,115,101,40,50,49,48,48,54,32,109,115,41,10]}} + {"type":"io","timestamp":"2023-02-10T15:07:30.439Z","buffer":{"type":"Buffer","data":[49]}} + {"type":"io","timestamp":"2023-02-10T15:07:30.480Z","buffer":{"type":"Buffer","data":[53,58,48,55,32,124,32,91,73,78,70,79,93,32,32,124,32,115,97,118,101,65,99,99,101,115,115,84,111,107,101,110,32,84,111,107,101,110,32,114,101,102,114,101,115,104,105,110,103,32,105,110,58,32,32,50,56,48,32,32,32,115,101,99,111,110,100,115,32,10]}} + {"type":"io","timestamp":"2023-02-10T15:08:02.106Z","buffer":{"type":"Buffer","data":[49]}} + {"type":"io","timestamp":"2023-02-10T15:08:02.155Z","buffer":{"type":"Buffer","data":[53,58,48,56,32,124,32,91,73,78,70,79,93,32,32,124,32,78,101,119,32,112,114,111,102,105,108,101,32,115,101,108,101,99,116,101,100,58,32,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,83,71,78,111,100,101,58,67,111,110,116,101,110,116,78,111,100,101,62,32,61,10,123,10,32,32,32,32,99,104,97,110,103,101,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,102,111,99,117,115,97,98,108,101,58,32,102,97,108,115,101,10,32,32,32,32,102,111,99,117,115,101,100,67,104,105,108,100,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,73,110,118,97,108,105,100,62,10,32,32,32,32,105,100,58,32,34,53,50,98,54,97,100,100,48,45,54,54,55,57,45,52,50,98,49,45,56,48,49,102,45,51,53,100,100,97,99,99,101,98,102,55,101,34,10,32,32,32,32,97,118,97,116,97,114,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,110,97,109,101,58,32,34,66,114,111,110,108,101,121,34,10,32,32,32,32,116,121,112,101,58,32,34,112,114,111,102,105,108,101,34,10,125,32,10,49,53,58,48,56,32,124,32,91,73,78,70,79,93,32,32,124,32,65,99,116,105,111,110,77,97,110,97,103,101,114,32,101,120,101,99,117,116,101,65,99,116,105,111,110,32,45,32,110,97,116,105,118,101,95,112,114,111,102,105,108,101,95,115,101,108,101,99,116,32,10]}} + {"type":"io","timestamp":"2023-02-10T15:08:02.264Z","buffer":{"type":"Buffer","data":[49]}} + {"type":"io","timestamp":"2023-02-10T15:08:02.309Z","buffer":{"type":"Buffer","data":[53,58,48,56,32,124,32,91,73,78,70,79,93,32,32,124,32,65,99,116,105,111,110,77,97,110,97,103,101,114,32,101,120,101,99,117,116,101,65,99,116,105,111,110,32,45,32,110,97,116,105,118,101,95,110,97,118,105,103,97,116,101,95,116,111,95,108,97,110,100,105,110,103,32,10,49,53,58,48,56,32,124,32,91,73,78,70,79,93,32,32,124,32,65,99,116,105,111,110,77,97,110,97,103,101,114,32,101,120,101,99,117,116,101,65,99,116,105,111,110,32,45,32,110,97,118,105,103,97,116,105,111,110,32,10]}} + {"type":"io","timestamp":"2023-02-10T15:08:02.728Z","buffer":{"type":"Buffer","data":[49]}} + {"type":"io","timestamp":"2023-02-10T15:08:02.771Z","buffer":{"type":"Buffer","data":[53,58,48,56,32,124,32,91,87,65,82,78,93,32,32,124,32,112,97,114,115,101,83,101,99,116,105,111,110,115,32,78,111,32,99,104,105,108,100,32,99,111,109,112,111,110,101,110,116,115,32,116,111,32,114,101,110,100,101,114,46,32,67,97,114,111,117,115,101,108,32,116,104,114,111,119,110,32,111,117,116,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,32,61,10,123,10,32,32,32,32,99,111,109,112,111,110,101,110,116,95,116,121,112,101,58,32,34,99,97,114,100,45,119,105,100,101,34,10,32,32,32,32,99,111,110,116,101,120,116,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,102,111,99,117,115,101,100,95,101,108,101,109,101,110,116,58,32,102,97,108,115,101,10,32,32,32,32,115,105,122,101,58,32,34,109,34,10,32,32,32,32,115,108,117,103,58,32,34,100,118,114,45,99,111,110,116,101,110,116,45,99,97,114,111,117,115,101,108,34,10,32,32,32,32,116,105,116,108,101,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,116,121,112,101,58,32,34,99,97,114,111,117,115,101,108,34,10,125,32,10,49,53,58,48,56,32,124,32,91,87,65,82,78,93,32,32,124,32,112,97,114,115,101,83,101,99,116,105,111,110,115,32,78,111,32,99,104,105,108,100,32,99,111,109,112,111,110,101,110,116,115,32,116,111,32,114,101,110,100,101,114,46,32,67,97,114,111,117,115,101,108,32,116,104,114,111,119,110,32,111,117,116,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,32,61,10,123,10,32,32,32,32,99,111,109,112,111,110,101,110,116,95,116,121,112,101,58,32,34,99,97,114,100,45,119,105,100,101,34,10,32,32,32,32,99,111,110,116,101,120,116,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,102,111,99,117,115,101,100,95,101,108,101,109,101,110,116,58,32,102,97,108,115,101,10,32,32,32,32,115,105,122,101,58,32,34,109,34,10,32,32,32,32,115,108,117,103,58,32,34,118,111,100,45,99,111,110,116,101,110,116,45,99,97,114,111,117,115,101,108,34,10,32,32,32,32,116,105,116,108,101,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,116,121,112,101,58,32,34,99,97,114,111,117,115,101,108,34,10,125,32,10]}} + {"type":"io","timestamp":"2023-02-10T15:08:02.851Z","buffer":{"type":"Buffer","data":[48]}} + {"type":"io","timestamp":"2023-02-10T15:08:02.893Z","buffer":{"type":"Buffer","data":[50,45,49,48,32,49,53,58,48,56,58,48,52,46,57,57,50,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,68,105,97,108,111,103,67,111,109,112,108,101,116,101,32,45,45,45,45,45,45,45,45,45,62,32,68,117,114,97,116,105,111,110,40,51,49,50,53,50,57,32,109,115,41,10,49,53,58,48,56,32,124,32,91,73,78,70,79,93,32,32,124,32,80,114,111,102,105,108,101,83,99,114,101,101,110,95,102,57,57,56,53,53,55,57,45,101,100,100,98,45,52,56,51,48,45,56,55,50,101,45,52,55,56,49,52,56,56,50,56,101,101,98,32,111,110,83,99,114,101,101,110,72,105,100,100,101,110,32,10,48,50,45,49,48,32,49,53,58,48,56,58,48,52,46,57,57,54,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,68,105,97,108,111,103,73,110,105,116,105,97,116,101,32,45,45,45,45,45,45,45,45,45,62,32,84,105,109,101,66,97,115,101,40,51,51,51,53,51,57,32,109,115,41,10,49,53,58,48,56,32,124,32,91,73,78,70,79,93,32,32,124,32,83,101,99,116,105,111,110,115,32,115,101,99,116,105,111,110,115,32,108,111,97,100,101,100,58,32,32,55,32,10]}} + {"type":"io","timestamp":"2023-02-10T15:08:03.749Z","buffer":{"type":"Buffer","data":[48]}} + {"type":"io","timestamp":"2023-02-10T15:08:03.793Z","buffer":{"type":"Buffer","data":[50,45,49,48,32,49,53,58,48,56,58,48,53,46,56,57,48,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,68,105,97,108,111,103,67,111,109,112,108,101,116,101,32,45,45,45,45,45,45,45,45,45,62,32,68,117,114,97,116,105,111,110,40,56,57,52,32,109,115,41,10,48,50,45,49,48,32,49,53,58,48,56,58,48,53,46,56,57,48,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,76,97,117,110,99,104,67,111,109,112,108,101,116,101,32,45,45,45,45,45,45,45,45,45,62,32,80,101,110,100,105,110,103,32,82,101,110,100,101,114,32,80,97,115,115,10]}} + {"type":"io","timestamp":"2023-02-10T15:08:03.898Z","buffer":{"type":"Buffer","data":[48]}} + {"type":"io","timestamp":"2023-02-10T15:08:03.948Z","buffer":{"type":"Buffer","data":[50,45,49,48,32,49,53,58,48,56,58,48,54,46,48,51,57,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,76,97,117,110,99,104,67,111,109,112,108,101,116,101,32,45,45,45,45,45,45,45,45,45,62,32,68,117,114,97,116,105,111,110,40,51,51,52,53,56,50,32,109,115,41,44,32,68,105,97,108,111,103,84,105,109,101,40,51,49,51,52,50,51,32,109,115,41,10]}} + {"type":"client-to-server","timestamp":"2023-02-10T15:08:06.804Z","buffer":{"type":"Buffer","data":[154,0,0,0,4,0,0,0,7,0,0,0,2,0,0,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,46,98,114,115,0,187,0,0,0,0,0,0,0,108,105,98,58,47,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,187,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:06.809Z","buffer":{"type":"Buffer","data":[40,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:06.852Z","buffer":{"type":"Buffer","data":[4,0,0,0,0,0,0,0,2,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:06.899Z","buffer":{"type":"Buffer","data":[1,0,0,0,0,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:06.976Z","buffer":{"type":"Buffer","data":[2,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:07.020Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:07.082Z","buffer":{"type":"Buffer","data":[28,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:07.128Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0]}} + {"type":"io","timestamp":"2023-02-10T15:08:10.120Z","buffer":{"type":"Buffer","data":[49]}} + {"type":"io","timestamp":"2023-02-10T15:08:10.174Z","buffer":{"type":"Buffer","data":[53,58,48,56,32,124,32,91,73,78,70,79,93,32,32,124,32,65,99,116,105,111,110,77,97,110,97,103,101,114,32,101,120,101,99,117,116,101,65,99,116,105,111,110,32,45,32,110,97,118,105,103,97,116,105,111,110,32,10]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:10.357Z","buffer":{"type":"Buffer","data":[28,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:10.409Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:11.357Z","buffer":{"type":"Buffer","data":[27,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:11.403Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,2,0,0,0,4,0,0,0,4,66,82,69,65,75,0]}} + {"type":"client-to-server","timestamp":"2023-02-10T15:08:11.751Z","buffer":{"type":"Buffer","data":[12,0,0,0,5,0,0,0,3,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:11.800Z","buffer":{"type":"Buffer","data":[55,3,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:11.843Z","buffer":{"type":"Buffer","data":[5,0,0,0,0,0,0,0,6,0,0,0,0,4,0,0,0,66,82,69,65,75,0,20,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,22,0,0,0,36,97,110,111,110,95,50,97,57,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,119,105,100,116,104,32,61,32,101,118,101,110,116,46,103,101,116,68,97,116,97,40,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,50,54,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,187,0,0,0,36,97,110,111,110,95,50,100,98,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,99,97,114,111,117,115,101,108,67,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,103,101,116,95,115,116,114,105,110,103,40,115,101,99,116,105,111,110,44,32,34,99,111,109,112,111,110,101,110,116,95,116,121,112,101,34,41,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} + {"type":"client-to-server","timestamp":"2023-02-10T15:08:11.860Z","buffer":{"type":"Buffer","data":[16,0,0,0,6,0,0,0,4,0,0,0,4,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-10T15:08:11.860Z","buffer":{"type":"Buffer","data":[16,0,0,0,7,0,0,0,4,0,0,0,0,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-10T15:08:11.860Z","buffer":{"type":"Buffer","data":[16,0,0,0,8,0,0,0,4,0,0,0,1,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-10T15:08:11.861Z","buffer":{"type":"Buffer","data":[16,0,0,0,9,0,0,0,4,0,0,0,2,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-10T15:08:11.861Z","buffer":{"type":"Buffer","data":[16,0,0,0,10,0,0,0,4,0,0,0,3,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-10T15:08:11.861Z","buffer":{"type":"Buffer","data":[16,0,0,0,11,0,0,0,4,0,0,0,5,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:11.875Z","buffer":{"type":"Buffer","data":[166,1,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:11.923Z","buffer":{"type":"Buffer","data":[6,0,0,0,0,0,0,0,5,0,0,0,187,0,0,0,36,97,110,111,110,95,50,100,98,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,14,0,0,0,36,97,110,111,110,95,50,100,98,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,99,114,101,101,110,115,47,67,97,116,97,108,111,103,83,99,114,101,101,110,47,67,97,116,97,108,111,103,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,71,0,0,0,36,97,110,111,110,95,50,100,98,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,80,97,114,115,101,114,115,95,95,108,105,98,48,46,98,114,115,0,16,0,0,0,36,97,110,111,110,95,50,100,98,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,238,0,0,0,36,97,110,111,110,95,50,100,98,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,66,97,115,101,95,95,108,105,98,48,46,98,114,115,0,46,0,0,0,7,0,0,0,0,0,0,0,1,0,0,0,20,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,129,0,0,0,8,0,0,0,0,0,0,0,1,0,0,0,22,0,0,0,36,97,110,111,110,95,50,97,57,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,104,0,0,0,9,0,0,0,0,0,0,0,1,0,0,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,110,0,0,0,10,0,0,0,0,0,0,0,1,0,0,0,19,0,0,0,32,50,50,54,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,116,0,0,0,11,0,0,0,0,0,0,0,1,0,0,0,21,0,0,0,32,50,50,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0]}} + {"type":"client-to-server","timestamp":"2023-02-10T15:08:12.021Z","buffer":{"type":"Buffer","data":[37,0,0,0,12,0,0,0,5,0,0,0,3,4,0,0,0,4,0,0,0,1,0,0,0,102,105,110,97,108,86,97,108,117,101,0,1]}} + {"type":"client-to-server","timestamp":"2023-02-10T15:08:12.022Z","buffer":{"type":"Buffer","data":[45,0,0,0,13,0,0,0,5,0,0,0,3,4,0,0,0,4,0,0,0,1,0,0,0,115,116,97,114,116,73,110,100,101,120,73,110,83,116,114,105,110,103,0,1]}} + {"type":"client-to-server","timestamp":"2023-02-10T15:08:12.023Z","buffer":{"type":"Buffer","data":[43,0,0,0,14,0,0,0,5,0,0,0,3,4,0,0,0,4,0,0,0,1,0,0,0,101,120,112,114,101,115,115,105,111,110,76,101,110,103,116,104,0,1]}} + {"type":"client-to-server","timestamp":"2023-02-10T15:08:12.023Z","buffer":{"type":"Buffer","data":[39,0,0,0,15,0,0,0,5,0,0,0,3,4,0,0,0,4,0,0,0,1,0,0,0,104,69,120,112,114,101,115,115,105,111,110,115,0,1]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:12.024Z","buffer":{"type":"Buffer","data":[20,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:12.026Z","buffer":{"type":"Buffer","data":[12,0,0,0,5,0,0,0,2,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:12.078Z","buffer":{"type":"Buffer","data":[20,0,0,0,13,0,0,0,5,0,0,0,2,0,0,0,0,0,0,0,20,0,0,0,14,0,0,0,5,0,0,0,2,0,0,0,0,0,0,0,20,0,0,0,15,0,0,0,5,0,0,0,2,0,0,0,0,0,0,0]}} + {"type":"client-to-server","timestamp":"2023-02-10T15:08:12.373Z","buffer":{"type":"Buffer","data":[25,0,0,0,16,0,0,0,5,0,0,0,1,4,0,0,0,4,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:12.377Z","buffer":{"type":"Buffer","data":[222,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:12.419Z","buffer":{"type":"Buffer","data":[16,0,0,0,0,0,0,0,14,0,0,0,93,1,99,111,110,116,101,110,116,0,2,0,0,0,13,3,0,0,0,41,8,103,108,111,98,97,108,0,105,102,71,108,111,98,97,108,0,29,1,109,0,5,0,0,0,13,15,0,0,0,29,2,115,101,99,116,105,111,110,115,0,1,0,0,0,7,0,0,0,0,93,1,115,101,99,116,105,111,110,0,2,0,0,0,13,8,0,0,0,57,13,99,111,109,112,111,110,101,110,116,116,121,112,101,0,2,0,0,0,99,97,114,111,117,115,101,108,0,9,16,99,97,114,111,117,115,101,108,99,111,109,112,111,110,101,110,116,116,121,112,101,0,9,16,116,97,98,108,105,115,116,0,9,16,99,104,105,112,108,105,115,116,0,9,16,99,97,114,111,117,115,101,108,0,9,16,103,114,105,100,0,9,16,104,101,114,111,0,9,16,108,105,115,116,0,9,16,102,97,108,108,98,97,99,107,0]}} + {"type":"client-to-server","timestamp":"2023-02-10T15:08:12.434Z","buffer":{"type":"Buffer","data":[12,0,0,0,17,0,0,0,2,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:12.435Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:12.481Z","buffer":{"type":"Buffer","data":[17,0,0,0,0,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:14.376Z","buffer":{"type":"Buffer","data":[27,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:14.419Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,2,0,0,0,3,0,0,0,4,66,82,69,65,75,0,27,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,1,0,0,0,4,66,82,69,65,75,0]}} + {"type":"client-to-server","timestamp":"2023-02-10T15:08:14.751Z","buffer":{"type":"Buffer","data":[12,0,0,0,18,0,0,0,3,0,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:14.792Z","buffer":{"type":"Buffer","data":[55,3,0,0]}} + {"type":"server-to-client","timestamp":"2023-02-10T15:08:14.840Z","buffer":{"type":"Buffer","data":[18,0,0,0,0,0,0,0,6,0,0,0,0,4,0,0,0,66,82,69,65,75,0,20,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,22,0,0,0,36,97,110,111,110,95,50,97,57,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,119,105,100,116,104,32,61,32,101,118,101,110,116,46,103,101,116,68,97,116,97,40,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,50,54,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,187,0,0,0,36,97,110,111,110,95,50,100,98,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,99,97,114,111,117,115,101,108,67,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,103,101,116,95,115,116,114,105,110,103,40,115,101,99,116,105,111,110,44,32,34,99,111,109,112,111,110,101,110,116,95,116,121,112,101,34,41,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} `; session = new DebugProtocolClientReplaySession({ bufferLog: text }); await session.run(); - expectClientReplayResult([], session.result); + expectClientReplayResult([ + HandshakeRequest, + HandshakeV3Response, + IOPortOpenedUpdate, + AllThreadsStoppedUpdate, + ThreadsRequest, + ThreadsResponse, + StackTraceRequest, + StackTraceV3Response, + ContinueRequest, + GenericV3Response, + AddBreakpointsRequest, + ListBreakpointsResponse, + AllThreadsStoppedUpdate, + ThreadsRequest, + ThreadsResponse, + StackTraceRequest, + StackTraceRequest, + StackTraceRequest, + StackTraceRequest, + StackTraceRequest, + StackTraceRequest, + StackTraceV3Response, + StackTraceV3Response, + StackTraceV3Response, + StackTraceV3Response, + StackTraceV3Response, + StackTraceV3Response, + VariablesRequest, + VariablesRequest, + VariablesRequest, + VariablesRequest, + GenericV3Response, + GenericV3Response, + GenericV3Response, + GenericV3Response, + VariablesRequest, + VariablesResponse, + ContinueRequest, + GenericV3Response, + AllThreadsStoppedUpdate, + ThreadAttachedUpdate, + ThreadsRequest + ], session.result); console.log(session); }); @@ -289,10 +354,17 @@ describe(DebugProtocolClientReplaySession.name, () => { }); }); -function expectClientReplayResult(expected: any[], result: DebugProtocolClientReplaySession['result']) { +// eslint-disable-next-line @typescript-eslint/ban-types +function expectClientReplayResult(expected: Array, result: DebugProtocolClientReplaySession['result']) { + expected = expected.map(x => { + if (typeof x === 'function') { + return x?.name; + } + return x; + }); let sanitizedResult = result.map((x, i) => { //if there is no expected object for this entry, or it's a constructor, then we will compare the constructor name - if (expected[i] === undefined || typeof expected[i] === 'function') { + if (expected[i] === undefined || typeof expected[i] === 'string') { return x?.constructor?.name; //deep compare the actual object } else { diff --git a/src/debugProtocol/DebugProtocolClientReplaySession.ts b/src/debugProtocol/DebugProtocolClientReplaySession.ts index 2190dd15..0ca0e8e1 100644 --- a/src/debugProtocol/DebugProtocolClientReplaySession.ts +++ b/src/debugProtocol/DebugProtocolClientReplaySession.ts @@ -1,6 +1,5 @@ import { DebugProtocolClient } from './client/DebugProtocolClient'; import { defer, util } from '../util'; -import * as portfinder from 'portfinder'; import type { ProtocolRequest, ProtocolResponse, ProtocolUpdate } from './events/ProtocolEvent'; import { DebugProtocolServer } from './server/DebugProtocolServer'; import * as Net from 'net'; @@ -29,12 +28,21 @@ export class DebugProtocolClientReplaySession { private entries: Array; private peekEntry() { + this.flushIO(); return this.entries[this.entryIndex]; } private advanceEntry() { + this.flushIO(); return this.entries[this.entryIndex++]; } + private flushIO() { + while (this.entries[this.entryIndex]?.type === 'io') { + const entry = this.entries[this.entryIndex++]; + this.ioSocket.write(entry.buffer); + } + } + private parseBufferLog(bufferLog: string) { this.entries = bufferLog .split(/\r?\n/g) @@ -55,8 +63,8 @@ export class DebugProtocolClientReplaySession { private ioPort: number; public async run() { - this.controlPort = await portfinder.getPortPromise({ port: 8000, stopPort: 8999 }); - this.ioPort = await portfinder.getPortPromise({ port: 9000, stopPort: 9999 }); + this.controlPort = await util.getPort(); + this.ioPort = await util.getPort(); await this.createServer(this.controlPort); @@ -310,7 +318,7 @@ function bufferStartsWith(subject: Buffer, search: Buffer) { } export interface BufferLogEntry { - type: 'client-to-server' | 'server-to-client'; + type: 'client-to-server' | 'server-to-client' | 'io'; timestamp: Date; buffer: Buffer; } diff --git a/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts b/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts index d22df575..11f0212c 100644 --- a/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts +++ b/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts @@ -46,7 +46,17 @@ export class DebugProtocolServerTestPlugin implements ProtocolServerPlugin { } public getLatestRequest() { - return this.latestRequest as T; + return this.latestRequest as unknown as T; + } + + /** + * Get the request at the specified index. Negative indexes count back from the last item in the array + */ + public getRequest(index: number) { + if (index < 0) { + index = this.requests.length - index; + } + return this.requests[index]; } /** diff --git a/src/debugProtocol/PluginInterface.ts b/src/debugProtocol/PluginInterface.ts index 05226b29..cd883c17 100644 --- a/src/debugProtocol/PluginInterface.ts +++ b/src/debugProtocol/PluginInterface.ts @@ -49,7 +49,7 @@ export default class PluginInterface { */ public remove(plugin: T) { for (let i = this.plugins.length - 1; i >= 0; i--) { - if (this.plugins[i] === plugin) { + if (this.plugins[i].plugin === plugin) { this.plugins.splice(i, 1); } } diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index f93b4f75..ed811ec4 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -4,7 +4,6 @@ import { expect } from 'chai'; import { createSandbox } from 'sinon'; import { Command, ErrorCode, StepType, StopReason } from '../Constants'; import { DebugProtocolServer } from '../server/DebugProtocolServer'; -import * as portfinder from 'portfinder'; import { defer, util } from '../../util'; import { HandshakeRequest } from '../events/requests/HandshakeRequest'; import { HandshakeResponse } from '../events/responses/HandshakeResponse'; @@ -36,7 +35,7 @@ import { StackTraceV3Response } from '../events/responses/StackTraceV3Response'; import { IOPortOpenedUpdate } from '../events/updates/IOPortOpenedUpdate'; import * as Net from 'net'; import { ThreadAttachedUpdate } from '../events/updates/ThreadAttachedUpdate'; - +process.on('uncaughtException', (err) => console.log('node js process error\n', err)); const sinon = createSandbox(); describe('DebugProtocolClient', () => { @@ -69,7 +68,7 @@ describe('DebugProtocolClient', () => { }; if (!options.controlPort) { - options.controlPort = await portfinder.getPortPromise(); + options.controlPort = await util.getPort(); } server = new DebugProtocolServer(options); plugin = server.plugins.add(new DebugProtocolServerTestPlugin()); @@ -81,9 +80,13 @@ describe('DebugProtocolClient', () => { }); afterEach(async () => { - client?.destroy(); + try { + client?.destroy(); + } catch (e) { } //shut down and destroy the server after each test - await server?.stop(); + try { + await server?.stop(); + } catch (e) { } await util.sleep(10); sinon.restore(); }); @@ -548,6 +551,34 @@ describe('DebugProtocolClient', () => { expect(client.isHandshakeComplete).to.be.equal(true); }); + it('discards unrecognized updates', async () => { + await connect(); + + //known update type + plugin.server['client'].write( + ThreadAttachedUpdate.fromJson({ + stopReason: StopReason.Break, + stopReasonDetail: 'before', + threadIndex: 0 + }).toBuffer() + ); + //unknown update type + + //known update type + plugin.server['client'].write( + ThreadAttachedUpdate.fromJson({ + stopReason: StopReason.Break, + stopReasonDetail: 'after', + threadIndex: 1 + }).toBuffer() + ); + //unk + + // //we should have the two known update types + // expect(plugin.getRequest(-2)).to.eql(); + // expect(plugin.getRequest(-1)).to.eql(); + }); + it('handles AllThreadsStoppedUpdate after handshake', async () => { await client.connect(); @@ -714,13 +745,15 @@ describe('DebugProtocolClient', () => { expect(update.data.stopReasonDetail).to.eql('because'); }); }); + describe('connectToIoPort', () => { let ioServer: Net.Server; let port: number; + let ioClient: Net.Socket; let socketPromise: Promise; beforeEach(async () => { - port = await portfinder.getPortPromise(); + port = await util.getPort(); ioServer = new Net.Server(); const deferred = defer(); socketPromise = deferred.promise; @@ -729,12 +762,20 @@ describe('DebugProtocolClient', () => { hostName: '0.0.0.0' }, () => { }); ioServer.on('connection', (socket) => { + socket.on('error', e => console.error(e)); deferred.resolve(socket); + ioClient = socket; }); + ioServer.on('error', e => console.error(e)); }); afterEach(() => { - ioServer?.close(); + try { + ioServer?.close(); + } catch { } + try { + ioClient?.destroy(); + } catch { } }); it('supports the IOPortOpened update', async () => { diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index faa00554..568f13e9 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -929,18 +929,18 @@ export class DebugProtocolClient { private shutdown(eventName: 'app-exit' | 'close') { if (this.controlSocket) { - this.controlSocket.removeAllListeners(); + // this.controlSocket.removeAllListeners(); this.controlSocket.destroy(); - this.controlSocket = undefined; + // this.controlSocket = undefined; } - if (this.ioSocket) { - this.ioSocket.removeAllListeners(); - this.ioSocket.destroy(); - this.ioSocket = undefined; - } + // if (this.ioSocket) { + // this.ioSocket.removeAllListeners(); + // this.ioSocket.destroy(); + // this.ioSocket = undefined; + // } - this.emit(eventName); + // this.emit(eventName); } } diff --git a/src/debugProtocol/server/DebugProtocolServer.ts b/src/debugProtocol/server/DebugProtocolServer.ts index 833d67f5..cb8f6e45 100644 --- a/src/debugProtocol/server/DebugProtocolServer.ts +++ b/src/debugProtocol/server/DebugProtocolServer.ts @@ -21,8 +21,7 @@ import { HandshakeV3Response } from '../events/responses/HandshakeV3Response'; import PluginInterface from '../PluginInterface'; import type { ProtocolServerPlugin } from './DebugProtocolServerPlugin'; import { logger } from '../../logging'; -import * as portfinder from 'portfinder'; -import { defer } from '../../util'; +import { defer, util } from '../../util'; import { protocolUtil } from '../ProtocolUtil'; import { SmartBuffer } from 'smart-buffer'; @@ -103,8 +102,18 @@ export class DebugProtocolServer { return true; }); }); + //handle connection errors + this.client.on('error', (e) => { + this.logger.error(e); + }); + }); + this._port = this.controlPort ?? await util.getPort(); + + //handle connection errors + this.server.on('error', (e) => { + this.logger.error(e); }); - this._port = this.controlPort ?? await portfinder.getPortPromise(); + this.server.listen({ port: this.options.controlPort ?? 8081, hostName: this.options.host ?? '0.0.0.0' @@ -121,7 +130,9 @@ export class DebugProtocolServer { public async stop() { //close the client socket await new Promise((resolve) => { - this.client.end(resolve); + this.client.end(() => { + resolve(); + }); }).catch(() => { }); //now close the server diff --git a/src/util.ts b/src/util.ts index 17850514..f16198a4 100644 --- a/src/util.ts +++ b/src/util.ts @@ -2,7 +2,7 @@ import * as fs from 'fs'; import * as fsExtra from 'fs-extra'; import * as net from 'net'; import * as url from 'url'; -import type { SmartBuffer } from 'smart-buffer'; +import * as portfinder from 'portfinder'; import type { BrightScriptDebugSession } from './debugSession/BrightScriptDebugSession'; import { LogOutputEvent } from './debugSession/Events'; import type { AssignmentStatement, Position, Range } from 'brighterscript'; @@ -385,6 +385,21 @@ class Util { public isNullish(value: any) { return value === undefined || value === null; } + + private minPort = 1; + private portWidth = 1000; + + public async getPort() { + if (this.minPort + this.portWidth >= 65535) { + this.minPort = 1; + } + const port = await portfinder.getPortPromise({ + port: this.minPort, + stopPort: this.minPort + this.portWidth + }); + this.minPort = port + 1; + return port; + } } export function defer() { From 2f65862b2565ef15a1fd4f9c3ddd14f2f41ecea2 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Tue, 14 Feb 2023 15:09:54 -0500 Subject: [PATCH 39/74] fix port issues --- src/debugProtocol/client/DebugProtocolClient.ts | 16 ++++++++-------- src/util.ts | 17 +++++++++++------ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 568f13e9..faa00554 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -929,18 +929,18 @@ export class DebugProtocolClient { private shutdown(eventName: 'app-exit' | 'close') { if (this.controlSocket) { - // this.controlSocket.removeAllListeners(); + this.controlSocket.removeAllListeners(); this.controlSocket.destroy(); - // this.controlSocket = undefined; + this.controlSocket = undefined; } - // if (this.ioSocket) { - // this.ioSocket.removeAllListeners(); - // this.ioSocket.destroy(); - // this.ioSocket = undefined; - // } + if (this.ioSocket) { + this.ioSocket.removeAllListeners(); + this.ioSocket.destroy(); + this.ioSocket = undefined; + } - // this.emit(eventName); + this.emit(eventName); } } diff --git a/src/util.ts b/src/util.ts index f16198a4..805abbe0 100644 --- a/src/util.ts +++ b/src/util.ts @@ -387,16 +387,21 @@ class Util { } private minPort = 1; - private portWidth = 1000; public async getPort() { - if (this.minPort + this.portWidth >= 65535) { + let port: number; + try { + port = await portfinder.getPortPromise({ + //startPort + port: this.minPort + }); + } catch { this.minPort = 1; + port = await portfinder.getPortPromise({ + //startPort + port: this.minPort + }); } - const port = await portfinder.getPortPromise({ - port: this.minPort, - stopPort: this.minPort + this.portWidth - }); this.minPort = port + 1; return port; } From c9587dbcc7cca360adda1c8b45e890924fe3c618 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 15 Feb 2023 15:10:20 -0500 Subject: [PATCH 40/74] try to prevent getting stuck --- src/debugProtocol/client/DebugProtocolClient.spec.ts | 2 +- src/managers/ProjectManager.spec.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index ed811ec4..b0f49cf1 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -516,7 +516,7 @@ describe('DebugProtocolClient', () => { expect(client.isHandshakeComplete).to.be.equal(true); }); - it('throws on magic mismatch', async () => { + it.skip('throws on magic mismatch', async () => { plugin.pushResponse( HandshakeV3Response.fromJson({ magic: 'not correct magic', diff --git a/src/managers/ProjectManager.spec.ts b/src/managers/ProjectManager.spec.ts index a5ee7d09..e49b1d1c 100644 --- a/src/managers/ProjectManager.spec.ts +++ b/src/managers/ProjectManager.spec.ts @@ -54,7 +54,7 @@ describe('ProjectManager', () => { describe('getLineNumberOffsetByBreakpoints', () => { let filePath = 'does not matter'; - it('accounts for the entry breakpoint', () => { + it.skip('accounts for the entry breakpoint', () => { manager.breakpointManager['permanentBreakpointsBySrcPath'].set(filePath, [{ line: 3 }, { @@ -304,7 +304,7 @@ describe('Project', () => { }); }); - it('copies the necessary properties onto the instance', () => { + it.skip('copies the necessary properties onto the instance', () => { expect(project.rootDir).to.equal(cwd); expect(project.files).to.eql(['a']); expect(project.bsConst).to.eql({ b: true }); From a7c1a0dcf1407be4ca1eced652ed38b94c206b1b Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 15 Feb 2023 15:12:52 -0500 Subject: [PATCH 41/74] only run the PR build --- .github/workflows/build.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 43b44df9..0dcf2245 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,11 @@ name: build -on: [push, pull_request] +on: + push: + branches: + - master + tags: + - v* + pull_request: jobs: ci: From 870ad7b94aa2b15f07a0ea60dd47ffc5f6ca1bc5 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 15 Feb 2023 15:18:35 -0500 Subject: [PATCH 42/74] Try fixing logging issues --- src/adapters/DebugProtocolAdapter.spec.ts | 4 ++-- src/debugProtocol/client/DebugProtocolClient.spec.ts | 5 +++-- src/managers/ProjectManager.spec.ts | 12 ++++++++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index 28dfc75d..79ad4ad6 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -24,7 +24,7 @@ describe('DebugProtocolAdapter', () => { let plugin: DebugProtocolServerTestPlugin; beforeEach(async () => { - // sinon.stub(console, 'log').callsFake((...args) => { }); + sinon.stub(console, 'log').callsFake((...args) => { }); const options = { controlPort: undefined as number, host: '127.0.0.1' @@ -45,11 +45,11 @@ describe('DebugProtocolAdapter', () => { }); afterEach(async () => { + sinon.restore(); client?.destroy(); //shut down and destroy the server after each test await server?.stop(); await util.sleep(10); - sinon.restore(); }); /** diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index b0f49cf1..46bb34bd 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -80,6 +80,8 @@ describe('DebugProtocolClient', () => { }); afterEach(async () => { + sinon.restore(); + try { client?.destroy(); } catch (e) { } @@ -88,7 +90,6 @@ describe('DebugProtocolClient', () => { await server?.stop(); } catch (e) { } await util.sleep(10); - sinon.restore(); }); it('knows when to enable the thread hopping workaround', () => { @@ -516,7 +517,7 @@ describe('DebugProtocolClient', () => { expect(client.isHandshakeComplete).to.be.equal(true); }); - it.skip('throws on magic mismatch', async () => { + it('throws on magic mismatch', async () => { plugin.pushResponse( HandshakeV3Response.fromJson({ magic: 'not correct magic', diff --git a/src/managers/ProjectManager.spec.ts b/src/managers/ProjectManager.spec.ts index e49b1d1c..dc977edb 100644 --- a/src/managers/ProjectManager.spec.ts +++ b/src/managers/ProjectManager.spec.ts @@ -52,9 +52,13 @@ describe('ProjectManager', () => { }); }); + afterEach(() => { + sinon.restore(); + }); + describe('getLineNumberOffsetByBreakpoints', () => { let filePath = 'does not matter'; - it.skip('accounts for the entry breakpoint', () => { + it('accounts for the entry breakpoint', () => { manager.breakpointManager['permanentBreakpointsBySrcPath'].set(filePath, [{ line: 3 }, { @@ -304,7 +308,11 @@ describe('Project', () => { }); }); - it.skip('copies the necessary properties onto the instance', () => { + afterEach(() => { + sinon.restore(); + }); + + it('copies the necessary properties onto the instance', () => { expect(project.rootDir).to.equal(cwd); expect(project.files).to.eql(['a']); expect(project.bsConst).to.eql({ b: true }); From adb66b8ce525643a0494b2f44dbb03637cdc4153 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 15 Feb 2023 15:33:24 -0500 Subject: [PATCH 43/74] skip failing test. --- src/debugProtocol/client/DebugProtocolClient.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index 46bb34bd..f20ddc6e 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -517,7 +517,7 @@ describe('DebugProtocolClient', () => { expect(client.isHandshakeComplete).to.be.equal(true); }); - it('throws on magic mismatch', async () => { + it.skip('throws on magic mismatch', async () => { plugin.pushResponse( HandshakeV3Response.fromJson({ magic: 'not correct magic', From 06f5c792d64350ae00fd83a1b439aac320fda8ff Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 15 Feb 2023 15:35:24 -0500 Subject: [PATCH 44/74] skip another --- src/debugProtocol/client/DebugProtocolClient.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index f20ddc6e..f3d019e2 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -684,7 +684,7 @@ describe('DebugProtocolClient', () => { }); describe('sendRequest', () => { - it('throws when controller is missing', async () => { + it.skip('throws when controller is missing', async () => { await connect(); delete client['controlSocket']; From 1b6a714ad683e51b2eef7f1961bfec10667c5393 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 15 Feb 2023 15:38:48 -0500 Subject: [PATCH 45/74] increase timeout for all tests, reenable disabled --- package.json | 1 + src/debugProtocol/client/DebugProtocolClient.spec.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fa98b4ab..ee6edf1b 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "watchFiles": [ "src/**/*" ], + "timeout": "10000", "fullTrace": true, "watchExtensions": [ "ts" diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index f3d019e2..46bb34bd 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -517,7 +517,7 @@ describe('DebugProtocolClient', () => { expect(client.isHandshakeComplete).to.be.equal(true); }); - it.skip('throws on magic mismatch', async () => { + it('throws on magic mismatch', async () => { plugin.pushResponse( HandshakeV3Response.fromJson({ magic: 'not correct magic', @@ -684,7 +684,7 @@ describe('DebugProtocolClient', () => { }); describe('sendRequest', () => { - it.skip('throws when controller is missing', async () => { + it('throws when controller is missing', async () => { await connect(); delete client['controlSocket']; From 424ee08d9aa204f425fbf440e0579edd2b647347 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Thu, 16 Feb 2023 08:18:31 -0500 Subject: [PATCH 46/74] Fix node 12 server never close --- .github/workflows/build.yml | 3 +- package-lock.json | 6182 +++-------------- package.json | 2 +- .../server/DebugProtocolServer.ts | 6 +- 4 files changed, 1061 insertions(+), 5132 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0dcf2245..f06feb37 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,8 +25,7 @@ jobs: - run: npm run build - run: npm run lint - run: npm run test - #code coverage server is failing, so disable it for now. (If you're reading this, re-enable please) - #- run: npm run publish-coverage + - run: npm run publish-coverage npm-release: #only run this task if a tag starting with 'v' was used to trigger this (i.e. a tagged release) if: startsWith(github.ref, 'refs/tags/v') diff --git a/package-lock.json b/package-lock.json index 86af3a4e..eedf5b87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5022 +1,13 @@ { "name": "roku-debug", "version": "0.18.3", - "lockfileVersion": 2, + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "roku-debug", - "version": "0.18.3", - "license": "MIT", - "dependencies": { - "@rokucommunity/logger": "^0.3.1", - "brighterscript": "^0.61.3", - "dateformat": "^4.6.3", - "eol": "^0.9.1", - "eventemitter3": "^4.0.7", - "find-in-files": "^0.5.0", - "fs-extra": "^10.0.0", - "natural-orderby": "^2.0.3", - "replace-in-file": "^6.3.2", - "replace-last": "^1.2.6", - "roku-deploy": "^3.9.3", - "semver": "^7.3.5", - "serialize-error": "^8.1.0", - "smart-buffer": "^4.2.0", - "source-map": "^0.7.4", - "telnet-client": "^1.4.9", - "vscode-debugadapter": "^1.49.0", - "vscode-debugprotocol": "^1.49.0", - "vscode-languageserver": "^6.1.1" - }, - "devDependencies": { - "@types/chai": "^4.2.22", - "@types/dedent": "^0.7.0", - "@types/find-in-files": "^0.5.1", - "@types/fs-extra": "^9.0.13", - "@types/glob": "^7.2.0", - "@types/mocha": "^9.0.0", - "@types/node": "^16.11.6", - "@types/request": "^2.48.7", - "@types/semver": "^7.3.9", - "@types/sinon": "^10.0.6", - "@types/vscode": "^1.61.0", - "@typescript-eslint/eslint-plugin": "^5.2.0", - "@typescript-eslint/parser": "^5.2.0", - "chai": "^4.3.4", - "coveralls": "^3.1.1", - "dedent": "^0.7.0", - "eslint": "^8.1.0", - "eslint-plugin-no-only-tests": "^2.6.0", - "get-port": "^5.1.1", - "mocha": "^9.1.3", - "nodemon": "^2.0.20", - "nyc": "^15.1.0", - "p-defer": "^4.0.0", - "portfinder": "^1.0.32", - "rimraf": "^3.0.2", - "rmfr": "^2.0.0", - "rxjs": "^7.4.0", - "sinon": "^11.1.2", - "source-map-support": "^0.5.20", - "ts-node": "^10.4.0", - "typescript": "^4.4.4" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.15.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/highlight": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.15.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.15.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.15.8", - "@babel/generator": "^7.15.8", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-module-transforms": "^7.15.8", - "@babel/helpers": "^7.15.4", - "@babel/parser": "^7.15.8", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.6", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/core/node_modules/source-map": { - "version": "0.5.7", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.15.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.15.6", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/source-map": { - "version": "0.5.7", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.15.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.15.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.15.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.15.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.15.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.15.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.15.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.15.4", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-simple-access": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/helper-validator-identifier": "^7.15.7", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.15.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.15.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.15.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.15.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.15.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.14.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.15.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.14.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.15.8", - "dev": true, - "license": "MIT", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/template": { - "version": "7.15.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.15.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/types": { - "version": "7.15.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@cspotcode/source-map-consumer": { - "version": "0.8.0", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 12" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-consumer": "0.8.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.0.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.6.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.0", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@rokucommunity/bslib": { - "version": "0.1.1", - "license": "MIT" - }, - "node_modules/@rokucommunity/logger": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@rokucommunity/logger/-/logger-0.3.1.tgz", - "integrity": "sha512-jTNRur/P2cnQgfulNsZRstXkpmQ12ex2HA4AneGQXnDlGcOeHQ21ia8Ka1KJA1IlLbiEUK2/fngFdZMfI11Zjw==", - "dependencies": { - "chalk": "^4.1.2", - "fs-extra": "^10.0.0", - "safe-json-stringify": "^1.2.0", - "serialize-error": "^8.1.0" - } - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.3", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "7.1.2", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "6.0.2", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^1.6.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" - } - }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.1", - "dev": true, - "license": "(Unlicense OR Apache-2.0)" - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.8", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.9", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/caseless": { - "version": "0.12.2", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/chai": { - "version": "4.2.22", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/dedent": { - "version": "0.7.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/find-in-files": { - "version": "0.5.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/fs-extra": { - "version": "9.0.13", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/glob": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.9", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/minimatch": { - "version": "3.0.5", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/mocha": { - "version": "9.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "16.11.6", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/request": { - "version": "2.48.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/caseless": "*", - "@types/node": "*", - "@types/tough-cookie": "*", - "form-data": "^2.5.0" - } - }, - "node_modules/@types/request/node_modules/form-data": { - "version": "2.5.1", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/@types/semver": { - "version": "7.3.9", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/sinon": { - "version": "10.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinonjs/fake-timers": "^7.1.0" - } - }, - "node_modules/@types/tough-cookie": { - "version": "4.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/vscode": { - "version": "1.61.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/experimental-utils": "5.2.0", - "@typescript-eslint/scope-manager": "5.2.0", - "debug": "^4.3.2", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.2.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "5.1.8", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.2.0", - "@typescript-eslint/types": "5.2.0", - "@typescript-eslint/typescript-estree": "5.2.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.2.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/scope-manager": "5.2.0", - "@typescript-eslint/types": "5.2.0", - "@typescript-eslint/typescript-estree": "5.2.0", - "debug": "^4.3.2" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "5.2.0", - "@typescript-eslint/visitor-keys": "5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.2.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "5.2.0", - "@typescript-eslint/visitor-keys": "5.2.0", - "debug": "^4.3.2", - "globby": "^11.0.4", - "is-glob": "^4.0.3", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "5.2.0", - "eslint-visitor-keys": "^3.0.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "3.0.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "dev": true, - "license": "ISC" - }, - "node_modules/@xml-tools/parser": { - "version": "1.0.11", - "license": "Apache-2.0", - "dependencies": { - "chevrotain": "7.1.1" - } - }, - "node_modules/@xml-tools/parser/node_modules/chevrotain": { - "version": "7.1.1", - "license": "Apache-2.0", - "dependencies": { - "regexp-to-ast": "0.5.0" - } - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.5.0", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.2", - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/append-transform": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "default-require-extensions": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/append-type": { - "version": "1.0.2", - "dev": true, - "license": "MIT-0" - }, - "node_modules/archy": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/arg": { - "version": "4.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "1.0.10", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/array-flat-polyfill": { - "version": "1.0.1", - "license": "CC0-1.0", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/array-to-sentence": { - "version": "1.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/array-union": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/asn1": { - "version": "0.2.4", - "license": "MIT", - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/assert-valid-glob-opts": { - "version": "1.0.0", - "dev": true, - "license": "CC0-1.0", - "dependencies": { - "glob-option-error": "^1.0.0", - "validate-glob-opts": "^1.0.0" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dev": true, - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "license": "MIT" - }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.11.0", - "license": "MIT" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "license": "MIT" - }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "license": "BSD-3-Clause", - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "license": "MIT", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/brighterscript": { - "version": "0.61.3", - "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.61.3.tgz", - "integrity": "sha512-8BDpOSCdmkS/QcTdPTUW/99nCBypuoa/Zz6PZHI6OiVylqTBidtrGI7lBZotqY6yvQ3KJl24thhLHK5XuIT/6w==", - "dependencies": { - "@rokucommunity/bslib": "^0.1.1", - "@xml-tools/parser": "^1.0.7", - "array-flat-polyfill": "^1.0.1", - "chalk": "^2.4.2", - "chevrotain": "^7.0.1", - "chokidar": "^3.5.1", - "clear": "^0.1.0", - "cross-platform-clear-console": "^2.3.0", - "debounce-promise": "^3.1.0", - "eventemitter3": "^4.0.0", - "fast-glob": "^3.2.11", - "file-url": "^3.0.0", - "fs-extra": "^8.0.0", - "jsonc-parser": "^2.3.0", - "long": "^3.2.0", - "luxon": "^2.5.2", - "minimatch": "^3.0.4", - "moment": "^2.23.0", - "p-settle": "^2.1.0", - "parse-ms": "^2.1.0", - "require-relative": "^0.8.7", - "roku-deploy": "^3.9.3", - "serialize-error": "^7.0.1", - "source-map": "^0.7.4", - "vscode-languageserver": "7.0.0", - "vscode-languageserver-protocol": "3.16.0", - "vscode-languageserver-textdocument": "^1.0.1", - "vscode-uri": "^2.1.1", - "xml2js": "^0.4.19", - "yargs": "^16.2.0" - }, - "bin": { - "bsc": "dist/cli.js" - } - }, - "node_modules/brighterscript/node_modules/ansi-styles": { - "version": "3.2.1", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/brighterscript/node_modules/chalk": { - "version": "2.4.2", - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/brighterscript/node_modules/cliui": { - "version": "7.0.4", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/brighterscript/node_modules/color-convert": { - "version": "1.9.3", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/brighterscript/node_modules/color-name": { - "version": "1.1.3", - "license": "MIT" - }, - "node_modules/brighterscript/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/brighterscript/node_modules/has-flag": { - "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/brighterscript/node_modules/serialize-error": { - "version": "7.0.1", - "license": "MIT", - "dependencies": { - "type-fest": "^0.13.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brighterscript/node_modules/supports-color": { - "version": "5.5.0", - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/brighterscript/node_modules/type-fest": { - "version": "0.13.1", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brighterscript/node_modules/vscode-languageserver": { - "version": "7.0.0", - "license": "MIT", - "dependencies": { - "vscode-languageserver-protocol": "3.16.0" - }, - "bin": { - "installServerIntoExtension": "bin/installServerIntoExtension" - } - }, - "node_modules/brighterscript/node_modules/wrap-ansi": { - "version": "7.0.0", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/brighterscript/node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/brighterscript/node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/brighterscript/node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "license": "MIT" - }, - "node_modules/brighterscript/node_modules/y18n": { - "version": "5.0.8", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/brighterscript/node_modules/yargs": { - "version": "16.2.0", - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/brighterscript/node_modules/yargs-parser": { - "version": "20.2.9", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "dev": true, - "license": "ISC" - }, - "node_modules/browserslist": { - "version": "4.17.5", - "dev": true, - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001271", - "electron-to-chromium": "^1.3.878", - "escalade": "^3.1.1", - "node-releases": "^2.0.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/caching-transform": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001436", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001436.tgz", - "integrity": "sha512-ZmWkKsnC2ifEPoWUvSAIGyOYwT+keAaaWPHiQ9DfMqS1t6tfuyFYoWR78TeZtznkEQ64+vGXH9cZrElwR2Mrxg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - } - ] - }, - "node_modules/caseless": { - "version": "0.12.0", - "license": "Apache-2.0" - }, - "node_modules/chai": { - "version": "4.3.4", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/check-error": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/chevrotain": { - "version": "7.1.2", - "license": "Apache-2.0", - "dependencies": { - "regexp-to-ast": "0.5.0" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/clear": { - "version": "0.1.0", - "engines": { - "node": "*" - } - }, - "node_modules/cliui": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "1.8.0", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "license": "MIT" - }, - "node_modules/coveralls": { - "version": "3.1.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "js-yaml": "^3.13.1", - "lcov-parse": "^1.0.0", - "log-driver": "^1.2.7", - "minimist": "^1.2.5", - "request": "^2.88.2" - }, - "bin": { - "coveralls": "bin/coveralls.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-platform-clear-console": { - "version": "2.3.0", - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/dashdash": { - "version": "1.14.1", - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/dateformat": { - "version": "4.6.3", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/dayjs": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.2.tgz", - "integrity": "sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw==" - }, - "node_modules/debounce-promise": { - "version": "3.1.2", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dedent": { - "version": "0.7.0", - "dev": true, - "license": "MIT" - }, - "node_modules/deep-eql": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/default-require-extensions": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "strip-bom": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/default-require-extensions/node_modules/strip-bom": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/diff": { - "version": "5.0.0", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "license": "MIT", - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.3.880", - "dev": true, - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "license": "MIT" - }, - "node_modules/enquirer": { - "version": "2.3.6", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/eol": { - "version": "0.9.1", - "license": "MIT" - }, - "node_modules/es6-error": { - "version": "4.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/escalade": { - "version": "3.1.1", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint": { - "version": "8.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint/eslintrc": "^1.0.3", - "@humanwhocodes/config-array": "^0.6.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^6.0.0", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.0.0", - "espree": "^9.0.0", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.2.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-plugin-no-only-tests": { - "version": "2.6.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "6.0.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "3.0.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/espree": { - "version": "9.0.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.5.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.0.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "3.0.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "license": "MIT" - }, - "node_modules/extend": { - "version": "3.0.2", - "license": "MIT" - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "engines": [ - "node >=0.6.0" - ], - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.13.0", - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/file-url": { - "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find": { - "version": "0.1.7", - "dependencies": { - "traverse-chain": "~0.1.0" - } - }, - "node_modules/find-cache-dir": { - "version": "3.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/find-cache-dir/node_modules/pkg-dir": { - "version": "4.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-in-files": { - "version": "0.5.0", - "license": "MIT", - "dependencies": { - "find": "^0.1.5", - "q": "^1.0.1" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.2", - "dev": true, - "license": "ISC" - }, - "node_modules/foreground-child": { - "version": "2.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/forever-agent": { - "version": "0.6.1", - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "2.3.3", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/fromentries": { - "version": "1.3.2", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/fs-extra": { - "version": "10.0.0", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/fs-extra/node_modules/jsonfile": { - "version": "6.1.0", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/fs-extra/node_modules/universalify": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.2", - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-port": { - "version": "5.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/getpass": { - "version": "0.1.7", - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "node_modules/glob": { - "version": "7.2.0", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-option-error": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globals": { - "version": "13.12.0", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby/node_modules/ignore": { - "version": "5.1.8", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.8", - "license": "ISC" - }, - "node_modules/growl": { - "version": "1.10.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.x" - } - }, - "node_modules/har-schema": { - "version": "2.0.0", - "license": "ISC", - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "license": "MIT", - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hasha": { - "version": "5.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/hasha/node_modules/type-fest": { - "version": "0.8.1", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/he": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/http-signature": { - "version": "1.2.0", - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, - "node_modules/ignore": { - "version": "4.0.6", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true - }, - "node_modules/immediate": { - "version": "3.0.6", - "license": "MIT" - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/indexed-filter": { - "version": "1.0.3", - "dev": true, - "license": "ISC", - "dependencies": { - "append-type": "^1.0.1" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "license": "ISC" - }, - "node_modules/inspect-with-kind": { - "version": "1.0.5", - "dev": true, - "license": "ISC", - "dependencies": { - "kind-of": "^6.0.2" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "license": "MIT" - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/isstream": { - "version": "0.1.2", - "license": "MIT" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-hook": { - "version": "3.0.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "append-transform": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.0", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-processinfo": { - "version": "2.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.0", - "istanbul-lib-coverage": "^3.0.0-alpha.1", - "make-dir": "^3.0.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^3.3.3" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-reports": { - "version": "3.0.5", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsbn": { - "version": "0.1.1", - "license": "MIT" - }, - "node_modules/jsesc": { - "version": "2.5.2", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-schema": { - "version": "0.4.0", - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "license": "ISC" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonc-parser": { - "version": "2.3.1", - "license": "MIT" - }, - "node_modules/jsonfile": { - "version": "4.0.0", - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsprim": { - "version": "1.4.2", - "license": "MIT", - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, - "node_modules/just-extend": { - "version": "4.2.1", - "dev": true, - "license": "MIT" - }, - "node_modules/kind-of": { - "version": "6.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lcov-parse": { - "version": "1.0.0", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "lcov-parse": "bin/cli.js" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lie": { - "version": "3.3.0", - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.flattendeep": { - "version": "4.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.get": { - "version": "4.4.2", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "dev": true, - "license": "MIT" - }, - "node_modules/log-driver": { - "version": "1.2.7", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=0.8.6" - } - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/long": { - "version": "3.2.0", - "license": "Apache-2.0", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/luxon": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-2.5.2.tgz", - "integrity": "sha512-Yg7/RDp4nedqmLgyH0LwgGRvMEKVzKbUdkBYyCosbHgJ+kaOUx0qzSiSatVc3DFygnirTPYnMM2P5dg2uH1WvA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "dev": true, - "license": "ISC" - }, - "node_modules/merge2": { - "version": "1.4.1", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.4", - "license": "MIT", - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.50.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.33", - "license": "MIT", - "dependencies": { - "mime-db": "1.50.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", - "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", - "dev": true, - "dependencies": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.3", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "4.2.1", - "ms": "2.1.3", - "nanoid": "3.3.1", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "workerpool": "6.2.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha/node_modules/argparse": { - "version": "2.0.1", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/mocha/node_modules/cliui": { - "version": "7.0.4", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/mocha/node_modules/escape-string-regexp": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/find-up": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/js-yaml": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/mocha/node_modules/locate-path": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", - "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/mocha/node_modules/p-limit": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/mocha/node_modules/wrap-ansi": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/mocha/node_modules/y18n": { - "version": "5.0.8", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/yargs": { - "version": "16.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/yargs-parser": { - "version": "20.2.4", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/natural-orderby": { - "version": "2.0.3", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/nise": { - "version": "5.1.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^1.7.0", - "@sinonjs/fake-timers": "^7.0.4", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "path-to-regexp": "^1.7.0" - } - }, - "node_modules/node-preload": { - "version": "0.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "process-on-spawn": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/node-releases": { - "version": "2.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/nodemon": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", - "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", - "dev": true, - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^3.2.7", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "simple-update-notifier": "^1.0.7", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nodemon/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/nodemon/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/nodemon/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/nodemon/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc": { - "version": "15.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^2.0.0", - "get-package-type": "^0.1.0", - "glob": "^7.1.6", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", - "istanbul-lib-processinfo": "^2.0.2", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "make-dir": "^3.0.0", - "node-preload": "^0.2.1", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "spawn-wrap": "^2.0.0", - "test-exclude": "^6.0.0", - "yargs": "^15.0.2" - }, - "bin": { - "nyc": "bin/nyc.js" - }, - "engines": { - "node": ">=8.9" - } - }, - "node_modules/nyc/node_modules/resolve-from": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "node_modules/once": { - "version": "1.4.0", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-defer": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-map": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-reflect": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/p-settle": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "p-limit": "^1.2.0", - "p-reflect": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-settle/node_modules/p-limit": { - "version": "1.3.0", - "license": "MIT", - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-settle/node_modules/p-try": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-hash": { - "version": "4.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pako": { - "version": "1.0.11", - "license": "(MIT AND Zlib)" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-ms": { - "version": "2.1.0", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-to-regexp": { - "version": "1.8.0", - "dev": true, - "license": "MIT", - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/path-to-regexp/node_modules/isarray": { - "version": "0.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/path-type": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathval": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/performance-now": { - "version": "2.1.0", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.0", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/portfinder": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", - "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", - "dev": true, - "dependencies": { - "async": "^2.6.4", - "debug": "^3.2.7", - "mkdirp": "^0.5.6" - }, - "engines": { - "node": ">= 0.12.0" - } - }, - "node_modules/portfinder/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/portfinder/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "license": "MIT" - }, - "node_modules/process-on-spawn": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "fromentries": "^1.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/progress": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/psl": { - "version": "1.8.0", - "license": "MIT" - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true - }, - "node_modules/punycode": { - "version": "2.1.1", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/q": { - "version": "1.5.1", - "license": "MIT", - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, - "node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/randombytes": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/readable-stream": { - "version": "2.3.7", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/regexp-to-ast": { - "version": "0.5.0", - "license": "MIT" - }, - "node_modules/regexpp": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/release-zalgo": { - "version": "1.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "es6-error": "^4.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/replace-in-file": { - "version": "6.3.2", - "license": "MIT", - "dependencies": { - "chalk": "^4.1.2", - "glob": "^7.2.0", - "yargs": "^17.2.1" - }, - "bin": { - "replace-in-file": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/replace-in-file/node_modules/cliui": { - "version": "7.0.4", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/replace-in-file/node_modules/wrap-ansi": { - "version": "7.0.0", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/replace-in-file/node_modules/y18n": { - "version": "5.0.8", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/replace-in-file/node_modules/yargs": { - "version": "17.2.1", - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/replace-in-file/node_modules/yargs-parser": { - "version": "20.2.9", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/replace-last": { - "version": "1.2.6", - "license": "ISC", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/request": { - "version": "2.88.2", - "license": "Apache-2.0", - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/require-relative": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", - "integrity": "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==" - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rmfr": { - "version": "2.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "assert-valid-glob-opts": "^1.0.0", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "inspect-with-kind": "^1.0.4", - "rimraf": "^2.6.2" - } - }, - "node_modules/rmfr/node_modules/rimraf": { - "version": "2.7.1", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/roku-deploy": { - "version": "3.9.3", - "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.9.3.tgz", - "integrity": "sha512-cjTx5ffZNt07rQS+0s2sTBHkZKUk283y9f6UnbI77X03lQ60vYlCnqsKswWisFYMHPIdvsTLLSfKsshAPwKHEQ==", - "dependencies": { - "chalk": "^2.4.2", - "dateformat": "^3.0.3", - "dayjs": "^1.11.0", - "fast-glob": "^3.2.11", - "fs-extra": "^7.0.1", - "is-glob": "^4.0.3", - "jsonc-parser": "^2.3.0", - "jszip": "^3.6.0", - "moment": "^2.29.1", - "parse-ms": "^2.1.0", - "picomatch": "^2.2.1", - "request": "^2.88.0", - "temp-dir": "^2.0.0", - "xml2js": "^0.4.23" - }, - "bin": { - "roku-deploy": "dist/cli.js" - } - }, - "node_modules/roku-deploy/node_modules/ansi-styles": { - "version": "3.2.1", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/roku-deploy/node_modules/chalk": { - "version": "2.4.2", - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/roku-deploy/node_modules/color-convert": { - "version": "1.9.3", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/roku-deploy/node_modules/color-name": { - "version": "1.1.3", - "license": "MIT" - }, - "node_modules/roku-deploy/node_modules/dateformat": { - "version": "3.0.3", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/roku-deploy/node_modules/fs-extra": { - "version": "7.0.1", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/roku-deploy/node_modules/has-flag": { - "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/roku-deploy/node_modules/supports-color": { - "version": "5.5.0", - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rxjs": { - "version": "7.4.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "~2.1.0" - } - }, - "node_modules/rxjs/node_modules/tslib": { - "version": "2.1.0", - "dev": true, - "license": "0BSD" - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "license": "MIT" - }, - "node_modules/safe-json-stringify": { - "version": "1.2.0", - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "license": "MIT" - }, - "node_modules/sax": { - "version": "1.2.4", - "license": "ISC" - }, - "node_modules/semver": { - "version": "7.3.5", - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/serialize-error": { - "version": "8.1.0", - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "3.0.5", - "dev": true, - "license": "ISC" - }, - "node_modules/simple-update-notifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", - "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", - "dev": true, - "dependencies": { - "semver": "~7.0.0" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/sinon": { - "version": "11.1.2", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^1.8.3", - "@sinonjs/fake-timers": "^7.1.2", - "@sinonjs/samsam": "^6.0.2", - "diff": "^5.0.0", - "nise": "^5.1.0", - "supports-color": "^7.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/source-map-support": { - "version": "0.5.20", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/spawn-wrap": { - "version": "2.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "which": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/sshpk": { - "version": "1.16.1", - "license": "MIT", - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/telnet-client": { - "version": "1.4.9", - "license": "MIT", - "dependencies": { - "bluebird": "^3.5.4" - }, - "funding": { - "type": "paypal", - "url": "https://paypal.me/kozjak" - } - }, - "node_modules/temp-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", - "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "dev": true, - "license": "MIT" - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dev": true, - "dependencies": { - "nopt": "~1.0.10" - }, - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "license": "BSD-3-Clause", - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/traverse-chain": { - "version": "0.1.0", - "license": "MIT" - }, - "node_modules/ts-node": { - "version": "10.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "0.7.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node/node_modules/diff": { - "version": "4.0.2", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/ts-node/node_modules/yn": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tslib": { - "version": "1.14.1", - "dev": true, - "license": "0BSD" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "license": "Unlicense" - }, - "node_modules/type-check": { - "version": "0.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "dev": true, - "license": "MIT", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/typescript": { - "version": "4.4.4", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true - }, - "node_modules/universalify": { - "version": "0.1.2", - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "license": "MIT" - }, - "node_modules/uuid": { - "version": "3.4.0", - "license": "MIT", - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "dev": true, - "license": "MIT" - }, - "node_modules/validate-glob-opts": { - "version": "1.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "array-to-sentence": "^1.1.0", - "indexed-filter": "^1.0.0", - "inspect-with-kind": "^1.0.4", - "is-plain-obj": "^1.1.0" - } - }, - "node_modules/validate-glob-opts/node_modules/is-plain-obj": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/verror": { - "version": "1.10.0", - "engines": [ - "node >=0.6.0" - ], - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "node_modules/verror/node_modules/core-util-is": { - "version": "1.0.2", - "license": "MIT" - }, - "node_modules/vscode-debugadapter": { - "version": "1.49.0", - "license": "MIT", - "dependencies": { - "mkdirp": "^1.0.4", - "vscode-debugprotocol": "1.49.0" - } - }, - "node_modules/vscode-debugprotocol": { - "version": "1.49.0", - "license": "MIT" - }, - "node_modules/vscode-jsonrpc": { - "version": "6.0.0", - "license": "MIT", - "engines": { - "node": ">=8.0.0 || >=10.0.0" - } - }, - "node_modules/vscode-languageserver": { - "version": "6.1.1", - "license": "MIT", - "dependencies": { - "vscode-languageserver-protocol": "^3.15.3" - }, - "bin": { - "installServerIntoExtension": "bin/installServerIntoExtension" - } - }, - "node_modules/vscode-languageserver-protocol": { - "version": "3.16.0", - "license": "MIT", - "dependencies": { - "vscode-jsonrpc": "6.0.0", - "vscode-languageserver-types": "3.16.0" - } - }, - "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.2", - "license": "MIT" - }, - "node_modules/vscode-languageserver-types": { - "version": "3.16.0", - "license": "MIT" - }, - "node_modules/vscode-uri": { - "version": "2.1.2", - "license": "MIT" - }, - "node_modules/which": { - "version": "2.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-module": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/xml2js": { - "version": "0.4.23", - "license": "MIT", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/y18n": { - "version": "4.0.3", - "dev": true, - "license": "ISC" - }, - "node_modules/yallist": { - "version": "4.0.0", - "license": "ISC" - }, - "node_modules/yargs": { - "version": "15.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs-parser": { - "version": "18.1.3", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/camelcase": { - "version": "6.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs-unparser/node_modules/decamelize": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, "dependencies": { "@babel/code-frame": { "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", + "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", "dev": true, "requires": { "@babel/highlight": "^7.14.5" @@ -5024,10 +15,14 @@ }, "@babel/compat-data": { "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", + "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", "dev": true }, "@babel/core": { "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.8.tgz", + "integrity": "sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og==", "dev": true, "requires": { "@babel/code-frame": "^7.15.8", @@ -5049,16 +44,22 @@ "dependencies": { "semver": { "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, "source-map": { "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "dev": true } } }, "@babel/generator": { "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.8.tgz", + "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", "dev": true, "requires": { "@babel/types": "^7.15.6", @@ -5068,12 +69,16 @@ "dependencies": { "source-map": { "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "dev": true } } }, "@babel/helper-compilation-targets": { "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", + "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", "dev": true, "requires": { "@babel/compat-data": "^7.15.0", @@ -5084,12 +89,16 @@ "dependencies": { "semver": { "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } }, "@babel/helper-function-name": { "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", + "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.15.4", @@ -5099,6 +108,8 @@ }, "@babel/helper-get-function-arity": { "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", + "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", "dev": true, "requires": { "@babel/types": "^7.15.4" @@ -5106,6 +117,8 @@ }, "@babel/helper-hoist-variables": { "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", + "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", "dev": true, "requires": { "@babel/types": "^7.15.4" @@ -5113,6 +126,8 @@ }, "@babel/helper-member-expression-to-functions": { "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", + "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", "dev": true, "requires": { "@babel/types": "^7.15.4" @@ -5120,6 +135,8 @@ }, "@babel/helper-module-imports": { "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", + "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", "dev": true, "requires": { "@babel/types": "^7.15.4" @@ -5127,6 +144,8 @@ }, "@babel/helper-module-transforms": { "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.8.tgz", + "integrity": "sha512-DfAfA6PfpG8t4S6npwzLvTUpp0sS7JrcuaMiy1Y5645laRJIp/LiLGIBbQKaXSInK8tiGNI7FL7L8UvB8gdUZg==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.15.4", @@ -5141,6 +160,8 @@ }, "@babel/helper-optimise-call-expression": { "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", + "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", "dev": true, "requires": { "@babel/types": "^7.15.4" @@ -5148,6 +169,8 @@ }, "@babel/helper-replace-supers": { "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", + "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", "dev": true, "requires": { "@babel/helper-member-expression-to-functions": "^7.15.4", @@ -5158,6 +181,8 @@ }, "@babel/helper-simple-access": { "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", + "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", "dev": true, "requires": { "@babel/types": "^7.15.4" @@ -5165,6 +190,8 @@ }, "@babel/helper-split-export-declaration": { "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", + "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", "dev": true, "requires": { "@babel/types": "^7.15.4" @@ -5172,14 +199,20 @@ }, "@babel/helper-validator-identifier": { "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", "dev": true }, "@babel/helper-validator-option": { "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", "dev": true }, "@babel/helpers": { "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", + "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", "dev": true, "requires": { "@babel/template": "^7.15.4", @@ -5189,6 +222,8 @@ }, "@babel/highlight": { "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.14.5", @@ -5198,6 +233,8 @@ "dependencies": { "ansi-styles": { "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -5205,6 +242,8 @@ }, "chalk": { "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { "ansi-styles": "^3.2.1", @@ -5214,6 +253,8 @@ }, "color-convert": { "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { "color-name": "1.1.3" @@ -5221,14 +262,20 @@ }, "color-name": { "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, "has-flag": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true }, "supports-color": { "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -5238,10 +285,14 @@ }, "@babel/parser": { "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", + "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", "dev": true }, "@babel/template": { "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", + "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", "dev": true, "requires": { "@babel/code-frame": "^7.14.5", @@ -5251,6 +302,8 @@ }, "@babel/traverse": { "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", + "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", "dev": true, "requires": { "@babel/code-frame": "^7.14.5", @@ -5266,12 +319,16 @@ "dependencies": { "globals": { "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true } } }, "@babel/types": { "version": "7.15.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", + "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.14.9", @@ -5280,10 +337,14 @@ }, "@cspotcode/source-map-consumer": { "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", "dev": true }, "@cspotcode/source-map-support": { "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", "dev": true, "requires": { "@cspotcode/source-map-consumer": "0.8.0" @@ -5291,6 +352,8 @@ }, "@eslint/eslintrc": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.3.tgz", + "integrity": "sha512-DHI1wDPoKCBPoLZA3qDR91+3te/wDSc1YhKg3jR8NxKKRJq2hwHwcWv31cSwSYvIBrmbENoYMWcenW8uproQqg==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -5306,6 +369,8 @@ }, "@humanwhocodes/config-array": { "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.6.0.tgz", + "integrity": "sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A==", "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.0", @@ -5315,10 +380,14 @@ }, "@humanwhocodes/object-schema": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", + "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", "dev": true }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, "requires": { "camelcase": "^5.3.1", @@ -5330,33 +399,45 @@ "dependencies": { "resolve-from": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true } } }, "@istanbuljs/schema": { "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true }, "@nodelib/fs.scandir": { "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "requires": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "@nodelib/fs.stat": { - "version": "2.0.5" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" }, "@nodelib/fs.walk": { "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "requires": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "@rokucommunity/bslib": { - "version": "0.1.1" + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@rokucommunity/bslib/-/bslib-0.1.1.tgz", + "integrity": "sha512-2ox6EUL+UTtccTbD4dbVjZK3QHa0PHCqpoKMF8lZz9ayzzEP3iVPF8KZR6hOi6bxsIcbGXVjqmtCVkpC4P9SrA==" }, "@rokucommunity/logger": { "version": "0.3.1", @@ -5371,6 +452,8 @@ }, "@sinonjs/commons": { "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", "dev": true, "requires": { "type-detect": "4.0.8" @@ -5378,6 +461,8 @@ }, "@sinonjs/fake-timers": { "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", "dev": true, "requires": { "@sinonjs/commons": "^1.7.0" @@ -5385,6 +470,8 @@ }, "@sinonjs/samsam": { "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.0.2.tgz", + "integrity": "sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ==", "dev": true, "requires": { "@sinonjs/commons": "^1.6.0", @@ -5394,42 +481,62 @@ }, "@sinonjs/text-encoding": { "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, "@tsconfig/node10": { "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", "dev": true }, "@tsconfig/node12": { "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", "dev": true }, "@tsconfig/node14": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", "dev": true }, "@tsconfig/node16": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", "dev": true }, "@types/caseless": { "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", "dev": true }, "@types/chai": { "version": "4.2.22", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.22.tgz", + "integrity": "sha512-tFfcE+DSTzWAgifkjik9AySNqIyNoYwmR+uecPwwD/XRNfvOjmC/FjCxpiUGDkDVDphPfCUecSQVFw+lN3M3kQ==", "dev": true }, "@types/dedent": { "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@types/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-EGlKlgMhnLt/cM4DbUSafFdrkeJoC9Mvnj0PUCU7tFmTjMjNRT957kXCx0wYm3JuEq4o4ZsS5vG+NlkM2DMd2A==", "dev": true }, "@types/find-in-files": { "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@types/find-in-files/-/find-in-files-0.5.1.tgz", + "integrity": "sha512-kUPtvVXZn99bBHx08jAJgrI1NKWspuoX6RgqQgfNlH2debcwcowUV41P6Kfg4VDaCAr5KNBW9qdjIyKRnXVuBA==", "dev": true }, "@types/fs-extra": { "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", "dev": true, "requires": { "@types/node": "*" @@ -5437,6 +544,8 @@ }, "@types/glob": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", "dev": true, "requires": { "@types/minimatch": "*", @@ -5445,22 +554,32 @@ }, "@types/json-schema": { "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, "@types/minimatch": { "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", "dev": true }, "@types/mocha": { "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.0.0.tgz", + "integrity": "sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA==", "dev": true }, "@types/node": { "version": "16.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz", + "integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==", "dev": true }, "@types/request": { "version": "2.48.7", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.7.tgz", + "integrity": "sha512-GWP9AZW7foLd4YQxyFZDBepl0lPsWLMEXDZUjQ/c1gqVPDPECrRZyEzuhJdnPWioFCq3Tv0qoGpMD6U+ygd4ZA==", "dev": true, "requires": { "@types/caseless": "*", @@ -5471,6 +590,8 @@ "dependencies": { "form-data": { "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", "dev": true, "requires": { "asynckit": "^0.4.0", @@ -5482,10 +603,14 @@ }, "@types/semver": { "version": "7.3.9", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", + "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==", "dev": true }, "@types/sinon": { "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.6.tgz", + "integrity": "sha512-6EF+wzMWvBNeGrfP3Nx60hhx+FfwSg1JJBLAAP/IdIUq0EYkqCYf70VT3PhuhPX9eLD+Dp+lNdpb/ZeHG8Yezg==", "dev": true, "requires": { "@sinonjs/fake-timers": "^7.1.0" @@ -5493,14 +618,20 @@ }, "@types/tough-cookie": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz", + "integrity": "sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==", "dev": true }, "@types/vscode": { "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.61.0.tgz", + "integrity": "sha512-9k5Nwq45hkRwdfCFY+eKXeQQSbPoA114mF7U/4uJXRBJeGIO7MuJdhF1PnaDN+lllL9iKGQtd6FFXShBXMNaFg==", "dev": true }, "@typescript-eslint/eslint-plugin": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.2.0.tgz", + "integrity": "sha512-qQwg7sqYkBF4CIQSyRQyqsYvP+g/J0To9ZPVNJpfxfekl5RmdvQnFFTVVwpRtaUDFNvjfe/34TgY/dpc3MgNTw==", "dev": true, "requires": { "@typescript-eslint/experimental-utils": "5.2.0", @@ -5515,12 +646,16 @@ "dependencies": { "ignore": { "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", "dev": true } } }, "@typescript-eslint/experimental-utils": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.2.0.tgz", + "integrity": "sha512-fWyT3Agf7n7HuZZRpvUYdFYbPk3iDCq6fgu3ulia4c7yxmPnwVBovdSOX7RL+k8u6hLbrXcdAehlWUVpGh6IEw==", "dev": true, "requires": { "@types/json-schema": "^7.0.9", @@ -5533,6 +668,8 @@ }, "@typescript-eslint/parser": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.2.0.tgz", + "integrity": "sha512-Uyy4TjJBlh3NuA8/4yIQptyJb95Qz5PX//6p8n7zG0QnN4o3NF9Je3JHbVU7fxf5ncSXTmnvMtd/LDQWDk0YqA==", "dev": true, "requires": { "@typescript-eslint/scope-manager": "5.2.0", @@ -5543,6 +680,8 @@ }, "@typescript-eslint/scope-manager": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.2.0.tgz", + "integrity": "sha512-RW+wowZqPzQw8MUFltfKYZfKXqA2qgyi6oi/31J1zfXJRpOn6tCaZtd9b5u9ubnDG2n/EMvQLeZrsLNPpaUiFQ==", "dev": true, "requires": { "@typescript-eslint/types": "5.2.0", @@ -5551,10 +690,14 @@ }, "@typescript-eslint/types": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.2.0.tgz", + "integrity": "sha512-cTk6x08qqosps6sPyP2j7NxyFPlCNsJwSDasqPNjEQ8JMD5xxj2NHxcLin5AJQ8pAVwpQ8BMI3bTxR0zxmK9qQ==", "dev": true }, "@typescript-eslint/typescript-estree": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.2.0.tgz", + "integrity": "sha512-RsdXq2XmVgKbm9nLsE3mjNUM7BTr/K4DYR9WfFVMUuozHWtH5gMpiNZmtrMG8GR385EOSQ3kC9HiEMJWimxd/g==", "dev": true, "requires": { "@typescript-eslint/types": "5.2.0", @@ -5568,6 +711,8 @@ }, "@typescript-eslint/visitor-keys": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.2.0.tgz", + "integrity": "sha512-Nk7HizaXWWCUBfLA/rPNKMzXzWS8Wg9qHMuGtT+v2/YpPij4nVXrVJc24N/r5WrrmqK31jCrZxeHqIgqRzs0Xg==", "dev": true, "requires": { "@typescript-eslint/types": "5.2.0", @@ -5576,22 +721,30 @@ "dependencies": { "eslint-visitor-keys": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz", + "integrity": "sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q==", "dev": true } } }, "@ungap/promise-all-settled": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, "@xml-tools/parser": { "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@xml-tools/parser/-/parser-1.0.11.tgz", + "integrity": "sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==", "requires": { "chevrotain": "7.1.1" }, "dependencies": { "chevrotain": { "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-7.1.1.tgz", + "integrity": "sha512-wy3mC1x4ye+O+QkEinVJkPf5u2vsrDIYW9G7ZuwFl6v/Yu0LwUuT2POsb+NUWApebyxfkQq6+yDfRExbnI5rcw==", "requires": { "regexp-to-ast": "0.5.0" } @@ -5606,19 +759,26 @@ }, "acorn": { "version": "8.5.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", + "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", "dev": true }, "acorn-jsx": { "version": "5.3.2", - "dev": true, - "requires": {} + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true }, "acorn-walk": { "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true }, "aggregate-error": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, "requires": { "clean-stack": "^2.0.0", @@ -5627,6 +787,8 @@ }, "ajv": { "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -5636,19 +798,27 @@ }, "ansi-colors": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, "ansi-regex": { - "version": "5.0.1" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "requires": { "color-convert": "^2.0.1" } }, "anymatch": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -5656,6 +826,8 @@ }, "append-transform": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", "dev": true, "requires": { "default-require-extensions": "^3.0.0" @@ -5663,45 +835,65 @@ }, "append-type": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-type/-/append-type-1.0.2.tgz", + "integrity": "sha512-hac740vT/SAbrFBLgLIWZqVT5PUAcGTWS5UkDDhr+OCizZSw90WKw6sWAEgGaYd2viIblggypMXwpjzHXOvAQg==", "dev": true }, "archy": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", "dev": true }, "arg": { "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, "argparse": { "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { "sprintf-js": "~1.0.2" } }, "array-flat-polyfill": { - "version": "1.0.1" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-flat-polyfill/-/array-flat-polyfill-1.0.1.tgz", + "integrity": "sha512-hfJmKupmQN0lwi0xG6FQ5U8Rd97RnIERplymOv/qpq8AoNKPPAnxJadjFA23FNWm88wykh9HmpLJUUwUtNU/iw==" }, "array-to-sentence": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-to-sentence/-/array-to-sentence-1.1.0.tgz", + "integrity": "sha512-YkwkMmPA2+GSGvXj1s9NZ6cc2LBtR+uSeWTy2IGi5MR1Wag4DdrcjTxA/YV/Fw+qKlBeXomneZgThEbm/wvZbw==", "dev": true }, "array-union": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, "asn1": { "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", "requires": { "safer-buffer": "~2.1.0" } }, "assert-plus": { - "version": "1.0.0" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" }, "assert-valid-glob-opts": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-valid-glob-opts/-/assert-valid-glob-opts-1.0.0.tgz", + "integrity": "sha512-/mttty5Xh7wE4o7ttKaUpBJl0l04xWe3y6muy1j27gyzSsnceK0AYU9owPtUoL9z8+9hnPxztmuhdFZ7jRoyWw==", "dev": true, "requires": { "glob-option-error": "^1.0.0", @@ -5710,6 +902,8 @@ }, "assertion-error": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, "async": { @@ -5722,31 +916,47 @@ } }, "asynckit": { - "version": "0.4.0" + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "aws-sign2": { - "version": "0.7.0" + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==" }, "aws4": { - "version": "1.11.0" + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, "balanced-match": { - "version": "1.0.2" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "bcrypt-pbkdf": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "requires": { "tweetnacl": "^0.14.3" } }, "binary-extensions": { - "version": "2.2.0" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" }, "bluebird": { - "version": "3.7.2" + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5754,6 +964,8 @@ }, "braces": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "requires": { "fill-range": "^7.0.1" } @@ -5797,12 +1009,16 @@ "dependencies": { "ansi-styles": { "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "requires": { "color-convert": "^1.9.0" } }, "chalk": { "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -5811,6 +1027,8 @@ }, "cliui": { "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -5819,12 +1037,16 @@ }, "color-convert": { "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "requires": { "color-name": "1.1.3" } }, "color-name": { - "version": "1.1.3" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "fs-extra": { "version": "8.1.0", @@ -5837,31 +1059,43 @@ } }, "has-flag": { - "version": "3.0.0" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" }, "serialize-error": { "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", "requires": { "type-fest": "^0.13.1" } }, "supports-color": { "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "requires": { "has-flag": "^3.0.0" } }, "type-fest": { - "version": "0.13.1" + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==" }, "vscode-languageserver": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz", + "integrity": "sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==", "requires": { "vscode-languageserver-protocol": "3.16.0" } }, "wrap-ansi": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -5870,26 +1104,36 @@ "dependencies": { "ansi-styles": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "requires": { "color-convert": "^2.0.1" } }, "color-convert": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "requires": { "color-name": "~1.1.4" } }, "color-name": { - "version": "1.1.4" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" } } }, "y18n": { - "version": "5.0.8" + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" }, "yargs": { "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -5901,16 +1145,22 @@ } }, "yargs-parser": { - "version": "20.2.9" + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" } } }, "browser-stdout": { "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, "browserslist": { "version": "4.17.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.5.tgz", + "integrity": "sha512-I3ekeB92mmpctWBoLXe0d5wPS2cBuRvvW0JyyJHMrk9/HmP2ZjrTboNAZ8iuGqaEIlKguljbQY32OkOJIRrgoA==", "dev": true, "requires": { "caniuse-lite": "^1.0.30001271", @@ -5922,10 +1172,14 @@ }, "buffer-from": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, "caching-transform": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", "dev": true, "requires": { "hasha": "^5.0.0", @@ -5936,10 +1190,14 @@ }, "callsites": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, "camelcase": { "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, "caniuse-lite": { @@ -5949,10 +1207,14 @@ "dev": true }, "caseless": { - "version": "0.12.0" + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" }, "chai": { "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", "dev": true, "requires": { "assertion-error": "^1.1.0", @@ -5965,6 +1227,8 @@ }, "chalk": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -5972,10 +1236,14 @@ }, "check-error": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", "dev": true }, "chevrotain": { "version": "7.1.2", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-7.1.2.tgz", + "integrity": "sha512-9bQsXVQ7UAvzMs7iUBBJ9Yv//exOy7bIR3PByOEk4M64vIE/LsiOiX7VIkMF/vEMlrSStwsaE884Bp9CpjtC5g==", "requires": { "regexp-to-ast": "0.5.0" } @@ -5997,13 +1265,19 @@ }, "clean-stack": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true }, "clear": { - "version": "0.1.0" + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/clear/-/clear-0.1.0.tgz", + "integrity": "sha512-qMjRnoL+JDPJHeLePZJuao6+8orzHMGP04A8CdwCNsKhRbOnKRjefxONR7bwILT3MHecxKBjHkKL/tkZ8r4Uzw==" }, "cliui": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, "requires": { "string-width": "^4.2.0", @@ -6013,38 +1287,54 @@ }, "color-convert": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "requires": { "color-name": "~1.1.4" } }, "color-name": { - "version": "1.1.4" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "combined-stream": { "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "requires": { "delayed-stream": "~1.0.0" } }, "commondir": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, "concat-map": { - "version": "0.0.1" + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "convert-source-map": { "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", "dev": true, "requires": { "safe-buffer": "~5.1.1" } }, "core-util-is": { - "version": "1.0.3" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "coveralls": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", + "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", "dev": true, "requires": { "js-yaml": "^3.13.1", @@ -6056,13 +1346,19 @@ }, "create-require": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, "cross-platform-clear-console": { - "version": "2.3.0" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cross-platform-clear-console/-/cross-platform-clear-console-2.3.0.tgz", + "integrity": "sha512-To+sJ6plHHC6k5DfdvSVn6F1GRGJh/R6p76bCpLbyMyHEmbqFyuMAeGwDcz/nGDWH3HUcjFTTX9iUSCzCg9Eiw==" }, "cross-spawn": { "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -6072,12 +1368,16 @@ }, "dashdash": { "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "requires": { "assert-plus": "^1.0.0" } }, "dateformat": { - "version": "4.6.3" + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==" }, "dayjs": { "version": "1.11.2", @@ -6085,7 +1385,9 @@ "integrity": "sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw==" }, "debounce-promise": { - "version": "3.1.2" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/debounce-promise/-/debounce-promise-3.1.2.tgz", + "integrity": "sha512-rZHcgBkbYavBeD9ej6sP56XfG53d51CD4dnaw989YX/nZ/ZJfgRx/9ePKmTNiUiyQvh4mtrMoS3OAWW+yoYtpg==" }, "debug": { "version": "4.3.3", @@ -6098,14 +1400,20 @@ }, "decamelize": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true }, "dedent": { "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", "dev": true }, "deep-eql": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { "type-detect": "^4.0.0" @@ -6113,10 +1421,14 @@ }, "deep-is": { "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, "default-require-extensions": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", "dev": true, "requires": { "strip-bom": "^4.0.0" @@ -6124,19 +1436,27 @@ "dependencies": { "strip-bom": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true } } }, "delayed-stream": { - "version": "1.0.0" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, "diff": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, "dir-glob": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "requires": { "path-type": "^4.0.0" @@ -6144,6 +1464,8 @@ }, "doctrine": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "requires": { "esutils": "^2.0.2" @@ -6151,6 +1473,8 @@ }, "ecc-jsbn": { "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -6158,33 +1482,49 @@ }, "electron-to-chromium": { "version": "1.3.880", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.880.tgz", + "integrity": "sha512-iwIP/6WoeSimzUKJIQtjtpVDsK8Ir8qQCMXsUBwg+rxJR2Uh3wTNSbxoYRfs+3UWx/9MAnPIxVZCyWkm8MT0uw==", "dev": true }, "emoji-regex": { - "version": "8.0.0" + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "enquirer": { "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, "requires": { "ansi-colors": "^4.1.1" } }, "eol": { - "version": "0.9.1" + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/eol/-/eol-0.9.1.tgz", + "integrity": "sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==" }, "es6-error": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true }, "escalade": { - "version": "3.1.1" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, "escape-string-regexp": { - "version": "1.0.5" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" }, "eslint": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.1.0.tgz", + "integrity": "sha512-JZvNneArGSUsluHWJ8g8MMs3CfIEzwaLx9KyH4tZ2i+R2/rPWzL8c0zg3rHdwYVpN/1sB9gqnjHwz9HoeJpGHw==", "dev": true, "requires": { "@eslint/eslintrc": "^1.0.3", @@ -6229,14 +1569,20 @@ "dependencies": { "argparse": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, "escape-string-regexp": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, "eslint-scope": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-6.0.0.tgz", + "integrity": "sha512-uRDL9MWmQCkaFus8RF5K9/L/2fn+80yoW3jkD53l4shjCh26fCtvJGasxjUqP5OT87SYTxCVA3BwTUzuELx9kA==", "dev": true, "requires": { "esrecurse": "^4.3.0", @@ -6245,14 +1591,20 @@ }, "eslint-visitor-keys": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz", + "integrity": "sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q==", "dev": true }, "estraverse": { "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true }, "glob-parent": { "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "requires": { "is-glob": "^4.0.3" @@ -6260,6 +1612,8 @@ }, "js-yaml": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "requires": { "argparse": "^2.0.1" @@ -6269,10 +1623,14 @@ }, "eslint-plugin-no-only-tests": { "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-only-tests/-/eslint-plugin-no-only-tests-2.6.0.tgz", + "integrity": "sha512-T9SmE/g6UV1uZo1oHAqOvL86XWl7Pl2EpRpnLI8g/bkJu+h7XBCB+1LnubRZ2CUQXj805vh4/CYZdnqtVaEo2Q==", "dev": true }, "eslint-scope": { "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "requires": { "esrecurse": "^4.3.0", @@ -6281,6 +1639,8 @@ }, "eslint-utils": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", "dev": true, "requires": { "eslint-visitor-keys": "^2.0.0" @@ -6288,10 +1648,14 @@ }, "eslint-visitor-keys": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true }, "espree": { "version": "9.0.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.0.0.tgz", + "integrity": "sha512-r5EQJcYZ2oaGbeR0jR0fFVijGOcwai07/690YRXLINuhmVeRY4UKSAsQPe/0BNuDgwP7Ophoc1PRsr2E3tkbdQ==", "dev": true, "requires": { "acorn": "^8.5.0", @@ -6301,16 +1665,22 @@ "dependencies": { "eslint-visitor-keys": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz", + "integrity": "sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q==", "dev": true } } }, "esprima": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "esquery": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "requires": { "estraverse": "^5.1.0" @@ -6318,12 +1688,16 @@ "dependencies": { "estraverse": { "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } }, "esrecurse": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { "estraverse": "^5.2.0" @@ -6331,29 +1705,43 @@ "dependencies": { "estraverse": { "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } }, "estraverse": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, "esutils": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, "eventemitter3": { - "version": "4.0.7" + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, "extend": { - "version": "3.0.2" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extsprintf": { - "version": "1.3.0" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==" }, "fast-deep-equal": { - "version": "3.1.3" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { "version": "3.2.11", @@ -6368,42 +1756,58 @@ } }, "fast-json-stable-stringify": { - "version": "2.1.0" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fast-levenshtein": { "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, "fastq": { "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", "requires": { "reusify": "^1.0.4" } }, "file-entry-cache": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "requires": { "flat-cache": "^3.0.4" } }, "file-url": { - "version": "3.0.0" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/file-url/-/file-url-3.0.0.tgz", + "integrity": "sha512-g872QGsHexznxkIAdK8UiZRe7SkE6kvylShU4Nsj8NvfvZag7S0QuQ4IgvPDkk75HxgjIVDwycFTDAgIiO4nDA==" }, "fill-range": { "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "requires": { "to-regex-range": "^5.0.1" } }, "find": { "version": "0.1.7", + "resolved": "https://registry.npmjs.org/find/-/find-0.1.7.tgz", + "integrity": "sha512-jPrupTOe/pO//3a9Ty2o4NqQCp0L46UG+swUnfFtdmtQVN8pEltKpAqR7Nuf6vWn0GBXx5w+R1MyZzqwjEIqdA==", "requires": { "traverse-chain": "~0.1.0" } }, "find-cache-dir": { "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", "dev": true, "requires": { "commondir": "^1.0.1", @@ -6413,6 +1817,8 @@ "dependencies": { "pkg-dir": { "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "requires": { "find-up": "^4.0.0" @@ -6422,6 +1828,8 @@ }, "find-in-files": { "version": "0.5.0", + "resolved": "https://registry.npmjs.org/find-in-files/-/find-in-files-0.5.0.tgz", + "integrity": "sha512-VraTc6HdtdSHmAp0yJpAy20yPttGKzyBWc7b7FPnnsX9TOgmKx0g9xajizpF/iuu4IvNK4TP0SpyBT9zAlwG+g==", "requires": { "find": "^0.1.5", "q": "^1.0.1" @@ -6429,6 +1837,8 @@ }, "find-up": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { "locate-path": "^5.0.0", @@ -6437,10 +1847,14 @@ }, "flat": { "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true }, "flat-cache": { "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "requires": { "flatted": "^3.1.0", @@ -6449,10 +1863,14 @@ }, "flatted": { "version": "3.2.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", + "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", "dev": true }, "foreground-child": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", "dev": true, "requires": { "cross-spawn": "^7.0.0", @@ -6460,10 +1878,14 @@ } }, "forever-agent": { - "version": "0.6.1" + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==" }, "form-data": { "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -6472,10 +1894,14 @@ }, "fromentries": { "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", "dev": true }, "fs-extra": { "version": "10.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", + "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", "requires": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -6484,54 +1910,78 @@ "dependencies": { "jsonfile": { "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "requires": { "graceful-fs": "^4.1.6", "universalify": "^2.0.0" } }, "universalify": { - "version": "2.0.0" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" } } }, "fs.realpath": { - "version": "1.0.0" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "fsevents": { "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "optional": true }, "functional-red-black-tree": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", "dev": true }, "gensync": { "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true }, "get-caller-file": { - "version": "2.0.5" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-func-name": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", "dev": true }, "get-package-type": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, "get-port": { "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", "dev": true }, "getpass": { "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", "requires": { "assert-plus": "^1.0.0" } }, "glob": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -6543,16 +1993,22 @@ }, "glob-option-error": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/glob-option-error/-/glob-option-error-1.0.0.tgz", + "integrity": "sha512-AD7lbWbwF2Ii9gBQsQIOEzwuqP/jsnyvK27/3JDq1kn/JyfDtYI6AWz3ZQwcPuQdHSBcFh+A2yT/SEep27LOGg==", "dev": true }, "glob-parent": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "requires": { "is-glob": "^4.0.1" } }, "globals": { "version": "13.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", + "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -6560,6 +2016,8 @@ }, "globby": { "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", "dev": true, "requires": { "array-union": "^2.1.0", @@ -6572,32 +2030,46 @@ "dependencies": { "ignore": { "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", "dev": true } } }, "graceful-fs": { - "version": "4.2.8" + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" }, "growl": { "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, "har-schema": { - "version": "2.0.0" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==" }, "har-validator": { "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "requires": { "ajv": "^6.12.3", "har-schema": "^2.0.0" } }, "has-flag": { - "version": "4.0.0" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "hasha": { "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", "dev": true, "requires": { "is-stream": "^2.0.0", @@ -6606,20 +2078,28 @@ "dependencies": { "type-fest": { "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true } } }, "he": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, "html-escaper": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, "http-signature": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -6628,6 +2108,8 @@ }, "ignore": { "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, "ignore-by-default": { @@ -6637,10 +2119,14 @@ "dev": true }, "immediate": { - "version": "3.0.6" + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" }, "import-fresh": { "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "requires": { "parent-module": "^1.0.0", @@ -6649,14 +2135,20 @@ }, "imurmurhash": { "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true }, "indent-string": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, "indexed-filter": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/indexed-filter/-/indexed-filter-1.0.3.tgz", + "integrity": "sha512-oBIzs6EARNMzrLgVg20fK52H19WcRHBiukiiEkw9rnnI//8rinEBMLrYdwEfJ9d4K7bjV1L6nSGft6H/qzHNgQ==", "dev": true, "requires": { "append-type": "^1.0.1" @@ -6664,16 +2156,22 @@ }, "inflight": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "requires": { "once": "^1.3.0", "wrappy": "1" } }, "inherits": { - "version": "2.0.4" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "inspect-with-kind": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/inspect-with-kind/-/inspect-with-kind-1.0.5.tgz", + "integrity": "sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==", "dev": true, "requires": { "kind-of": "^6.0.2" @@ -6681,60 +2179,90 @@ }, "is-binary-path": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "requires": { "binary-extensions": "^2.0.0" } }, "is-extglob": { - "version": "2.1.1" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" }, "is-fullwidth-code-point": { - "version": "3.0.0" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-glob": { "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "requires": { "is-extglob": "^2.1.1" } }, "is-number": { - "version": "7.0.0" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-plain-obj": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true }, "is-stream": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, "is-typedarray": { - "version": "1.0.0" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, "is-unicode-supported": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true }, "is-windows": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, "isarray": { - "version": "1.0.0" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "isexe": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, "isstream": { - "version": "0.1.2" + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" }, "istanbul-lib-coverage": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", "dev": true }, "istanbul-lib-hook": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", "dev": true, "requires": { "append-transform": "^2.0.0" @@ -6742,6 +2270,8 @@ }, "istanbul-lib-instrument": { "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", "dev": true, "requires": { "@babel/core": "^7.7.5", @@ -6752,12 +2282,16 @@ "dependencies": { "semver": { "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } }, "istanbul-lib-processinfo": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", + "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", "dev": true, "requires": { "archy": "^1.0.0", @@ -6771,6 +2305,8 @@ }, "istanbul-lib-report": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", "dev": true, "requires": { "istanbul-lib-coverage": "^3.0.0", @@ -6780,6 +2316,8 @@ }, "istanbul-lib-source-maps": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, "requires": { "debug": "^4.1.1", @@ -6789,12 +2327,16 @@ "dependencies": { "source-map": { "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } }, "istanbul-reports": { "version": "3.0.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.5.tgz", + "integrity": "sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ==", "dev": true, "requires": { "html-escaper": "^2.0.0", @@ -6803,10 +2345,14 @@ }, "js-tokens": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, "js-yaml": { "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -6814,24 +2360,36 @@ } }, "jsbn": { - "version": "0.1.1" + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" }, "jsesc": { "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, "json-schema": { - "version": "0.4.0" + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, "json-schema-traverse": { - "version": "0.4.1" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, "json-stringify-safe": { - "version": "5.0.1" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" }, "json5": { "version": "2.2.3", @@ -6840,16 +2398,22 @@ "dev": true }, "jsonc-parser": { - "version": "2.3.1" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", + "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==" }, "jsonfile": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "requires": { "graceful-fs": "^4.1.6" } }, "jsprim": { "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -6870,18 +2434,26 @@ }, "just-extend": { "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "dev": true }, "kind-of": { "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, "lcov-parse": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", + "integrity": "sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ==", "dev": true }, "levn": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "requires": { "prelude-ls": "^1.2.1", @@ -6890,12 +2462,16 @@ }, "lie": { "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", "requires": { "immediate": "~3.0.5" } }, "locate-path": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { "p-locate": "^4.1.0" @@ -6909,22 +2485,32 @@ }, "lodash.flattendeep": { "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", "dev": true }, "lodash.get": { "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "dev": true }, "lodash.merge": { "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, "log-driver": { "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", "dev": true }, "log-symbols": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "requires": { "chalk": "^4.1.0", @@ -6932,10 +2518,14 @@ } }, "long": { - "version": "3.2.0" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", + "integrity": "sha512-ZYvPPOMqUwPoDsbJaR10iQJYnMuZhRTvHYl62ErLIEX7RgFlziSBUUvrt3OVfc47QlHHpzPZYP17g3Fv7oeJkg==" }, "lru-cache": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "requires": { "yallist": "^4.0.0" } @@ -6947,6 +2537,8 @@ }, "make-dir": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "requires": { "semver": "^6.0.0" @@ -6954,29 +2546,41 @@ "dependencies": { "semver": { "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } }, "make-error": { "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, "merge2": { - "version": "1.4.1" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" }, "micromatch": { "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", "requires": { "braces": "^3.0.1", "picomatch": "^2.2.3" } }, "mime-db": { - "version": "1.50.0" + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", + "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==" }, "mime-types": { "version": "2.1.33", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", + "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", "requires": { "mime-db": "1.50.0" } @@ -6996,7 +2600,9 @@ "dev": true }, "mkdirp": { - "version": "1.0.4" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" }, "mocha": { "version": "9.2.2", @@ -7032,10 +2638,14 @@ "dependencies": { "argparse": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, "cliui": { "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { "string-width": "^4.2.0", @@ -7045,10 +2655,14 @@ }, "escape-string-regexp": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, "find-up": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "requires": { "locate-path": "^6.0.0", @@ -7057,6 +2671,8 @@ }, "js-yaml": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "requires": { "argparse": "^2.0.1" @@ -7064,6 +2680,8 @@ }, "locate-path": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "requires": { "p-locate": "^5.0.0" @@ -7080,10 +2698,14 @@ }, "ms": { "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "p-limit": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "requires": { "yocto-queue": "^0.1.0" @@ -7091,6 +2713,8 @@ }, "p-locate": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "requires": { "p-limit": "^3.0.2" @@ -7098,6 +2722,8 @@ }, "supports-color": { "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -7105,6 +2731,8 @@ }, "wrap-ansi": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { "ansi-styles": "^4.0.0", @@ -7114,10 +2742,14 @@ }, "y18n": { "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true }, "yargs": { "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { "cliui": "^7.0.2", @@ -7131,6 +2763,8 @@ }, "yargs-parser": { "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", "dev": true } } @@ -7142,6 +2776,8 @@ }, "ms": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "nanoid": { @@ -7152,13 +2788,19 @@ }, "natural-compare": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, "natural-orderby": { - "version": "2.0.3" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/natural-orderby/-/natural-orderby-2.0.3.tgz", + "integrity": "sha512-p7KTHxU0CUrcOXe62Zfrb5Z13nLvPhSWR/so3kFulUQU0sgUll2Z0LwpsLN351eOOD+hRGu/F1g+6xDfPeD++Q==" }, "nise": { "version": "5.1.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.0.tgz", + "integrity": "sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ==", "dev": true, "requires": { "@sinonjs/commons": "^1.7.0", @@ -7170,6 +2812,8 @@ }, "node-preload": { "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", "dev": true, "requires": { "process-on-spawn": "^1.0.0" @@ -7177,6 +2821,8 @@ }, "node-releases": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", + "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", "dev": true }, "nodemon": { @@ -7239,10 +2885,14 @@ } }, "normalize-path": { - "version": "3.0.0" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "nyc": { "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", "dev": true, "requires": { "@istanbuljs/load-nyc-config": "^1.0.0", @@ -7276,21 +2926,29 @@ "dependencies": { "resolve-from": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true } } }, "oauth-sign": { - "version": "0.9.0" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "once": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "requires": { "wrappy": "1" } }, "optionator": { "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "dev": true, "requires": { "deep-is": "^0.1.3", @@ -7303,10 +2961,14 @@ }, "p-defer": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-4.0.0.tgz", + "integrity": "sha512-Vb3QRvQ0Y5XnF40ZUWW7JfLogicVh/EnA5gBIvKDJoYpeI82+1E3AlB9yOcKFS0AhHrWVnAQO39fbR0G99IVEQ==", "dev": true }, "p-limit": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -7314,6 +2976,8 @@ }, "p-locate": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { "p-limit": "^2.2.0" @@ -7321,16 +2985,22 @@ }, "p-map": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", "dev": true, "requires": { "aggregate-error": "^3.0.0" } }, "p-reflect": { - "version": "1.0.0" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-reflect/-/p-reflect-1.0.0.tgz", + "integrity": "sha512-rlngKS+EX3nvI7xIzA0xKNVEAguWdIqAZVbn02z1m73ehXBdX66aTdD0bCvIu0cDwbU3TK9w3RYrppKpO3EnKQ==" }, "p-settle": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-settle/-/p-settle-2.1.0.tgz", + "integrity": "sha512-NHFIUYc+fQTFRrzzAugq0l1drwi57PB522smetcY8C/EoTYs6cU/fC6TJj0N3rq5NhhJJbhf0VGWziL3jZDnjA==", "requires": { "p-limit": "^1.2.0", "p-reflect": "^1.0.0" @@ -7338,21 +3008,29 @@ "dependencies": { "p-limit": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "requires": { "p-try": "^1.0.0" } }, "p-try": { - "version": "1.0.0" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==" } } }, "p-try": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, "package-hash": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", "dev": true, "requires": { "graceful-fs": "^4.1.15", @@ -7362,31 +3040,45 @@ } }, "pako": { - "version": "1.0.11" + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, "parent-module": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "requires": { "callsites": "^3.0.0" } }, "parse-ms": { - "version": "2.1.0" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==" }, "path-exists": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, "path-is-absolute": { - "version": "1.0.1" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" }, "path-key": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, "path-to-regexp": { "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", "dev": true, "requires": { "isarray": "0.0.1" @@ -7394,27 +3086,39 @@ "dependencies": { "isarray": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", "dev": true } } }, "path-type": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, "pathval": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, "performance-now": { - "version": "2.1.0" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "picocolors": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "dev": true }, "picomatch": { - "version": "2.3.0" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" }, "portfinder": { "version": "1.0.32", @@ -7449,13 +3153,19 @@ }, "prelude-ls": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, "process-nextick-args": { - "version": "2.0.1" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "process-on-spawn": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", "dev": true, "requires": { "fromentries": "^1.2.0" @@ -7463,10 +3173,14 @@ }, "progress": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, "psl": { - "version": "1.8.0" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, "pstree.remy": { "version": "1.1.8", @@ -7475,10 +3189,14 @@ "dev": true }, "punycode": { - "version": "2.1.1" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "q": { - "version": "1.5.1" + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==" }, "qs": { "version": "6.5.3", @@ -7486,10 +3204,14 @@ "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" }, "queue-microtask": { - "version": "1.2.3" + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" }, "randombytes": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, "requires": { "safe-buffer": "^5.1.0" @@ -7497,6 +3219,8 @@ }, "readable-stream": { "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -7516,14 +3240,20 @@ } }, "regexp-to-ast": { - "version": "0.5.0" + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==" }, "regexpp": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, "release-zalgo": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", "dev": true, "requires": { "es6-error": "^4.0.1" @@ -7531,6 +3261,8 @@ }, "replace-in-file": { "version": "6.3.2", + "resolved": "https://registry.npmjs.org/replace-in-file/-/replace-in-file-6.3.2.tgz", + "integrity": "sha512-Dbt5pXKvFVPL3WAaEB3ZX+95yP0CeAtIPJDwYzHbPP5EAHn+0UoegH/Wg3HKflU9dYBH8UnBC2NvY3P+9EZtTg==", "requires": { "chalk": "^4.1.2", "glob": "^7.2.0", @@ -7539,6 +3271,8 @@ "dependencies": { "cliui": { "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -7547,6 +3281,8 @@ }, "wrap-ansi": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -7554,10 +3290,14 @@ } }, "y18n": { - "version": "5.0.8" + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" }, "yargs": { "version": "17.2.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.2.1.tgz", + "integrity": "sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q==", "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -7569,15 +3309,21 @@ } }, "yargs-parser": { - "version": "20.2.9" + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" } } }, "replace-last": { - "version": "1.2.6" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/replace-last/-/replace-last-1.2.6.tgz", + "integrity": "sha512-Cj+MK38VtNu1S5J73mEZY3ciQb9dJajNq1Q8inP4dn/MhJMjHwoAF3Z3FjspwAEV9pfABl565MQucmrjOkty4g==" }, "request": { "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -7602,10 +3348,14 @@ } }, "require-directory": { - "version": "2.1.1" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" }, "require-main-filename": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, "require-relative": { @@ -7615,13 +3365,19 @@ }, "resolve-from": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, "reusify": { - "version": "1.0.4" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" }, "rimraf": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { "glob": "^7.1.3" @@ -7629,6 +3385,8 @@ }, "rmfr": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/rmfr/-/rmfr-2.0.0.tgz", + "integrity": "sha512-nQptLCZeyyJfgbpf2x97k5YE8vzDn7bhwx9NlvODdhgbU0mL1ruh71X0HYdRaOEvWC7Cr+SfV0p5p+Ib5yOl7A==", "dev": true, "requires": { "assert-valid-glob-opts": "^1.0.0", @@ -7640,6 +3398,8 @@ "dependencies": { "rimraf": { "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { "glob": "^7.1.3" @@ -7670,12 +3430,16 @@ "dependencies": { "ansi-styles": { "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "requires": { "color-convert": "^1.9.0" } }, "chalk": { "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -7684,18 +3448,26 @@ }, "color-convert": { "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "requires": { "color-name": "1.1.3" } }, "color-name": { - "version": "1.1.3" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "dateformat": { - "version": "3.0.3" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" }, "fs-extra": { "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "requires": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -7703,10 +3475,14 @@ } }, "has-flag": { - "version": "3.0.0" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" }, "supports-color": { "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "requires": { "has-flag": "^3.0.0" } @@ -7715,12 +3491,16 @@ }, "run-parallel": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "requires": { "queue-microtask": "^1.2.2" } }, "rxjs": { "version": "7.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz", + "integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==", "dev": true, "requires": { "tslib": "~2.1.0" @@ -7728,36 +3508,52 @@ "dependencies": { "tslib": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", "dev": true } } }, "safe-buffer": { - "version": "5.1.2" + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-json-stringify": { - "version": "1.2.0" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", + "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==" }, "safer-buffer": { - "version": "2.1.2" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sax": { - "version": "1.2.4" + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "semver": { "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "requires": { "lru-cache": "^6.0.0" } }, "serialize-error": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", + "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", "requires": { "type-fest": "^0.20.2" } }, "serialize-javascript": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", "dev": true, "requires": { "randombytes": "^2.1.0" @@ -7765,6 +3561,8 @@ }, "set-blocking": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, "setimmediate": { @@ -7774,6 +3572,8 @@ }, "shebang-command": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { "shebang-regex": "^3.0.0" @@ -7781,10 +3581,14 @@ }, "shebang-regex": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, "signal-exit": { "version": "3.0.5", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", + "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==", "dev": true }, "simple-update-notifier": { @@ -7806,6 +3610,8 @@ }, "sinon": { "version": "11.1.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-11.1.2.tgz", + "integrity": "sha512-59237HChms4kg7/sXhiRcUzdSkKuydDeTiamT/jesUVHshBgL8XAmhgFo0GfK6RruMDM/iRSij1EybmMog9cJw==", "dev": true, "requires": { "@sinonjs/commons": "^1.8.3", @@ -7818,10 +3624,14 @@ }, "slash": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, "smart-buffer": { - "version": "4.2.0" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" }, "source-map": { "version": "0.7.4", @@ -7830,6 +3640,8 @@ }, "source-map-support": { "version": "0.5.20", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", + "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -7838,12 +3650,16 @@ "dependencies": { "source-map": { "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } }, "spawn-wrap": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", "dev": true, "requires": { "foreground-child": "^2.0.0", @@ -7856,10 +3672,14 @@ }, "sprintf-js": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, "sshpk": { "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -7872,38 +3692,50 @@ "tweetnacl": "~0.14.0" } }, - "string_decoder": { - "version": "1.1.1", - "requires": { - "safe-buffer": "~5.1.0" - } - }, "string-width": { "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, "strip-ansi": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "requires": { "ansi-regex": "^5.0.1" } }, "strip-json-comments": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, "supports-color": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "requires": { "has-flag": "^4.0.0" } }, "telnet-client": { "version": "1.4.9", + "resolved": "https://registry.npmjs.org/telnet-client/-/telnet-client-1.4.9.tgz", + "integrity": "sha512-ryF0E3mg6am1EnCQZj7OoBnueS3l8IT7lDyDyFR8FdIshRRKBpbKjX7AUnt1ImVd43WKl/AxYE5MTkX3LjhGaQ==", "requires": { "bluebird": "^3.5.4" } @@ -7915,6 +3747,8 @@ }, "test-exclude": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, "requires": { "@istanbuljs/schema": "^0.1.2", @@ -7924,14 +3758,20 @@ }, "text-table": { "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, "to-fast-properties": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true }, "to-regex-range": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "requires": { "is-number": "^7.0.0" } @@ -7947,16 +3787,22 @@ }, "tough-cookie": { "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "requires": { "psl": "^1.1.28", "punycode": "^2.1.1" } }, "traverse-chain": { - "version": "0.1.0" + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz", + "integrity": "sha512-up6Yvai4PYKhpNp5PkYtx50m3KbwQrqDwbuZP/ItyL64YEWHAvH6Md83LFLV/GRSk/BoUVwwgUzX6SOQSbsfAg==" }, "ts-node": { "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", + "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", "dev": true, "requires": { "@cspotcode/source-map-support": "0.7.0", @@ -7975,20 +3821,28 @@ "dependencies": { "diff": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, "yn": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true } } }, "tslib": { "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, "tsutils": { "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, "requires": { "tslib": "^1.8.1" @@ -7996,15 +3850,21 @@ }, "tunnel-agent": { "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "requires": { "safe-buffer": "^5.0.1" } }, "tweetnacl": { - "version": "0.14.5" + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" }, "type-check": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "requires": { "prelude-ls": "^1.2.1" @@ -8012,13 +3872,19 @@ }, "type-detect": { "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, "type-fest": { - "version": "0.20.2" + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" }, "typedarray-to-buffer": { "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", "dev": true, "requires": { "is-typedarray": "^1.0.0" @@ -8026,6 +3892,8 @@ }, "typescript": { "version": "4.4.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", + "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", "dev": true }, "undefsafe": { @@ -8035,26 +3903,38 @@ "dev": true }, "universalify": { - "version": "0.1.2" + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" }, "uri-js": { "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "requires": { "punycode": "^2.1.0" } }, "util-deprecate": { - "version": "1.0.2" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "uuid": { - "version": "3.4.0" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, "v8-compile-cache": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, "validate-glob-opts": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/validate-glob-opts/-/validate-glob-opts-1.0.2.tgz", + "integrity": "sha512-3PKjRQq/R514lUcG9OEiW0u9f7D4fP09A07kmk1JbNn2tfeQdAHhlT+A4dqERXKu2br2rrxSM3FzagaEeq9w+A==", "dev": true, "requires": { "array-to-sentence": "^1.1.0", @@ -8065,12 +3945,16 @@ "dependencies": { "is-plain-obj": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", "dev": true } } }, "verror": { "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -8078,47 +3962,67 @@ }, "dependencies": { "core-util-is": { - "version": "1.0.2" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" } } }, "vscode-debugadapter": { "version": "1.49.0", + "resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.49.0.tgz", + "integrity": "sha512-nhes9zaLanFcHuchytOXGsLTGpU5qkz10mC9gVchiwNuX2Bljmc6+wsNbCyE5dOxu6F0pn3f+LEJQGMU1kcnvQ==", "requires": { "mkdirp": "^1.0.4", "vscode-debugprotocol": "1.49.0" } }, "vscode-debugprotocol": { - "version": "1.49.0" + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.49.0.tgz", + "integrity": "sha512-3VkK3BmaqN+BGIq4lavWp9a2IC6VYgkWkkMQm6Sa5ACkhBF6ThJDrkP+/3rFE4G7F8+mM3f4bhhJhhMax2IPfg==" }, "vscode-jsonrpc": { - "version": "6.0.0" + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz", + "integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==" }, "vscode-languageserver": { "version": "6.1.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-6.1.1.tgz", + "integrity": "sha512-DueEpkUAkD5XTR4MLYNr6bQIp/UFR0/IPApgXU3YfCBCB08u2sm9hRCs6DxYZELkk++STPjpcjksR2H8qI3cDQ==", "requires": { "vscode-languageserver-protocol": "^3.15.3" } }, "vscode-languageserver-protocol": { "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz", + "integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==", "requires": { "vscode-jsonrpc": "6.0.0", "vscode-languageserver-types": "3.16.0" } }, "vscode-languageserver-textdocument": { - "version": "1.0.2" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.2.tgz", + "integrity": "sha512-T7uPC18+f8mYE4lbVZwb3OSmvwTZm3cuFhrdx9Bn2l11lmp3SvSuSVjy2JtvrghzjAo4G6Trqny2m9XGnFnWVA==" }, "vscode-languageserver-types": { - "version": "3.16.0" + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", + "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==" }, "vscode-uri": { - "version": "2.1.2" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.2.tgz", + "integrity": "sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==" }, "which": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { "isexe": "^2.0.0" @@ -8126,10 +4030,14 @@ }, "which-module": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", "dev": true }, "word-wrap": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, "workerpool": { @@ -8140,6 +4048,8 @@ }, "wrap-ansi": { "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "requires": { "ansi-styles": "^4.0.0", @@ -8148,10 +4058,14 @@ } }, "wrappy": { - "version": "1.0.2" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "write-file-atomic": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "dev": true, "requires": { "imurmurhash": "^0.1.4", @@ -8162,23 +4076,33 @@ }, "xml2js": { "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", "requires": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "xmlbuilder": { - "version": "11.0.1" + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" }, "y18n": { "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "dev": true }, "yallist": { - "version": "4.0.0" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yargs": { "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "dev": true, "requires": { "cliui": "^6.0.0", @@ -8196,6 +4120,8 @@ }, "yargs-parser": { "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -8204,6 +4130,8 @@ }, "yargs-unparser": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, "requires": { "camelcase": "^6.0.0", @@ -8214,16 +4142,22 @@ "dependencies": { "camelcase": { "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", "dev": true }, "decamelize": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true } } }, "yocto-queue": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true } } diff --git a/package.json b/package.json index ee6edf1b..77c6420a 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "watchFiles": [ "src/**/*" ], - "timeout": "10000", + "timeout": "2000", "fullTrace": true, "watchExtensions": [ "ts" diff --git a/src/debugProtocol/server/DebugProtocolServer.ts b/src/debugProtocol/server/DebugProtocolServer.ts index cb8f6e45..b4420df3 100644 --- a/src/debugProtocol/server/DebugProtocolServer.ts +++ b/src/debugProtocol/server/DebugProtocolServer.ts @@ -129,11 +129,7 @@ export class DebugProtocolServer { public async stop() { //close the client socket - await new Promise((resolve) => { - this.client.end(() => { - resolve(); - }); - }).catch(() => { }); + this.client.destroy(); //now close the server return new Promise((resolve, reject) => { From 4780cfb79e8d8ac7f417746cd09480da77eadd7f Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Thu, 16 Feb 2023 09:18:13 -0500 Subject: [PATCH 47/74] Better socket teardowns --- .../client/DebugProtocolClient.spec.ts | 6 ++-- .../server/DebugProtocolServer.ts | 30 +++++++++++++------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index 46bb34bd..74743542 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -87,7 +87,7 @@ describe('DebugProtocolClient', () => { } catch (e) { } //shut down and destroy the server after each test try { - await server?.stop(); + await server?.destroy(); } catch (e) { } await util.sleep(10); }); @@ -763,9 +763,9 @@ describe('DebugProtocolClient', () => { hostName: '0.0.0.0' }, () => { }); ioServer.on('connection', (socket) => { - socket.on('error', e => console.error(e)); - deferred.resolve(socket); ioClient = socket; + ioClient.on('error', e => console.error(e)); + deferred.resolve(ioClient); }); ioServer.on('error', e => console.error(e)); }); diff --git a/src/debugProtocol/server/DebugProtocolServer.ts b/src/debugProtocol/server/DebugProtocolServer.ts index b4420df3..b48c0473 100644 --- a/src/debugProtocol/server/DebugProtocolServer.ts +++ b/src/debugProtocol/server/DebugProtocolServer.ts @@ -129,18 +129,30 @@ export class DebugProtocolServer { public async stop() { //close the client socket - this.client.destroy(); + this.client?.destroy(); + //now close the server - return new Promise((resolve, reject) => { - this.server.close((err) => { - if (err) { - reject(err); - } else { - resolve(); - } + try { + await new Promise((resolve, reject) => { + this.server.close((err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); }); - }); + } finally { + this.client?.removeAllListeners(); + delete this.client; + this.server?.removeAllListeners(); + delete this.server; + } + } + + public async destroy() { + await this.stop(); } /** From 6547df70373b52f9f7e33e9c2d5227bc6bc8aad9 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Thu, 16 Feb 2023 12:12:39 -0500 Subject: [PATCH 48/74] fix broken breakpoint response in ProtocolClient --- .../client/DebugProtocolClient.spec.ts | 30 +++++++++++++++++++ .../client/DebugProtocolClient.ts | 1 + 2 files changed, 31 insertions(+) diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index 74743542..cad8b193 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -339,6 +339,36 @@ describe('DebugProtocolClient', () => { }); describe('addBreakpoints', () => { + it('returns the proper response', async () => { + await connect(); + + const responseBreakpoins = [{ + errorCode: 0, + id: 1, + ignoreCount: 0 + }, + { + errorCode: 0, + id: 1, + ignoreCount: 0 + }]; + plugin.pushResponse( + AddBreakpointsResponse.fromJson({ + requestId: 10, + breakpoints: responseBreakpoins + }) + ); + + const response = await client.addBreakpoints([{ + filePath: 'pkg:/source/main.brs', + lineNumber: 10 + }, { + filePath: 'pkg:/source/lib.brs', + lineNumber: 15 + }]); + expect(response.data.breakpoints).to.eql(responseBreakpoins); + }); + it('skips sending command on empty breakpoints array', async () => { await connect(); await client.addBreakpoints(undefined); diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index ceb53a6c..40aab657 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -531,6 +531,7 @@ export class DebugProtocolClient { breakpoints: response.data.breakpoints }); } + return response; } return AddBreakpointsResponse.fromBuffer(null); } From 578253b7bd50a83c046af8b920d4802c9fb589c9 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Thu, 16 Feb 2023 14:10:11 -0500 Subject: [PATCH 49/74] Better tests for breakpoint requests --- package-lock.json | 5518 ++++++++++++++++- src/adapters/DebugProtocolAdapter.spec.ts | 87 +- src/adapters/DebugProtocolAdapter.ts | 2 +- .../DebugProtocolServerTestPlugin.spec.ts | 3 +- .../client/DebugProtocolClient.spec.ts | 7 +- .../client/DebugProtocolClient.ts | 14 +- .../responses/ListBreakpointsResponse.ts | 2 +- 7 files changed, 5599 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index eedf5b87..c130c7f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,5505 @@ { "name": "roku-debug", "version": "0.18.3", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "roku-debug", + "version": "0.18.3", + "license": "MIT", + "dependencies": { + "@rokucommunity/logger": "^0.3.1", + "brighterscript": "^0.61.3", + "dateformat": "^4.6.3", + "eol": "^0.9.1", + "eventemitter3": "^4.0.7", + "find-in-files": "^0.5.0", + "fs-extra": "^10.0.0", + "natural-orderby": "^2.0.3", + "replace-in-file": "^6.3.2", + "replace-last": "^1.2.6", + "roku-deploy": "^3.9.3", + "semver": "^7.3.5", + "serialize-error": "^8.1.0", + "smart-buffer": "^4.2.0", + "source-map": "^0.7.4", + "telnet-client": "^1.4.9", + "vscode-debugadapter": "^1.49.0", + "vscode-debugprotocol": "^1.49.0", + "vscode-languageserver": "^6.1.1" + }, + "devDependencies": { + "@types/chai": "^4.2.22", + "@types/dedent": "^0.7.0", + "@types/find-in-files": "^0.5.1", + "@types/fs-extra": "^9.0.13", + "@types/glob": "^7.2.0", + "@types/mocha": "^9.0.0", + "@types/node": "^16.11.6", + "@types/request": "^2.48.7", + "@types/semver": "^7.3.9", + "@types/sinon": "^10.0.6", + "@types/vscode": "^1.61.0", + "@typescript-eslint/eslint-plugin": "^5.2.0", + "@typescript-eslint/parser": "^5.2.0", + "chai": "^4.3.4", + "coveralls": "^3.1.1", + "dedent": "^0.7.0", + "eslint": "^8.1.0", + "eslint-plugin-no-only-tests": "^2.6.0", + "get-port": "^5.1.1", + "mocha": "^9.1.3", + "nodemon": "^2.0.20", + "nyc": "^15.1.0", + "p-defer": "^4.0.0", + "portfinder": "^1.0.32", + "rimraf": "^3.0.2", + "rmfr": "^2.0.0", + "rxjs": "^7.4.0", + "sinon": "^11.1.2", + "source-map-support": "^0.5.20", + "ts-node": "^10.4.0", + "typescript": "^4.4.4" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", + "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", + "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.8.tgz", + "integrity": "sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.15.8", + "@babel/generator": "^7.15.8", + "@babel/helper-compilation-targets": "^7.15.4", + "@babel/helper-module-transforms": "^7.15.8", + "@babel/helpers": "^7.15.4", + "@babel/parser": "^7.15.8", + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.6", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/core/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.8.tgz", + "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.6", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", + "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.15.0", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.16.6", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", + "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", + "dev": true, + "dependencies": { + "@babel/helper-get-function-arity": "^7.15.4", + "@babel/template": "^7.15.4", + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-get-function-arity": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", + "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", + "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", + "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", + "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.8.tgz", + "integrity": "sha512-DfAfA6PfpG8t4S6npwzLvTUpp0sS7JrcuaMiy1Y5645laRJIp/LiLGIBbQKaXSInK8tiGNI7FL7L8UvB8gdUZg==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.15.4", + "@babel/helper-replace-supers": "^7.15.4", + "@babel/helper-simple-access": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/helper-validator-identifier": "^7.15.7", + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", + "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", + "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", + "dev": true, + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.15.4", + "@babel/helper-optimise-call-expression": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", + "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", + "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", + "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", + "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", + "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", + "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.15.4", + "@babel/helper-function-name": "^7.15.4", + "@babel/helper-hoist-variables": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.15.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", + "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.9", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.3.tgz", + "integrity": "sha512-DHI1wDPoKCBPoLZA3qDR91+3te/wDSc1YhKg3jR8NxKKRJq2hwHwcWv31cSwSYvIBrmbENoYMWcenW8uproQqg==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.0.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.6.0.tgz", + "integrity": "sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", + "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rokucommunity/bslib": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@rokucommunity/bslib/-/bslib-0.1.1.tgz", + "integrity": "sha512-2ox6EUL+UTtccTbD4dbVjZK3QHa0PHCqpoKMF8lZz9ayzzEP3iVPF8KZR6hOi6bxsIcbGXVjqmtCVkpC4P9SrA==" + }, + "node_modules/@rokucommunity/logger": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@rokucommunity/logger/-/logger-0.3.1.tgz", + "integrity": "sha512-jTNRur/P2cnQgfulNsZRstXkpmQ12ex2HA4AneGQXnDlGcOeHQ21ia8Ka1KJA1IlLbiEUK2/fngFdZMfI11Zjw==", + "dependencies": { + "chalk": "^4.1.2", + "fs-extra": "^10.0.0", + "safe-json-stringify": "^1.2.0", + "serialize-error": "^8.1.0" + } + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.0.2.tgz", + "integrity": "sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "node_modules/@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", + "dev": true + }, + "node_modules/@types/chai": { + "version": "4.2.22", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.22.tgz", + "integrity": "sha512-tFfcE+DSTzWAgifkjik9AySNqIyNoYwmR+uecPwwD/XRNfvOjmC/FjCxpiUGDkDVDphPfCUecSQVFw+lN3M3kQ==", + "dev": true + }, + "node_modules/@types/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@types/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-EGlKlgMhnLt/cM4DbUSafFdrkeJoC9Mvnj0PUCU7tFmTjMjNRT957kXCx0wYm3JuEq4o4ZsS5vG+NlkM2DMd2A==", + "dev": true + }, + "node_modules/@types/find-in-files": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@types/find-in-files/-/find-in-files-0.5.1.tgz", + "integrity": "sha512-kUPtvVXZn99bBHx08jAJgrI1NKWspuoX6RgqQgfNlH2debcwcowUV41P6Kfg4VDaCAr5KNBW9qdjIyKRnXVuBA==", + "dev": true + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "node_modules/@types/mocha": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.0.0.tgz", + "integrity": "sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "16.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz", + "integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==", + "dev": true + }, + "node_modules/@types/request": { + "version": "2.48.7", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.7.tgz", + "integrity": "sha512-GWP9AZW7foLd4YQxyFZDBepl0lPsWLMEXDZUjQ/c1gqVPDPECrRZyEzuhJdnPWioFCq3Tv0qoGpMD6U+ygd4ZA==", + "dev": true, + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "node_modules/@types/request/node_modules/form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/@types/semver": { + "version": "7.3.9", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", + "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==", + "dev": true + }, + "node_modules/@types/sinon": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.6.tgz", + "integrity": "sha512-6EF+wzMWvBNeGrfP3Nx60hhx+FfwSg1JJBLAAP/IdIUq0EYkqCYf70VT3PhuhPX9eLD+Dp+lNdpb/ZeHG8Yezg==", + "dev": true, + "dependencies": { + "@sinonjs/fake-timers": "^7.1.0" + } + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz", + "integrity": "sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==", + "dev": true + }, + "node_modules/@types/vscode": { + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.61.0.tgz", + "integrity": "sha512-9k5Nwq45hkRwdfCFY+eKXeQQSbPoA114mF7U/4uJXRBJeGIO7MuJdhF1PnaDN+lllL9iKGQtd6FFXShBXMNaFg==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.2.0.tgz", + "integrity": "sha512-qQwg7sqYkBF4CIQSyRQyqsYvP+g/J0To9ZPVNJpfxfekl5RmdvQnFFTVVwpRtaUDFNvjfe/34TgY/dpc3MgNTw==", + "dev": true, + "dependencies": { + "@typescript-eslint/experimental-utils": "5.2.0", + "@typescript-eslint/scope-manager": "5.2.0", + "debug": "^4.3.2", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.1.8", + "regexpp": "^3.2.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.2.0.tgz", + "integrity": "sha512-fWyT3Agf7n7HuZZRpvUYdFYbPk3iDCq6fgu3ulia4c7yxmPnwVBovdSOX7RL+k8u6hLbrXcdAehlWUVpGh6IEw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.2.0", + "@typescript-eslint/types": "5.2.0", + "@typescript-eslint/typescript-estree": "5.2.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.2.0.tgz", + "integrity": "sha512-Uyy4TjJBlh3NuA8/4yIQptyJb95Qz5PX//6p8n7zG0QnN4o3NF9Je3JHbVU7fxf5ncSXTmnvMtd/LDQWDk0YqA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.2.0", + "@typescript-eslint/types": "5.2.0", + "@typescript-eslint/typescript-estree": "5.2.0", + "debug": "^4.3.2" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.2.0.tgz", + "integrity": "sha512-RW+wowZqPzQw8MUFltfKYZfKXqA2qgyi6oi/31J1zfXJRpOn6tCaZtd9b5u9ubnDG2n/EMvQLeZrsLNPpaUiFQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.2.0", + "@typescript-eslint/visitor-keys": "5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.2.0.tgz", + "integrity": "sha512-cTk6x08qqosps6sPyP2j7NxyFPlCNsJwSDasqPNjEQ8JMD5xxj2NHxcLin5AJQ8pAVwpQ8BMI3bTxR0zxmK9qQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.2.0.tgz", + "integrity": "sha512-RsdXq2XmVgKbm9nLsE3mjNUM7BTr/K4DYR9WfFVMUuozHWtH5gMpiNZmtrMG8GR385EOSQ3kC9HiEMJWimxd/g==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.2.0", + "@typescript-eslint/visitor-keys": "5.2.0", + "debug": "^4.3.2", + "globby": "^11.0.4", + "is-glob": "^4.0.3", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.2.0.tgz", + "integrity": "sha512-Nk7HizaXWWCUBfLA/rPNKMzXzWS8Wg9qHMuGtT+v2/YpPij4nVXrVJc24N/r5WrrmqK31jCrZxeHqIgqRzs0Xg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.2.0", + "eslint-visitor-keys": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz", + "integrity": "sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "node_modules/@xml-tools/parser": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@xml-tools/parser/-/parser-1.0.11.tgz", + "integrity": "sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==", + "dependencies": { + "chevrotain": "7.1.1" + } + }, + "node_modules/@xml-tools/parser/node_modules/chevrotain": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-7.1.1.tgz", + "integrity": "sha512-wy3mC1x4ye+O+QkEinVJkPf5u2vsrDIYW9G7ZuwFl6v/Yu0LwUuT2POsb+NUWApebyxfkQq6+yDfRExbnI5rcw==", + "dependencies": { + "regexp-to-ast": "0.5.0" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", + "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/append-type": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-type/-/append-type-1.0.2.tgz", + "integrity": "sha512-hac740vT/SAbrFBLgLIWZqVT5PUAcGTWS5UkDDhr+OCizZSw90WKw6sWAEgGaYd2viIblggypMXwpjzHXOvAQg==", + "dev": true + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flat-polyfill": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-flat-polyfill/-/array-flat-polyfill-1.0.1.tgz", + "integrity": "sha512-hfJmKupmQN0lwi0xG6FQ5U8Rd97RnIERplymOv/qpq8AoNKPPAnxJadjFA23FNWm88wykh9HmpLJUUwUtNU/iw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/array-to-sentence": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-to-sentence/-/array-to-sentence-1.1.0.tgz", + "integrity": "sha512-YkwkMmPA2+GSGvXj1s9NZ6cc2LBtR+uSeWTy2IGi5MR1Wag4DdrcjTxA/YV/Fw+qKlBeXomneZgThEbm/wvZbw==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/assert-valid-glob-opts": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-valid-glob-opts/-/assert-valid-glob-opts-1.0.0.tgz", + "integrity": "sha512-/mttty5Xh7wE4o7ttKaUpBJl0l04xWe3y6muy1j27gyzSsnceK0AYU9owPtUoL9z8+9hnPxztmuhdFZ7jRoyWw==", + "dev": true, + "dependencies": { + "glob-option-error": "^1.0.0", + "validate-glob-opts": "^1.0.0" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brighterscript": { + "version": "0.61.3", + "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.61.3.tgz", + "integrity": "sha512-8BDpOSCdmkS/QcTdPTUW/99nCBypuoa/Zz6PZHI6OiVylqTBidtrGI7lBZotqY6yvQ3KJl24thhLHK5XuIT/6w==", + "dependencies": { + "@rokucommunity/bslib": "^0.1.1", + "@xml-tools/parser": "^1.0.7", + "array-flat-polyfill": "^1.0.1", + "chalk": "^2.4.2", + "chevrotain": "^7.0.1", + "chokidar": "^3.5.1", + "clear": "^0.1.0", + "cross-platform-clear-console": "^2.3.0", + "debounce-promise": "^3.1.0", + "eventemitter3": "^4.0.0", + "fast-glob": "^3.2.11", + "file-url": "^3.0.0", + "fs-extra": "^8.0.0", + "jsonc-parser": "^2.3.0", + "long": "^3.2.0", + "luxon": "^2.5.2", + "minimatch": "^3.0.4", + "moment": "^2.23.0", + "p-settle": "^2.1.0", + "parse-ms": "^2.1.0", + "require-relative": "^0.8.7", + "roku-deploy": "^3.9.3", + "serialize-error": "^7.0.1", + "source-map": "^0.7.4", + "vscode-languageserver": "7.0.0", + "vscode-languageserver-protocol": "3.16.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-uri": "^2.1.1", + "xml2js": "^0.4.19", + "yargs": "^16.2.0" + }, + "bin": { + "bsc": "dist/cli.js" + } + }, + "node_modules/brighterscript/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/brighterscript/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/brighterscript/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/brighterscript/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/brighterscript/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/brighterscript/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/brighterscript/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/brighterscript/node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brighterscript/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/brighterscript/node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brighterscript/node_modules/vscode-languageserver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz", + "integrity": "sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==", + "dependencies": { + "vscode-languageserver-protocol": "3.16.0" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/brighterscript/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/brighterscript/node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/brighterscript/node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/brighterscript/node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/brighterscript/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/brighterscript/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/brighterscript/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.5.tgz", + "integrity": "sha512-I3ekeB92mmpctWBoLXe0d5wPS2cBuRvvW0JyyJHMrk9/HmP2ZjrTboNAZ8iuGqaEIlKguljbQY32OkOJIRrgoA==", + "dev": true, + "dependencies": { + "caniuse-lite": "^1.0.30001271", + "electron-to-chromium": "^1.3.878", + "escalade": "^3.1.1", + "node-releases": "^2.0.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001436", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001436.tgz", + "integrity": "sha512-ZmWkKsnC2ifEPoWUvSAIGyOYwT+keAaaWPHiQ9DfMqS1t6tfuyFYoWR78TeZtznkEQ64+vGXH9cZrElwR2Mrxg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, + "node_modules/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/chevrotain": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-7.1.2.tgz", + "integrity": "sha512-9bQsXVQ7UAvzMs7iUBBJ9Yv//exOy7bIR3PByOEk4M64vIE/LsiOiX7VIkMF/vEMlrSStwsaE884Bp9CpjtC5g==", + "dependencies": { + "regexp-to-ast": "0.5.0" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/clear": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/clear/-/clear-0.1.0.tgz", + "integrity": "sha512-qMjRnoL+JDPJHeLePZJuao6+8orzHMGP04A8CdwCNsKhRbOnKRjefxONR7bwILT3MHecxKBjHkKL/tkZ8r4Uzw==", + "engines": { + "node": "*" + } + }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/coveralls": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", + "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", + "dev": true, + "dependencies": { + "js-yaml": "^3.13.1", + "lcov-parse": "^1.0.0", + "log-driver": "^1.2.7", + "minimist": "^1.2.5", + "request": "^2.88.2" + }, + "bin": { + "coveralls": "bin/coveralls.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-platform-clear-console": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cross-platform-clear-console/-/cross-platform-clear-console-2.3.0.tgz", + "integrity": "sha512-To+sJ6plHHC6k5DfdvSVn6F1GRGJh/R6p76bCpLbyMyHEmbqFyuMAeGwDcz/nGDWH3HUcjFTTX9iUSCzCg9Eiw==" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "engines": { + "node": "*" + } + }, + "node_modules/dayjs": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.2.tgz", + "integrity": "sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw==" + }, + "node_modules/debounce-promise": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/debounce-promise/-/debounce-promise-3.1.2.tgz", + "integrity": "sha512-rZHcgBkbYavBeD9ej6sP56XfG53d51CD4dnaw989YX/nZ/ZJfgRx/9ePKmTNiUiyQvh4mtrMoS3OAWW+yoYtpg==" + }, + "node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "node_modules/deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "dev": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/default-require-extensions/node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.3.880", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.880.tgz", + "integrity": "sha512-iwIP/6WoeSimzUKJIQtjtpVDsK8Ir8qQCMXsUBwg+rxJR2Uh3wTNSbxoYRfs+3UWx/9MAnPIxVZCyWkm8MT0uw==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/eol": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/eol/-/eol-0.9.1.tgz", + "integrity": "sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==" + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.1.0.tgz", + "integrity": "sha512-JZvNneArGSUsluHWJ8g8MMs3CfIEzwaLx9KyH4tZ2i+R2/rPWzL8c0zg3rHdwYVpN/1sB9gqnjHwz9HoeJpGHw==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.0.3", + "@humanwhocodes/config-array": "^0.6.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^6.0.0", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.0.0", + "espree": "^9.0.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.2.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-no-only-tests": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-only-tests/-/eslint-plugin-no-only-tests-2.6.0.tgz", + "integrity": "sha512-T9SmE/g6UV1uZo1oHAqOvL86XWl7Pl2EpRpnLI8g/bkJu+h7XBCB+1LnubRZ2CUQXj805vh4/CYZdnqtVaEo2Q==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-6.0.0.tgz", + "integrity": "sha512-uRDL9MWmQCkaFus8RF5K9/L/2fn+80yoW3jkD53l4shjCh26fCtvJGasxjUqP5OT87SYTxCVA3BwTUzuELx9kA==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz", + "integrity": "sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/espree": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.0.0.tgz", + "integrity": "sha512-r5EQJcYZ2oaGbeR0jR0fFVijGOcwai07/690YRXLINuhmVeRY4UKSAsQPe/0BNuDgwP7Ophoc1PRsr2E3tkbdQ==", + "dev": true, + "dependencies": { + "acorn": "^8.5.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz", + "integrity": "sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-url": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/file-url/-/file-url-3.0.0.tgz", + "integrity": "sha512-g872QGsHexznxkIAdK8UiZRe7SkE6kvylShU4Nsj8NvfvZag7S0QuQ4IgvPDkk75HxgjIVDwycFTDAgIiO4nDA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/find/-/find-0.1.7.tgz", + "integrity": "sha512-jPrupTOe/pO//3a9Ty2o4NqQCp0L46UG+swUnfFtdmtQVN8pEltKpAqR7Nuf6vWn0GBXx5w+R1MyZzqwjEIqdA==", + "dependencies": { + "traverse-chain": "~0.1.0" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-cache-dir/node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-in-files": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/find-in-files/-/find-in-files-0.5.0.tgz", + "integrity": "sha512-VraTc6HdtdSHmAp0yJpAy20yPttGKzyBWc7b7FPnnsX9TOgmKx0g9xajizpF/iuu4IvNK4TP0SpyBT9zAlwG+g==", + "dependencies": { + "find": "^0.1.5", + "q": "^1.0.1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", + "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", + "dev": true + }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/fs-extra": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", + "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-extra/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/fs-extra/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-option-error": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/glob-option-error/-/glob-option-error-1.0.0.tgz", + "integrity": "sha512-AD7lbWbwF2Ii9gBQsQIOEzwuqP/jsnyvK27/3JDq1kn/JyfDtYI6AWz3ZQwcPuQdHSBcFh+A2yT/SEep27LOGg==", + "dev": true + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", + "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/indexed-filter": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/indexed-filter/-/indexed-filter-1.0.3.tgz", + "integrity": "sha512-oBIzs6EARNMzrLgVg20fK52H19WcRHBiukiiEkw9rnnI//8rinEBMLrYdwEfJ9d4K7bjV1L6nSGft6H/qzHNgQ==", + "dev": true, + "dependencies": { + "append-type": "^1.0.1" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/inspect-with-kind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/inspect-with-kind/-/inspect-with-kind-1.0.5.tgz", + "integrity": "sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", + "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.0", + "istanbul-lib-coverage": "^3.0.0-alpha.1", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^3.3.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.5.tgz", + "integrity": "sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", + "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==" + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lcov-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", + "integrity": "sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ==", + "dev": true, + "bin": { + "lcov-parse": "bin/cli.js" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "dev": true, + "engines": { + "node": ">=0.8.6" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/long": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", + "integrity": "sha512-ZYvPPOMqUwPoDsbJaR10iQJYnMuZhRTvHYl62ErLIEX7RgFlziSBUUvrt3OVfc47QlHHpzPZYP17g3Fv7oeJkg==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/luxon": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-2.5.2.tgz", + "integrity": "sha512-Yg7/RDp4nedqmLgyH0LwgGRvMEKVzKbUdkBYyCosbHgJ+kaOUx0qzSiSatVc3DFygnirTPYnMM2P5dg2uH1WvA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", + "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.33", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", + "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", + "dependencies": { + "mime-db": "1.50.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", + "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "dev": true, + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.3", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "4.2.1", + "ms": "2.1.3", + "nanoid": "3.3.1", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "workerpool": "6.2.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/mocha/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/mocha/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", + "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mocha/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/mocha/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/mocha/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-orderby": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/natural-orderby/-/natural-orderby-2.0.3.tgz", + "integrity": "sha512-p7KTHxU0CUrcOXe62Zfrb5Z13nLvPhSWR/so3kFulUQU0sgUll2Z0LwpsLN351eOOD+hRGu/F1g+6xDfPeD++Q==", + "engines": { + "node": "*" + } + }, + "node_modules/nise": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.0.tgz", + "integrity": "sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^7.0.4", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-releases": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", + "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", + "dev": true + }, + "node_modules/nodemon": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", + "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-defer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-4.0.0.tgz", + "integrity": "sha512-Vb3QRvQ0Y5XnF40ZUWW7JfLogicVh/EnA5gBIvKDJoYpeI82+1E3AlB9yOcKFS0AhHrWVnAQO39fbR0G99IVEQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-reflect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-reflect/-/p-reflect-1.0.0.tgz", + "integrity": "sha512-rlngKS+EX3nvI7xIzA0xKNVEAguWdIqAZVbn02z1m73ehXBdX66aTdD0bCvIu0cDwbU3TK9w3RYrppKpO3EnKQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-settle": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-settle/-/p-settle-2.1.0.tgz", + "integrity": "sha512-NHFIUYc+fQTFRrzzAugq0l1drwi57PB522smetcY8C/EoTYs6cU/fC6TJj0N3rq5NhhJJbhf0VGWziL3jZDnjA==", + "dependencies": { + "p-limit": "^1.2.0", + "p-reflect": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-settle/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-settle/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/path-to-regexp/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/portfinder": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", + "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", + "dev": true, + "dependencies": { + "async": "^2.6.4", + "debug": "^3.2.7", + "mkdirp": "^0.5.6" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/portfinder/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/portfinder/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regexp-to-ast": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==" + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/replace-in-file": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/replace-in-file/-/replace-in-file-6.3.2.tgz", + "integrity": "sha512-Dbt5pXKvFVPL3WAaEB3ZX+95yP0CeAtIPJDwYzHbPP5EAHn+0UoegH/Wg3HKflU9dYBH8UnBC2NvY3P+9EZtTg==", + "dependencies": { + "chalk": "^4.1.2", + "glob": "^7.2.0", + "yargs": "^17.2.1" + }, + "bin": { + "replace-in-file": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/replace-in-file/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/replace-in-file/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/replace-in-file/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/replace-in-file/node_modules/yargs": { + "version": "17.2.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.2.1.tgz", + "integrity": "sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/replace-in-file/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, + "node_modules/replace-last": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/replace-last/-/replace-last-1.2.6.tgz", + "integrity": "sha512-Cj+MK38VtNu1S5J73mEZY3ciQb9dJajNq1Q8inP4dn/MhJMjHwoAF3Z3FjspwAEV9pfABl565MQucmrjOkty4g==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/require-relative": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", + "integrity": "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rmfr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/rmfr/-/rmfr-2.0.0.tgz", + "integrity": "sha512-nQptLCZeyyJfgbpf2x97k5YE8vzDn7bhwx9NlvODdhgbU0mL1ruh71X0HYdRaOEvWC7Cr+SfV0p5p+Ib5yOl7A==", + "dev": true, + "dependencies": { + "assert-valid-glob-opts": "^1.0.0", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "inspect-with-kind": "^1.0.4", + "rimraf": "^2.6.2" + } + }, + "node_modules/rmfr/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/roku-deploy": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.9.3.tgz", + "integrity": "sha512-cjTx5ffZNt07rQS+0s2sTBHkZKUk283y9f6UnbI77X03lQ60vYlCnqsKswWisFYMHPIdvsTLLSfKsshAPwKHEQ==", + "dependencies": { + "chalk": "^2.4.2", + "dateformat": "^3.0.3", + "dayjs": "^1.11.0", + "fast-glob": "^3.2.11", + "fs-extra": "^7.0.1", + "is-glob": "^4.0.3", + "jsonc-parser": "^2.3.0", + "jszip": "^3.6.0", + "moment": "^2.29.1", + "parse-ms": "^2.1.0", + "picomatch": "^2.2.1", + "request": "^2.88.0", + "temp-dir": "^2.0.0", + "xml2js": "^0.4.23" + }, + "bin": { + "roku-deploy": "dist/cli.js" + } + }, + "node_modules/roku-deploy/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/roku-deploy/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/roku-deploy/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/roku-deploy/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/roku-deploy/node_modules/dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "engines": { + "node": "*" + } + }, + "node_modules/roku-deploy/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/roku-deploy/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/roku-deploy/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz", + "integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==", + "dev": true, + "dependencies": { + "tslib": "~2.1.0" + } + }, + "node_modules/rxjs/node_modules/tslib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", + "dev": true + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safe-json-stringify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", + "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-error": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", + "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", + "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==", + "dev": true + }, + "node_modules/simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dev": true, + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/sinon": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-11.1.2.tgz", + "integrity": "sha512-59237HChms4kg7/sXhiRcUzdSkKuydDeTiamT/jesUVHshBgL8XAmhgFo0GfK6RruMDM/iRSij1EybmMog9cJw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^7.1.2", + "@sinonjs/samsam": "^6.0.2", + "diff": "^5.0.0", + "nise": "^5.1.0", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-support": { + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", + "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/telnet-client": { + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/telnet-client/-/telnet-client-1.4.9.tgz", + "integrity": "sha512-ryF0E3mg6am1EnCQZj7OoBnueS3l8IT7lDyDyFR8FdIshRRKBpbKjX7AUnt1ImVd43WKl/AxYE5MTkX3LjhGaQ==", + "dependencies": { + "bluebird": "^3.5.4" + }, + "funding": { + "type": "paypal", + "url": "https://paypal.me/kozjak" + } + }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/traverse-chain": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz", + "integrity": "sha512-up6Yvai4PYKhpNp5PkYtx50m3KbwQrqDwbuZP/ItyL64YEWHAvH6Md83LFLV/GRSk/BoUVwwgUzX6SOQSbsfAg==" + }, + "node_modules/ts-node": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", + "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ts-node/node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", + "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/validate-glob-opts": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/validate-glob-opts/-/validate-glob-opts-1.0.2.tgz", + "integrity": "sha512-3PKjRQq/R514lUcG9OEiW0u9f7D4fP09A07kmk1JbNn2tfeQdAHhlT+A4dqERXKu2br2rrxSM3FzagaEeq9w+A==", + "dev": true, + "dependencies": { + "array-to-sentence": "^1.1.0", + "indexed-filter": "^1.0.0", + "inspect-with-kind": "^1.0.4", + "is-plain-obj": "^1.1.0" + } + }, + "node_modules/validate-glob-opts/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/verror/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, + "node_modules/vscode-debugadapter": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.49.0.tgz", + "integrity": "sha512-nhes9zaLanFcHuchytOXGsLTGpU5qkz10mC9gVchiwNuX2Bljmc6+wsNbCyE5dOxu6F0pn3f+LEJQGMU1kcnvQ==", + "deprecated": "This package has been renamed to @vscode/debugadapter, please update to the new name", + "dependencies": { + "mkdirp": "^1.0.4", + "vscode-debugprotocol": "1.49.0" + } + }, + "node_modules/vscode-debugprotocol": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.49.0.tgz", + "integrity": "sha512-3VkK3BmaqN+BGIq4lavWp9a2IC6VYgkWkkMQm6Sa5ACkhBF6ThJDrkP+/3rFE4G7F8+mM3f4bhhJhhMax2IPfg==", + "deprecated": "This package has been renamed to @vscode/debugprotocol, please update to the new name" + }, + "node_modules/vscode-jsonrpc": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz", + "integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==", + "engines": { + "node": ">=8.0.0 || >=10.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-6.1.1.tgz", + "integrity": "sha512-DueEpkUAkD5XTR4MLYNr6bQIp/UFR0/IPApgXU3YfCBCB08u2sm9hRCs6DxYZELkk++STPjpcjksR2H8qI3cDQ==", + "dependencies": { + "vscode-languageserver-protocol": "^3.15.3" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz", + "integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==", + "dependencies": { + "vscode-jsonrpc": "6.0.0", + "vscode-languageserver-types": "3.16.0" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.2.tgz", + "integrity": "sha512-T7uPC18+f8mYE4lbVZwb3OSmvwTZm3cuFhrdx9Bn2l11lmp3SvSuSVjy2JtvrghzjAo4G6Trqny2m9XGnFnWVA==" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", + "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==" + }, + "node_modules/vscode-uri": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.2.tgz", + "integrity": "sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", + "dev": true + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workerpool": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, "dependencies": { "@babel/code-frame": { "version": "7.15.8", @@ -767,7 +6264,8 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "acorn-walk": { "version": "8.2.0", @@ -3692,6 +9190,14 @@ "tweetnacl": "~0.14.0" } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -3702,14 +9208,6 @@ "strip-ansi": "^6.0.1" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index 79ad4ad6..f3d9d864 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -3,20 +3,32 @@ import { expect } from 'chai'; import { DebugProtocolClient } from '../debugProtocol/client/DebugProtocolClient'; import { DebugProtocolAdapter } from './DebugProtocolAdapter'; import { createSandbox } from 'sinon'; -import type { Variable } from '../debugProtocol/events/responses/VariablesResponse'; import { VariableType } from '../debugProtocol/events/responses/VariablesResponse'; // eslint-disable-next-line @typescript-eslint/no-duplicate-imports import { VariablesResponse } from '../debugProtocol/events/responses/VariablesResponse'; import { DebugProtocolServer } from '../debugProtocol/server/DebugProtocolServer'; import { util } from '../util'; +import { standardizePath as s } from 'brighterscript'; import { DebugProtocolServerTestPlugin } from '../debugProtocol/DebugProtocolServerTestPlugin.spec'; import { AllThreadsStoppedUpdate } from '../debugProtocol/events/updates/AllThreadsStoppedUpdate'; import { StopReason } from '../debugProtocol/Constants'; import { ThreadsResponse } from '../debugProtocol/events/responses/ThreadsResponse'; import { StackTraceV3Response } from '../debugProtocol/events/responses/StackTraceV3Response'; - +import { AddBreakpointsResponse } from '../debugProtocol/events/responses/AddBreakpointsResponse'; +import { BreakpointManager } from '../managers/BreakpointManager'; +import { SourceMapManager } from '../managers/SourceMapManager'; +import { LocationManager } from '../managers/LocationManager'; +import { Project, ProjectManager } from '../managers/ProjectManager'; +import { AddBreakpointsRequest } from '../debugProtocol/events/requests/AddBreakpointsRequest'; +import { AddConditionalBreakpointsRequest } from '../debugProtocol/events/requests/AddConditionalBreakpointsRequest'; +import { AddConditionalBreakpointsResponse } from '../debugProtocol/events/responses/AddConditionalBreakpointsResponse'; const sinon = createSandbox(); +let cwd = s`${process.cwd()}`; +let tmpDir = s`${cwd}/.tmp`; +let rootDir = s`${tmpDir}/rootDir`; +const outDir = s`${tmpDir}/out`; + describe('DebugProtocolAdapter', () => { let adapter: DebugProtocolAdapter; let server: DebugProtocolServer; @@ -29,8 +41,16 @@ describe('DebugProtocolAdapter', () => { controlPort: undefined as number, host: '127.0.0.1' }; - - adapter = new DebugProtocolAdapter(options, undefined, undefined); + const sourcemapManager = new SourceMapManager(); + const locationManager = new LocationManager(sourcemapManager); + const breakpointManager = new BreakpointManager(sourcemapManager, locationManager); + const projectManager = new ProjectManager(breakpointManager, locationManager); + projectManager.mainProject = new Project({ + rootDir: rootDir, + files: [], + outDir: outDir + }); + adapter = new DebugProtocolAdapter(options, projectManager, breakpointManager); if (!options.controlPort) { options.controlPort = await util.getPort(); @@ -98,6 +118,65 @@ describe('DebugProtocolAdapter', () => { await adapter.getStackTrace(0); } + it('skips sending AddBreakpoints and AddConditionalBreakpoints command when there are no breakpoints', async () => { + await initialize(); + + await adapter.syncBreakpoints(); + const reqs = [ + plugin.getRequest(-2)?.constructor.name, + plugin.getRequest(-1)?.constructor.name + ]; + expect(reqs).not.to.include(AddBreakpointsRequest.name); + expect(reqs).not.to.include(AddConditionalBreakpointsRequest.name); + }); + + it('skips sending AddConditionalBreakpoints command when there were only standard breakpoints', async () => { + await initialize(); + + adapter['breakpointManager'].setBreakpoint(`${rootDir}/source/main.brs`, { + line: 12 + }); + + //let the "add" request go through + plugin.pushResponse( + AddConditionalBreakpointsResponse.fromJson({ + breakpoints: [], + requestId: 1 + }) + ); + await adapter.syncBreakpoints(); + const reqs = [ + plugin.getRequest(-2)?.constructor.name, + plugin.getRequest(-1)?.constructor.name + ]; + expect(reqs).to.include(AddBreakpointsRequest.name); + expect(reqs).not.to.include(AddConditionalBreakpointsRequest.name); + }); + + it('skips sending AddBreakpoints command when there only conditional breakpoints', async () => { + await initialize(); + + adapter['breakpointManager'].setBreakpoint(`${rootDir}/source/main.brs`, { + line: 12, + condition: 'true' + }); + + //let the "add" request go through + plugin.pushResponse( + AddBreakpointsResponse.fromJson({ + breakpoints: [], + requestId: 1 + }) + ); + await adapter.syncBreakpoints(); + const reqs = [ + plugin.getRequest(-2)?.constructor.name, + plugin.getRequest(-1)?.constructor.name + ]; + expect(reqs).not.to.include(AddBreakpointsRequest.name); + expect(reqs).to.include(AddConditionalBreakpointsRequest.name); + }); + describe('getVariable', () => { it('works for local vars', async () => { await initialize(); diff --git a/src/adapters/DebugProtocolAdapter.ts b/src/adapters/DebugProtocolAdapter.ts index 5336b030..bfdaff3d 100644 --- a/src/adapters/DebugProtocolAdapter.ts +++ b/src/adapters/DebugProtocolAdapter.ts @@ -762,7 +762,7 @@ export class DebugProtocolAdapter { for (const breakpoints of [standardBreakpoints, conditionalBreakpoints]) { const response = await this.socketDebugger.addBreakpoints(breakpoints); if (response.data.errorCode === ErrorCode.OK) { - for (let i = 0; i < response.data.breakpoints.length; i++) { + for (let i = 0; i < response?.data?.breakpoints?.length ?? 0; i++) { const deviceBreakpoint = response.data.breakpoints[i]; //sync this breakpoint's deviceId with the roku-assigned breakpoint ID this.breakpointManager.setBreakpointDeviceId( diff --git a/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts b/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts index 11f0212c..5945c5e6 100644 --- a/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts +++ b/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts @@ -54,7 +54,8 @@ export class DebugProtocolServerTestPlugin implements ProtocolServerPlugin { */ public getRequest(index: number) { if (index < 0) { - index = this.requests.length - index; + //add the negative index to the length to "subtract" from the end + index = this.requests.length + index; } return this.requests[index]; } diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index cad8b193..e9bba806 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -369,13 +369,8 @@ describe('DebugProtocolClient', () => { expect(response.data.breakpoints).to.eql(responseBreakpoins); }); - it('skips sending command on empty breakpoints array', async () => { - await connect(); - await client.addBreakpoints(undefined); - expect(plugin.latestRequest).not.instanceof(AddBreakpointsResponse); + it('skips sending command when there are zero conditional breakpoints', async () => { - await client.addBreakpoints([]); - expect(plugin.latestRequest).not.instanceof(AddBreakpointsRequest); }); it('sends AddBreakpointsRequest when conditional breakpoints are NOT supported', async () => { diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 40aab657..f70803f9 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -249,6 +249,7 @@ export class DebugProtocolClient { this.controlSocket.on('data', (data) => { this.emit('data', data); + this.logger.info('received server-to-client data\n', { type: 'server-to-client', data: data.toJSON() }); //queue up processing the new data, chunk by chunk void this.bufferQueue.run(async () => { this.buffer = Buffer.concat([this.buffer, data]); @@ -257,16 +258,6 @@ export class DebugProtocolClient { } return true; }); - - // this.buffer = Buffer.concat([this.buffer, data]); - - // this.logger.debug(`on('data'): incoming bytes`, data.length); - // const startBufferSize = this.buffer.length; - - // this.process(); - - // const endBufferSize = this.buffer?.length ?? 0; - // this.logger.debug(`buffer size before:`, startBufferSize, ', buffer size after:', endBufferSize, ', bytes consumed:', startBufferSize - endBufferSize); }); this.controlSocket.on('end', () => { @@ -632,7 +623,7 @@ export class DebugProtocolClient { this.logger.log(`Request ${request?.data?.requestId}`, request); if (this.controlSocket) { const buffer = request.toBuffer(); - console.log('client sent', JSON.stringify(buffer.toJSON().data)); + this.logger.info('received client-to-server data\n', { type: 'client-to-server', data: buffer.toJSON() }); this.controlSocket.write(buffer); void this.plugins.emit('afterSendRequest', { client: this, @@ -928,6 +919,7 @@ export class DebugProtocolClient { let lastPartialLine = ''; this.ioSocket.on('data', (buffer) => { + this.logger.info('received IO data\n', { type: 'io', data: buffer.toJSON() }); let responseText = buffer.toString(); if (!responseText.endsWith('\n')) { // buffer was split, save the partial line diff --git a/src/debugProtocol/events/responses/ListBreakpointsResponse.ts b/src/debugProtocol/events/responses/ListBreakpointsResponse.ts index 03fc74ee..e093e4cc 100644 --- a/src/debugProtocol/events/responses/ListBreakpointsResponse.ts +++ b/src/debugProtocol/events/responses/ListBreakpointsResponse.ts @@ -63,7 +63,7 @@ export class ListBreakpointsResponse implements ProtocolResponse { public readOffset = 0; public data = { - breakpoints: undefined as BreakpointInfo[], + breakpoints: [] as BreakpointInfo[], // response fields packetLength: undefined as number, From d0687558e5409ecb9bed0d1e6ca4f435d58e65d3 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Thu, 16 Feb 2023 14:20:32 -0500 Subject: [PATCH 50/74] Fix empty stack trace response --- src/adapters/DebugProtocolAdapter.spec.ts | 109 ++++++++++++---------- src/adapters/DebugProtocolAdapter.ts | 2 +- 2 files changed, 62 insertions(+), 49 deletions(-) diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index f3d9d864..6fc242a9 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -118,63 +118,76 @@ describe('DebugProtocolAdapter', () => { await adapter.getStackTrace(0); } - it('skips sending AddBreakpoints and AddConditionalBreakpoints command when there are no breakpoints', async () => { - await initialize(); - - await adapter.syncBreakpoints(); - const reqs = [ - plugin.getRequest(-2)?.constructor.name, - plugin.getRequest(-1)?.constructor.name - ]; - expect(reqs).not.to.include(AddBreakpointsRequest.name); - expect(reqs).not.to.include(AddConditionalBreakpointsRequest.name); + describe('getStackTrace', () => { + it('recovers when there are no stack frames', async () => { + await initialize(); + //should not throw exception + expect( + await adapter.getStackTrace(-1) + ).to.eql([]); + }); }); - it('skips sending AddConditionalBreakpoints command when there were only standard breakpoints', async () => { - await initialize(); + describe('syncBreakpoints', () => { + + it('skips sending AddBreakpoints and AddConditionalBreakpoints command when there are no breakpoints', async () => { + await initialize(); - adapter['breakpointManager'].setBreakpoint(`${rootDir}/source/main.brs`, { - line: 12 + await adapter.syncBreakpoints(); + const reqs = [ + plugin.getRequest(-2)?.constructor.name, + plugin.getRequest(-1)?.constructor.name + ]; + expect(reqs).not.to.include(AddBreakpointsRequest.name); + expect(reqs).not.to.include(AddConditionalBreakpointsRequest.name); }); - //let the "add" request go through - plugin.pushResponse( - AddConditionalBreakpointsResponse.fromJson({ - breakpoints: [], - requestId: 1 - }) - ); - await adapter.syncBreakpoints(); - const reqs = [ - plugin.getRequest(-2)?.constructor.name, - plugin.getRequest(-1)?.constructor.name - ]; - expect(reqs).to.include(AddBreakpointsRequest.name); - expect(reqs).not.to.include(AddConditionalBreakpointsRequest.name); - }); + it('skips sending AddConditionalBreakpoints command when there were only standard breakpoints', async () => { + await initialize(); - it('skips sending AddBreakpoints command when there only conditional breakpoints', async () => { - await initialize(); + adapter['breakpointManager'].setBreakpoint(`${rootDir}/source/main.brs`, { + line: 12 + }); - adapter['breakpointManager'].setBreakpoint(`${rootDir}/source/main.brs`, { - line: 12, - condition: 'true' + //let the "add" request go through + plugin.pushResponse( + AddConditionalBreakpointsResponse.fromJson({ + breakpoints: [], + requestId: 1 + }) + ); + await adapter.syncBreakpoints(); + const reqs = [ + plugin.getRequest(-2)?.constructor.name, + plugin.getRequest(-1)?.constructor.name + ]; + expect(reqs).to.include(AddBreakpointsRequest.name); + expect(reqs).not.to.include(AddConditionalBreakpointsRequest.name); }); - //let the "add" request go through - plugin.pushResponse( - AddBreakpointsResponse.fromJson({ - breakpoints: [], - requestId: 1 - }) - ); - await adapter.syncBreakpoints(); - const reqs = [ - plugin.getRequest(-2)?.constructor.name, - plugin.getRequest(-1)?.constructor.name - ]; - expect(reqs).not.to.include(AddBreakpointsRequest.name); - expect(reqs).to.include(AddConditionalBreakpointsRequest.name); + it('skips sending AddBreakpoints command when there only conditional breakpoints', async () => { + await initialize(); + + adapter['breakpointManager'].setBreakpoint(`${rootDir}/source/main.brs`, { + line: 12, + condition: 'true' + }); + + //let the "add" request go through + plugin.pushResponse( + AddBreakpointsResponse.fromJson({ + breakpoints: [], + requestId: 1 + }) + ); + await adapter.syncBreakpoints(); + const reqs = [ + plugin.getRequest(-2)?.constructor.name, + plugin.getRequest(-1)?.constructor.name + ]; + expect(reqs).not.to.include(AddBreakpointsRequest.name); + expect(reqs).to.include(AddConditionalBreakpointsRequest.name); + }); }); describe('getVariable', () => { diff --git a/src/adapters/DebugProtocolAdapter.ts b/src/adapters/DebugProtocolAdapter.ts index bfdaff3d..8e0ba8fd 100644 --- a/src/adapters/DebugProtocolAdapter.ts +++ b/src/adapters/DebugProtocolAdapter.ts @@ -461,7 +461,7 @@ export class DebugProtocolAdapter { let thread = await this.getThreadByThreadId(threadIndex); let frames: StackFrame[] = []; let stackTraceData = await this.socketDebugger.getStackTrace(threadIndex); - for (let i = 0; i < stackTraceData.data.entries.length; i++) { + for (let i = 0; i < stackTraceData?.data?.entries?.length ?? 0; i++) { let frameData = stackTraceData.data.entries[i]; let stackFrame: StackFrame = { frameId: this.nextFrameId++, From bd52c634b844369c54cbba010c99cb8a01d25e90 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Fri, 17 Feb 2023 12:21:19 -0500 Subject: [PATCH 51/74] Refactor protocol vars logic (local vars broken) --- src/adapters/DebugProtocolAdapter.spec.ts | 28 ++- src/adapters/DebugProtocolAdapter.ts | 188 +++++++++++------- src/adapters/TelnetAdapter.ts | 12 +- .../client/DebugProtocolClient.ts | 11 +- .../BrightScriptDebugSession.spec.ts | 3 +- src/debugSession/BrightScriptDebugSession.ts | 8 +- 6 files changed, 154 insertions(+), 96 deletions(-) diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index 6fc242a9..bede8544 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { DebugProtocolClient } from '../debugProtocol/client/DebugProtocolClient'; -import { DebugProtocolAdapter } from './DebugProtocolAdapter'; +import { DebugProtocolAdapter, EvaluateContainer, KeyType } from './DebugProtocolAdapter'; import { createSandbox } from 'sinon'; import { VariableType } from '../debugProtocol/events/responses/VariablesResponse'; // eslint-disable-next-line @typescript-eslint/no-duplicate-imports @@ -22,6 +22,7 @@ import { Project, ProjectManager } from '../managers/ProjectManager'; import { AddBreakpointsRequest } from '../debugProtocol/events/requests/AddBreakpointsRequest'; import { AddConditionalBreakpointsRequest } from '../debugProtocol/events/requests/AddConditionalBreakpointsRequest'; import { AddConditionalBreakpointsResponse } from '../debugProtocol/events/responses/AddConditionalBreakpointsResponse'; +import { HighLevelType } from '../interfaces'; const sinon = createSandbox(); let cwd = s`${process.cwd()}`; @@ -219,7 +220,7 @@ describe('DebugProtocolAdapter', () => { ] }) ); - const vars = await adapter.getVariable('', 1); + const vars = await adapter.getLocalVariables(1); expect( vars?.children.map(x => x.evaluateName) ).to.eql([ @@ -228,9 +229,13 @@ describe('DebugProtocolAdapter', () => { ]); }); - it.skip('works for object properties', async () => { + it('works for object properties', async () => { await initialize(); + //load the stack trace which is required for variable requests to work + const frames = await adapter.getStackTrace(0); + const frameId = frames[0].frameId; + plugin.pushResponse( VariablesResponse.fromJson({ requestId: undefined, @@ -261,13 +266,26 @@ describe('DebugProtocolAdapter', () => { ] }) ); - const vars = await adapter.getVariable('person', 0); + const container = await adapter.getVariable('person', frameId); expect( - vars?.children.map(x => x.evaluateName) + container?.children.map(x => x.evaluateName) ).to.eql([ 'person["name"]', 'person["age"]' ]); + //the top level object should be an AA + expect(container.type).to.eql(VariableType.AA); + expect(container.keyType).to.eql(KeyType.string); + expect(container.elementCount).to.eql(2); + + //the children should NOT look like objects + expect(container.children[0].keyType).not.to.exist; + expect(container.children[0].children).not.to.exist; + expect(container.children[0].elementCount).not.to.exist; + + expect(container.children[1].keyType).not.to.exist; + expect(container.children[1].children).not.to.exist; + expect(container.children[1].elementCount).not.to.exist; }); }); }); diff --git a/src/adapters/DebugProtocolAdapter.ts b/src/adapters/DebugProtocolAdapter.ts index 8e0ba8fd..db06240c 100644 --- a/src/adapters/DebugProtocolAdapter.ts +++ b/src/adapters/DebugProtocolAdapter.ts @@ -7,7 +7,7 @@ import { RendezvousTracker } from '../RendezvousTracker'; import type { ChanperfData } from '../ChanperfTracker'; import { ChanperfTracker } from '../ChanperfTracker'; import type { SourceLocation } from '../managers/LocationManager'; -import { ErrorCode, PROTOCOL_ERROR_CODES, StopReasonCode } from '../debugProtocol/Constants'; +import { ErrorCode, PROTOCOL_ERROR_CODES } from '../debugProtocol/Constants'; import { defer, util } from '../util'; import { logger } from '../logging'; import * as semver from 'semver'; @@ -17,8 +17,9 @@ import type { ProjectManager } from '../managers/ProjectManager'; import { ActionQueue } from '../managers/ActionQueue'; import type { BreakpointsVerifiedEvent, ConstructorOptions, ProtocolVersionDetails } from '../debugProtocol/client/DebugProtocolClient'; import { DebugProtocolClient } from '../debugProtocol/client/DebugProtocolClient'; +import type { Variable } from '../debugProtocol/events/responses/VariablesResponse'; import { VariableType } from '../debugProtocol/events/responses/VariablesResponse'; -import type { VerifiedBreakpoint } from '../debugProtocol/events/updates/BreakpointVerifiedUpdate'; +import type { TelnetAdapter } from './TelnetAdapter'; /** * A class that connects to a Roku device over telnet debugger port and provides a standardized way of interacting with it. @@ -495,10 +496,11 @@ export class DebugProtocolAdapter { } /** - * Given an expression, evaluate that statement ON the roku - * @param expression + * Get info about the specified variable. + * @param expression the expression for the specified variable (i.e. `m`, `someVar.value`, `arr[1][2].three`). If empty string/undefined is specified, all local variables are retrieved instead */ - public async getVariable(expression: string, frameId: number) { + private async getVariablesResponse(expression: string, frameId: number) { + const isScopesRequest = expression === ''; const logger = this.logger.createLogger(' getVariable'); logger.info('begin', { expression }); if (!this.isAtDebuggerPrompt) { @@ -526,79 +528,118 @@ export class DebugProtocolAdapter { variablePath = expression === '' ? [] : util.getVariablePath(expression?.toLowerCase()); response = await this.socketDebugger.getVariables(variablePath, frame.frameIndex, frame.threadIndex); } + return response; + } + /** + * Get the variable for the specified expression. + */ + public async getVariable(expression: string, frameId: number) { + const response = await this.getVariablesResponse(expression, frameId); + + if (response?.data?.errorCode === ErrorCode.OK && Array.isArray(response?.data?.variables)) { + const container = this.createEvaluateContainer( + response.data.variables[0], + //the name of the top container is the expression itself + expression, + //this is the top-level container, so there are no parent keys to this entry + undefined + ); + return container; + } + } - if (response.data.errorCode === ErrorCode.OK) { - let mainContainer: EvaluateContainer; - let children: EvaluateContainer[] = []; - let firstHandled = false; - for (let variable of response.data.variables) { - let value; - let variableType = variable.type as string; - if (variable.value === null) { - value = 'roInvalid'; - } else if (variableType === 'String') { - value = `\"${variable.value}\"`; - } else { - value = variable.value; - } - if (variableType === VariableType.SubtypedObject) { - //subtyped objects can only have string values - let parts = (variable.value as string).split('; '); - variableType = `${parts[0]} (${parts[1]})`; - } else if (variableType === 'AA') { - variableType = VariableType.AA; - } + /** + * Get the list of local variables + */ + public async getLocalVariables(frameId: number) { + const response = await this.getVariablesResponse('', frameId); + + if (response?.data?.errorCode === ErrorCode.OK && Array.isArray(response?.data?.variables)) { + //create a top-level container to hold all the local vars + const container = this.createEvaluateContainer( + //dummy data + { + isConst: false, + isContainer: true, + keyType: VariableType.String, + refCount: undefined, + type: VariableType.AA, + value: undefined, + children: response.data.variables + }, + //no name, this is a dummy container + undefined, + //there's no parent path + undefined + ); + return container; + } + } - let container = { - name: expression, - evaluateName: expression, - variablePath: variablePath, - type: variableType, - value: value, - keyType: variable.keyType === VariableType.Integer ? 'Integer' : 'String', - highLevelType: null, - children: null, - elementCount: variable.childCount - }; + /** + * Create an EvaluateContainer for the given variable. If the variable has children, those are created and attached as well + * @param variable a Variable object from the debug protocol debugger + * @param name the name of this variable. For example, `alpha.beta.charlie`, this value would be `charlie`. For local vars, this is the root variable name (i.e. `alpha`) + * @param parentEvaluateName the string used to derive the parent, _excluding_ this variable's name (i.e. `alpha.beta` or `alpha[0]`) + */ + private createEvaluateContainer(variable: Variable, name: string, parentEvaluateName: string) { + let value; + let variableType = variable.type as string; + if (variable.value === null) { + value = 'roInvalid'; + } else if (variableType === 'String') { + value = `\"${variable.value}\"`; + } else { + value = variable.value; + } - if (!firstHandled && variablePath.length > 0) { - firstHandled = true; - mainContainer = container; - } else { - if (!firstHandled && variablePath.length === 0) { - // If this is a scope request there will be no entries in the variable path - // We will need to create a fake mainContainer - firstHandled = true; - mainContainer = { - name: expression, - evaluateName: expression, - variablePath: variablePath, - type: '', - value: null, - keyType: 'String', - elementCount: response.data.variables.length - }; - } + if (variableType === VariableType.SubtypedObject) { + //subtyped objects can only have string values + let parts = (variable.value as string).split('; '); + variableType = `${parts[0]} (${parts[1]})`; + } else if (variableType === 'AA') { + variableType = VariableType.AA; + } - let pathAddition = mainContainer.keyType === 'Integer' ? children.length : variable.name; - container.name = pathAddition.toString(); - if (mainContainer.evaluateName) { - container.evaluateName = `${mainContainer.evaluateName}["${pathAddition}"]`; - } else { - container.evaluateName = pathAddition.toString(); - } - container.variablePath = [].concat(container.variablePath, [pathAddition.toString()]); - if (container.keyType) { - container.children = []; - } - children.push(container); - } + //build full evaluate name for this var. (i.e. `alpha["beta"]` + ["charlie"]` === `alpha["beta"]["charlie"]`) + let evaluateName: string; + if (!parentEvaluateName?.trim()) { + evaluateName = name; + } else if (typeof name === 'string') { + evaluateName = `${parentEvaluateName}["${name}"]`; + } else if (typeof name === 'number') { + evaluateName = `${parentEvaluateName}[${name}]`; + } + + let container: EvaluateContainer = { + name: name, + evaluateName: evaluateName, + type: variableType, + value: value, + highLevelType: undefined, + //non object/array variables don't have a key type + keyType: variable.keyType as unknown as KeyType, + elementCount: variable.childCount ?? variable.children?.length ?? undefined, + //non object/array variables still need to have an empty `children` array to help upstream logic. The `keyType` being null is how we know it doesn't actually have children + children: [] + }; + + //recursively generate children containers + if ([KeyType.integer, KeyType.string].includes(container.keyType) && Array.isArray(variable.children)) { + container.children = []; + for (let i = 0; i < variable.children.length; i++) { + const childVariable = variable.children[i]; + const childContainer = this.createEvaluateContainer( + childVariable, + container.keyType === KeyType.integer ? i.toString() : childVariable.name, + container.evaluateName + ); + container.children.push(childContainer); } - mainContainer.children = children; - return mainContainer; } + return container; } /** @@ -801,10 +842,9 @@ export enum EventName { export interface EvaluateContainer { name: string; evaluateName: string; - variablePath: string[]; type: string; value: string; - keyType: KeyType; + keyType?: KeyType; elementCount: number; highLevelType: HighLevelType; children: EvaluateContainer[]; @@ -830,3 +870,7 @@ interface BrightScriptRuntimeError { message: string; errorCode: string; } + +export function isDebugProtocolAdapter(adapter: TelnetAdapter | DebugProtocolAdapter): adapter is DebugProtocolAdapter { + return adapter?.constructor.name === DebugProtocolAdapter.name; +} diff --git a/src/adapters/TelnetAdapter.ts b/src/adapters/TelnetAdapter.ts index 67b4858d..47952644 100644 --- a/src/adapters/TelnetAdapter.ts +++ b/src/adapters/TelnetAdapter.ts @@ -15,6 +15,7 @@ import { logger } from '../logging'; import type { AdapterOptions, RokuAdapterEvaluateResponse } from '../interfaces'; import { HighLevelType } from '../interfaces'; import { TelnetRequestPipeline } from './TelnetRequestPipeline'; +import type { DebugProtocolAdapter } from './DebugProtocolAdapter'; /** * A class that connects to a Roku device over telnet debugger port and provides a standardized way of interacting with it. @@ -519,10 +520,9 @@ export class TelnetAdapter { /** * Gets a string array of all the local variables using the var command - * @param scope */ - public async getScopeVariables(scope?: string) { - this.logger.log('getScopeVariables', { scope }); + public async getScopeVariables() { + this.logger.log('getScopeVariables'); if (!this.isAtDebuggerPrompt) { throw new Error('Cannot resolve variable: debugger is not paused'); } @@ -876,7 +876,6 @@ export class TelnetAdapter { type: '', highLevelType: HighLevelType.uninitialized, evaluateName: undefined, - variablePath: [], elementCount: -1, value: '', keyType: KeyType.legacy, @@ -1086,7 +1085,6 @@ export enum EventName { export interface EvaluateContainer { name: string; evaluateName: string; - variablePath: string[]; type: string; value: string; keyType: KeyType; @@ -1137,3 +1135,7 @@ interface BrightScriptRuntimeError { message: string; errorCode: string; } + +export function isTelnetAdapterAdapter(adapter: TelnetAdapter | DebugProtocolAdapter): adapter is TelnetAdapter { + return adapter?.constructor.name === TelnetAdapter.name; +} diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index f70803f9..10fbb9c6 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -441,7 +441,7 @@ export class DebugProtocolClient { * @param threadIndex the index (or perhaps ID?) of the thread to get variables for */ public async getVariables(variablePathEntries: Array = [], stackFrameIndex: number = this.stackFrameIndex, threadIndex: number = this.primaryThread) { - return this.processVariablesRequest( + const response = await this.processVariablesRequest( VariablesRequest.fromJson({ requestId: this.requestIdSequence++, threadIndex: threadIndex, @@ -456,6 +456,7 @@ export class DebugProtocolClient { enableForceCaseInsensitivity: semver.satisfies(this.protocolVersion, '>=3.1.0') && variablePathEntries.length > 0 }) ); + return response; } private async processVariablesRequest(request: VariablesRequest) { @@ -676,15 +677,9 @@ export class DebugProtocolClient { return false; } - //we have a valid event. Clear the buffer of this data + //we have a valid event. Remove this data from the buffer this.buffer = this.buffer.slice(responseOrUpdate.readOffset); - //TODO why did we ever do this? Just to handle when we misread incoming data? I think this should be scrapped - // if (event.data.requestId > this.totalRequests) { - // this.removedProcessedBytes(genericResponse, slicedBuffer, packetLength); - // return true; - // } - if (responseOrUpdate.data.errorCode !== ErrorCode.OK) { this.logger.error(responseOrUpdate.data.errorCode, responseOrUpdate); } diff --git a/src/debugSession/BrightScriptDebugSession.spec.ts b/src/debugSession/BrightScriptDebugSession.spec.ts index 16c76076..e488280e 100644 --- a/src/debugSession/BrightScriptDebugSession.spec.ts +++ b/src/debugSession/BrightScriptDebugSession.spec.ts @@ -17,7 +17,6 @@ import { util as bscUtil, standardizePath as s } from 'brighterscript'; import { DefaultFiles } from 'roku-deploy'; import type { AddProjectParams, ComponentLibraryConstructorParams } from '../managers/ProjectManager'; import { ComponentLibraryProject, Project } from '../managers/ProjectManager'; -import { ConsoleTransport } from '@rokucommunity/logger'; const sinon = sinonActual.createSandbox(); const tempDir = s`${__dirname}/../../.tmp`; @@ -221,7 +220,7 @@ describe('BrightScriptDebugSession', () => { const stub = sinon.stub(session['rokuAdapter'], 'evaluate').callsFake(x => { return Promise.resolve({ type: 'message', message: '' }); }); - sinon.stub(rokuAdapter, 'getScopeVariables').callsFake(x => { + sinon.stub(rokuAdapter, 'getScopeVariables').callsFake(() => { return Promise.resolve(['m', 'top', `${session.tempVarPrefix}eval`]); }); sinon.stub(rokuAdapter, 'getVariable').callsFake(x => { diff --git a/src/debugSession/BrightScriptDebugSession.ts b/src/debugSession/BrightScriptDebugSession.ts index 328b800f..20fcde66 100644 --- a/src/debugSession/BrightScriptDebugSession.ts +++ b/src/debugSession/BrightScriptDebugSession.ts @@ -27,7 +27,7 @@ import { fileUtils, standardizePath as s } from '../FileUtils'; import { ComponentLibraryServer } from '../ComponentLibraryServer'; import { ProjectManager, Project, ComponentLibraryProject } from '../managers/ProjectManager'; import type { EvaluateContainer } from '../adapters/DebugProtocolAdapter'; -import { DebugProtocolAdapter } from '../adapters/DebugProtocolAdapter'; +import { isDebugProtocolAdapter, DebugProtocolAdapter } from '../adapters/DebugProtocolAdapter'; import { TelnetAdapter } from '../adapters/TelnetAdapter'; import type { BSDebugDiagnostic } from '../CompileErrorProcessor'; import { @@ -733,14 +733,14 @@ export class BrightScriptDebugSession extends BaseDebugSession { try { const scopes = new Array(); - if (this.enableDebugProtocol) { + if (isDebugProtocolAdapter(this.rokuAdapter)) { let refId = this.getEvaluateRefId('', args.frameId); let v: AugmentedVariable; //if we already looked this item up, return it if (this.variables[refId]) { v = this.variables[refId]; } else { - let result = await this.rokuAdapter.getVariable('', args.frameId); + let result = await this.rokuAdapter.getLocalVariables(args.frameId); if (!result) { throw new Error(`Could not get scopes`); } @@ -841,7 +841,7 @@ export class BrightScriptDebugSession extends BaseDebugSession { logger.log('reference', reference); // NOTE: Legacy telnet support for local vars if (this.launchConfiguration.enableVariablesPanel) { - const vars = await (this.rokuAdapter as TelnetAdapter).getScopeVariables(reference); + const vars = await (this.rokuAdapter as TelnetAdapter).getScopeVariables(); for (const varName of vars) { let result = await this.rokuAdapter.getVariable(varName, -1); From f74467b3a878deb5b67d49030e4f55e67dac6b56 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Fri, 17 Feb 2023 12:25:40 -0500 Subject: [PATCH 52/74] fix tests --- src/adapters/DebugProtocolAdapter.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index bede8544..3a166643 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -280,12 +280,12 @@ describe('DebugProtocolAdapter', () => { //the children should NOT look like objects expect(container.children[0].keyType).not.to.exist; - expect(container.children[0].children).not.to.exist; expect(container.children[0].elementCount).not.to.exist; + expect(container.children[0].children).to.eql([]); expect(container.children[1].keyType).not.to.exist; - expect(container.children[1].children).not.to.exist; expect(container.children[1].elementCount).not.to.exist; + expect(container.children[1].children).to.eql([]); }); }); }); From fd25572c3e90c22509f390615834db5a87600a11 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Fri, 17 Feb 2023 15:26:46 -0500 Subject: [PATCH 53/74] Fix local vars and add array size --- src/adapters/DebugProtocolAdapter.ts | 9 ++++----- src/debugSession/BrightScriptDebugSession.ts | 6 +++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/adapters/DebugProtocolAdapter.ts b/src/adapters/DebugProtocolAdapter.ts index db06240c..69b7f8df 100644 --- a/src/adapters/DebugProtocolAdapter.ts +++ b/src/adapters/DebugProtocolAdapter.ts @@ -549,7 +549,6 @@ export class DebugProtocolAdapter { } } - /** * Get the list of local variables */ @@ -614,10 +613,10 @@ export class DebugProtocolAdapter { } let container: EvaluateContainer = { - name: name, - evaluateName: evaluateName, - type: variableType, - value: value, + name: name ?? '', + evaluateName: evaluateName ?? '', + type: variableType ?? '', + value: value ?? null, highLevelType: undefined, //non object/array variables don't have a key type keyType: variable.keyType as unknown as KeyType, diff --git a/src/debugSession/BrightScriptDebugSession.ts b/src/debugSession/BrightScriptDebugSession.ts index 20fcde66..0cd3e05e 100644 --- a/src/debugSession/BrightScriptDebugSession.ts +++ b/src/debugSession/BrightScriptDebugSession.ts @@ -49,7 +49,7 @@ import type { AugmentedSourceBreakpoint } from '../managers/BreakpointManager'; import { BreakpointManager } from '../managers/BreakpointManager'; import type { LogMessage } from '../logging'; import { logger, debugServerLogOutputEventTransport } from '../logging'; -import { waitForDebugger } from 'inspector'; +import { VariableType } from '../debugProtocol/events/responses/VariablesResponse'; export class BrightScriptDebugSession extends BaseDebugSession { public constructor() { @@ -1194,6 +1194,10 @@ export class BrightScriptDebugSession extends BaseDebugSession { } v.childVariables = childVariables; } + // if the var is an array and debugProtocol is enabled, include the array size + if (this.enableDebugProtocol && v.type === VariableType.Array) { + v.value = `${v.type}(${result.elementCount})` as any; + } } return v; } From d89fceada5fb8d24df1e89a4fada9b3ad3fa5d9d Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Tue, 21 Feb 2023 12:01:53 -0500 Subject: [PATCH 54/74] rename "AA" to "AssociativeArray" --- src/adapters/DebugProtocolAdapter.spec.ts | 6 ++--- src/adapters/DebugProtocolAdapter.ts | 4 +-- .../responses/VariablesResponse.spec.ts | 26 +++++++++---------- .../events/responses/VariablesResponse.ts | 10 +++---- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index 3a166643..c98451b7 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -203,7 +203,7 @@ describe('DebugProtocolAdapter', () => { isConst: false, isContainer: true, refCount: 1, - type: VariableType.AA, + type: VariableType.AssociativeArray, value: undefined, childCount: 4, keyType: VariableType.String, @@ -244,7 +244,7 @@ describe('DebugProtocolAdapter', () => { isConst: false, isContainer: true, refCount: 1, - type: VariableType.AA, + type: VariableType.AssociativeArray, value: undefined, keyType: VariableType.String, children: [{ @@ -274,7 +274,7 @@ describe('DebugProtocolAdapter', () => { 'person["age"]' ]); //the top level object should be an AA - expect(container.type).to.eql(VariableType.AA); + expect(container.type).to.eql(VariableType.AssociativeArray); expect(container.keyType).to.eql(KeyType.string); expect(container.elementCount).to.eql(2); diff --git a/src/adapters/DebugProtocolAdapter.ts b/src/adapters/DebugProtocolAdapter.ts index 69b7f8df..c7c0c25e 100644 --- a/src/adapters/DebugProtocolAdapter.ts +++ b/src/adapters/DebugProtocolAdapter.ts @@ -564,7 +564,7 @@ export class DebugProtocolAdapter { isContainer: true, keyType: VariableType.String, refCount: undefined, - type: VariableType.AA, + type: VariableType.AssociativeArray, value: undefined, children: response.data.variables }, @@ -599,7 +599,7 @@ export class DebugProtocolAdapter { let parts = (variable.value as string).split('; '); variableType = `${parts[0]} (${parts[1]})`; } else if (variableType === 'AA') { - variableType = VariableType.AA; + variableType = VariableType.AssociativeArray; } //build full evaluate name for this var. (i.e. `alpha["beta"]` + ["charlie"]` === `alpha["beta"]["charlie"]`) diff --git a/src/debugProtocol/events/responses/VariablesResponse.spec.ts b/src/debugProtocol/events/responses/VariablesResponse.spec.ts index e49c9a8b..a12eaeb6 100644 --- a/src/debugProtocol/events/responses/VariablesResponse.spec.ts +++ b/src/debugProtocol/events/responses/VariablesResponse.spec.ts @@ -29,7 +29,7 @@ describe('VariablesResponse', () => { let response = VariablesResponse.fromJson({ requestId: 2, variables: [{ - type: VariableType.AA, + type: VariableType.AssociativeArray, children: [] }, { type: VariableType.Array, @@ -55,7 +55,7 @@ describe('VariablesResponse', () => { VariablesResponse.fromJson({ requestId: 2, variables: [{ - type: VariableType.AA + type: VariableType.AssociativeArray }] as any[] }); }, 'Container variable must have one of these properties defined: childCount, children'); @@ -76,7 +76,7 @@ describe('VariablesResponse', () => { expect(VariablesResponse.prototype['readVariableValue'](VariableType.Uninitialized, new SmartBuffer())).to.eql(null); expect(VariablesResponse.prototype['readVariableValue'](VariableType.Unknown, new SmartBuffer())).to.eql(null); expect(VariablesResponse.prototype['readVariableValue'](VariableType.Invalid, new SmartBuffer())).to.eql(null); - expect(VariablesResponse.prototype['readVariableValue'](VariableType.AA, new SmartBuffer())).to.eql(null); + expect(VariablesResponse.prototype['readVariableValue'](VariableType.AssociativeArray, new SmartBuffer())).to.eql(null); expect(VariablesResponse.prototype['readVariableValue'](VariableType.Array, new SmartBuffer())).to.eql(null); expect(VariablesResponse.prototype['readVariableValue'](VariableType.List, new SmartBuffer())).to.eql(null); }); @@ -117,7 +117,7 @@ describe('VariablesResponse', () => { isConst: false, isContainer: true, children: [], - type: VariableType.AA, + type: VariableType.AssociativeArray, keyType: VariableType.String, value: undefined }] @@ -135,7 +135,7 @@ describe('VariablesResponse', () => { refCount: 2, isConst: false, isContainer: true, - type: VariableType.AA, + type: VariableType.AssociativeArray, keyType: VariableType.String, value: undefined, children: [{ @@ -165,7 +165,7 @@ describe('VariablesResponse', () => { refCount: 2, isConst: false, isContainer: true, - type: VariableType.AA, + type: VariableType.AssociativeArray, keyType: 'String', value: undefined, children: [{ @@ -203,7 +203,7 @@ describe('VariablesResponse', () => { refCount: 2, // 4 bytes isConst: false, // 0 bytes -- part of flags isContainer: true, // 0 bytes -- part of flags - type: VariableType.AA, // 1 byte + type: VariableType.AssociativeArray, // 1 byte keyType: 'String', // 1 byte // element_count // 4 bytes children: [{ @@ -247,7 +247,7 @@ describe('VariablesResponse', () => { v('l', VariableType.Uninitialized, undefined), v('m', VariableType.Unknown, undefined), v('n', VariableType.Invalid, undefined), - v('o', VariableType.AA, undefined), + v('o', VariableType.AssociativeArray, undefined), v('p', VariableType.Array, undefined), v('q', VariableType.List, undefined) ] @@ -277,7 +277,7 @@ describe('VariablesResponse', () => { ['l', VariableType.Uninitialized, undefined], ['m', VariableType.Unknown, undefined], ['n', VariableType.Invalid, undefined], - ['o', VariableType.AA, undefined], + ['o', VariableType.AssociativeArray, undefined], ['p', VariableType.Array, undefined], ['q', VariableType.List, undefined] ].map(x => ({ @@ -307,7 +307,7 @@ describe('VariablesResponse', () => { VariablesResponse.prototype['writeVariableValue'](VariableType.Uninitialized, undefined, buffer); VariablesResponse.prototype['writeVariableValue'](VariableType.Unknown, undefined, buffer); VariablesResponse.prototype['writeVariableValue'](VariableType.Invalid, undefined, buffer); - VariablesResponse.prototype['writeVariableValue'](VariableType.AA, undefined, buffer); + VariablesResponse.prototype['writeVariableValue'](VariableType.AssociativeArray, undefined, buffer); VariablesResponse.prototype['writeVariableValue'](VariableType.Array, undefined, buffer); VariablesResponse.prototype['writeVariableValue'](VariableType.List, undefined, buffer); expect(buffer.length).to.eql(0); @@ -358,7 +358,7 @@ describe('VariablesResponse', () => { isConst: false, isContainer: true, childCount: 3, - type: VariableType.AA, + type: VariableType.AssociativeArray, keyType: VariableType.String, value: undefined }, { @@ -387,7 +387,7 @@ describe('VariablesResponse', () => { variables: [{ isConst: false, isContainer: true, - type: VariableType.AA, + type: VariableType.AssociativeArray, name: 'm', refCount: 2, keyType: VariableType.String, @@ -427,7 +427,7 @@ describe('VariablesResponse', () => { // flags // 1 byte isConst: false, // 0 bytes -- part of flags isContainer: true, // 0 bytes -- part of flags - type: VariableType.AA, // 1 byte + type: VariableType.AssociativeArray, // 1 byte name: 'm', // 2 bytes refCount: 2, // 4 bytes keyType: VariableType.String, // 1 byte diff --git a/src/debugProtocol/events/responses/VariablesResponse.ts b/src/debugProtocol/events/responses/VariablesResponse.ts index 3ea0470a..73e6c523 100644 --- a/src/debugProtocol/events/responses/VariablesResponse.ts +++ b/src/debugProtocol/events/responses/VariablesResponse.ts @@ -23,7 +23,7 @@ export class VariablesResponse { delete variable.childCount; } if (util.isNullish(variable.isContainer)) { - variable.isContainer = [VariableType.AA, VariableType.Array, VariableType.List, VariableType.Object, VariableType.SubtypedObject].includes(variable.type); + variable.isContainer = [VariableType.AssociativeArray, VariableType.Array, VariableType.List, VariableType.Object, VariableType.SubtypedObject].includes(variable.type); } if (variable.isContainer && util.isNullish(variable.childCount) && !hasChildrenArray) { throw new Error('Container variable must have one of these properties defined: childCount, children'); @@ -136,7 +136,7 @@ export class VariablesResponse { case VariableType.Uninitialized: case VariableType.Unknown: case VariableType.Invalid: - case VariableType.AA: + case VariableType.AssociativeArray: case VariableType.Array: case VariableType.List: return null; @@ -258,7 +258,7 @@ export class VariablesResponse { case VariableType.Uninitialized: case VariableType.Unknown: case VariableType.Invalid: - case VariableType.AA: + case VariableType.AssociativeArray: case VariableType.Array: case VariableType.List: break; @@ -318,7 +318,7 @@ export enum VariableFlags { * Every type of variable supported by the protocol */ export enum VariableType { - AA = 'AA', + AssociativeArray = 'AssociativeArray', Array = 'Array', Boolean = 'Boolean', Double = 'Double', @@ -341,7 +341,7 @@ export enum VariableType { * An enum used to convert VariableType strings to their protocol integer value */ enum VariableTypeCode { - AA = 1, + AssociativeArray = 1, Array = 2, Boolean = 3, Double = 4, From 46292310442c2bf7134955fb6589a4f656fec666 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Mon, 27 Feb 2023 08:49:53 -0500 Subject: [PATCH 55/74] add new util.hasNonNullishProperty func --- src/util.spec.ts | 29 +++++++++++++++++++++++++---- src/util.ts | 9 +++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/util.spec.ts b/src/util.spec.ts index d363ca48..f0e5b2c7 100644 --- a/src/util.spec.ts +++ b/src/util.spec.ts @@ -17,6 +17,27 @@ beforeEach(() => { describe('Util', () => { + describe('hasNonNullishProperty', () => { + it('detects objects with only nullish props or no props at all', () => { + expect(util.hasNonNullishProperty({})).to.be.false; + expect(util.hasNonNullishProperty([])).to.be.false; + expect(util.hasNonNullishProperty(null)).to.be.false; + expect(util.hasNonNullishProperty(undefined)).to.be.false; + expect(util.hasNonNullishProperty(1 as any)).to.be.false; + expect(util.hasNonNullishProperty(true as any)).to.be.false; + expect(util.hasNonNullishProperty(/asdf/)).to.be.false; + expect(util.hasNonNullishProperty({ nullish: null })).to.be.false; + expect(util.hasNonNullishProperty({ nullish: undefined })).to.be.false; + }); + + it('detects objects with defined props', () => { + expect(util.hasNonNullishProperty({ val: true })).to.be.true; + expect(util.hasNonNullishProperty({ val: true, nullish: undefined })).to.be.true; + expect(util.hasNonNullishProperty({ val: false })).to.be.true; + expect(util.hasNonNullishProperty({ val: false, nullish: false })).to.be.true; + }); + }); + describe('isAssignableExpression', () => { it('works', () => { expect(util.isAssignableExpression('function test(): endFunction')).to.be.false; @@ -324,7 +345,7 @@ describe('Util', () => { 1* pkg:/components/MainScene.brs(6) STOP *selected - Brightscript Debugger> + Brightscript Debugger> `)) ).to.eql(dedent` ID Location Source Code @@ -332,7 +353,7 @@ describe('Util', () => { 1* pkg:/components/MainScene.brs(6) STOP *selected - Brightscript Debugger> + Brightscript Debugger> `); }); @@ -343,7 +364,7 @@ describe('Util', () => { 1* pkg:/components/MainScene.brs(6) STOP *selected - Brightscript Debugger> + Brightscript Debugger> `; expect( util.removeThreadAttachedText(text) @@ -353,7 +374,7 @@ describe('Util', () => { it('matches truncated file paths', () => { const text = ` Thread attached: ...Modules/MainMenu/MainMenu.brs(309) renderTracking = m.top.renderTracking - + Brightscript Debugger> `; expect( diff --git a/src/util.ts b/src/util.ts index b2907189..931acfb3 100644 --- a/src/util.ts +++ b/src/util.ts @@ -413,6 +413,15 @@ class Util { return value === undefined || value === null; } + /** + * Does the supplied value have at least one defined property with a non-nullish value? + */ + public hasNonNullishProperty(value: Record) { + return Object.values( + value ?? {} + ).some(x => !this.isNullish(x)); + } + private minPort = 1; public async getPort() { From a3c5d61f013d4292eb6bd5abea7c3098d46bb91d Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Mon, 27 Feb 2023 09:24:22 -0500 Subject: [PATCH 56/74] Load errorData for requests. Handle variablesResponse error data --- src/adapters/DebugProtocolAdapter.spec.ts | 8 +- src/adapters/DebugProtocolAdapter.ts | 8 +- src/debugProtocol/Constants.ts | 5 + src/debugProtocol/ProtocolUtil.ts | 50 ++++++- .../client/DebugProtocolClient.spec.ts | 127 +++++++++++++++++- .../client/DebugProtocolClient.ts | 80 ++++++++++- src/debugProtocol/events/ProtocolEvent.ts | 17 +++ .../responses/GenericV3Response.spec.ts | 34 +++++ .../events/responses/GenericV3Response.ts | 10 +- .../events/responses/VariablesResponse.ts | 16 +-- src/debugSession/BrightScriptDebugSession.ts | 20 ++- 11 files changed, 340 insertions(+), 35 deletions(-) diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index c98451b7..018ed1b0 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -3,15 +3,13 @@ import { expect } from 'chai'; import { DebugProtocolClient } from '../debugProtocol/client/DebugProtocolClient'; import { DebugProtocolAdapter, EvaluateContainer, KeyType } from './DebugProtocolAdapter'; import { createSandbox } from 'sinon'; -import { VariableType } from '../debugProtocol/events/responses/VariablesResponse'; -// eslint-disable-next-line @typescript-eslint/no-duplicate-imports -import { VariablesResponse } from '../debugProtocol/events/responses/VariablesResponse'; +import { VariableType, VariablesResponse } from '../debugProtocol/events/responses/VariablesResponse'; import { DebugProtocolServer } from '../debugProtocol/server/DebugProtocolServer'; import { util } from '../util'; import { standardizePath as s } from 'brighterscript'; import { DebugProtocolServerTestPlugin } from '../debugProtocol/DebugProtocolServerTestPlugin.spec'; import { AllThreadsStoppedUpdate } from '../debugProtocol/events/updates/AllThreadsStoppedUpdate'; -import { StopReason } from '../debugProtocol/Constants'; +import { ErrorCode, StopReason } from '../debugProtocol/Constants'; import { ThreadsResponse } from '../debugProtocol/events/responses/ThreadsResponse'; import { StackTraceV3Response } from '../debugProtocol/events/responses/StackTraceV3Response'; import { AddBreakpointsResponse } from '../debugProtocol/events/responses/AddBreakpointsResponse'; @@ -22,7 +20,7 @@ import { Project, ProjectManager } from '../managers/ProjectManager'; import { AddBreakpointsRequest } from '../debugProtocol/events/requests/AddBreakpointsRequest'; import { AddConditionalBreakpointsRequest } from '../debugProtocol/events/requests/AddConditionalBreakpointsRequest'; import { AddConditionalBreakpointsResponse } from '../debugProtocol/events/responses/AddConditionalBreakpointsResponse'; -import { HighLevelType } from '../interfaces'; +import { GenericV3Response } from '../debugProtocol/events/responses/GenericV3Response'; const sinon = createSandbox(); let cwd = s`${process.cwd()}`; diff --git a/src/adapters/DebugProtocolAdapter.ts b/src/adapters/DebugProtocolAdapter.ts index c7c0c25e..796e2160 100644 --- a/src/adapters/DebugProtocolAdapter.ts +++ b/src/adapters/DebugProtocolAdapter.ts @@ -537,7 +537,7 @@ export class DebugProtocolAdapter { public async getVariable(expression: string, frameId: number) { const response = await this.getVariablesResponse(expression, frameId); - if (response?.data?.errorCode === ErrorCode.OK && Array.isArray(response?.data?.variables)) { + if (Array.isArray(response?.data?.variables)) { const container = this.createEvaluateContainer( response.data.variables[0], //the name of the top container is the expression itself @@ -585,7 +585,7 @@ export class DebugProtocolAdapter { */ private createEvaluateContainer(variable: Variable, name: string, parentEvaluateName: string) { let value; - let variableType = variable.type as string; + let variableType = variable.type; if (variable.value === null) { value = 'roInvalid'; } else if (variableType === 'String') { @@ -597,8 +597,8 @@ export class DebugProtocolAdapter { if (variableType === VariableType.SubtypedObject) { //subtyped objects can only have string values let parts = (variable.value as string).split('; '); - variableType = `${parts[0]} (${parts[1]})`; - } else if (variableType === 'AA') { + (variableType as string) = `${parts[0]} (${parts[1]})`; + } else if (variableType === VariableType.AssociativeArray) { variableType = VariableType.AssociativeArray; } diff --git a/src/debugProtocol/Constants.ts b/src/debugProtocol/Constants.ts index 673ae150..dacc6ca8 100644 --- a/src/debugProtocol/Constants.ts +++ b/src/debugProtocol/Constants.ts @@ -116,6 +116,11 @@ export enum ErrorCode { INVALID_ARGS = 5 } +export enum ErrorFlags { + INVALID_VALUE_IN_PATH = 0x0001, + MISSING_KEY_IN_PATH = 0x0002 +} + export enum StopReason { /** * Uninitialized stopReason. diff --git a/src/debugProtocol/ProtocolUtil.ts b/src/debugProtocol/ProtocolUtil.ts index b1f42a2f..a7b81cca 100644 --- a/src/debugProtocol/ProtocolUtil.ts +++ b/src/debugProtocol/ProtocolUtil.ts @@ -1,6 +1,7 @@ import { SmartBuffer } from 'smart-buffer'; +import { util } from '../util'; import type { Command, UpdateType } from './Constants'; -import { CommandCode, UpdateTypeCode } from './Constants'; +import { CommandCode, UpdateTypeCode, ErrorCode, ErrorFlags } from './Constants'; import type { ProtocolRequest, ProtocolResponse, ProtocolUpdate } from './events/ProtocolEvent'; export class ProtocolUtil { @@ -56,10 +57,24 @@ export class ProtocolUtil { /** * Load the common DebuggerResponse */ - public loadCommonResponseFields(request: ProtocolResponse, smartBuffer: SmartBuffer) { - request.data.packetLength = smartBuffer.readUInt32LE(); // packet_length - request.data.requestId = smartBuffer.readUInt32LE(); // request_id - request.data.errorCode = smartBuffer.readUInt32LE(); // error_code + public loadCommonResponseFields(response: ProtocolResponse, smartBuffer: SmartBuffer) { + response.data.packetLength = smartBuffer.readUInt32LE(); // packet_length + response.data.requestId = smartBuffer.readUInt32LE(); // request_id + response.data.errorCode = smartBuffer.readUInt32LE(); // error_code + + //if the error code is non-zero, and we have more bytes, then there will be additional data about the error + if (response.data.errorCode !== ErrorCode.OK && response.data.packetLength > smartBuffer.readOffset) { + response.data.errorData = {}; + const errorFlags = smartBuffer.readUInt32LE(); // error_flags + // eslint-disable-next-line no-bitwise + if (errorFlags & ErrorFlags.INVALID_VALUE_IN_PATH) { + response.data.errorData.invalidPathIndex = smartBuffer.readUInt32LE(); // invalid_path_index + } + // eslint-disable-next-line no-bitwise + if (errorFlags & ErrorFlags.MISSING_KEY_IN_PATH) { + response.data.errorData.missingKeyIndex = smartBuffer.readUInt32LE(); // missing_key_index + } + } } public loadCommonUpdateFields(update: ProtocolUpdate, smartBuffer: SmartBuffer, updateType: UpdateType) { @@ -94,6 +109,31 @@ export class ProtocolUtil { } public insertCommonResponseFields(response: ProtocolResponse, smartBuffer: SmartBuffer) { + //insert error data + const flags = ( + // eslint-disable-next-line no-bitwise + 0 | + (util.isNullish(response?.data?.errorData?.invalidPathIndex) ? 0 : ErrorFlags.INVALID_VALUE_IN_PATH) | + (util.isNullish(response?.data?.errorData?.missingKeyIndex) ? 0 : ErrorFlags.MISSING_KEY_IN_PATH) + ); + if ( + response.data.errorCode !== ErrorCode.OK && + //there's some error data + Object.values(response.data.errorData ?? {}).some(x => !util.isNullish(x)) + ) { + //do these in reverse order since we're writing to the start of the buffer + + if (!util.isNullish(response.data.errorData.missingKeyIndex)) { + smartBuffer.insertUInt32LE(response.data.errorData.missingKeyIndex, 0); + } + //write error data + if (!util.isNullish(response.data.errorData.invalidPathIndex)) { + smartBuffer.insertUInt32LE(response.data.errorData.invalidPathIndex, 0); + } + + //write flags + smartBuffer.insertUInt32LE(flags, 0); + } smartBuffer.insertUInt32LE(response.data.errorCode, 0); // error_code smartBuffer.insertUInt32LE(response.data.requestId, 0); // request_id smartBuffer.insertUInt32LE(smartBuffer.writeOffset + 4, 0); // packet_length diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index e9bba806..58057e4d 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -9,7 +9,8 @@ import { HandshakeRequest } from '../events/requests/HandshakeRequest'; import { HandshakeResponse } from '../events/responses/HandshakeResponse'; import { HandshakeV3Response } from '../events/responses/HandshakeV3Response'; import { AllThreadsStoppedUpdate } from '../events/updates/AllThreadsStoppedUpdate'; -import { VariablesResponse } from '../events/responses/VariablesResponse'; +import type { Variable } from '../events/responses/VariablesResponse'; +import { VariablesResponse, VariableType } from '../events/responses/VariablesResponse'; import { VariablesRequest } from '../events/requests/VariablesRequest'; import { DebugProtocolServerTestPlugin } from '../DebugProtocolServerTestPlugin.spec'; import { ContinueRequest } from '../events/requests/ContinueRequest'; @@ -638,6 +639,130 @@ describe('DebugProtocolClient', () => { expect(plugin.latestRequest).not.instanceof(VariablesRequest); }); + it('returns `uninitialized` for never-defined leftmost variable', async () => { + await connect(); + + plugin.pushResponse( + GenericV3Response.fromJson({ + errorCode: ErrorCode.INVALID_ARGS, + requestId: 1, + errorData: { + missingKeyIndex: 0 + } + }) + ); + + //variable was never defined + const response = await client.getVariables(['notThere']); + expect(response.data.variables[0]).to.eql({ + name: 'notThere', + type: VariableType.Uninitialized, + value: null, + childCount: 0, + isConst: false, + isContainer: false, + refCount: 0 + } as Variable); + }); + + it('returns generic response when accessing a property on never-defined variable', async () => { + await connect(); + + plugin.pushResponse( + GenericV3Response.fromJson({ + errorCode: ErrorCode.INVALID_ARGS, + requestId: 1, + errorData: { + missingKeyIndex: 0 + } + }) + ); + + //getting prop from variable that was never defined + const response = await client.getVariables(['notThere', 'definitelyNotThere']); + expect(response.data.variables).not.to.exist; + }); + + it('returns `invalid` when accessing a property on a defined AA', async () => { + await connect(); + + plugin.pushResponse( + GenericV3Response.fromJson({ + errorCode: ErrorCode.INVALID_ARGS, + requestId: 1, + errorData: { + missingKeyIndex: 1 + } + }) + ); + + //getting prop from variable that was never defined + const response = await client.getVariables(['there', 'notThere']); + expect(response.data.variables[0]).to.eql({ + name: 'notThere', + type: VariableType.Invalid, + value: null, + childCount: 0, + isConst: false, + isContainer: false, + refCount: 0 + } as Variable); + }); + + it('returns generic response when accessing a property on a property that does not exist', async () => { + await connect(); + + plugin.pushResponse( + GenericV3Response.fromJson({ + errorCode: ErrorCode.INVALID_ARGS, + requestId: 1, + errorData: { + missingKeyIndex: 1 + } + }) + ); + + //getting prop from variable that was never defined + const response = await client.getVariables(['there', 'notThere', 'definitelyNotThere']); + expect(response.data.variables).not.to.exist; + }); + + it('returns generic response when accessing a property on a variable with the value of `invalid`', async () => { + await connect(); + + plugin.pushResponse( + GenericV3Response.fromJson({ + errorCode: ErrorCode.INVALID_ARGS, + requestId: 1, + errorData: { + invalidPathIndex: 0 + } + }) + ); + + //getting prop from variable that was never defined + const response = await client.getVariables(['setToInvalid', 'notThere']); + expect(response.data.variables).not.to.exist; + }); + + it('returns generic response when accessing a property on a property with the value of `invalid`', async () => { + await connect(); + + plugin.pushResponse( + GenericV3Response.fromJson({ + errorCode: ErrorCode.INVALID_ARGS, + requestId: 1, + errorData: { + invalidPathIndex: 1 + } + }) + ); + + //getting prop from variable that was never defined + const response = await client.getVariables(['someObj', 'somePropWithValueSetToInvalid', 'notThere']); + expect(response.data.variables).not.to.exist; + }); + it('honors protocol version when deciding to send forceCaseInsensitive variable information', async () => { await client.connect(); //send the AllThreadsStopped event, and also wait for the client to suspend diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 10fbb9c6..d6a78900 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -7,7 +7,7 @@ import { ExecuteV3Response } from '../events/responses/ExecuteV3Response'; import { ListBreakpointsResponse } from '../events/responses/ListBreakpointsResponse'; import { AddBreakpointsResponse } from '../events/responses/AddBreakpointsResponse'; import { RemoveBreakpointsResponse } from '../events/responses/RemoveBreakpointsResponse'; -import { util } from '../../util'; +import { defer, util } from '../../util'; import { BreakpointErrorUpdate } from '../events/updates/BreakpointErrorUpdate'; import { ContinueRequest } from '../events/requests/ContinueRequest'; import { StopRequest } from '../events/requests/StopRequest'; @@ -31,7 +31,8 @@ import { CompileErrorUpdate } from '../events/updates/CompileErrorUpdate'; import { GenericResponse } from '../events/responses/GenericResponse'; import type { StackTraceResponse } from '../events/responses/StackTraceResponse'; import { ThreadsResponse } from '../events/responses/ThreadsResponse'; -import { VariablesResponse } from '../events/responses/VariablesResponse'; +import type { Variable } from '../events/responses/VariablesResponse'; +import { VariablesResponse, VariablesResponseData, VariableType } from '../events/responses/VariablesResponse'; import { IOPortOpenedUpdate, isIOPortOpenedUpdate } from '../events/updates/IOPortOpenedUpdate'; import { ThreadAttachedUpdate } from '../events/updates/ThreadAttachedUpdate'; import { StackTraceV3Response } from '../events/responses/StackTraceV3Response'; @@ -456,6 +457,81 @@ export class DebugProtocolClient { enableForceCaseInsensitivity: semver.satisfies(this.protocolVersion, '>=3.1.0') && variablePathEntries.length > 0 }) ); + + //if there was an issue, build a "fake" variables response for several known situations + if (util.hasNonNullishProperty(response.data.errorData)) { + let variable = { + value: null, + isContainer: false, + isConst: false, + refCount: 0, + childCount: 0 + } as Variable; + const simulatedResponse = VariablesResponse.fromJson({ + ...response.data, + variables: [variable] + }); + + if (!util.isNullish(response.data.errorData.missingKeyIndex)) { + const { missingKeyIndex } = response.data.errorData; + //leftmost var is uninitialized, tried to read it + //ex: variablePathEntries = [`notThere`] + if (variablePathEntries.length === 1 && missingKeyIndex === 0) { + variable.name = variablePathEntries[0]; + variable.type = VariableType.Uninitialized; + return simulatedResponse; + } + + //TODO improve this logic once we know the TYPE of the var at missingKeyIndex-1 + // //leftmost var was uninitialized, and tried to read a prop on it + // //ex: variablePathEntries = ["notThere", "definitelyNotThere"] + // if (missingKeyIndex === 0 && variablePathEntries.length > 1) { + // throw new Error(`Cannot read property '${variablePathEntries[missingKeyIndex + 1]}' of uninitialized`); + // } + + + // // prop at the end doesn't exist. Treat like `invalid`. + // // ex: variablePathEntries = ['there', 'notThere'] + // if (missingKeyIndex === variablePathEntries.length - 1) { + // variable.name = variablePathEntries[variablePathEntries.length - 1]; + // variable.type = VariableType.Invalid; + // return simulatedResponse; + // } + + //prop in the middle is missing, tried reading a prop on it + // ex: variablePathEntries = ["there", "notThere", "definitelyNotThere"] + throw new Error(`Cannot read property '${variablePathEntries[missingKeyIndex]}'`); + } + + if (!util.isNullish(response.data.errorData.invalidPathIndex)) { + const { invalidPathIndex } = response.data.errorData; + + //leftmost var is literal `invalid`, tried to read it + if (variablePathEntries.length === 1 && invalidPathIndex === 0) { + variable.name = variablePathEntries[variablePathEntries.length - 1]; + variable.type = VariableType.Invalid; + return simulatedResponse; + } + + //TODO improve this logic once we know the TYPE of the var at invalidPathIndex-1 + // //leftmost var is set to literal `invalid`, tried to read prop + // if (invalidPathIndex === 0 && variablePathEntries.length > 1) { + // throw new Error(`Cannot read property '${variablePathEntries[invalidPathIndex + 1]}' of invalid`); + // } + + // // prop at the end doesn't exist. Treat like `invalid`. + // // ex: variablePathEntries = ['there', 'notThere'] + // if (invalidPathIndex === variablePathEntries.length - 1) { + // variable.name = variablePathEntries[variablePathEntries.length - 1]; + // variable.type = VariableType.Invalid; + // return simulatedResponse; + // } + + //prop in the middle is missing, tried reading a prop on it + // ex: variablePathEntries = ["there", "notThere", "definitelyNotThere"] + throw new Error(`Cannot read property '${variablePathEntries[invalidPathIndex]}'`); + } + } return response; } diff --git a/src/debugProtocol/events/ProtocolEvent.ts b/src/debugProtocol/events/ProtocolEvent.ts index abfd7cf9..3363a59a 100644 --- a/src/debugProtocol/events/ProtocolEvent.ts +++ b/src/debugProtocol/events/ProtocolEvent.ts @@ -52,6 +52,23 @@ export interface ProtocolResponseData { packetLength: number; requestId: number; errorCode: number; + /** + * Data included whenever errorCode > 0. + * @since OS 11.5 + */ + errorData?: ProtocolResponseErrorData; +} +export interface ProtocolResponseErrorData { + /** + * The index of the element in the requested path that exists, but has invalid or unknown value. + * (applies to `VariablesResponse`) + */ + invalidPathIndex?: number; + /** + * The index of the element in path that was not found + * (applies to `VariablesResponse`) + */ + missingKeyIndex?: number; } export type ProtocolResponse = ProtocolEvent; diff --git a/src/debugProtocol/events/responses/GenericV3Response.spec.ts b/src/debugProtocol/events/responses/GenericV3Response.spec.ts index f5edd363..e88b28d5 100644 --- a/src/debugProtocol/events/responses/GenericV3Response.spec.ts +++ b/src/debugProtocol/events/responses/GenericV3Response.spec.ts @@ -57,4 +57,38 @@ describe('GenericV3Response', () => { requestId: 3 // 4 bytes }); }); + + it('includes error data', () => { + const response = GenericV3Response.fromJson({ + requestId: 3, + errorCode: ErrorCode.INVALID_ARGS, + errorData: { + invalidPathIndex: 1, + missingKeyIndex: 2 + } + }); + + expect(response.data).to.eql({ + packetLength: undefined, + errorCode: ErrorCode.INVALID_ARGS, + requestId: 3, + errorData: { + invalidPathIndex: 1, + missingKeyIndex: 2 + } + }); + + expect( + GenericV3Response.fromBuffer(response.toBuffer()).data + ).to.eql({ + packetLength: 24, // 4 bytes + errorCode: ErrorCode.INVALID_ARGS, // 4 bytes + requestId: 3, // 4 bytes + //error_flags // 4 bytes + errorData: { + invalidPathIndex: 1, // 4 bytes + missingKeyIndex: 2 // 4 bytes + } + }); + }); }); diff --git a/src/debugProtocol/events/responses/GenericV3Response.ts b/src/debugProtocol/events/responses/GenericV3Response.ts index 3b5891a7..86d014f3 100644 --- a/src/debugProtocol/events/responses/GenericV3Response.ts +++ b/src/debugProtocol/events/responses/GenericV3Response.ts @@ -1,11 +1,13 @@ import { SmartBuffer } from 'smart-buffer'; import type { ErrorCode } from '../../Constants'; import { protocolUtil } from '../../ProtocolUtil'; +import type { ProtocolResponse, ProtocolResponseData, ProtocolResponseErrorData } from '../ProtocolEvent'; -export class GenericV3Response { +export class GenericV3Response implements ProtocolResponse { public static fromJson(data: { requestId: number; errorCode: ErrorCode; + errorData?: ProtocolResponseErrorData; }) { const response = new GenericV3Response(); protocolUtil.loadJson(response, data); @@ -15,9 +17,7 @@ export class GenericV3Response { public static fromBuffer(buffer: Buffer) { const response = new GenericV3Response(); protocolUtil.bufferLoaderHelper(response, buffer, 12, (smartBuffer: SmartBuffer) => { - response.data.packetLength = smartBuffer.readUInt32LE(); // packet_length - response.data.requestId = smartBuffer.readUInt32LE(); // request_id - response.data.errorCode = smartBuffer.readUInt32LE(); // error_code + protocolUtil.loadCommonResponseFields(response, smartBuffer); //this is a generic response, so we don't actually know what the rest of the payload is. //so just consume the rest of the payload as throwaway data @@ -40,5 +40,5 @@ export class GenericV3Response { packetLength: undefined as number, requestId: Number.MAX_SAFE_INTEGER, errorCode: undefined as ErrorCode - }; + } as ProtocolResponseData; } diff --git a/src/debugProtocol/events/responses/VariablesResponse.ts b/src/debugProtocol/events/responses/VariablesResponse.ts index 73e6c523..f4c5646c 100644 --- a/src/debugProtocol/events/responses/VariablesResponse.ts +++ b/src/debugProtocol/events/responses/VariablesResponse.ts @@ -3,12 +3,14 @@ import { SmartBuffer } from 'smart-buffer'; import { util } from '../../../util'; import { ErrorCode } from '../../Constants'; import { protocolUtil } from '../../ProtocolUtil'; +import type { ProtocolResponse, ProtocolResponseData, ProtocolResponseErrorData } from '../ProtocolEvent'; -export class VariablesResponse { +export class VariablesResponse implements ProtocolResponse { public static fromJson(data: { requestId: number; variables: Variable[]; + errorData?: ProtocolResponseErrorData; }) { const response = new VariablesResponse(); protocolUtil.loadJson(response, data); @@ -271,14 +273,10 @@ export class VariablesResponse { public readOffset = 0; - public data = { - variables: undefined as Variable[], - - // response fields - packetLength: undefined as number, - requestId: undefined as number, - errorCode: ErrorCode.OK - }; + public data = {} as VariablesResponseData; +} +export interface VariablesResponseData extends ProtocolResponseData { + variables: Variable[]; } export enum VariableFlags { diff --git a/src/debugSession/BrightScriptDebugSession.ts b/src/debugSession/BrightScriptDebugSession.ts index 0cd3e05e..7ac02d39 100644 --- a/src/debugSession/BrightScriptDebugSession.ts +++ b/src/debugSession/BrightScriptDebugSession.ts @@ -7,6 +7,7 @@ import type { RokuDeploy, RokuDeployOptions } from 'roku-deploy'; import { BreakpointEvent, DebugSession as BaseDebugSession, + ErrorDestination, Handles, InitializedEvent, InvalidatedEvent, @@ -944,7 +945,8 @@ export class BrightScriptDebugSession extends BaseDebugSession { let varIndex = this.getNextVarIndex(args.frameId); let arrayVarName = this.tempVarPrefix + 'eval'; if (varIndex === 0) { - await this.rokuAdapter.evaluate(`${arrayVarName} = []`, args.frameId); + const response = await this.rokuAdapter.evaluate(`${arrayVarName} = []`, args.frameId); + console.log(response); } let statement = `${arrayVarName}[${varIndex}] = ${args.expression}`; args.expression = `${arrayVarName}[${varIndex}]`; @@ -962,8 +964,9 @@ export class BrightScriptDebugSession extends BaseDebugSession { } else { let result = await this.rokuAdapter.getVariable(args.expression, args.frameId); if (!result) { - throw new Error(`bad variable request "${args.expression}"`); + throw new Error('Error: unable to evaluate expression'); } + v = this.getVariableFromResult(result, args.frameId); //TODO - testing something, remove later // eslint-disable-next-line camelcase @@ -1008,8 +1011,9 @@ export class BrightScriptDebugSession extends BaseDebugSession { } } catch (error) { this.logger.error('Error during variables request', error); + response.success = false; + response.message = error?.message ?? error; } - // try { this.sendResponse(response); } catch { } @@ -1158,7 +1162,15 @@ export class BrightScriptDebugSession extends BaseDebugSession { v = new Variable(result.name, result.type, refId, 0, result.elementCount); } } else { - v = new Variable(result.name, `${result.value}`); + let value: string; + if (result.type === VariableType.Invalid) { + value = 'Invalid'; + } else if (result.type === VariableType.Uninitialized) { + value = 'Uninitialized'; + } else { + value = `${result.value}`; + } + v = new Variable(result.name, value); } this.variables[refId] = v; } else { From b9daa89219cdab10dfbdd8125febf2e0da5b5e82 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Mon, 27 Feb 2023 10:58:14 -0500 Subject: [PATCH 57/74] Improve bad variable presentations --- .../client/DebugProtocolClient.ts | 84 ++++++++++++------- src/debugSession/BrightScriptDebugSession.ts | 2 +- 2 files changed, 54 insertions(+), 32 deletions(-) diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index d6a78900..6b408c2a 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -458,7 +458,7 @@ export class DebugProtocolClient { }) ); - //if there was an issue, build a "fake" variables response for several known situations + //if there was an issue, build a "fake" variables response for several known situationsm or throw nicer errors if (util.hasNonNullishProperty(response.data.errorData)) { let variable = { value: null, @@ -472,9 +472,20 @@ export class DebugProtocolClient { variables: [variable] }); + const getParentVarType = async () => { + //fetch the variable one level back to get its type + const parentVar = await this.getVariables( + variablePathEntries.slice(0, -1), + stackFrameIndex, + threadIndex + ); + return parentVar?.data?.variables?.[0]?.type; + }; + let parentVarType: VariableType; + if (!util.isNullish(response.data.errorData.missingKeyIndex)) { const { missingKeyIndex } = response.data.errorData; - //leftmost var is uninitialized, tried to read it + //leftmost var is uninitialized, and we tried to read it //ex: variablePathEntries = [`notThere`] if (variablePathEntries.length === 1 && missingKeyIndex === 0) { variable.name = variablePathEntries[0]; @@ -482,25 +493,29 @@ export class DebugProtocolClient { return simulatedResponse; } - //TODO improve this logic once we know the TYPE of the var at missingKeyIndex-1 - // //leftmost var was uninitialized, and tried to read a prop on it - // //ex: variablePathEntries = ["notThere", "definitelyNotThere"] - // if (missingKeyIndex === 0 && variablePathEntries.length > 1) { - // throw new Error(`Cannot read property '${variablePathEntries[missingKeyIndex + 1]}' of uninitialized`); - // } - - - // // prop at the end doesn't exist. Treat like `invalid`. - // // ex: variablePathEntries = ['there', 'notThere'] - // if (missingKeyIndex === variablePathEntries.length - 1) { - // variable.name = variablePathEntries[variablePathEntries.length - 1]; - // variable.type = VariableType.Invalid; - // return simulatedResponse; - // } + if (variablePathEntries.length > 1) { + parentVarType = await getParentVarType(); + //leftmost var was uninitialized, and tried to read a prop on it + //ex: variablePathEntries = ["notThere", "definitelyNotThere"] + if (missingKeyIndex === 0 && variablePathEntries.length > 1) { + throw new Error(`Cannot read '${variablePathEntries[missingKeyIndex + 1]}' on type '${parentVarType}'`); + } + // prop at the end of Node or AA doesn't exist. Treat like `invalid`. + // ex: variablePathEntries = ['there', 'notThere'] + if ( + missingKeyIndex === variablePathEntries.length - 1 && + [VariableType.AssociativeArray, VariableType.SubtypedObject].includes(parentVarType) + ) { + variable.name = variablePathEntries[variablePathEntries.length - 1]; + variable.type = VariableType.Invalid; + variable.value = 'Invalid (not defined)'; + return simulatedResponse; + } + } //prop in the middle is missing, tried reading a prop on it // ex: variablePathEntries = ["there", "notThere", "definitelyNotThere"] - throw new Error(`Cannot read property '${variablePathEntries[missingKeyIndex]}'`); + throw new Error(`Cannot read '${variablePathEntries[missingKeyIndex]}'${parentVarType ? ` on type '${parentVarType}'` : ''}`); } if (!util.isNullish(response.data.errorData.invalidPathIndex)) { @@ -513,23 +528,30 @@ export class DebugProtocolClient { return simulatedResponse; } - //TODO improve this logic once we know the TYPE of the var at invalidPathIndex-1 - // //leftmost var is set to literal `invalid`, tried to read prop - // if (invalidPathIndex === 0 && variablePathEntries.length > 1) { - // throw new Error(`Cannot read property '${variablePathEntries[invalidPathIndex + 1]}' of invalid`); - // } + if (variablePathEntries.length > 1) { + parentVarType = await getParentVarType(); - // // prop at the end doesn't exist. Treat like `invalid`. - // // ex: variablePathEntries = ['there', 'notThere'] - // if (invalidPathIndex === variablePathEntries.length - 1) { - // variable.name = variablePathEntries[variablePathEntries.length - 1]; - // variable.type = VariableType.Invalid; - // return simulatedResponse; - // } + //leftmost var is set to literal `invalid`, tried to read prop + if (invalidPathIndex === 0 && variablePathEntries.length > 1) { + throw new Error(`Cannot read '${variablePathEntries[invalidPathIndex + 1]}' on type '${parentVarType}'`); + } + // prop at the end doesn't exist. Treat like `invalid`. + // ex: variablePathEntries = ['there', 'notThere'] + if ( + invalidPathIndex === variablePathEntries.length - 1 && + [VariableType.AssociativeArray, VariableType.SubtypedObject].includes(parentVarType) + ) { + variable.name = variablePathEntries[variablePathEntries.length - 1]; + variable.type = VariableType.Invalid; + variable.value = 'Invalid (not defined)'; + return simulatedResponse; + } + } //prop in the middle is missing, tried reading a prop on it // ex: variablePathEntries = ["there", "notThere", "definitelyNotThere"] - throw new Error(`Cannot read property '${variablePathEntries[invalidPathIndex]}'`); + throw new Error(`Cannot read '${variablePathEntries[invalidPathIndex]}'${parentVarType ? ` on type '${parentVarType}'` : ''}`); + } } return response; diff --git a/src/debugSession/BrightScriptDebugSession.ts b/src/debugSession/BrightScriptDebugSession.ts index 7ac02d39..5f5d904f 100644 --- a/src/debugSession/BrightScriptDebugSession.ts +++ b/src/debugSession/BrightScriptDebugSession.ts @@ -1164,7 +1164,7 @@ export class BrightScriptDebugSession extends BaseDebugSession { } else { let value: string; if (result.type === VariableType.Invalid) { - value = 'Invalid'; + value = result.value ?? 'Invalid'; } else if (result.type === VariableType.Uninitialized) { value = 'Uninitialized'; } else { From 6aaf7e29cadf1b59621076111da7674b8e5ca360 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Mon, 27 Feb 2023 15:21:03 -0500 Subject: [PATCH 58/74] Fix tests for invalid/uninitialized vals --- .../client/DebugProtocolClient.spec.ts | 96 ++++++++++++++++--- .../client/DebugProtocolClient.ts | 18 ++-- .../events/responses/VariablesResponse.ts | 5 +- 3 files changed, 99 insertions(+), 20 deletions(-) diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index 58057e4d..dafcfd22 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -31,11 +31,12 @@ import { ListBreakpointsRequest } from '../events/requests/ListBreakpointsReques import { ListBreakpointsResponse } from '../events/responses/ListBreakpointsResponse'; import { RemoveBreakpointsResponse } from '../events/responses/RemoveBreakpointsResponse'; import { RemoveBreakpointsRequest } from '../events/requests/RemoveBreakpointsRequest'; -import { expectThrowsAsync } from '../../testHelpers.spec'; +import { expectThrows, expectThrowsAsync } from '../../testHelpers.spec'; import { StackTraceV3Response } from '../events/responses/StackTraceV3Response'; import { IOPortOpenedUpdate } from '../events/updates/IOPortOpenedUpdate'; import * as Net from 'net'; import { ThreadAttachedUpdate } from '../events/updates/ThreadAttachedUpdate'; +import { KeyType } from '../../adapters/DebugProtocolAdapter'; process.on('uncaughtException', (err) => console.log('node js process error\n', err)); const sinon = createSandbox(); @@ -678,9 +679,27 @@ describe('DebugProtocolClient', () => { }) ); + //another response for the "go one level up to get type info" request + plugin.pushResponse( + VariablesResponse.fromJson({ + requestId: 1, + variables: [{ + name: 'someObj', + type: VariableType.Uninitialized, + isConst: false, + isContainer: true, + refCount: 1, + value: undefined, + childCount: 2, + keyType: VariableType.String + }] + }) + ); + //getting prop from variable that was never defined - const response = await client.getVariables(['notThere', 'definitelyNotThere']); - expect(response.data.variables).not.to.exist; + await expectThrowsAsync(async () => { + await client.getVariables(['notThere', 'definitelyNotThere']); + }, `Cannot read 'definitelyNotThere' on type 'Uninitialized'`); }); it('returns `invalid` when accessing a property on a defined AA', async () => { @@ -696,12 +715,29 @@ describe('DebugProtocolClient', () => { }) ); + //another response for the "go one level up to get type info" request + plugin.pushResponse( + VariablesResponse.fromJson({ + requestId: 1, + variables: [{ + name: 'there', + type: VariableType.AssociativeArray, + isConst: false, + isContainer: true, + refCount: 1, + value: undefined, + childCount: 2, + keyType: VariableType.String + }] + }) + ); + //getting prop from variable that was never defined const response = await client.getVariables(['there', 'notThere']); expect(response.data.variables[0]).to.eql({ name: 'notThere', type: VariableType.Invalid, - value: null, + value: 'Invalid (not defined)', childCount: 0, isConst: false, isContainer: false, @@ -722,9 +758,27 @@ describe('DebugProtocolClient', () => { }) ); + //another response for the "go one level up to get type info" request + plugin.pushResponse( + VariablesResponse.fromJson({ + requestId: 1, + variables: [{ + name: 'notThere', + type: VariableType.Invalid, + isConst: false, + isContainer: false, + refCount: 1, + value: undefined + }] + }) + ); + //getting prop from variable that was never defined - const response = await client.getVariables(['there', 'notThere', 'definitelyNotThere']); - expect(response.data.variables).not.to.exist; + + //getting prop from variable that was assigned to invalid (i.e. `setToInvalid = invalid`) + await expectThrowsAsync(async () => { + await client.getVariables(['there', 'notThere', 'definitelyNotThere']); + }, `Cannot read 'notThere' on type 'Invalid'`); }); it('returns generic response when accessing a property on a variable with the value of `invalid`', async () => { @@ -740,14 +794,16 @@ describe('DebugProtocolClient', () => { }) ); - //getting prop from variable that was never defined - const response = await client.getVariables(['setToInvalid', 'notThere']); - expect(response.data.variables).not.to.exist; + //getting prop from variable that was assigned to invalid (i.e. `setToInvalid = invalid`) + await expectThrowsAsync(async () => { + await client.getVariables(['setToInvalid', 'notThere']); + }, `Cannot read 'setToInvalid'`); }); it('returns generic response when accessing a property on a property with the value of `invalid`', async () => { await connect(); + //the initial response plugin.pushResponse( GenericV3Response.fromJson({ errorCode: ErrorCode.INVALID_ARGS, @@ -758,9 +814,27 @@ describe('DebugProtocolClient', () => { }) ); + //another response for the "go one level up to get type info" request + plugin.pushResponse( + VariablesResponse.fromJson({ + requestId: 1, + variables: [{ + name: 'someObj', + type: VariableType.AssociativeArray, + isConst: false, + isContainer: true, + refCount: 1, + value: undefined, + childCount: 2, + keyType: VariableType.String + }] + }) + ); + //getting prop from variable that was never defined - const response = await client.getVariables(['someObj', 'somePropWithValueSetToInvalid', 'notThere']); - expect(response.data.variables).not.to.exist; + await expectThrowsAsync(async () => { + await client.getVariables(['someObj', 'somePropWithValueSetToInvalid', 'notThere']); + }, `Cannot read 'somePropWithValueSetToInvalid' on type 'AssociativeArray'`); }); it('honors protocol version when deciding to send forceCaseInsensitive variable information', async () => { diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 6b408c2a..2403d0bc 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -459,7 +459,7 @@ export class DebugProtocolClient { ); //if there was an issue, build a "fake" variables response for several known situationsm or throw nicer errors - if (util.hasNonNullishProperty(response.data.errorData)) { + if (util.hasNonNullishProperty(response?.data.errorData)) { let variable = { value: null, isContainer: false, @@ -493,13 +493,15 @@ export class DebugProtocolClient { return simulatedResponse; } - if (variablePathEntries.length > 1) { + //leftmost var was uninitialized, and tried to read a prop on it + //ex: variablePathEntries = ["notThere", "definitelyNotThere"] + if (missingKeyIndex === 0 && variablePathEntries.length > 1) { + throw new Error(`Cannot read '${variablePathEntries[missingKeyIndex + 1]}' on type 'Uninitialized'`); + } + + if (variablePathEntries.length > 1 && missingKeyIndex > 0) { parentVarType = await getParentVarType(); - //leftmost var was uninitialized, and tried to read a prop on it - //ex: variablePathEntries = ["notThere", "definitelyNotThere"] - if (missingKeyIndex === 0 && variablePathEntries.length > 1) { - throw new Error(`Cannot read '${variablePathEntries[missingKeyIndex + 1]}' on type '${parentVarType}'`); - } + // prop at the end of Node or AA doesn't exist. Treat like `invalid`. // ex: variablePathEntries = ['there', 'notThere'] @@ -528,7 +530,7 @@ export class DebugProtocolClient { return simulatedResponse; } - if (variablePathEntries.length > 1) { + if (variablePathEntries.length > 1 && invalidPathIndex > 0) { parentVarType = await getParentVarType(); //leftmost var is set to literal `invalid`, tried to read prop diff --git a/src/debugProtocol/events/responses/VariablesResponse.ts b/src/debugProtocol/events/responses/VariablesResponse.ts index f4c5646c..f8d02777 100644 --- a/src/debugProtocol/events/responses/VariablesResponse.ts +++ b/src/debugProtocol/events/responses/VariablesResponse.ts @@ -273,7 +273,10 @@ export class VariablesResponse implements ProtocolResponse { public readOffset = 0; - public data = {} as VariablesResponseData; + public data = { + packetLength: undefined as number, + errorCode: ErrorCode.OK + } as VariablesResponseData; } export interface VariablesResponseData extends ProtocolResponseData { variables: Variable[]; From 258a4dcb2f57baca0e44318df28fcffc27adb1ca Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Mon, 27 Feb 2023 16:12:26 -0500 Subject: [PATCH 59/74] Fix parent var type lookup --- .../DebugProtocolServerTestPlugin.spec.ts | 4 +- .../client/DebugProtocolClient.spec.ts | 41 ++++++++++++++++++- .../client/DebugProtocolClient.ts | 10 ++--- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts b/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts index 5945c5e6..c52ec710 100644 --- a/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts +++ b/src/debugProtocol/DebugProtocolServerTestPlugin.spec.ts @@ -52,12 +52,12 @@ export class DebugProtocolServerTestPlugin implements ProtocolServerPlugin { /** * Get the request at the specified index. Negative indexes count back from the last item in the array */ - public getRequest(index: number) { + public getRequest(index: number) { if (index < 0) { //add the negative index to the length to "subtract" from the end index = this.requests.length + index; } - return this.requests[index]; + return this.requests[index] as T; } /** diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index dafcfd22..afcac6df 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -773,12 +773,49 @@ describe('DebugProtocolClient', () => { }) ); - //getting prop from variable that was never defined - //getting prop from variable that was assigned to invalid (i.e. `setToInvalid = invalid`) await expectThrowsAsync(async () => { await client.getVariables(['there', 'notThere', 'definitelyNotThere']); }, `Cannot read 'notThere' on type 'Invalid'`); + + //make sure we requested the correct variable + expect(plugin.getRequest(-1).data.variablePathEntries.map(x => x.name)).to.eql(['there']); + }); + + it('returns generic response when accessing a property on a property that does not exist in the middle', async () => { + await connect(); + + plugin.pushResponse( + GenericV3Response.fromJson({ + errorCode: ErrorCode.INVALID_ARGS, + requestId: 1, + errorData: { + missingKeyIndex: 1 + } + }) + ); + + //another response for the "go one level up to get type info" request + plugin.pushResponse( + VariablesResponse.fromJson({ + requestId: 1, + variables: [{ + name: 'notThere', + type: VariableType.Invalid, + isConst: false, + isContainer: false, + refCount: 1, + value: undefined + }] + }) + ); + //getting prop from variable that was assigned to invalid (i.e. `setToInvalid = invalid`) + await expectThrowsAsync(async () => { + await client.getVariables(['there', 'notThere', 'definitelyNotThere', 'reallyNotThere']); + }, `Cannot read 'notThere' on type 'Invalid'`); + + //make sure we requested the correct variable + expect(plugin.getRequest(-1).data.variablePathEntries.map(x => x.name)).to.eql(['there']); }); it('returns generic response when accessing a property on a variable with the value of `invalid`', async () => { diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 2403d0bc..437e7026 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -472,10 +472,10 @@ export class DebugProtocolClient { variables: [variable] }); - const getParentVarType = async () => { - //fetch the variable one level back to get its type + const getParentVarType = async (index: number) => { + //fetch the variable one level back from the bad one to get its type const parentVar = await this.getVariables( - variablePathEntries.slice(0, -1), + variablePathEntries.slice(0, index), stackFrameIndex, threadIndex ); @@ -500,7 +500,7 @@ export class DebugProtocolClient { } if (variablePathEntries.length > 1 && missingKeyIndex > 0) { - parentVarType = await getParentVarType(); + parentVarType = await getParentVarType(missingKeyIndex); // prop at the end of Node or AA doesn't exist. Treat like `invalid`. @@ -531,7 +531,7 @@ export class DebugProtocolClient { } if (variablePathEntries.length > 1 && invalidPathIndex > 0) { - parentVarType = await getParentVarType(); + parentVarType = await getParentVarType(invalidPathIndex); //leftmost var is set to literal `invalid`, tried to read prop if (invalidPathIndex === 0 && variablePathEntries.length > 1) { From bb51a5c851ad0525b375536c3c35b4f48c322c49 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Mon, 27 Feb 2023 16:39:56 -0500 Subject: [PATCH 60/74] Fixed wording for subtyped objects. --- .../client/DebugProtocolClient.ts | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 437e7026..a4cd7c07 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -472,16 +472,23 @@ export class DebugProtocolClient { variables: [variable] }); - const getParentVarType = async (index: number) => { + let parentVarType: VariableType; + let parentVarTypeText: string; + const loadParentVarInfo = async (index: number) => { //fetch the variable one level back from the bad one to get its type const parentVar = await this.getVariables( variablePathEntries.slice(0, index), stackFrameIndex, threadIndex ); - return parentVar?.data?.variables?.[0]?.type; + parentVarType = parentVar?.data?.variables?.[0]?.type; + parentVarTypeText = parentVarType; + //convert `roSGNode; Node` to `roSGNode (Node)` + if (parentVarType === VariableType.SubtypedObject) { + const chunks = parentVar?.data?.variables?.[0]?.value?.toString().split(';').map(x => x.trim()); + parentVarTypeText = `${chunks[0]} (${chunks[1]})`; + } }; - let parentVarType: VariableType; if (!util.isNullish(response.data.errorData.missingKeyIndex)) { const { missingKeyIndex } = response.data.errorData; @@ -500,8 +507,7 @@ export class DebugProtocolClient { } if (variablePathEntries.length > 1 && missingKeyIndex > 0) { - parentVarType = await getParentVarType(missingKeyIndex); - + await loadParentVarInfo(missingKeyIndex); // prop at the end of Node or AA doesn't exist. Treat like `invalid`. // ex: variablePathEntries = ['there', 'notThere'] @@ -517,7 +523,7 @@ export class DebugProtocolClient { } //prop in the middle is missing, tried reading a prop on it // ex: variablePathEntries = ["there", "notThere", "definitelyNotThere"] - throw new Error(`Cannot read '${variablePathEntries[missingKeyIndex]}'${parentVarType ? ` on type '${parentVarType}'` : ''}`); + throw new Error(`Cannot read '${variablePathEntries[missingKeyIndex]}'${parentVarType ? ` on type '${parentVarTypeText}'` : ''}`); } if (!util.isNullish(response.data.errorData.invalidPathIndex)) { @@ -531,11 +537,11 @@ export class DebugProtocolClient { } if (variablePathEntries.length > 1 && invalidPathIndex > 0) { - parentVarType = await getParentVarType(invalidPathIndex); + await loadParentVarInfo(invalidPathIndex); //leftmost var is set to literal `invalid`, tried to read prop if (invalidPathIndex === 0 && variablePathEntries.length > 1) { - throw new Error(`Cannot read '${variablePathEntries[invalidPathIndex + 1]}' on type '${parentVarType}'`); + throw new Error(`Cannot read '${variablePathEntries[invalidPathIndex + 1]}' on type '${parentVarTypeText}'`); } // prop at the end doesn't exist. Treat like `invalid`. @@ -552,7 +558,7 @@ export class DebugProtocolClient { } //prop in the middle is missing, tried reading a prop on it // ex: variablePathEntries = ["there", "notThere", "definitelyNotThere"] - throw new Error(`Cannot read '${variablePathEntries[invalidPathIndex]}'${parentVarType ? ` on type '${parentVarType}'` : ''}`); + throw new Error(`Cannot read '${variablePathEntries[invalidPathIndex]}'${parentVarType ? ` on type '${parentVarTypeText}'` : ''}`); } } From 5cc22ee11782966c4056187d2785897e23d40af4 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Tue, 28 Feb 2023 09:34:20 -0500 Subject: [PATCH 61/74] Add better test coverage --- .../client/DebugProtocolClient.spec.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index afcac6df..7a5a0f5e 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -818,6 +818,42 @@ describe('DebugProtocolClient', () => { expect(plugin.getRequest(-1).data.variablePathEntries.map(x => x.name)).to.eql(['there']); }); + it('shows node and subtype for failed prop access', async () => { + await connect(); + + plugin.pushResponse( + GenericV3Response.fromJson({ + errorCode: ErrorCode.INVALID_ARGS, + requestId: 1, + errorData: { + missingKeyIndex: 1 + } + }) + ); + + //another response for the "go one level up to get type info" request + plugin.pushResponse( + VariablesResponse.fromJson({ + requestId: 1, + variables: [{ + name: 'notThere', + type: VariableType.SubtypedObject, + isConst: false, + isContainer: false, + refCount: 1, + value: 'roSGNode; Node' + }] + }) + ); + //getting prop from variable that was assigned to invalid (i.e. `setToInvalid = invalid`) + await expectThrowsAsync(async () => { + await client.getVariables(['there', 'notThere', 'definitelyNotThere', 'reallyNotThere']); + }, `Cannot read 'notThere' on type 'roSGNode (Node)'`); + + //make sure we requested the correct variable + expect(plugin.getRequest(-1).data.variablePathEntries.map(x => x.name)).to.eql(['there']); + }); + it('returns generic response when accessing a property on a variable with the value of `invalid`', async () => { await connect(); From 78c633a483824b0e2407ad1758e6042f59d95bf7 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Tue, 28 Feb 2023 11:59:52 -0500 Subject: [PATCH 62/74] Fix issues in null var lookup stuff --- .../client/DebugProtocolClient.spec.ts | 105 ++++++++++++++++-- .../client/DebugProtocolClient.ts | 16 ++- 2 files changed, 107 insertions(+), 14 deletions(-) diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index 7a5a0f5e..e226287c 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -854,7 +854,7 @@ describe('DebugProtocolClient', () => { expect(plugin.getRequest(-1).data.variablePathEntries.map(x => x.name)).to.eql(['there']); }); - it('returns generic response when accessing a property on a variable with the value of `invalid`', async () => { + it('returns faked variable response with invalid', async () => { await connect(); plugin.pushResponse( @@ -867,10 +867,99 @@ describe('DebugProtocolClient', () => { }) ); + //getting prop from variable that was assigned to invalid (i.e. `setToInvalid = invalid`) + const response = await client.getVariables(['notThere']); + expect(response?.data?.variables?.[0]?.type).to.eql(VariableType.Invalid); + }); + + it('throws when reading prop on invalid', async () => { + await connect(); + + plugin.pushResponse( + GenericV3Response.fromJson({ + errorCode: ErrorCode.INVALID_ARGS, + requestId: 1, + errorData: { + invalidPathIndex: 1 + } + }) + ); + + //another response for the "go one level up to get type info" request + plugin.pushResponse( + VariablesResponse.fromJson({ + requestId: 1, + variables: [{ + name: 'there', + type: VariableType.Invalid, + isConst: false, + isContainer: false, + refCount: 1, + value: 'Invalid' + }] + }) + ); //getting prop from variable that was assigned to invalid (i.e. `setToInvalid = invalid`) + await expectThrowsAsync(async () => { + await client.getVariables(['there', 'setToInvalid', 'notThere']); + }, `Cannot read 'notThere' on type 'Invalid'`); + + //make sure we requested the correct variable + expect(plugin.getRequest(-1).data.variablePathEntries.map(x => x.name)).to.eql(['there', 'setToInvalid']); + }); + + it('returns invalid when left-hand item is an AA but right-hand item is missing', async () => { + await connect(); + + plugin.pushResponse( + GenericV3Response.fromJson({ + errorCode: ErrorCode.INVALID_ARGS, + requestId: 1, + errorData: { + invalidPathIndex: 1 + } + }) + ); + + //another response for the "go one level up to get type info" request + plugin.pushResponse( + VariablesResponse.fromJson({ + requestId: 1, + variables: [{ + name: 'there', + type: VariableType.Invalid, + isConst: false, + isContainer: false, + refCount: 1, + value: 'Invalid' + }] + }) + ); + //getting prop from variable that was assigned to invalid (i.e. `setToInvalid = invalid`) + await expectThrowsAsync(async () => { + await client.getVariables(['there', 'setToInvalid', 'notThere']); + }, `Cannot read 'notThere' on type 'Invalid'`); + + //make sure we requested the correct variable + expect(plugin.getRequest(-1).data.variablePathEntries.map(x => x.name)).to.eql(['there', 'setToInvalid']); + }); + + it('returns generic response when accessing a property on a variable with the value of `invalid`', async () => { + await connect(); + + plugin.pushResponse( + GenericV3Response.fromJson({ + errorCode: ErrorCode.INVALID_ARGS, + requestId: 1, + errorData: { + invalidPathIndex: 0 + } + }) + ); + await expectThrowsAsync(async () => { await client.getVariables(['setToInvalid', 'notThere']); - }, `Cannot read 'setToInvalid'`); + }, `Cannot read 'notThere'`); }); it('returns generic response when accessing a property on a property with the value of `invalid`', async () => { @@ -892,14 +981,12 @@ describe('DebugProtocolClient', () => { VariablesResponse.fromJson({ requestId: 1, variables: [{ - name: 'someObj', - type: VariableType.AssociativeArray, + name: 'somePropWithValueSetToInvalid', + type: VariableType.Invalid, isConst: false, - isContainer: true, + isContainer: false, refCount: 1, - value: undefined, - childCount: 2, - keyType: VariableType.String + value: undefined }] }) ); @@ -907,7 +994,7 @@ describe('DebugProtocolClient', () => { //getting prop from variable that was never defined await expectThrowsAsync(async () => { await client.getVariables(['someObj', 'somePropWithValueSetToInvalid', 'notThere']); - }, `Cannot read 'somePropWithValueSetToInvalid' on type 'AssociativeArray'`); + }, `Cannot read 'notThere' on type 'Invalid'`); }); it('honors protocol version when deciding to send forceCaseInsensitive variable information', async () => { diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index a4cd7c07..b48ee6de 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -526,6 +526,7 @@ export class DebugProtocolClient { throw new Error(`Cannot read '${variablePathEntries[missingKeyIndex]}'${parentVarType ? ` on type '${parentVarTypeText}'` : ''}`); } + //this flow is when the item at the index exists, but is set to literally `invalid` or is an unknown value if (!util.isNullish(response.data.errorData.invalidPathIndex)) { const { invalidPathIndex } = response.data.errorData; @@ -536,8 +537,13 @@ export class DebugProtocolClient { return simulatedResponse; } - if (variablePathEntries.length > 1 && invalidPathIndex > 0) { - await loadParentVarInfo(invalidPathIndex); + if ( + variablePathEntries.length > 1 && + invalidPathIndex > 0 && + //only do this logic if the invalid item is not the last item + invalidPathIndex < variablePathEntries.length - 1 + ) { + await loadParentVarInfo(invalidPathIndex + 1); //leftmost var is set to literal `invalid`, tried to read prop if (invalidPathIndex === 0 && variablePathEntries.length > 1) { @@ -556,10 +562,10 @@ export class DebugProtocolClient { return simulatedResponse; } } + console.log('Bronley'); //prop in the middle is missing, tried reading a prop on it - // ex: variablePathEntries = ["there", "notThere", "definitelyNotThere"] - throw new Error(`Cannot read '${variablePathEntries[invalidPathIndex]}'${parentVarType ? ` on type '${parentVarTypeText}'` : ''}`); - + // ex: variablePathEntries = ["there", "thereButSetToInvalid", "definitelyNotThere"] + throw new Error(`Cannot read '${variablePathEntries[invalidPathIndex + 1]}'${parentVarType ? ` on type '${parentVarTypeText}'` : ''}`); } } return response; From a7246bdb21cf7fed5a24792e331ab7470b2e9d98 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Tue, 28 Feb 2023 12:46:59 -0500 Subject: [PATCH 63/74] Fix breakpoint bugs --- src/adapters/DebugProtocolAdapter.spec.ts | 27 ++++++++++++++++++- src/adapters/DebugProtocolAdapter.ts | 7 ++--- .../client/DebugProtocolClient.ts | 3 +++ src/managers/ActionQueue.spec.ts | 17 ++++++++++++ src/managers/ActionQueue.ts | 18 ++++++++++--- 5 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 src/managers/ActionQueue.spec.ts diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index 018ed1b0..8f768647 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -20,7 +20,7 @@ import { Project, ProjectManager } from '../managers/ProjectManager'; import { AddBreakpointsRequest } from '../debugProtocol/events/requests/AddBreakpointsRequest'; import { AddConditionalBreakpointsRequest } from '../debugProtocol/events/requests/AddConditionalBreakpointsRequest'; import { AddConditionalBreakpointsResponse } from '../debugProtocol/events/responses/AddConditionalBreakpointsResponse'; -import { GenericV3Response } from '../debugProtocol/events/responses/GenericV3Response'; +import { RemoveBreakpointsResponse } from '../debugProtocol/events/responses/RemoveBreakpointsResponse'; const sinon = createSandbox(); let cwd = s`${process.cwd()}`; @@ -129,6 +129,31 @@ describe('DebugProtocolAdapter', () => { describe('syncBreakpoints', () => { + it('excludes non-numeric breakpoint IDs', async () => { + await initialize(); + + const breakpoint = adapter['breakpointManager'].setBreakpoint(`${rootDir}/source/main.brs`, { + line: 12 + }); + plugin.pushResponse( + AddBreakpointsResponse.fromJson({ + breakpoints: [{ id: 10 } as any], + requestId: 1 + }) + ); + //sync the breakpoints to mark this one as "sent to device" + await adapter.syncBreakpoints(); + + //replace the breakpoints before they were verified + adapter['breakpointManager'].replaceBreakpoints(`${rootDir}/source/main.brs`, []); + breakpoint.deviceId = undefined; + + //sync the breakpoints again. Since the breakpoint doesn't have an ID, we shouldn't send any request + await adapter.syncBreakpoints(); + + expect(plugin.latestRequest?.constructor.name).not.to.eql(RemoveBreakpointsResponse.name); + }); + it('skips sending AddBreakpoints and AddConditionalBreakpoints command when there are no breakpoints', async () => { await initialize(); diff --git a/src/adapters/DebugProtocolAdapter.ts b/src/adapters/DebugProtocolAdapter.ts index 796e2160..7668a65f 100644 --- a/src/adapters/DebugProtocolAdapter.ts +++ b/src/adapters/DebugProtocolAdapter.ts @@ -754,7 +754,7 @@ export class DebugProtocolAdapter { public async syncBreakpoints() { //we can't send breakpoints unless we're stopped (or in a protocol version that supports sending them while running). //So...if we're not stopped, quit now. (we'll get called again when the stop event happens) - if (!this.socketDebugger.supportsBreakpointRegistrationWhileRunning && !this.isAtDebuggerPrompt) { + if (!this.socketDebugger?.supportsBreakpointRegistrationWhileRunning && !this.isAtDebuggerPrompt) { return; } @@ -765,11 +765,12 @@ export class DebugProtocolAdapter { if (diff.removed.length > 0) { await this.actionQueue.run(async () => { const response = await this.socketDebugger.removeBreakpoints( - diff.removed.map(x => x.deviceId) + //TODO handle retrying to remove unverified breakpoints that might get verified in the future AFTER we've removed them (that's hard...) + diff.removed.map(x => x.deviceId).filter(x => typeof x === 'number') ); //return true to mark this action as complete, or false to retry the task again in the future return response.success && response.data.errorCode === ErrorCode.OK; - }); + }, 10); } if (diff.added.length > 0) { diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index b48ee6de..52e43f6c 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -661,6 +661,9 @@ export class DebugProtocolClient { } private async processRemoveBreakpointsRequest(request: RemoveBreakpointsRequest) { + //throw out null breakpoints + request.data.breakpointIds = request.data.breakpointIds?.filter(x => typeof x === 'number') ?? []; + if (request.data.breakpointIds?.length > 0) { return this.sendRequest(request); } diff --git a/src/managers/ActionQueue.spec.ts b/src/managers/ActionQueue.spec.ts new file mode 100644 index 00000000..7a613984 --- /dev/null +++ b/src/managers/ActionQueue.spec.ts @@ -0,0 +1,17 @@ +import { expect } from "chai"; +import { expectThrowsAsync } from "../testHelpers.spec"; +import { ActionQueue } from "./ActionQueue"; + +describe('ActionQueue', () => { + it('rejects after maxTries is reached', async () => { + const queue = new ActionQueue(); + let count = 0; + await expectThrowsAsync(async () => { + return queue.run(() => { + count++; + return false; + }, 3); + }, 'Exceeded the 3 maximum tries for this ActionQueue action'); + expect(count).to.eql(3); + }); +}); \ No newline at end of file diff --git a/src/managers/ActionQueue.ts b/src/managers/ActionQueue.ts index 2f188f66..9559c40c 100644 --- a/src/managers/ActionQueue.ts +++ b/src/managers/ActionQueue.ts @@ -10,18 +10,24 @@ export class ActionQueue { private queueItems: Array<{ action: () => boolean | Promise; deferred: Deferred; + maxTries: number; + tryCount: number; }> = []; /** * Run an action in the queue. * @param action return true or Promise to mark the action as finished */ - public async run(action: () => boolean | Promise) { - this.queueItems.push({ + public async run(action: () => boolean | Promise, maxTries: number = undefined) { + const queueItem = { action: action, - deferred: defer() - }); + deferred: defer(), + maxTries: maxTries, + tryCount: 0 + }; + this.queueItems.push(queueItem); await this._runActions(); + return queueItem.deferred.promise; } private isRunning = false; @@ -34,12 +40,16 @@ export class ActionQueue { while (this.queueItems.length > 0) { const queueItem = this.queueItems[0]; try { + queueItem.tryCount++; const isFinished = await Promise.resolve( queueItem.action() ); + if (isFinished) { this.queueItems.shift(); queueItem.deferred.resolve(); + } else if (typeof queueItem.maxTries === 'number' && queueItem.tryCount >= queueItem.maxTries) { + throw new Error(`Exceeded the ${queueItem.maxTries} maximum tries for this ActionQueue action`); } } catch (error) { this.queueItems.shift(); From ce4660902c5be66decbab3c4ccf76073bd5045d4 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Tue, 28 Feb 2023 13:09:38 -0500 Subject: [PATCH 64/74] fix lint errors --- src/managers/ActionQueue.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/managers/ActionQueue.spec.ts b/src/managers/ActionQueue.spec.ts index 7a613984..c6507a78 100644 --- a/src/managers/ActionQueue.spec.ts +++ b/src/managers/ActionQueue.spec.ts @@ -1,6 +1,6 @@ -import { expect } from "chai"; -import { expectThrowsAsync } from "../testHelpers.spec"; -import { ActionQueue } from "./ActionQueue"; +import { expect } from 'chai'; +import { expectThrowsAsync } from '../testHelpers.spec'; +import { ActionQueue } from './ActionQueue'; describe('ActionQueue', () => { it('rejects after maxTries is reached', async () => { From 4961675ebd791bf5f8b33986f2342000f0d19373 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 1 Mar 2023 08:09:48 -0500 Subject: [PATCH 65/74] Fix lint issue --- src/managers/ActionQueue.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/managers/ActionQueue.spec.ts b/src/managers/ActionQueue.spec.ts index c6507a78..8d55d4dd 100644 --- a/src/managers/ActionQueue.spec.ts +++ b/src/managers/ActionQueue.spec.ts @@ -14,4 +14,4 @@ describe('ActionQueue', () => { }, 'Exceeded the 3 maximum tries for this ActionQueue action'); expect(count).to.eql(3); }); -}); \ No newline at end of file +}); From bb376a6c118ea372cf805f1d397d8f24e4ccf04f Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Thu, 2 Mar 2023 15:36:16 -0500 Subject: [PATCH 66/74] Discard breakpoints that got deleted before verified (#138) * Discard breakpoints that got deleted before verified * Add log entry for when we removed the breakpoint * disable coveralls publishing --- .github/workflows/build.yml | 2 +- src/adapters/DebugProtocolAdapter.spec.ts | 110 ++++++++++++++++-- src/adapters/DebugProtocolAdapter.ts | 12 +- src/debugProtocol/PluginInterface.ts | 34 ++++++ .../client/DebugProtocolClientPlugin.ts | 6 +- .../server/DebugProtocolServerPlugin.ts | 4 +- src/managers/BreakpointManager.ts | 5 +- 7 files changed, 157 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f06feb37..e78be09c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,7 +25,7 @@ jobs: - run: npm run build - run: npm run lint - run: npm run test - - run: npm run publish-coverage + #- run: npm run publish-coverage npm-release: #only run this task if a tag starting with 'v' was used to trigger this (i.e. a tagged release) if: startsWith(github.ref, 'refs/tags/v') diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index 8f768647..d5970126 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -1,11 +1,11 @@ import { expect } from 'chai'; -import { DebugProtocolClient } from '../debugProtocol/client/DebugProtocolClient'; -import { DebugProtocolAdapter, EvaluateContainer, KeyType } from './DebugProtocolAdapter'; +import type { DebugProtocolClient } from '../debugProtocol/client/DebugProtocolClient'; +import { DebugProtocolAdapter, KeyType } from './DebugProtocolAdapter'; import { createSandbox } from 'sinon'; import { VariableType, VariablesResponse } from '../debugProtocol/events/responses/VariablesResponse'; import { DebugProtocolServer } from '../debugProtocol/server/DebugProtocolServer'; -import { util } from '../util'; +import { defer, util } from '../util'; import { standardizePath as s } from 'brighterscript'; import { DebugProtocolServerTestPlugin } from '../debugProtocol/DebugProtocolServerTestPlugin.spec'; import { AllThreadsStoppedUpdate } from '../debugProtocol/events/updates/AllThreadsStoppedUpdate'; @@ -21,18 +21,26 @@ import { AddBreakpointsRequest } from '../debugProtocol/events/requests/AddBreak import { AddConditionalBreakpointsRequest } from '../debugProtocol/events/requests/AddConditionalBreakpointsRequest'; import { AddConditionalBreakpointsResponse } from '../debugProtocol/events/responses/AddConditionalBreakpointsResponse'; import { RemoveBreakpointsResponse } from '../debugProtocol/events/responses/RemoveBreakpointsResponse'; +import { BreakpointVerifiedUpdate } from '../debugProtocol/events/updates/BreakpointVerifiedUpdate'; +import { RemoveBreakpointsRequest } from '../debugProtocol/events/requests/RemoveBreakpointsRequest'; +import type { AfterSendRequestEvent } from '../debugProtocol/client/DebugProtocolClientPlugin'; const sinon = createSandbox(); let cwd = s`${process.cwd()}`; let tmpDir = s`${cwd}/.tmp`; let rootDir = s`${tmpDir}/rootDir`; const outDir = s`${tmpDir}/out`; +/** + * A path to main.brs + */ +const srcPath = `${rootDir}/source/main.brs`; describe('DebugProtocolAdapter', () => { let adapter: DebugProtocolAdapter; let server: DebugProtocolServer; let client: DebugProtocolClient; let plugin: DebugProtocolServerTestPlugin; + let breakpointManager: BreakpointManager; beforeEach(async () => { sinon.stub(console, 'log').callsFake((...args) => { }); @@ -42,7 +50,7 @@ describe('DebugProtocolAdapter', () => { }; const sourcemapManager = new SourceMapManager(); const locationManager = new LocationManager(sourcemapManager); - const breakpointManager = new BreakpointManager(sourcemapManager, locationManager); + breakpointManager = new BreakpointManager(sourcemapManager, locationManager); const projectManager = new ProjectManager(breakpointManager, locationManager); projectManager.mainProject = new Project({ rootDir: rootDir, @@ -57,10 +65,6 @@ describe('DebugProtocolAdapter', () => { server = new DebugProtocolServer(options); plugin = server.plugins.add(new DebugProtocolServerTestPlugin()); await server.start(); - - client = new DebugProtocolClient(options); - //disable logging for tests because they clutter the test output - client['logger'].logLevel = 'off'; }); afterEach(async () => { @@ -76,6 +80,9 @@ describe('DebugProtocolAdapter', () => { */ async function initialize() { await adapter.connect(); + client = adapter['socketDebugger']; + //disable logging for tests because they clutter the test output + client['logger'].logLevel = 'off'; await Promise.all([ adapter.once('suspend'), plugin.server.sendUpdate( @@ -128,6 +135,93 @@ describe('DebugProtocolAdapter', () => { }); describe('syncBreakpoints', () => { + it('removes "added" breakpoints that show up after a breakpoint was already removed', async () => { + const bpId = 123; + const bpLine = 12; + await initialize(); + + //force the client to expect the device to verify breakpoints (instead of auto-verifying them as soon as seen) + client.protocolVersion = '3.2.0'; + + breakpointManager.setBreakpoint(srcPath, { + line: bpLine + }); + + let bpResponseDeferred = defer(); + + //once the breakpoint arrives at the server + let bpAtServerPromise = new Promise((resolve) => { + let handled = false; + const tempPlugin = server.plugins.add({ + provideResponse: (event) => { + if (!handled && event.request instanceof AddBreakpointsRequest) { + handled = true; + //resolve the outer promise + resolve(); + //return a deferred promise for us to flush later + return bpResponseDeferred.promise; + } + } + }); + }); + + plugin.pushResponse( + AddBreakpointsResponse.fromJson({ + requestId: 1, + breakpoints: [{ + id: bpId, + errorCode: ErrorCode.OK, + ignoreCount: 0 + }] + }) + ); + //sync the breakpoints to mark this one as "sent to device" + void adapter.syncBreakpoints(); + //wait for the request to arrive at the server (it will be stuck pending until we resolve the bpResponseDeferred) + await bpAtServerPromise; + + //delete the breakpoint (before we ever got the deviceId from the server) + breakpointManager.replaceBreakpoints(srcPath, []); + + //sync the breakpoints again, forcing the bp to be fully deleted + let syncPromise = adapter.syncBreakpoints(); + //since the breakpoints were deleted before getting deviceIDs, there should be no request sent + bpResponseDeferred.resolve(); + //wait for the second sync to finish + await syncPromise; + + //response for the "remove breakpoints" request triggered later + plugin.pushResponse( + RemoveBreakpointsResponse.fromJson({ + requestId: 1, + breakpoints: [{ + id: bpId, + errorCode: ErrorCode.OK, + ignoreCount: 0 + }] + }) + ); + + //listen for the next sent RemoveBreakpointsRequest + const sentRequestPromise = client.plugins.onceIf>('afterSendRequest', (event) => { + return event.request instanceof RemoveBreakpointsRequest; + }, 0); + + //now push the "bp verified" event + //the client should recognize that these breakpoints aren't avaiable client-side, and ask the server to delete them + await server.sendUpdate( + BreakpointVerifiedUpdate.fromJson({ + breakpoints: [{ + id: bpId + }] + }) + ); + + //wait for the request to be sent + expect( + (await sentRequestPromise).request?.data.breakpointIds + ).to.eql([bpId]); + }); it('excludes non-numeric breakpoint IDs', async () => { await initialize(); diff --git a/src/adapters/DebugProtocolAdapter.ts b/src/adapters/DebugProtocolAdapter.ts index 7668a65f..34cc3f55 100644 --- a/src/adapters/DebugProtocolAdapter.ts +++ b/src/adapters/DebugProtocolAdapter.ts @@ -283,9 +283,19 @@ export class DebugProtocolAdapter { //handle when the device verifies breakpoints this.socketDebugger.on('breakpoints-verified', (event) => { + let unverifiableDeviceIds = [] as number[]; + //mark the breakpoints as verified for (let breakpoint of event?.breakpoints ?? []) { - this.breakpointManager.verifyBreakpoint(breakpoint.id, true); + const success = this.breakpointManager.verifyBreakpoint(breakpoint.id, true); + if (!success) { + unverifiableDeviceIds.push(breakpoint.id); + } + } + //if there were any unsuccessful breakpoint verifications, we need to ask the device to delete those breakpoints as they've gone missing on our side + if (unverifiableDeviceIds.length > 0) { + this.logger.warn('Could not find breakpoints to verify. Removing from device:', { deviceBreakpointIds: unverifiableDeviceIds }); + void this.socketDebugger.removeBreakpoints(unverifiableDeviceIds); } this.emit('breakpoints-verified', event); }); diff --git a/src/debugProtocol/PluginInterface.ts b/src/debugProtocol/PluginInterface.ts index cd883c17..248543ab 100644 --- a/src/debugProtocol/PluginInterface.ts +++ b/src/debugProtocol/PluginInterface.ts @@ -28,6 +28,8 @@ export default class PluginInterface { /** * Add a plugin to the end of the list of plugins + * @param plugin the plugin + * @param priority the priority for the plugin. Lower number means higher priority. (ex: 1 executes before 5) */ public add(plugin: T, priority = 1) { const container = { @@ -44,6 +46,38 @@ export default class PluginInterface { return plugin; } + /** + * Adds a temporary plugin with a single event hook, and resolve a promise with the event from the next occurance of that event. + * Once the event fires for the first time, the plugin is unregistered. + * @param eventName the name of the event to subscribe to + * @param priority the priority for this event. Lower number means higher priority. (ex: 1 executes before 5) + */ + public once(eventName: keyof TPlugin, priority = 1): Promise { + return this.onceIf(eventName, () => true, priority); + } + + /** + * Adds a temporary plugin with a single event hook, and resolve a promise with the event from the next occurance of that event. + * Once the event fires for the first time and the matcher evaluates to true, the plugin is unregistered. + * @param eventName the name of the event to subscribe to + * @param matcher a function to call that, when true, will deregister this hander and return the event + * @param priority the priority for this event. Lower number means higher priority. (ex: 1 executes before 5) + */ + public onceIf(eventName: keyof TPlugin, matcher: (TEventType) => boolean, priority = 1): Promise { + return new Promise((resolve) => { + const tempPlugin = {} as any; + tempPlugin[eventName] = (event) => { + if (matcher(event)) { + //remove the temp plugin + this.remove(tempPlugin); + //resolve the promise with this event + resolve(event); + } + }; + this.add(tempPlugin, priority); + }) as any; + } + /** * Remove the specified plugin */ diff --git a/src/debugProtocol/client/DebugProtocolClientPlugin.ts b/src/debugProtocol/client/DebugProtocolClientPlugin.ts index 88b6a8eb..aab44459 100644 --- a/src/debugProtocol/client/DebugProtocolClientPlugin.ts +++ b/src/debugProtocol/client/DebugProtocolClientPlugin.ts @@ -31,11 +31,11 @@ export interface ProvideResponseOrUpdateEvent { responseOrUpdate?: ProtocolResponse | ProtocolUpdate; } -export interface BeforeSendRequestEvent { +export interface BeforeSendRequestEvent { client: DebugProtocolClient; - request: ProtocolRequest; + request: TRequest; } -export type AfterSendRequestEvent = BeforeSendRequestEvent; +export type AfterSendRequestEvent = BeforeSendRequestEvent; export interface OnUpdateEvent { client: DebugProtocolClient; diff --git a/src/debugProtocol/server/DebugProtocolServerPlugin.ts b/src/debugProtocol/server/DebugProtocolServerPlugin.ts index 5f7c2aca..a76e473f 100644 --- a/src/debugProtocol/server/DebugProtocolServerPlugin.ts +++ b/src/debugProtocol/server/DebugProtocolServerPlugin.ts @@ -1,13 +1,14 @@ import type { DebugProtocolServer } from './DebugProtocolServer'; import type { Socket } from 'net'; import type { ProtocolRequest, ProtocolResponse } from '../events/ProtocolEvent'; +import { DebugProtocolServerTestPlugin } from '../DebugProtocolServerTestPlugin.spec'; export interface ProtocolServerPlugin { onServerStart?: Handler; onClientConnected?: Handler; provideRequest?: Handler; - provideResponse: Handler; + provideResponse?: Handler; beforeSendResponse?: Handler; afterSendResponse?: Handler; @@ -46,4 +47,3 @@ export interface BeforeSendResponseEvent { export type AfterSendResponseEvent = BeforeSendResponseEvent; export type Handler = (event: T) => R; - diff --git a/src/managers/BreakpointManager.ts b/src/managers/BreakpointManager.ts index b5fef312..d96fdd5b 100644 --- a/src/managers/BreakpointManager.ts +++ b/src/managers/BreakpointManager.ts @@ -188,8 +188,11 @@ export class BreakpointManager { if (breakpoint) { breakpoint.verified = isVerified; this.queueVerifyEvent(breakpoint.hash); + return true; + } else { + //couldn't find the breakpoint. return false so the caller can handle that properly + return false; } - //TODO handle the else case, (might be caused by timing issues?) } /** From 452f1b848ebd45cdb68359c8554eb50de1a40456 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Thu, 2 Mar 2023 21:48:44 -0500 Subject: [PATCH 67/74] Logging improvements (#140) * Discard breakpoints that got deleted before verified * Add log entry for when we removed the breakpoint * disable coveralls publishing * Improve logging output for request/response/update * increase timeout for DebugProtocolAdapter tests --- src/adapters/DebugProtocolAdapter.spec.ts | 5 +- src/adapters/DebugProtocolAdapter.ts | 11 +++-- src/adapters/TelnetAdapter.ts | 2 +- .../client/DebugProtocolClient.ts | 49 +++++++++++++++---- .../events/responses/ThreadsResponse.ts | 2 +- src/debugSession/BrightScriptDebugSession.ts | 2 +- src/logging.ts | 2 +- 7 files changed, 54 insertions(+), 19 deletions(-) diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index d5970126..e3f8c189 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable prefer-arrow-callback */ import { expect } from 'chai'; import type { DebugProtocolClient } from '../debugProtocol/client/DebugProtocolClient'; @@ -35,7 +36,9 @@ const outDir = s`${tmpDir}/out`; */ const srcPath = `${rootDir}/source/main.brs`; -describe('DebugProtocolAdapter', () => { +describe('DebugProtocolAdapter', function() { + //allow these tests to run for longer since there's more IO overhead due to the socket logic + this.timeout(3000); let adapter: DebugProtocolAdapter; let server: DebugProtocolServer; let client: DebugProtocolClient; diff --git a/src/adapters/DebugProtocolAdapter.ts b/src/adapters/DebugProtocolAdapter.ts index 34cc3f55..fd01b324 100644 --- a/src/adapters/DebugProtocolAdapter.ts +++ b/src/adapters/DebugProtocolAdapter.ts @@ -48,7 +48,7 @@ export class DebugProtocolAdapter { }); } - private logger = logger.createLogger(`[${DebugProtocolAdapter.name}]`); + private logger = logger.createLogger(`[padapter]`); /** * Indicates whether the adapter has successfully established a connection with the device @@ -511,7 +511,7 @@ export class DebugProtocolAdapter { */ private async getVariablesResponse(expression: string, frameId: number) { const isScopesRequest = expression === ''; - const logger = this.logger.createLogger(' getVariable'); + const logger = this.logger.createLogger('[getVariable]'); logger.info('begin', { expression }); if (!this.isAtDebuggerPrompt) { throw new Error('Cannot resolve variable: debugger is not paused'); @@ -522,7 +522,7 @@ export class DebugProtocolAdapter { throw new Error('Cannot request variable without a corresponding frame'); } - logger.log(`Expression:`, expression); + logger.info(`Expression:`, JSON.stringify(expression)); let variablePath = expression === '' ? [] : util.getVariablePath(expression); // Temporary workaround related to casing issues over the protocol @@ -685,7 +685,7 @@ export class DebugProtocolAdapter { // isSelected: threadInfo.isPrimary, filePath: threadInfo.filePath, functionName: threadInfo.functionName, - lineNumber: threadInfo.lineNumber + 1, //protocol is 1-based + lineNumber: threadInfo.lineNumber, //threadInfo.lineNumber is 1-based. Thread requires 1-based line numbers lineContents: threadInfo.codeSnippet, threadId: i }; @@ -869,6 +869,9 @@ export enum KeyType { export interface Thread { isSelected: boolean; + /** + * The 1-based line number for the thread + */ lineNumber: number; filePath: string; functionName: string; diff --git a/src/adapters/TelnetAdapter.ts b/src/adapters/TelnetAdapter.ts index 47952644..b8c391d6 100644 --- a/src/adapters/TelnetAdapter.ts +++ b/src/adapters/TelnetAdapter.ts @@ -49,7 +49,7 @@ export class TelnetAdapter { }); } - public logger = logger.createLogger(`[${TelnetAdapter.name}]`); + public logger = logger.createLogger(`[tadapter]`); /** * Indicates whether the adapter has successfully established a connection with the device */ diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 52e43f6c..d033f2d9 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -32,7 +32,7 @@ import { GenericResponse } from '../events/responses/GenericResponse'; import type { StackTraceResponse } from '../events/responses/StackTraceResponse'; import { ThreadsResponse } from '../events/responses/ThreadsResponse'; import type { Variable } from '../events/responses/VariablesResponse'; -import { VariablesResponse, VariablesResponseData, VariableType } from '../events/responses/VariablesResponse'; +import { VariablesResponse, VariableType } from '../events/responses/VariablesResponse'; import { IOPortOpenedUpdate, isIOPortOpenedUpdate } from '../events/updates/IOPortOpenedUpdate'; import { ThreadAttachedUpdate } from '../events/updates/ThreadAttachedUpdate'; import { StackTraceV3Response } from '../events/responses/StackTraceV3Response'; @@ -42,10 +42,11 @@ import PluginInterface from '../PluginInterface'; import type { VerifiedBreakpoint } from '../events/updates/BreakpointVerifiedUpdate'; import { BreakpointVerifiedUpdate } from '../events/updates/BreakpointVerifiedUpdate'; import type { AddConditionalBreakpointsResponse } from '../events/responses/AddConditionalBreakpointsResponse'; +import * as chalk from 'chalk'; export class DebugProtocolClient { - public logger = logger.createLogger(`[${DebugProtocolClient.name}]`); + public logger = logger.createLogger(`[client]`); // The highest tested version of the protocol we support. public supportedVersionRange = '<=3.0.0'; @@ -736,7 +737,7 @@ export class DebugProtocolClient { } }); - this.logger.log(`Request ${request?.data?.requestId}`, request); + this.logEvent(request); if (this.controlSocket) { const buffer = request.toBuffer(); this.logger.info('received client-to-server data\n', { type: 'client-to-server', data: buffer.toJSON() }); @@ -766,6 +767,26 @@ export class DebugProtocolClient { } } + /** + * A counter to help give a unique id to each update (mostly just for logging purposes) + */ + private updateSequence = 1; + + private logEvent(event: ProtocolRequest | ProtocolResponse | ProtocolUpdate) { + const [, eventName, eventType] = /(.+?)((?:v\d+_?\d*)?(?:request|response|update))/ig.exec(event?.constructor.name) ?? []; + if (isProtocolRequest(event)) { + this.logger.log(`${eventName} ${event.data.requestId} (${eventType})`, event, `(${event?.constructor.name})`); + } else if (isProtocolUpdate(event)) { + this.logger.log(`${eventName} ${this.updateSequence++} (${eventType})`, event, `(${event?.constructor.name})`); + } else { + if (event.data.errorCode === ErrorCode.OK) { + this.logger.log(`${eventName} ${event.data.requestId} (${eventType})`, event, `(${event?.constructor.name})`); + } else { + this.logger.log(`[error] ${eventName} ${event.data.requestId} (${eventType})`, event, `(${event?.constructor.name})`); + } + } + } + private async process(): Promise { try { this.logger.info('[process()]: buffer=', this.buffer.toJSON()); @@ -796,21 +817,21 @@ export class DebugProtocolClient { this.buffer = this.buffer.slice(responseOrUpdate.readOffset); if (responseOrUpdate.data.errorCode !== ErrorCode.OK) { - this.logger.error(responseOrUpdate.data.errorCode, responseOrUpdate); + this.logEvent(responseOrUpdate); } //we got a result if (responseOrUpdate) { //emit the corresponding event if (isProtocolUpdate(responseOrUpdate)) { - this.logger.log(`Update:`, responseOrUpdate); + this.logEvent(responseOrUpdate); this.emit('update', responseOrUpdate); await this.plugins.emit('onUpdate', { client: this, update: responseOrUpdate }); } else { - this.logger.log(`Response ${responseOrUpdate?.data?.requestId}:`, responseOrUpdate); + this.logEvent(responseOrUpdate); this.emit('response', responseOrUpdate); await this.plugins.emit('onResponse', { client: this, @@ -1135,18 +1156,26 @@ export interface ConstructorOptions { } /** - * Is the event a ProtocolUpdate update + * Is the event a ProtocolRequest */ -export function isProtocolUpdate(event: ProtocolUpdate | ProtocolResponse): event is ProtocolUpdate { - return event?.constructor?.name.endsWith('Update') && event?.data?.requestId === 0; +export function isProtocolRequest(event: ProtocolRequest | ProtocolResponse | ProtocolUpdate): event is ProtocolRequest { + return event?.constructor?.name.endsWith('Request') && event?.data?.requestId > 0; } + /** * Is the event a ProtocolResponse */ -export function isProtocolResponse(event: ProtocolUpdate | ProtocolResponse): event is ProtocolResponse { +export function isProtocolResponse(event: ProtocolRequest | ProtocolResponse | ProtocolUpdate): event is ProtocolResponse { return event?.constructor?.name.endsWith('Response') && event?.data?.requestId !== 0; } +/** + * Is the event a ProtocolUpdate update + */ +export function isProtocolUpdate(event: ProtocolRequest | ProtocolResponse | ProtocolUpdate): event is ProtocolUpdate { + return event?.constructor?.name.endsWith('Update') && event?.data?.requestId === 0; +} + export interface BreakpointsVerifiedEvent { breakpoints: VerifiedBreakpoint[]; } diff --git a/src/debugProtocol/events/responses/ThreadsResponse.ts b/src/debugProtocol/events/responses/ThreadsResponse.ts index 0e8977aa..50359748 100644 --- a/src/debugProtocol/events/responses/ThreadsResponse.ts +++ b/src/debugProtocol/events/responses/ThreadsResponse.ts @@ -94,7 +94,7 @@ export interface ThreadInfo { */ stopReasonDetail: string; /** - * The line number where the stop or failure occurred. + * The 1-based line number where the stop or failure occurred. */ lineNumber: number; /** diff --git a/src/debugSession/BrightScriptDebugSession.ts b/src/debugSession/BrightScriptDebugSession.ts index 5f5d904f..de0fc18a 100644 --- a/src/debugSession/BrightScriptDebugSession.ts +++ b/src/debugSession/BrightScriptDebugSession.ts @@ -88,7 +88,7 @@ export class BrightScriptDebugSession extends BaseDebugSession { } } - public logger = logger.createLogger(`[${BrightScriptDebugSession.name}]`); + public logger = logger.createLogger(`[session]`); /** * A sequence used to help identify log statements for requests diff --git a/src/logging.ts b/src/logging.ts index c5614196..05ec5066 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -1,7 +1,7 @@ import { default as defaultLogger } from '@rokucommunity/logger'; import type { Logger } from '@rokucommunity/logger'; import { QueuedTransport } from '@rokucommunity/logger/dist/transports/QueuedTransport'; -const logger = defaultLogger.createLogger('[roku-debug]'); +const logger = defaultLogger.createLogger('[dap]'); //disable colors logger.enableColor = false; From b119986ccf3bf5c52998eec2afe572be9d1fc7cd Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Fri, 10 Mar 2023 10:24:00 -0500 Subject: [PATCH 68/74] More graceful shutdown (#141) * More graceful shutdown * remove unnecessary test --- package-lock.json | 24 ++++ package.json | 2 + src/adapters/DebugProtocolAdapter.ts | 12 +- .../DebugProtocolClientReplaySession.spec.ts | 4 +- .../DebugProtocolClientReplaySession.ts | 6 +- .../client/DebugProtocolClient.spec.ts | 19 +-- .../client/DebugProtocolClient.ts | 130 ++++++++++++++---- .../responses/HandshakeV3Response.spec.ts | 35 +++++ src/util.ts | 10 ++ 9 files changed, 187 insertions(+), 55 deletions(-) diff --git a/package-lock.json b/package-lock.json index c130c7f6..fff5a583 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@rokucommunity/logger": "^0.3.1", "brighterscript": "^0.61.3", "dateformat": "^4.6.3", + "debounce": "^1.2.1", "eol": "^0.9.1", "eventemitter3": "^4.0.7", "find-in-files": "^0.5.0", @@ -31,6 +32,7 @@ }, "devDependencies": { "@types/chai": "^4.2.22", + "@types/debounce": "^1.2.1", "@types/dedent": "^0.7.0", "@types/find-in-files": "^0.5.1", "@types/fs-extra": "^9.0.13", @@ -704,6 +706,12 @@ "integrity": "sha512-tFfcE+DSTzWAgifkjik9AySNqIyNoYwmR+uecPwwD/XRNfvOjmC/FjCxpiUGDkDVDphPfCUecSQVFw+lN3M3kQ==", "dev": true }, + "node_modules/@types/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-epMsEE85fi4lfmJUH/89/iV/LI+F5CvNIvmgs5g5jYFPfhO2S/ae8WSsLOKWdwtoaZw9Q2IhJ4tQ5tFCcS/4HA==", + "dev": true + }, "node_modules/@types/dedent": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/@types/dedent/-/dedent-0.7.0.tgz", @@ -1832,6 +1840,11 @@ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.2.tgz", "integrity": "sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw==" }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" + }, "node_modules/debounce-promise": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/debounce-promise/-/debounce-promise-3.1.2.tgz", @@ -6018,6 +6031,12 @@ "integrity": "sha512-tFfcE+DSTzWAgifkjik9AySNqIyNoYwmR+uecPwwD/XRNfvOjmC/FjCxpiUGDkDVDphPfCUecSQVFw+lN3M3kQ==", "dev": true }, + "@types/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-epMsEE85fi4lfmJUH/89/iV/LI+F5CvNIvmgs5g5jYFPfhO2S/ae8WSsLOKWdwtoaZw9Q2IhJ4tQ5tFCcS/4HA==", + "dev": true + }, "@types/dedent": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/@types/dedent/-/dedent-0.7.0.tgz", @@ -6882,6 +6901,11 @@ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.2.tgz", "integrity": "sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw==" }, + "debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" + }, "debounce-promise": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/debounce-promise/-/debounce-promise-3.1.2.tgz", diff --git a/package.json b/package.json index 77c6420a..e5a80bf5 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ }, "devDependencies": { "@types/chai": "^4.2.22", + "@types/debounce": "^1.2.1", "@types/dedent": "^0.7.0", "@types/find-in-files": "^0.5.1", "@types/fs-extra": "^9.0.13", @@ -95,6 +96,7 @@ "@rokucommunity/logger": "^0.3.1", "brighterscript": "^0.61.3", "dateformat": "^4.6.3", + "debounce": "^1.2.1", "eol": "^0.9.1", "eventemitter3": "^4.0.7", "find-in-files": "^0.5.0", diff --git a/src/adapters/DebugProtocolAdapter.ts b/src/adapters/DebugProtocolAdapter.ts index fd01b324..6c44e085 100644 --- a/src/adapters/DebugProtocolAdapter.ts +++ b/src/adapters/DebugProtocolAdapter.ts @@ -710,22 +710,22 @@ export class DebugProtocolAdapter { } public removeAllListeners() { - this.emitter?.removeAllListeners(); + if (this.emitter) { + this.emitter.removeAllListeners(); + } } /** * Disconnect from the telnet session and unset all objects */ public async destroy() { + // destroy the debug client if it's defined if (this.socketDebugger) { - // destroy might be called due to a compile error so the socket debugger might not exist yet - await this.socketDebugger.exitChannel(); + await this.socketDebugger.destroy(); } this.cache = undefined; - if (this.emitter) { - this.emitter.removeAllListeners(); - } + this.removeAllListeners(); this.emitter = undefined; } diff --git a/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts b/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts index 577fa8f6..c24e00be 100644 --- a/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts +++ b/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts @@ -21,8 +21,8 @@ import { ThreadAttachedUpdate } from './events/updates/ThreadAttachedUpdate'; describe.skip(DebugProtocolClientReplaySession.name, () => { let session: DebugProtocolClientReplaySession; - afterEach(() => { - session.destroy(); + afterEach(async () => { + await session.destroy(); }); it.skip('works for the failing breakpoint flow in fubo', async function test() { diff --git a/src/debugProtocol/DebugProtocolClientReplaySession.ts b/src/debugProtocol/DebugProtocolClientReplaySession.ts index 0ca0e8e1..5c680c6b 100644 --- a/src/debugProtocol/DebugProtocolClientReplaySession.ts +++ b/src/debugProtocol/DebugProtocolClientReplaySession.ts @@ -120,7 +120,7 @@ export class DebugProtocolClientReplaySession { //stuff to run when the session is disposed this.disposables.push(() => { - this.client.destroy(); + return this.client.destroy(); }); } @@ -272,10 +272,10 @@ export class DebugProtocolClientReplaySession { await util.sleep(gap); } - public destroy() { + public async destroy() { for (const dispose of this.disposables) { try { - dispose(); + await Promise.resolve(dispose()); } catch { } } } diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index e226287c..16c1e8c4 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -7,7 +7,7 @@ import { DebugProtocolServer } from '../server/DebugProtocolServer'; import { defer, util } from '../../util'; import { HandshakeRequest } from '../events/requests/HandshakeRequest'; import { HandshakeResponse } from '../events/responses/HandshakeResponse'; -import { HandshakeV3Response } from '../events/responses/HandshakeV3Response'; +import type { HandshakeV3Response } from '../events/responses/HandshakeV3Response'; import { AllThreadsStoppedUpdate } from '../events/updates/AllThreadsStoppedUpdate'; import type { Variable } from '../events/responses/VariablesResponse'; import { VariablesResponse, VariableType } from '../events/responses/VariablesResponse'; @@ -544,23 +544,6 @@ describe('DebugProtocolClient', () => { expect(client.isHandshakeComplete).to.be.equal(true); }); - it('throws on magic mismatch', async () => { - plugin.pushResponse( - HandshakeV3Response.fromJson({ - magic: 'not correct magic', - protocolVersion: '3.1.0', - revisionTimestamp: new Date(2022, 1, 1) - }) - ); - - const verifyHandshakePromise = client.once('handshake-verified'); - - await client.connect(); - - //wait for the debugger to finish verifying the handshake - expect(await verifyHandshakePromise).to.be.false; - }); - it('handles legacy handshake', async () => { expect(client.watchPacketLength).to.be.equal(false); diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index d033f2d9..8e0fab93 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -1,4 +1,5 @@ import * as Net from 'net'; +import * as debounce from 'debounce'; import * as EventEmitter from 'eventemitter3'; import * as semver from 'semver'; import { PROTOCOL_ERROR_CODES, Command, StepType, ErrorCode, UpdateType, UpdateTypeCode, StopReason } from '../Constants'; @@ -42,7 +43,6 @@ import PluginInterface from '../PluginInterface'; import type { VerifiedBreakpoint } from '../events/updates/BreakpointVerifiedUpdate'; import { BreakpointVerifiedUpdate } from '../events/updates/BreakpointVerifiedUpdate'; import type { AddConditionalBreakpointsResponse } from '../events/responses/AddConditionalBreakpointsResponse'; -import * as chalk from 'chalk'; export class DebugProtocolClient { @@ -97,10 +97,18 @@ export class DebugProtocolClient { * The primary socket for this session. It's used to communicate with the debugger by sending commands and receives responses or updates */ private controlSocket: Net.Socket; + /** + * Promise that is resolved when the control socket is closed + */ + private controlSocketClosed = defer(); /** * A socket where the debug server will send stdio */ private ioSocket: Net.Socket; + /** + * Resolves when the ioSocket has closed + */ + private ioSocketClosed = defer(); /** * The buffer where all unhandled data will be stored until successfully consumed */ @@ -204,7 +212,9 @@ export class DebugProtocolClient { const pendingSockets = new Set(); const connection = await new Promise((resolve) => { util.setInterval((cancelInterval) => { - const socket = new Net.Socket(); + const socket = new Net.Socket({ + allowHalfOpen: false + }); pendingSockets.add(socket); socket.on('error', (error) => { console.debug(Date.now(), 'Encountered an error connecting to the debug protocol socket. Ignoring and will try again soon', error); @@ -262,16 +272,17 @@ export class DebugProtocolClient { }); }); - this.controlSocket.on('end', () => { - this.logger.log('TCP connection closed'); - this.shutdown('app-exit'); + this.controlSocket.on('close', () => { + this.logger.log('Control socket closed'); + this.controlSocketClosed.tryResolve(); + void this.shutdown('app-exit'); }); // Don't forget to catch error, for your own sake. this.controlSocket.once('error', (error) => { //the Roku closed the connection for some unknown reason... - console.error(`TCP connection error on control port`, error); - this.shutdown('close'); + console.error(`error on control port`, error); + void this.shutdown('close'); }); if (sendHandshake) { @@ -328,6 +339,9 @@ export class DebugProtocolClient { } } + /** + * Send the "exit channel" command, which will tell the debug session to immediately quit + */ public async exitChannel() { return this.sendRequest( ExitChannelRequest.fromJson({ @@ -798,7 +812,7 @@ export class DebugProtocolClient { }); if (!responseOrUpdate) { - responseOrUpdate = this.getResponseOrUpdate(this.buffer); + responseOrUpdate = await this.getResponseOrUpdate(this.buffer); } //if the event failed to parse, or the buffer doesn't have enough bytes to satisfy the packetLength, exit here (new data will re-trigger this function) @@ -848,7 +862,7 @@ export class DebugProtocolClient { /** * Given a buffer, try to parse into a specific ProtocolResponse or ProtocolUpdate */ - public getResponseOrUpdate(buffer: Buffer): ProtocolResponse | ProtocolUpdate { + public async getResponseOrUpdate(buffer: Buffer): Promise { //if we haven't seen a handshake yet, try to convert the buffer into a handshake if (!this.isHandshakeComplete) { let handshake: HandshakeV3Response | HandshakeResponse; @@ -859,7 +873,7 @@ export class DebugProtocolClient { handshake = HandshakeResponse.fromBuffer(buffer); } if (handshake.success) { - this.verifyHandshake(handshake); + await this.verifyHandshake(handshake); return handshake; } return; @@ -987,7 +1001,7 @@ export class DebugProtocolClient { /** * Verify all the handshake data */ - private verifyHandshake(response: HandshakeResponse | HandshakeV3Response): boolean { + private async verifyHandshake(response: HandshakeResponse | HandshakeV3Response): Promise { if (DebugProtocolClient.DEBUGGER_MAGIC === response.data.magic) { this.logger.log('Magic is valid.'); @@ -1017,7 +1031,7 @@ export class DebugProtocolClient { message: `Protocol Version ${this.protocolVersion} is not supported.\nIf you believe this is an error please open an issue at https://github.com/rokucommunity/roku-debug/issues`, errorCode: PROTOCOL_ERROR_CODES.NOT_SUPPORTED }); - this.shutdown('close'); + await this.shutdown('close'); handshakeVerified = false; } @@ -1026,7 +1040,7 @@ export class DebugProtocolClient { } else { this.logger.log('Closing connection due to bad debugger magic', response.data.magic); this.emit('handshake-verified', false); - this.shutdown('close'); + await this.shutdown('close'); return false; } } @@ -1037,7 +1051,9 @@ export class DebugProtocolClient { private connectToIoPort(update: IOPortOpenedUpdate) { if (update.success) { // Create a new TCP client. - this.ioSocket = new Net.Socket(); + this.ioSocket = new Net.Socket({ + allowHalfOpen: false + }); // Send a connection request to the server. this.logger.log(`Connect to IO Port ${this.options.host}:${update.data.port}`); this.ioSocket.connect({ @@ -1066,9 +1082,9 @@ export class DebugProtocolClient { } }); - this.ioSocket.on('end', () => { - this.ioSocket.end(); - this.logger.log('Requested an end to the IO connection'); + this.ioSocket.on('close', () => { + this.logger.log('IO socket closed'); + this.ioSocketClosed.tryResolve(); }); // Don't forget to catch error, for your own sake. @@ -1082,24 +1098,86 @@ export class DebugProtocolClient { return false; } - public destroy() { - this.shutdown('close'); + public async destroy() { + await this.shutdown('close'); + } + + private async shutdown(eventName: 'app-exit' | 'close') { + this.logger.log('Shutting down!'); + //tell the device to exit the channel + try { + //ask the device to terminate the debug session. We have to wait for this to come back. + //The device might be running unstoppable code, so this might take a while. Wait for the device to send back + //the response before we continue with the teardown process + await Promise.race([ + this.exitChannel().finally(() => this.logger.log('exit channel completed')), + //if the exit channel request took this long to finish, something's terribly wrong + util.sleep(30_000) + ]); + } finally { } + + + const maxTimeout = 10_000; + await Promise.all([ + this.destroyControlSocket(maxTimeout), + this.destroyIOSocket(maxTimeout) + ]); + this.emit(eventName); } - private shutdown(eventName: 'app-exit' | 'close') { - if (this.controlSocket) { + private isDestroyingControlSocket = false; + + private async destroyControlSocket(timeout: number) { + if (this.controlSocket && !this.isDestroyingControlSocket) { + this.isDestroyingControlSocket = true; + + //wait for the controlSocket to be closed + await Promise.race([ + this.controlSocketClosed.promise, + util.sleep(timeout) + ]); + + this.logger.log('[destroy] controlSocket is: ', this.controlSocketClosed.isResolved ? 'closed' : 'not closed'); + + //destroy the controlSocket this.controlSocket.removeAllListeners(); this.controlSocket.destroy(); this.controlSocket = undefined; + this.isDestroyingControlSocket = false; } + } - if (this.ioSocket) { - this.ioSocket.removeAllListeners(); - this.ioSocket.destroy(); + private isDestroyingIOSocket = false; + + private async destroyIOSocket(timeout: number) { + if (this.ioSocket && !this.isDestroyingIOSocket) { + this.isDestroyingIOSocket = true; + //wait for the ioSocket to be closed + await Promise.race([ + this.ioSocketClosed.promise.then(() => this.logger.log('IO socket closed')), + util.sleep(timeout) + ]); + + //if the io socket is not closed, wait for it to at least settle + if (!this.ioSocketClosed.isCompleted) { + await new Promise((resolve) => { + const callback = debounce(() => { + resolve(); + }, 250); + //trigger the current callback once. + callback(); + this.ioSocket?.on('drain', callback as () => void); + }); + } + + this.logger.log('[destroy] ioSocket is: ', this.ioSocketClosed.isResolved ? 'closed' : 'not closed'); + + //destroy the ioSocket + this.ioSocket?.removeAllListeners?.(); + this.ioSocket?.destroy?.(); this.ioSocket = undefined; + this.isDestroyingIOSocket = false; } - - this.emit(eventName); } } diff --git a/src/debugProtocol/events/responses/HandshakeV3Response.spec.ts b/src/debugProtocol/events/responses/HandshakeV3Response.spec.ts index 1c547183..e10d7b2c 100644 --- a/src/debugProtocol/events/responses/HandshakeV3Response.spec.ts +++ b/src/debugProtocol/events/responses/HandshakeV3Response.spec.ts @@ -123,4 +123,39 @@ describe('HandshakeV3Response', () => { let handshakeV3 = HandshakeV3Response.fromBuffer(response.toBuffer()); expect(handshakeV3.success).to.equal(false); }); + + it('parses properly with nonstandard magic', () => { + const response = HandshakeV3Response.fromJson({ + magic: 'not correct magic', + protocolVersion: '3.1.0', + revisionTimestamp: date + }); + + expect(response.data).to.eql({ + packetLength: undefined, + errorCode: ErrorCode.OK, + requestId: HandshakeRequest.REQUEST_ID, + + magic: 'not correct magic', + protocolVersion: '3.1.0', + revisionTimestamp: date + }); + + expect( + HandshakeV3Response.fromBuffer(response.toBuffer()).data + ).to.eql({ + packetLength: undefined, + errorCode: ErrorCode.OK, + requestId: HandshakeRequest.REQUEST_ID, + + magic: 'not correct magic', // 18 bytes + protocolVersion: '3.1.0', // 12 bytes (each number is sent as uint32) + //remaining_packet_length // 4 bytes + revisionTimestamp: date // 8 bytes (int64) + }); + + expect(response.toBuffer().length).to.eql(42); + const parsed = HandshakeV3Response.fromBuffer(response.toBuffer()); + expect(parsed.readOffset).to.eql(42); + }); }); diff --git a/src/util.ts b/src/util.ts index 931acfb3..eca5db89 100644 --- a/src/util.ts +++ b/src/util.ts @@ -452,6 +452,11 @@ export function defer() { }); return { promise: promise, + tryResolve: function tryResolve(value?: PromiseLike | T) { + if (!this.isCompleted) { + this.resolve(value); + } + }, resolve: function resolve(value?: PromiseLike | T) { if (!this.isResolved) { this.isResolved = true; @@ -464,6 +469,11 @@ export function defer() { ); } }, + tryReject: function tryReject(reason?: any) { + if (!this.isCompleted) { + this.reject(reason); + } + }, reject: function reject(reason?: any) { if (!this.isCompleted) { this.isRejected = true; From ebf9a52275262090ae1f0d4e628a5de36f732344 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Fri, 10 Mar 2023 10:46:34 -0500 Subject: [PATCH 69/74] Better exec recovery (#142) * More graceful shutdown * remove unnecessary test * Don't store failed exec commands --- src/debugSession/BrightScriptDebugSession.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/debugSession/BrightScriptDebugSession.ts b/src/debugSession/BrightScriptDebugSession.ts index de0fc18a..1ccf2533 100644 --- a/src/debugSession/BrightScriptDebugSession.ts +++ b/src/debugSession/BrightScriptDebugSession.ts @@ -951,6 +951,9 @@ export class BrightScriptDebugSession extends BaseDebugSession { let statement = `${arrayVarName}[${varIndex}] = ${args.expression}`; args.expression = `${arrayVarName}[${varIndex}]`; let commandResults = await this.rokuAdapter.evaluate(statement, args.frameId); + if (commandResults.type === 'error') { + throw new Error(commandResults.message); + } variablePath = [arrayVarName, varIndex.toString()]; } From 54928a724c10db4188bb6d954ba1ed29f13e9183 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 31 May 2023 14:18:11 -0400 Subject: [PATCH 70/74] Support new logging format for replay session --- src/debugProtocol/DebugProtocolClientReplaySession.spec.ts | 4 ++-- src/debugProtocol/DebugProtocolClientReplaySession.ts | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts b/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts index c24e00be..baa6fb63 100644 --- a/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts +++ b/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts @@ -1,4 +1,3 @@ -/* eslint-disable prefer-arrow-callback */ import { expect } from 'chai'; import { DebugProtocolClientReplaySession } from './DebugProtocolClientReplaySession'; import type { ProtocolRequest, ProtocolResponse, ProtocolUpdate } from './events/ProtocolEvent'; @@ -17,8 +16,9 @@ import { VariablesResponse } from './events/responses/VariablesResponse'; import { AllThreadsStoppedUpdate } from './events/updates/AllThreadsStoppedUpdate'; import { IOPortOpenedUpdate } from './events/updates/IOPortOpenedUpdate'; import { ThreadAttachedUpdate } from './events/updates/ThreadAttachedUpdate'; +import * as fsExtra from 'fs-extra'; -describe.skip(DebugProtocolClientReplaySession.name, () => { +describe(DebugProtocolClientReplaySession.name, () => { let session: DebugProtocolClientReplaySession; afterEach(async () => { diff --git a/src/debugProtocol/DebugProtocolClientReplaySession.ts b/src/debugProtocol/DebugProtocolClientReplaySession.ts index 5c680c6b..e878eec6 100644 --- a/src/debugProtocol/DebugProtocolClientReplaySession.ts +++ b/src/debugProtocol/DebugProtocolClientReplaySession.ts @@ -46,7 +46,10 @@ export class DebugProtocolClientReplaySession { private parseBufferLog(bufferLog: string) { this.entries = bufferLog .split(/\r?\n/g) - .map(x => x.trim()) + //only keep lines that include the `[[bufferLog]]` magic text + .filter(x => x.includes('[[bufferLog]]')) + //remove leading text, leaving only the raw bufferLog entry + .map(x => x.replace(/.*?\[\[bufferLog\]\]:/, '').trim()) .filter(x => !!x) .map(line => { const entry = JSON.parse(line); @@ -55,6 +58,7 @@ export class DebugProtocolClientReplaySession { entry.buffer = Buffer.from(entry.buffer); return entry; }); + return this.entries; } public result: Array = []; From 38ff9b8f1ef92ddcf837f67c48120200f7ef6ce4 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Tue, 1 Aug 2023 16:45:36 -0400 Subject: [PATCH 71/74] Merge branch 'master' of https://github.com/rokucommunity/roku-debug into DebugProtocolServer --- CHANGELOG.md | 77 ++ package-lock.json | 895 ++++++++++-------- package.json | 20 +- src/LaunchConfiguration.ts | 86 ++ src/RendezvousTracker.spec.ts | 149 ++- src/RendezvousTracker.ts | 333 +++++-- src/adapters/DebugProtocolAdapter.spec.ts | 4 +- src/adapters/DebugProtocolAdapter.ts | 23 +- src/adapters/TelnetAdapter.spec.ts | 16 +- src/adapters/TelnetAdapter.ts | 24 +- .../client/DebugProtocolClient.ts | 19 +- .../BrightScriptDebugSession.spec.ts | 37 +- src/debugSession/BrightScriptDebugSession.ts | 183 +++- src/logging.spec.ts | 248 +++++ src/logging.ts | 145 +++ src/util.ts | 29 +- 16 files changed, 1713 insertions(+), 575 deletions(-) create mode 100644 src/logging.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index f838aeca..9457cdd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,83 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +## [0.20.3](https://github.com/rokucommunity/roku-debug/compare/v0.20.2...0.20.3) - 2023-07-26 +### Added + - Add `deleteDevChannelBeforeInstall` launch option ([#158](https://github.com/rokucommunity/roku-debug/pull/158)) + + + +## [0.20.2](https://github.com/rokucommunity/roku-debug/compare/v0.20.1...v0.20.2) - 2023-07-24 +### Changed + - Bump word-wrap from 1.2.3 to 1.2.4 ([#157](https://github.com/rokucommunity/roku-debug/pull/157)) + - upgrade to [brighterscript@0.65.4](https://github.com/rokucommunity/brighterscript/blob/master/CHANGELOG.md#0654---2023-07-24). Notable changes since 0.65.1: + - Bump word-wrap from 1.2.3 to 1.2.4 ([brighterscript#851](https://github.com/rokucommunity/brighterscript/pull/851)) + - Bump semver from 6.3.0 to 6.3.1 in /benchmarks ([brighterscript#838](https://github.com/rokucommunity/brighterscript/pull/838)) + - Bump semver from 5.7.1 to 5.7.2 ([brighterscript#837](https://github.com/rokucommunity/brighterscript/pull/837)) + - Prevent crashing when diagnostic is missing range. ([brighterscript#832](https://github.com/rokucommunity/brighterscript/pull/832)) + - Prevent crash when diagnostic is missing range ([brighterscript#831](https://github.com/rokucommunity/brighterscript/pull/831)) + - upgrade to [roku-deploy@3.10.3](https://github.com/rokucommunity/roku-deploy/blob/master/CHANGELOG.md#3103---2023-07-22). Notable changes since 3.10.2: + - Bump word-wrap from 1.2.3 to 1.2.4 ([roku-deploy#117](https://github.com/rokucommunity/roku-deploy/pull/117)) + + + +## [0.20.1](https://github.com/rokucommunity/roku-debug/compare/v0.20.0...v0.20.1) - 2023-07-07 +### Changed + - Fix rendezvous crash ([#156](https://github.com/rokucommunity/roku-debug/pull/156)) + + + +## [0.20.0](https://github.com/rokucommunity/roku-debug/compare/v0.19.1...v0.20.0) - 2023-07-05 +### Added + - Support sgrendezvous through ECP ([#150](https://github.com/rokucommunity/roku-debug/pull/150)) +### Changed + - upgrade to [brighterscript@0.65.1](https://github.com/rokucommunity/brighterscript/blob/master/CHANGELOG.md#0651---2023-06-09) + + + +## [0.19.1](https://github.com/rokucommunity/roku-debug/compare/v0.19.0...v0.19.1) - 2023-06-08 +### Changed + - Move @types/request to deps to fix d.bs files ([691a7be](https://github.com/rokucommunity/roku-debug/commit/691a7be)) + + + +## [0.19.0](https://github.com/rokucommunity/roku-debug/compare/v0.18.12...v0.19.0) - 2023-06-01 +### Added + - File logging ([#155](https://github.com/rokucommunity/roku-debug/pull/155)) + + + +## [0.18.12](https://github.com/rokucommunity/roku-debug/compare/v0.18.11...v0.18.12) - 2023-05-18 +### Changed + - remove axios in favor of postman-request ([#153](https://github.com/rokucommunity/roku-debug/pull/153)) +### Fixed + - Fix `file already exists` error and hung process ([#152](https://github.com/rokucommunity/roku-debug/pull/152)) + + + +## [0.18.11](https://github.com/rokucommunity/roku-debug/compare/v0.18.10...v0.18.11) - 2023-05-17 +### Changed + - Fix crash by using postman-request ([#151](https://github.com/rokucommunity/roku-debug/pull/151)) + + + +## [0.18.10](https://github.com/rokucommunity/roku-debug/compare/v0.18.9...v0.18.10) - 2023-05-17 +### Changed + - upgrade to [brighterscript@0.65.0](https://github.com/rokucommunity/brighterscript/blob/master/CHANGELOG.md#0650---2023-05-17) + - upgrade to [@rokucommunity/logger@0.3.3](https://github.com/rokucommunity/logger/blob/master/CHANGELOG.md#033---2023-05-17). Notable changes since 0.3.2: + - Fix dependencies ([#@rokucommunity/logger04af7a0](https://github.com/rokucommunity/logger/commit/04af7a0)) + + + +## [0.18.9](https://github.com/rokucommunity/roku-debug/compare/v0.18.8...v0.18.9) - 2023-05-10 +### Changed + - upgrade to [brighterscript@0.64.4](https://github.com/rokucommunity/brighterscript/blob/master/CHANGELOG.md#0644---2023-05-10) + - upgrade to [roku-deploy@3.10.2](https://github.com/rokucommunity/roku-deploy/blob/master/CHANGELOG.md#3102---2023-05-10). Notable changes since 3.10.1: + - Fix audit issues ([roku-deploy#116](https://github.com/rokucommunity/roku-deploy/pull/116)) + - fix nodejs 19 bug ([roku-deploy#115](https://github.com/rokucommunity/roku-deploy/pull/115)) + + + ## [0.18.8](https://github.com/rokucommunity/roku-debug/compare/v0.18.7...v0.18.8) - 2023-04-28 ### Changed - Make axios a prod dependency ([#148](https://github.com/rokucommunity/roku-debug/pull/148)) diff --git a/package-lock.json b/package-lock.json index 6486d2df..8e0690e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,28 +1,30 @@ { "name": "roku-debug", - "version": "0.18.8", + "version": "0.20.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "roku-debug", - "version": "0.18.8", + "version": "0.20.3", "license": "MIT", "dependencies": { - "@rokucommunity/logger": "^0.3.2", - "axios": "^1.2.2", - "brighterscript": "^0.64.3", + "@rokucommunity/logger": "^0.3.3", + "@types/request": "^2.48.8", + "brighterscript": "^0.65.4", "dateformat": "^4.6.3", "debounce": "^1.2.1", "eol": "^0.9.1", "eventemitter3": "^4.0.7", + "fast-glob": "^3.2.11", "find-in-files": "^0.5.0", "fs-extra": "^10.0.0", "natural-orderby": "^2.0.3", + "postman-request": "^2.88.1-postman.32", "replace-in-file": "^6.3.2", "replace-last": "^1.2.6", - "roku-deploy": "^3.10.1", - "semver": "^7.3.5", + "roku-deploy": "^3.10.3", + "semver": "^7.5.3", "serialize-error": "^8.1.0", "smart-buffer": "^4.2.0", "source-map": "^0.7.4", @@ -34,6 +36,7 @@ }, "devDependencies": { "@types/chai": "^4.2.22", + "@types/dateformat": "~3", "@types/debounce": "^1.2.1", "@types/decompress": "^4.2.4", "@types/dedent": "^0.7.0", @@ -42,14 +45,13 @@ "@types/glob": "^7.2.0", "@types/mocha": "^9.0.0", "@types/node": "^16.11.6", - "@types/request": "^2.48.7", "@types/semver": "^7.3.9", "@types/sinon": "^10.0.6", "@types/vscode": "^1.61.0", "@typescript-eslint/eslint-plugin": "^5.2.0", "@typescript-eslint/parser": "^5.2.0", "chai": "^4.3.4", - "coveralls": "^3.1.1", + "coveralls-next": "^4.2.0", "decompress": "^4.2.1", "dedent": "^0.7.0", "eslint": "^8.1.0", @@ -623,15 +625,61 @@ "node": ">= 8" } }, + "node_modules/@postman/form-data": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.1.tgz", + "integrity": "sha512-vjh8Q2a8S6UCm/KKs31XFJqEEgmbjBmpPNVV2eVav6905wyFAwaUOBGA1NPBI4ERH9MMZc6w0umFgM6WbEPMdg==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@postman/tough-cookie": { + "version": "4.1.2-postman.2", + "resolved": "https://registry.npmjs.org/@postman/tough-cookie/-/tough-cookie-4.1.2-postman.2.tgz", + "integrity": "sha512-nrBdX3jA5HzlxTrGI/I0g6pmUKic7xbGA4fAMLFgmJCA3DL2Ma+3MvmD+Sdiw9gLEzZJIF4fz33sT8raV/L/PQ==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@postman/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/@postman/tunnel-agent": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.3.tgz", + "integrity": "sha512-k57fzmAZ2PJGxfOA4SGR05ejorHbVAa/84Hxh/2nAztjNXc4ZjOm9NUIk6/Z6LCrBvJZqjRZbN8e/nROVUPVdg==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/@rokucommunity/bslib": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@rokucommunity/bslib/-/bslib-0.1.1.tgz", "integrity": "sha512-2ox6EUL+UTtccTbD4dbVjZK3QHa0PHCqpoKMF8lZz9ayzzEP3iVPF8KZR6hOi6bxsIcbGXVjqmtCVkpC4P9SrA==" }, "node_modules/@rokucommunity/logger": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@rokucommunity/logger/-/logger-0.3.2.tgz", - "integrity": "sha512-s2Uzt+SAyLXs0mBvcgRv1iuU3Xig7m1LrHCth6YIxQu2/DWqlefV5NP4fo/wdzvhAo/ozoMk/nCVrt8LlGiMtA==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@rokucommunity/logger/-/logger-0.3.3.tgz", + "integrity": "sha512-4nmLHHr6pzBoGE11idjjIfCuhXbuo6urqNLfymiqNg8BHtBVLL+PqjIXq3OFFzv2x37w64LoHK+nDk9EHrkxbw==", "dependencies": { "chalk": "^4.1.2", "fs-extra": "^10.0.0", @@ -701,8 +749,7 @@ "node_modules/@types/caseless": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", - "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", - "dev": true + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" }, "node_modules/@types/chai": { "version": "4.2.22", @@ -710,6 +757,12 @@ "integrity": "sha512-tFfcE+DSTzWAgifkjik9AySNqIyNoYwmR+uecPwwD/XRNfvOjmC/FjCxpiUGDkDVDphPfCUecSQVFw+lN3M3kQ==", "dev": true }, + "node_modules/@types/dateformat": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/dateformat/-/dateformat-3.0.1.tgz", + "integrity": "sha512-KlPPdikagvL6ELjWsljbyDIPzNCeliYkqRpI+zea99vBBbCIA5JNshZAwQKTON139c87y9qvTFVgkFd14rtS4g==", + "dev": true + }, "node_modules/@types/debounce": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.1.tgz", @@ -776,15 +829,12 @@ }, "node_modules/@types/node": { "version": "16.11.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz", - "integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==", - "dev": true + "license": "MIT" }, "node_modules/@types/request": { - "version": "2.48.7", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.7.tgz", - "integrity": "sha512-GWP9AZW7foLd4YQxyFZDBepl0lPsWLMEXDZUjQ/c1gqVPDPECrRZyEzuhJdnPWioFCq3Tv0qoGpMD6U+ygd4ZA==", - "dev": true, + "version": "2.48.8", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.8.tgz", + "integrity": "sha512-whjk1EDJPcAR2kYHRbFl/lKeeKYTi05A15K9bnLInCVroNDCtXce57xKdI0/rQaA3K+6q0eFyUBPmqfSndUZdQ==", "dependencies": { "@types/caseless": "*", "@types/node": "*", @@ -792,20 +842,6 @@ "form-data": "^2.5.0" } }, - "node_modules/@types/request/node_modules/form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/@types/semver": { "version": "7.3.9", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", @@ -822,10 +858,9 @@ } }, "node_modules/@types/tough-cookie": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz", - "integrity": "sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==", - "dev": true + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", + "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==" }, "node_modules/@types/vscode": { "version": "1.61.0", @@ -1257,32 +1292,9 @@ } }, "node_modules/aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" - }, - "node_modules/axios": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.2.tgz", - "integrity": "sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==", - "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/axios/node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" }, "node_modules/balanced-match": { "version": "1.0.2", @@ -1293,7 +1305,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -1361,9 +1372,9 @@ } }, "node_modules/brighterscript": { - "version": "0.64.3", - "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.64.3.tgz", - "integrity": "sha512-W0k0ID3yCbFGba80d8RlggldvRs6u5Y9F6F+AQ4fWDdJcWeHJ3vy5/pabzDbYD9sen3trVxiZ2yXdA7XK35V5g==", + "version": "0.65.4", + "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.65.4.tgz", + "integrity": "sha512-bp2aVhLOM1xRuZiyKx1WQp4JS8Fm6wfD7kAI4rE3YYMYeMhZ/SxTCvBtmTsaW/Z40oB8bBJXuKSuXgz2uF+ywQ==", "dependencies": { "@rokucommunity/bslib": "^0.1.1", "@xml-tools/parser": "^1.0.7", @@ -1372,6 +1383,7 @@ "chevrotain": "^7.0.1", "chokidar": "^3.5.1", "clear": "^0.1.0", + "coveralls-next": "^4.2.0", "cross-platform-clear-console": "^2.3.0", "debounce-promise": "^3.1.0", "eventemitter3": "^4.0.0", @@ -1387,7 +1399,7 @@ "parse-ms": "^2.1.0", "readline": "^1.3.0", "require-relative": "^0.8.7", - "roku-deploy": "^3.10.1", + "roku-deploy": "^3.10.3", "serialize-error": "^7.0.1", "source-map": "^0.7.4", "vscode-languageserver": "7.0.0", @@ -1595,6 +1607,14 @@ "node": ">=10" } }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "dependencies": { + "base64-js": "^1.1.2" + } + }, "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -1719,9 +1739,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001436", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001436.tgz", - "integrity": "sha512-ZmWkKsnC2ifEPoWUvSAIGyOYwT+keAaaWPHiQ9DfMqS1t6tfuyFYoWR78TeZtznkEQ64+vGXH9cZrElwR2Mrxg==", + "version": "1.0.30001492", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001492.tgz", + "integrity": "sha512-2efF8SAZwgAX1FJr87KWhvuJxnGJKOnctQa8xLOskAXNXq8oiuqgl6u1kk3fFpsp3GgvzlRjiK1sl63hNtFADw==", "dev": true, "funding": [ { @@ -1731,6 +1751,10 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ] }, @@ -1900,23 +1924,51 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, - "node_modules/coveralls": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", - "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", - "dev": true, + "node_modules/coveralls-next": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/coveralls-next/-/coveralls-next-4.2.0.tgz", + "integrity": "sha512-zg41a/4QDSASPtlV6gp+6owoU43U5CguxuPZR3nPZ26M5ZYdEK3MdUe7HwE+AnCZPkucudfhqqJZehCNkz2rYg==", "dependencies": { - "js-yaml": "^3.13.1", - "lcov-parse": "^1.0.0", - "log-driver": "^1.2.7", - "minimist": "^1.2.5", - "request": "^2.88.2" + "form-data": "4.0.0", + "js-yaml": "4.1.0", + "lcov-parse": "1.0.0", + "log-driver": "1.2.7", + "minimist": "1.2.7" }, "bin": { "coveralls": "bin/coveralls.js" }, "engines": { - "node": ">=6" + "node": ">=14" + } + }, + "node_modules/coveralls-next/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/coveralls-next/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/coveralls-next/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, "node_modules/create-require": { @@ -2768,25 +2820,6 @@ "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", "dev": true }, - "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/foreground-child": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", @@ -2809,9 +2842,9 @@ } }, "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -3134,20 +3167,6 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -3564,20 +3583,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -3606,9 +3611,7 @@ }, "node_modules/lcov-parse": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", - "integrity": "sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ==", - "dev": true, + "license": "BSD-3-Clause", "bin": { "lcov-parse": "bin/cli.js" } @@ -3672,9 +3675,7 @@ }, "node_modules/log-driver": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", - "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", - "dev": true, + "license": "ISC", "engines": { "node": ">=0.8.6" } @@ -3773,19 +3774,19 @@ } }, "node_modules/mime-db": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", - "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.33", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", - "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { - "mime-db": "1.50.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" @@ -3803,10 +3804,12 @@ } }, "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/mkdirp": { "version": "1.0.4", @@ -4594,6 +4597,73 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/postman-request": { + "version": "2.88.1-postman.32", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.32.tgz", + "integrity": "sha512-Zf5D0b2G/UmnmjRwQKhYy4TBkuahwD0AMNyWwFK3atxU1u5GS38gdd7aw3vyR6E7Ii+gD//hREpflj2dmpbE7w==", + "dependencies": { + "@postman/form-data": "~3.1.1", + "@postman/tough-cookie": "~4.1.2-postman.1", + "@postman/tunnel-agent": "^0.6.3", + "aws-sign2": "~0.7.0", + "aws4": "^1.12.0", + "brotli": "^1.3.3", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "har-validator": "~5.1.3", + "http-signature": "~1.3.1", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "^2.1.35", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.3", + "safe-buffer": "^5.1.2", + "stream-length": "^1.0.2", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postman-request/node_modules/http-signature": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", + "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.14.1" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/postman-request/node_modules/jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "node_modules/postman-request/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -4629,11 +4699,6 @@ "node": ">=0.4.0" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "node_modules/psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -4670,6 +4735,11 @@ "node": ">=0.6" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4840,37 +4910,6 @@ "node": ">= 4.0.0" } }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -4890,6 +4929,11 @@ "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", "integrity": "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==" }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -4949,9 +4993,9 @@ } }, "node_modules/roku-deploy": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.10.1.tgz", - "integrity": "sha512-utPNny0a2m/N0AQT6zyVLXtrr81KR5QeJqPUbc59VBcqGM+WIi7rQ9hBLmp5dFqIo/7JVKQYFs+nFGebOA6F7w==", + "version": "3.10.3", + "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.10.3.tgz", + "integrity": "sha512-COJSQ638QklcM+8AN1nujFuzT04rTZLFuLSww35edm8w/y0l60oF/Iu7TQ46m75DwoGFzGFfomLEmA1ltQk9mA==", "dependencies": { "chalk": "^2.4.2", "dateformat": "^3.0.3", @@ -4964,7 +5008,7 @@ "micromatch": "^4.0.4", "moment": "^2.29.1", "parse-ms": "^2.1.0", - "request": "^2.88.0", + "postman-request": "^2.88.1-postman.32", "temp-dir": "^2.0.0", "xml2js": "^0.5.0" }, @@ -5120,9 +5164,9 @@ } }, "node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -5325,6 +5369,19 @@ "node": ">=0.10.0" } }, + "node_modules/stream-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-length/-/stream-length-1.0.2.tgz", + "integrity": "sha512-aI+qKFiwoDV4rsXiS7WRoCt+v2RX1nUj17+KJC5r2gfh5xoSJIfP6Y3Do/HtvesFcTSWthIuJ3l1cvKQY/+nZg==", + "dependencies": { + "bluebird": "^2.6.2" + } + }, + "node_modules/stream-length/node_modules/bluebird": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", + "integrity": "sha512-UfFSr22dmHPQqPP9XWHRhq+gWnHCYguQGkXQlbyPtW5qTnhFWA8/iXg765tH0cAjy7l/zPJ1aBTO0g5XgA7kvQ==" + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -5491,18 +5548,6 @@ "nodetouch": "bin/nodetouch.js" } }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/traverse-chain": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz", @@ -5588,17 +5633,6 @@ "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", @@ -5690,6 +5724,15 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -5697,9 +5740,8 @@ }, "node_modules/uuid": { "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "license": "MIT", "bin": { "uuid": "bin/uuid" } @@ -5830,9 +5872,9 @@ "dev": true }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -6443,15 +6485,51 @@ "fastq": "^1.6.0" } }, + "@postman/form-data": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.1.tgz", + "integrity": "sha512-vjh8Q2a8S6UCm/KKs31XFJqEEgmbjBmpPNVV2eVav6905wyFAwaUOBGA1NPBI4ERH9MMZc6w0umFgM6WbEPMdg==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "@postman/tough-cookie": { + "version": "4.1.2-postman.2", + "resolved": "https://registry.npmjs.org/@postman/tough-cookie/-/tough-cookie-4.1.2-postman.2.tgz", + "integrity": "sha512-nrBdX3jA5HzlxTrGI/I0g6pmUKic7xbGA4fAMLFgmJCA3DL2Ma+3MvmD+Sdiw9gLEzZJIF4fz33sT8raV/L/PQ==", + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "dependencies": { + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==" + } + } + }, + "@postman/tunnel-agent": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.3.tgz", + "integrity": "sha512-k57fzmAZ2PJGxfOA4SGR05ejorHbVAa/84Hxh/2nAztjNXc4ZjOm9NUIk6/Z6LCrBvJZqjRZbN8e/nROVUPVdg==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "@rokucommunity/bslib": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@rokucommunity/bslib/-/bslib-0.1.1.tgz", "integrity": "sha512-2ox6EUL+UTtccTbD4dbVjZK3QHa0PHCqpoKMF8lZz9ayzzEP3iVPF8KZR6hOi6bxsIcbGXVjqmtCVkpC4P9SrA==" }, "@rokucommunity/logger": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@rokucommunity/logger/-/logger-0.3.2.tgz", - "integrity": "sha512-s2Uzt+SAyLXs0mBvcgRv1iuU3Xig7m1LrHCth6YIxQu2/DWqlefV5NP4fo/wdzvhAo/ozoMk/nCVrt8LlGiMtA==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@rokucommunity/logger/-/logger-0.3.3.tgz", + "integrity": "sha512-4nmLHHr6pzBoGE11idjjIfCuhXbuo6urqNLfymiqNg8BHtBVLL+PqjIXq3OFFzv2x37w64LoHK+nDk9EHrkxbw==", "requires": { "chalk": "^4.1.2", "fs-extra": "^10.0.0", @@ -6521,8 +6599,7 @@ "@types/caseless": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", - "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", - "dev": true + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" }, "@types/chai": { "version": "4.2.22", @@ -6530,6 +6607,12 @@ "integrity": "sha512-tFfcE+DSTzWAgifkjik9AySNqIyNoYwmR+uecPwwD/XRNfvOjmC/FjCxpiUGDkDVDphPfCUecSQVFw+lN3M3kQ==", "dev": true }, + "@types/dateformat": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/dateformat/-/dateformat-3.0.1.tgz", + "integrity": "sha512-KlPPdikagvL6ELjWsljbyDIPzNCeliYkqRpI+zea99vBBbCIA5JNshZAwQKTON139c87y9qvTFVgkFd14rtS4g==", + "dev": true + }, "@types/debounce": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.1.tgz", @@ -6595,34 +6678,17 @@ "dev": true }, "@types/node": { - "version": "16.11.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz", - "integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==", - "dev": true + "version": "16.11.6" }, "@types/request": { - "version": "2.48.7", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.7.tgz", - "integrity": "sha512-GWP9AZW7foLd4YQxyFZDBepl0lPsWLMEXDZUjQ/c1gqVPDPECrRZyEzuhJdnPWioFCq3Tv0qoGpMD6U+ygd4ZA==", - "dev": true, + "version": "2.48.8", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.8.tgz", + "integrity": "sha512-whjk1EDJPcAR2kYHRbFl/lKeeKYTi05A15K9bnLInCVroNDCtXce57xKdI0/rQaA3K+6q0eFyUBPmqfSndUZdQ==", "requires": { "@types/caseless": "*", "@types/node": "*", "@types/tough-cookie": "*", "form-data": "^2.5.0" - }, - "dependencies": { - "form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - } } }, "@types/semver": { @@ -6641,10 +6707,9 @@ } }, "@types/tough-cookie": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz", - "integrity": "sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==", - "dev": true + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", + "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==" }, "@types/vscode": { "version": "1.61.0", @@ -6951,31 +7016,9 @@ "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==" }, "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" - }, - "axios": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.2.tgz", - "integrity": "sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==", - "requires": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - }, - "dependencies": { - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - } - } + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" }, "balanced-match": { "version": "1.0.2", @@ -6985,8 +7028,7 @@ "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "bcrypt-pbkdf": { "version": "1.0.2", @@ -7034,9 +7076,9 @@ } }, "brighterscript": { - "version": "0.64.3", - "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.64.3.tgz", - "integrity": "sha512-W0k0ID3yCbFGba80d8RlggldvRs6u5Y9F6F+AQ4fWDdJcWeHJ3vy5/pabzDbYD9sen3trVxiZ2yXdA7XK35V5g==", + "version": "0.65.4", + "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.65.4.tgz", + "integrity": "sha512-bp2aVhLOM1xRuZiyKx1WQp4JS8Fm6wfD7kAI4rE3YYMYeMhZ/SxTCvBtmTsaW/Z40oB8bBJXuKSuXgz2uF+ywQ==", "requires": { "@rokucommunity/bslib": "^0.1.1", "@xml-tools/parser": "^1.0.7", @@ -7045,6 +7087,7 @@ "chevrotain": "^7.0.1", "chokidar": "^3.5.1", "clear": "^0.1.0", + "coveralls-next": "^4.2.0", "cross-platform-clear-console": "^2.3.0", "debounce-promise": "^3.1.0", "eventemitter3": "^4.0.0", @@ -7060,7 +7103,7 @@ "parse-ms": "^2.1.0", "readline": "^1.3.0", "require-relative": "^0.8.7", - "roku-deploy": "^3.10.1", + "roku-deploy": "^3.10.3", "serialize-error": "^7.0.1", "source-map": "^0.7.4", "vscode-languageserver": "7.0.0", @@ -7215,6 +7258,14 @@ } } }, + "brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "requires": { + "base64-js": "^1.1.2" + } + }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -7303,9 +7354,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001436", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001436.tgz", - "integrity": "sha512-ZmWkKsnC2ifEPoWUvSAIGyOYwT+keAaaWPHiQ9DfMqS1t6tfuyFYoWR78TeZtznkEQ64+vGXH9cZrElwR2Mrxg==", + "version": "1.0.30001492", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001492.tgz", + "integrity": "sha512-2efF8SAZwgAX1FJr87KWhvuJxnGJKOnctQa8xLOskAXNXq8oiuqgl6u1kk3fFpsp3GgvzlRjiK1sl63hNtFADw==", "dev": true }, "caseless": { @@ -7439,17 +7490,41 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, - "coveralls": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", - "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", - "dev": true, + "coveralls-next": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/coveralls-next/-/coveralls-next-4.2.0.tgz", + "integrity": "sha512-zg41a/4QDSASPtlV6gp+6owoU43U5CguxuPZR3nPZ26M5ZYdEK3MdUe7HwE+AnCZPkucudfhqqJZehCNkz2rYg==", "requires": { - "js-yaml": "^3.13.1", - "lcov-parse": "^1.0.0", - "log-driver": "^1.2.7", - "minimist": "^1.2.5", - "request": "^2.88.2" + "form-data": "4.0.0", + "js-yaml": "4.1.0", + "lcov-parse": "1.0.0", + "log-driver": "1.2.7", + "minimist": "1.2.7" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + } } }, "create-require": { @@ -8124,11 +8199,6 @@ "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", "dev": true }, - "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" - }, "foreground-child": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", @@ -8145,9 +8215,9 @@ "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==" }, "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -8374,16 +8444,6 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -8700,17 +8760,6 @@ "graceful-fs": "^4.1.6" } }, - "jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, "jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -8735,10 +8784,7 @@ "dev": true }, "lcov-parse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", - "integrity": "sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ==", - "dev": true + "version": "1.0.0" }, "levn": { "version": "0.4.1", @@ -8792,10 +8838,7 @@ "dev": true }, "log-driver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", - "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", - "dev": true + "version": "1.2.7" }, "log-symbols": { "version": "4.1.0", @@ -8863,16 +8906,16 @@ } }, "mime-db": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", - "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==" + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { - "version": "2.1.33", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", - "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "mime-db": "1.50.0" + "mime-db": "1.52.0" } }, "minimatch": { @@ -8884,10 +8927,9 @@ } }, "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" }, "mkdirp": { "version": "1.0.4", @@ -9474,6 +9516,63 @@ } } }, + "postman-request": { + "version": "2.88.1-postman.32", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.32.tgz", + "integrity": "sha512-Zf5D0b2G/UmnmjRwQKhYy4TBkuahwD0AMNyWwFK3atxU1u5GS38gdd7aw3vyR6E7Ii+gD//hREpflj2dmpbE7w==", + "requires": { + "@postman/form-data": "~3.1.1", + "@postman/tough-cookie": "~4.1.2-postman.1", + "@postman/tunnel-agent": "^0.6.3", + "aws-sign2": "~0.7.0", + "aws4": "^1.12.0", + "brotli": "^1.3.3", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "har-validator": "~5.1.3", + "http-signature": "~1.3.1", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "^2.1.35", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.3", + "safe-buffer": "^5.1.2", + "stream-length": "^1.0.2", + "uuid": "^8.3.2" + }, + "dependencies": { + "http-signature": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", + "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.14.1" + } + }, + "jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + } + } + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -9500,11 +9599,6 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -9531,6 +9625,11 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -9653,33 +9752,6 @@ "resolved": "https://registry.npmjs.org/replace-last/-/replace-last-1.2.6.tgz", "integrity": "sha512-Cj+MK38VtNu1S5J73mEZY3ciQb9dJajNq1Q8inP4dn/MhJMjHwoAF3Z3FjspwAEV9pfABl565MQucmrjOkty4g==" }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -9696,6 +9768,11 @@ "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", "integrity": "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==" }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -9741,9 +9818,9 @@ } }, "roku-deploy": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.10.1.tgz", - "integrity": "sha512-utPNny0a2m/N0AQT6zyVLXtrr81KR5QeJqPUbc59VBcqGM+WIi7rQ9hBLmp5dFqIo/7JVKQYFs+nFGebOA6F7w==", + "version": "3.10.3", + "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.10.3.tgz", + "integrity": "sha512-COJSQ638QklcM+8AN1nujFuzT04rTZLFuLSww35edm8w/y0l60oF/Iu7TQ46m75DwoGFzGFfomLEmA1ltQk9mA==", "requires": { "chalk": "^2.4.2", "dateformat": "^3.0.3", @@ -9756,7 +9833,7 @@ "micromatch": "^4.0.4", "moment": "^2.29.1", "parse-ms": "^2.1.0", - "request": "^2.88.0", + "postman-request": "^2.88.1-postman.32", "temp-dir": "^2.0.0", "xml2js": "^0.5.0" }, @@ -9877,9 +9954,9 @@ } }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "requires": { "lru-cache": "^6.0.0" } @@ -10034,6 +10111,21 @@ "tweetnacl": "~0.14.0" } }, + "stream-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-length/-/stream-length-1.0.2.tgz", + "integrity": "sha512-aI+qKFiwoDV4rsXiS7WRoCt+v2RX1nUj17+KJC5r2gfh5xoSJIfP6Y3Do/HtvesFcTSWthIuJ3l1cvKQY/+nZg==", + "requires": { + "bluebird": "^2.6.2" + }, + "dependencies": { + "bluebird": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", + "integrity": "sha512-UfFSr22dmHPQqPP9XWHRhq+gWnHCYguQGkXQlbyPtW5qTnhFWA8/iXg765tH0cAjy7l/zPJ1aBTO0g5XgA7kvQ==" + } + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -10163,15 +10255,6 @@ "nopt": "~1.0.10" } }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, "traverse-chain": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz", @@ -10226,14 +10309,6 @@ "tslib": "^1.8.1" } }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", @@ -10303,6 +10378,15 @@ "punycode": "^2.1.0" } }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -10310,8 +10394,7 @@ }, "uuid": { "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + "dev": true }, "v8-compile-cache": { "version": "2.3.0", @@ -10423,9 +10506,9 @@ "dev": true }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", "dev": true }, "workerpool": { diff --git a/package.json b/package.json index 24c32781..f518b1cc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "roku-debug", - "version": "0.18.8", + "version": "0.20.3", "description": "Debug adapter for Roku application development using Node.js", "main": "dist/index.js", "scripts": { @@ -61,6 +61,7 @@ "devDependencies": { "@types/chai": "^4.2.22", "@types/debounce": "^1.2.1", + "@types/dateformat": "~3", "@types/decompress": "^4.2.4", "@types/dedent": "^0.7.0", "@types/find-in-files": "^0.5.1", @@ -68,14 +69,13 @@ "@types/glob": "^7.2.0", "@types/mocha": "^9.0.0", "@types/node": "^16.11.6", - "@types/request": "^2.48.7", "@types/semver": "^7.3.9", "@types/sinon": "^10.0.6", "@types/vscode": "^1.61.0", "@typescript-eslint/eslint-plugin": "^5.2.0", "@typescript-eslint/parser": "^5.2.0", "chai": "^4.3.4", - "coveralls": "^3.1.1", + "coveralls-next": "^4.2.0", "decompress": "^4.2.1", "dedent": "^0.7.0", "eslint": "^8.1.0", @@ -95,20 +95,22 @@ "typescript": "^4.4.4" }, "dependencies": { - "@rokucommunity/logger": "^0.3.2", - "axios": "^1.2.2", - "brighterscript": "^0.64.3", + "@rokucommunity/logger": "^0.3.3", + "brighterscript": "^0.65.4", "dateformat": "^4.6.3", "debounce": "^1.2.1", + "@types/request": "^2.48.8", "eol": "^0.9.1", "eventemitter3": "^4.0.7", + "fast-glob": "^3.2.11", "find-in-files": "^0.5.0", "fs-extra": "^10.0.0", "natural-orderby": "^2.0.3", + "postman-request": "^2.88.1-postman.32", "replace-in-file": "^6.3.2", "replace-last": "^1.2.6", - "roku-deploy": "^3.10.1", - "semver": "^7.3.5", + "roku-deploy": "^3.10.3", + "semver": "^7.5.3", "serialize-error": "^8.1.0", "smart-buffer": "^4.2.0", "source-map": "^0.7.4", @@ -118,4 +120,4 @@ "vscode-languageserver": "^6.1.1", "xml2js": "^0.5.0" } -} +} \ No newline at end of file diff --git a/src/LaunchConfiguration.ts b/src/LaunchConfiguration.ts index a78385b8..fe5abfb4 100644 --- a/src/LaunchConfiguration.ts +++ b/src/LaunchConfiguration.ts @@ -6,6 +6,10 @@ import type { LogLevel } from './logging'; * This interface should always match the schema found in the mock-debug extension manifest. */ export interface LaunchConfiguration extends DebugProtocol.LaunchRequestArguments { + /** + * The current working directory of the launcher. When running from vscode, this should be the value from `${workspaceFolder}` + */ + cwd: string; /** * The host or ip address for the target Roku */ @@ -79,6 +83,75 @@ export interface LaunchConfiguration extends DebugProtocol.LaunchRequestArgument */ consoleOutput: 'full' | 'normal'; + fileLogging?: boolean | { + /** + * Should file logging be enabled + */ + enabled?: boolean; + /** + * Directory where log files should be stored. used when filename is relative + */ + dir?: string; + /** + * The number of log files to keep. undefined or < 0 means keep all + */ + logLimit?: number; + /** + * File logging for the telnet or IO output from the Roku device currently being debugged. (i.e. all the stuff produced by `print` statements in your code) + */ + rokuDevice?: boolean | { + /** + * Should file logging be enabled for this logging type? + */ + enabled?: boolean; + /** + * Directory where log files should be stored. used when filename is relative + */ + dir?: string; + /** + * The name of the log file. When mode==='session', a datestamp will be prepended to this filename. + * Can be absolute or relative, and relative paths will be relative to `this.dir` + */ + filename?: string; + /** + * - 'session' means a unique timestamped file will be created on every debug session. + * - 'append' means all logs will be appended to a single file + */ + mode?: 'session' | 'append'; + /** + * The number of log files to keep. undefined or < 0 means keep all + */ + logLimit?: number; + }; + /** + * File logging for the debugger. Mostly used to provide crash logs to the RokuCommunity team. + */ + debugger?: boolean | { + /** + * Should file logging be enabled for this logging type? + */ + enabled?: boolean; + /** + * Directory where log files should be stored. used when filename is relative + */ + dir?: string; + /** + * The name of the log file. When mode==='session', a datestamp will be prepended to this filename. + * Can be absolute or relative, and relative paths will be relative to `this.dir` + */ + filename?: string; + /** + * - 'session' means a unique timestamped file will be created on every debug session. + * - 'append' means all logs will be appended to a single file + */ + mode?: 'session' | 'append'; + /** + * The number of log files to keep. undefined or < 0 means keep all + */ + logLimit?: number; + }; + }; + /** * If specified, the debug session will start the roku app using the deep link */ @@ -180,6 +253,19 @@ export interface LaunchConfiguration extends DebugProtocol.LaunchRequestArgument * Show variables that are prefixed with a special prefix designated to be hidden */ showHiddenVariables: boolean; + + /** + * If true: turn on ECP rendezvous tracking, or turn on 8080 rendezvous tracking if ECP unsupported + * If false, turn off both. + * @default true + */ + rendezvousTracking: boolean; + + /** + * Delete any currently installed dev channel before starting the debug session + * @default false + */ + deleteDevChannelBeforeInstall: boolean; } export interface ComponentLibraryConfiguration { diff --git a/src/RendezvousTracker.spec.ts b/src/RendezvousTracker.spec.ts index 4f8dc6eb..1399186d 100644 --- a/src/RendezvousTracker.spec.ts +++ b/src/RendezvousTracker.spec.ts @@ -1,7 +1,9 @@ -import * as sinon from 'sinon'; +import { createSandbox } from 'sinon'; +const sinon = createSandbox(); import { assert, expect } from 'chai'; import type { RendezvousHistory } from './RendezvousTracker'; import { RendezvousTracker } from './RendezvousTracker'; +import { SceneGraphDebugCommandController } from './SceneGraphDebugCommandController'; describe('BrightScriptFileUtils ', () => { let rendezvousTracker: RendezvousTracker; @@ -10,7 +12,12 @@ describe('BrightScriptFileUtils ', () => { let expectedHistory: RendezvousHistory; beforeEach(() => { - rendezvousTracker = new RendezvousTracker(); + let deviceInfo = { + 'software-version': '11.5.0', + 'host': '192.168.1.5', + 'remotePort': 8060 + }; + rendezvousTracker = new RendezvousTracker(deviceInfo); rendezvousTracker.registerSourceLocator(async (debuggerPath: string, lineNumber: number) => { //remove preceding pkg: if (debuggerPath.toLowerCase().startsWith('pkg:')) { @@ -259,13 +266,146 @@ describe('BrightScriptFileUtils ', () => { }); - afterEach(() => { + afterEach(async () => { + sinon.restore(); rendezvousTrackerMock.restore(); + + //prevent hitting the network during teardown + rendezvousTracker.toggleEcpRendezvousTracking = () => Promise.resolve() as any; + rendezvousTracker['runSGLogrendezvousCommand'] = () => Promise.resolve() as any; + + await rendezvousTracker?.destroy(); + }); + + describe('isEcpRendezvousTrackingSupported ', () => { + it('works', () => { + rendezvousTracker['deviceInfo']['software-version'] = '11.0.0'; + expect(rendezvousTracker.doesHostSupportEcpRendezvousTracking).to.be.false; + + rendezvousTracker['deviceInfo']['software-version'] = '11.5.0'; + expect(rendezvousTracker.doesHostSupportEcpRendezvousTracking).to.be.true; + + rendezvousTracker['deviceInfo']['software-version'] = '12.0.1'; + expect(rendezvousTracker.doesHostSupportEcpRendezvousTracking).to.be.true; + }); + }); + + describe('on', () => { + it('supports unsubscribing', () => { + const spy = sinon.spy(); + const disconnect = rendezvousTracker.on('rendezvous', spy); + rendezvousTracker['emit']('rendezvous', {}); + rendezvousTracker['emit']('rendezvous', {}); + expect(spy.callCount).to.eql(2); + disconnect(); + expect(spy.callCount).to.eql(2); + //disconnect again to fix code coverage + delete rendezvousTracker['emitter']; + disconnect(); + }); + }); + + describe('getIsTelnetRendezvousTrackingEnabled', () => { + async function doTest(rawResponse: string, expectedValue: boolean) { + const stub = sinon.stub(SceneGraphDebugCommandController.prototype, 'logrendezvous').returns(Promise.resolve({ + result: { + rawResponse: 'on\n' + } + } as any)); + expect( + await rendezvousTracker.getIsTelnetRendezvousTrackingEnabled() + ).to.be.true; + stub.restore(); + } + + it('handles various responses', async () => { + await doTest('on', true); + await doTest('on\n', true); + await doTest('on \n', true); + await doTest('off', false); + await doTest('off\n', false); + await doTest('off \n', false); + }); + + it('does not crash on missing response', async () => { + await doTest(undefined, true); + }); + + it('logs an error', async () => { + const stub = sinon.stub(rendezvousTracker['logger'], 'warn'); + sinon.stub( + SceneGraphDebugCommandController.prototype, 'logrendezvous' + ).returns( + Promise.reject(new Error('crash')) + ); + await rendezvousTracker.getIsTelnetRendezvousTrackingEnabled(); + expect(stub.called).to.be.true; + }); + }); + + describe('startEcpPingTimer', () => { + it('only sets the timer once', () => { + rendezvousTracker.startEcpPingTimer(); + const ecpPingTimer = rendezvousTracker['ecpPingTimer']; + rendezvousTracker.startEcpPingTimer(); + //the timer reference shouldn't have changed + expect(ecpPingTimer).to.eql(rendezvousTracker['ecpPingTimer']); + //stop the timer + rendezvousTracker.stopEcpPingTimer(); + expect(rendezvousTracker['ecpPingTimer']).to.be.undefined; + //stopping while stopped is a noop + rendezvousTracker.stopEcpPingTimer(); + }); + }); + + describe('pingEcpRendezvous ', () => { + it('works', async () => { + sinon.stub(rendezvousTracker, 'getEcpRendezvous').returns(Promise.resolve({ 'trackingEnabled': true, 'items': [{ 'id': '1403', 'startTime': '97771301', 'endTime': '97771319', 'lineNumber': '11', 'file': 'pkg:/components/Tasks/GetSubReddit.brs' }, { 'id': '1404', 'startTime': '97771322', 'endTime': '97771322', 'lineNumber': '15', 'file': 'pkg:/components/Tasks/GetSubReddit.brs' }] })); + await rendezvousTracker.pingEcpRendezvous(); + expect(rendezvousTracker['rendezvousHistory']).to.eql({ 'hitCount': 2, 'occurrences': { 'pkg:/components/Tasks/GetSubReddit.brs': { 'occurrences': { '11': { 'clientLineNumber': 11, 'clientPath': '/components/Tasks/GetSubReddit.brs', 'hitCount': 1, 'totalTime': 0.018, 'type': 'lineInfo' }, '15': { 'clientLineNumber': 15, 'clientPath': '/components/Tasks/GetSubReddit.brs', 'hitCount': 1, 'totalTime': 0, 'type': 'lineInfo' } }, 'hitCount': 2, 'totalTime': 0.018, 'type': 'fileInfo', 'zeroCostHitCount': 1 } }, 'totalTime': 0.018, 'type': 'historyInfo', 'zeroCostHitCount': 1 }); + }); + }); + + describe('activateEcpTracking', () => { + beforeEach(() => { + sinon.stub(rendezvousTracker, 'pingEcpRendezvous').returns(Promise.resolve()); + sinon.stub(rendezvousTracker, 'startEcpPingTimer').callsFake(() => { }); + sinon.stub(rendezvousTracker, 'toggleEcpRendezvousTracking').returns(Promise.resolve(true)); + }); + + it('does not activate if telnet and ecp are both off', async () => { + sinon.stub(rendezvousTracker as any, 'runSGLogrendezvousCommand').returns(Promise.resolve('')); + sinon.stub(rendezvousTracker, 'getIsEcpRendezvousTrackingEnabled').returns(Promise.resolve(false)); + sinon.stub(rendezvousTracker, 'getIsTelnetRendezvousTrackingEnabled').returns(Promise.resolve(false)); + expect( + await rendezvousTracker.activate() + ).to.be.false; + }); + + it('activates if telnet is enabled but ecp is disabled', async () => { + sinon.stub(rendezvousTracker as any, 'runSGLogrendezvousCommand').returns(Promise.resolve('')); + sinon.stub(rendezvousTracker, 'getIsEcpRendezvousTrackingEnabled').returns(Promise.resolve(false)); + sinon.stub(rendezvousTracker, 'getIsTelnetRendezvousTrackingEnabled').returns(Promise.resolve(true)); + expect( + await rendezvousTracker.activate() + ).to.be.true; + }); + + it('activates if telnet is disabled but ecp is enabled', async () => { + sinon.stub(rendezvousTracker as any, 'runSGLogrendezvousCommand').returns(Promise.resolve('')); + sinon.stub(rendezvousTracker, 'getIsEcpRendezvousTrackingEnabled').returns(Promise.resolve(true)); + sinon.stub(rendezvousTracker, 'getIsTelnetRendezvousTrackingEnabled').returns(Promise.resolve(false)); + expect( + await rendezvousTracker.activate() + ).to.be.true; + }); }); describe('processLog ', () => { it('filters out all rendezvous log lines', async () => { rendezvousTrackerMock.expects('emit').withArgs('rendezvous').once(); + rendezvousTracker['trackingSource'] = 'telnet'; + let expected = `channel: Start\nStarting data processing\nData processing completed\n`; assert.equal(await rendezvousTracker.processLog(logString), expected); assert.deepEqual(rendezvousTracker.getRendezvousHistory, expectedHistory); @@ -275,6 +415,8 @@ describe('BrightScriptFileUtils ', () => { it('does not filter out rendezvous log lines', async () => { rendezvousTrackerMock.expects('emit').withArgs('rendezvous').once(); rendezvousTracker.setConsoleOutput('full'); + rendezvousTracker['trackingSource'] = 'telnet'; + assert.equal(await rendezvousTracker.processLog(logString), logString); assert.deepEqual(rendezvousTracker.getRendezvousHistory, expectedHistory); rendezvousTrackerMock.verify(); @@ -304,6 +446,7 @@ describe('BrightScriptFileUtils ', () => { it('to reset the history data', async () => { rendezvousTrackerMock.expects('emit').withArgs('rendezvous').twice(); let expected = `channel: Start\nStarting data processing\nData processing completed\n`; + rendezvousTracker['trackingSource'] = 'telnet'; assert.equal(await rendezvousTracker.processLog(logString), expected); assert.deepEqual(rendezvousTracker.getRendezvousHistory, expectedHistory); diff --git a/src/RendezvousTracker.ts b/src/RendezvousTracker.ts index 9e7fd200..f5661ada 100644 --- a/src/RendezvousTracker.ts +++ b/src/RendezvousTracker.ts @@ -2,9 +2,19 @@ import { EventEmitter } from 'events'; import * as path from 'path'; import * as replaceLast from 'replace-last'; import type { SourceLocation } from './managers/LocationManager'; +import { logger } from './logging'; +import { SceneGraphDebugCommandController } from './SceneGraphDebugCommandController'; +import * as xml2js from 'xml2js'; +import * as request from 'request'; +import { util } from './util'; +import * as semver from 'semver'; + +const telnetRendezvousString = 'on\n'; export class RendezvousTracker { - constructor() { + constructor( + private deviceInfo + ) { this.clientPathsMap = {}; this.emitter = new EventEmitter(); this.filterOutLogs = true; @@ -18,6 +28,20 @@ export class RendezvousTracker { private rendezvousBlocks: RendezvousBlocks; private rendezvousHistory: RendezvousHistory; + /** + * Where should the rendezvous data be tracked from? If ecp, then the ecp ping data will be reported. If telnet, then any + * rendezvous data from telnet will reported. If 'off', then no data will be reported + */ + private trackingSource: 'telnet' | 'ecp' | 'off' = 'off'; + + /** + * Determine if the current Roku device supports the ECP rendezvous tracking feature + */ + public get doesHostSupportEcpRendezvousTracking() { + return semver.gte(this.deviceInfo['software-version'] as string, '11.5.0'); + } + + public logger = logger.createLogger(`[${RendezvousTracker.name}]`); public on(eventname: 'rendezvous', handler: (output: RendezvousHistory) => void); public on(eventName: string, handler: (payload: any) => void) { this.emitter.on(eventName, handler); @@ -60,10 +84,173 @@ export class RendezvousTracker { * Clears the current rendezvous history */ public clearHistory() { + this.logger.log('Clear rendezvous history'); this.rendezvousHistory = this.createNewRendezvousHistory(); this.emit('rendezvous', this.rendezvousHistory); } + private ecpPingTimer: NodeJS.Timer; + + public startEcpPingTimer(): void { + this.logger.log('Start ecp ping timer'); + if (!this.ecpPingTimer) { + this.ecpPingTimer = setInterval(() => { + void this.pingEcpRendezvous(); + }, 1000); + } + } + + public stopEcpPingTimer() { + if (this.ecpPingTimer) { + clearInterval(this.ecpPingTimer); + this.ecpPingTimer = undefined; + } + } + + public async pingEcpRendezvous(): Promise { + try { + // Get ECP rendezvous data, parse it, and send it to event emitter + let ecpData = await this.getEcpRendezvous(); + const items = ecpData?.items ?? []; + if (items.length > 0) { + for (let blockInfo of items) { + let duration = ((parseInt(blockInfo.endTime) - parseInt(blockInfo.startTime)) / 1000).toString(); + this.rendezvousBlocks[blockInfo.id] = { + fileName: await this.updateClientPathMap(blockInfo.file, parseInt(blockInfo.lineNumber)), + lineNumber: blockInfo.lineNumber + }; + this.parseRendezvousLog(this.rendezvousBlocks[blockInfo.id], duration); + } + this.emit('rendezvous', this.rendezvousHistory); + } + } catch (e) { + //if there was an error pinging rendezvous, log the error but don't bring down the app + console.error('There was an error fetching rendezvous data', e?.stack); + } + } + + /** + * Determine if rendezvous tracking is enabled via the 8080 telnet command + */ + public async getIsTelnetRendezvousTrackingEnabled() { + return (await this.runSGLogrendezvousCommand('status'))?.trim()?.toLowerCase() === 'on'; + } + + /** + * Run a SceneGraph logendezvous 8080 command and get the text output + */ + private async runSGLogrendezvousCommand(command: 'status' | 'on' | 'off'): Promise { + let sgDebugCommandController = new SceneGraphDebugCommandController(this.deviceInfo.host as string); + try { + this.logger.info(`port 8080 command: logrendezvous ${command}`); + return (await sgDebugCommandController.logrendezvous(command)).result.rawResponse; + } catch (error) { + this.logger.warn(`An error occurred running SG command "${command}"`, error); + } finally { + await sgDebugCommandController.end(); + } + } + + /** + * Determine if rendezvous tracking is enabled via the ECP command + */ + public async getIsEcpRendezvousTrackingEnabled() { + let ecpData = await this.getEcpRendezvous(); + return ecpData.trackingEnabled; + } + + public async activate(): Promise { + //if ECP tracking is supported, turn that on + if (this.doesHostSupportEcpRendezvousTracking) { + this.logger.log('Activating rendezvous tracking'); + // Toggle ECP tracking off and on to clear the log and then continue tracking + let untrack = await this.toggleEcpRendezvousTracking('untrack'); + let track = await this.toggleEcpRendezvousTracking('track'); + const isEcpTrackingEnabled = untrack && track && await this.getIsEcpRendezvousTrackingEnabled(); + if (isEcpTrackingEnabled) { + this.logger.info('ECP tracking is enabled'); + this.trackingSource = 'ecp'; + this.startEcpPingTimer(); + + //disable telnet rendezvous tracking since ECP is working + try { + await this.runSGLogrendezvousCommand('off'); + } catch { } + return true; + } + } + + this.logger.log('ECP tracking is not supported or had an issue. Trying to use telnet rendezvous tracking'); + //ECP tracking is not supported (or had an issue). Try enabling telnet rendezvous tracking (that only works with run_as_process=0, but worth a try...) + await this.runSGLogrendezvousCommand('on'); + if (await this.getIsTelnetRendezvousTrackingEnabled()) { + this.logger.log('telnet rendezvous tracking is enabled'); + this.trackingSource = 'telnet'; + return true; + } else { + this.logger.log('telnet rendezvous tracking is disabled or encountered an issue. rendezvous tracking is now disabled'); + } + return false; + } + + /** + * Get the response from an ECP sgrendezvous request from the Roku + */ + public async getEcpRendezvous(): Promise { + const url = `http://${this.deviceInfo.host}:${this.deviceInfo.remotePort}/query/sgrendezvous`; + this.logger.info(`Sending ECP rendezvous request:`, url); + // Send rendezvous query to ECP + const rendezvousQuery = await util.httpGet(url); + let rendezvousQueryData = rendezvousQuery.body; + let ecpData: EcpRendezvousData = { + trackingEnabled: false, + items: [] + }; + + this.logger.debug('Parsing rendezvous response', rendezvousQuery); + // Parse rendezvous query data + await new Promise((resolve, reject) => { + xml2js.parseString(rendezvousQueryData, (err, result) => { + if (err) { + reject(err); + } else { + const itemArray = result.sgrendezvous.data[0].item; + ecpData.trackingEnabled = result.sgrendezvous.data[0]['tracking-enabled'][0]; + if (Array.isArray(itemArray)) { + ecpData.items = itemArray.map((obj: any) => ({ + id: obj.id[0], + startTime: obj['start-tm'][0], + endTime: obj['end-tm'][0], + lineNumber: obj['line-number'][0], + file: obj.file[0] + })); + } + resolve(ecpData); + } + }); + }); + this.logger.debug('Parsed ECP rendezvous data:', ecpData); + return ecpData; + } + + /** + * Enable/Disable ECP Rendezvous tracking on the Roku device + * @returns true if successful, false if there was an issue setting the value + */ + public async toggleEcpRendezvousTracking(toggle: 'track' | 'untrack'): Promise { + try { + this.logger.log(`Sending ecp sgrendezvous request: ${toggle}`); + const response = await util.httpPost( + `http://${this.deviceInfo.host}:${this.deviceInfo.remotePort}/sgrendezvous/${toggle}`, + //not sure if we need this, but it works...so probably better to just leave it here + { body: '' } + ); + return true; + } catch (e) { + return false; + } + } + /** * Takes the debug output from the device and parses it for any rendezvous information. * Also if consoleOutput was not set to 'full' then any rendezvous output will be filtered from the output. @@ -79,68 +266,30 @@ export class RendezvousTracker { let match = /\[sg\.node\.(BLOCK|UNBLOCK)\s{0,}\] Rendezvous\[(\d+)\](?:\s\w+\n|\s\w{2}\s(.*)\((\d+)\)|[\s\w]+(\d+\.\d+)+|\s\w+)/g.exec(line); // see the following for an explanation for this regex: https://regex101.com/r/In0t7d/6 if (match) { - let [, type, id, fileName, lineNumber, duration] = match; - if (type === 'BLOCK') { - // detected the start of a rendezvous event - this.rendezvousBlocks[id] = { - fileName: await this.updateClientPathMap(fileName, parseInt(lineNumber)), - lineNumber: lineNumber - }; - } else if (type === 'UNBLOCK' && this.rendezvousBlocks[id]) { - // detected the completion of a rendezvous event - dataChanged = true; - let blockInfo = this.rendezvousBlocks[id]; - let clientLineNumber: string = this.clientPathsMap[blockInfo.fileName]?.clientLines[blockInfo.lineNumber].toString() ?? blockInfo.lineNumber; - - if (this.rendezvousHistory.occurrences[blockInfo.fileName]) { - // file is in history - if (this.rendezvousHistory.occurrences[blockInfo.fileName].occurrences[clientLineNumber]) { - // line is in history, just update it - this.rendezvousHistory.occurrences[blockInfo.fileName].occurrences[clientLineNumber].totalTime += this.getTime(duration); - this.rendezvousHistory.occurrences[blockInfo.fileName].occurrences[clientLineNumber].hitCount++; - } else { - // new line to be added to a file in history - this.rendezvousHistory.occurrences[blockInfo.fileName].occurrences[clientLineNumber] = this.createLineObject(blockInfo.fileName, parseInt(clientLineNumber), duration); - } - } else { - // new file to be added to the history - this.rendezvousHistory.occurrences[blockInfo.fileName] = { - occurrences: { - [clientLineNumber]: this.createLineObject(blockInfo.fileName, parseInt(clientLineNumber), duration) - }, - hitCount: 0, - totalTime: 0, - type: 'fileInfo', - zeroCostHitCount: 0 + if (this.trackingSource === 'telnet') { + let [, type, id, fileName, lineNumber, duration] = match; + if (type === 'BLOCK') { + // detected the start of a rendezvous event + this.rendezvousBlocks[id] = { + fileName: await this.updateClientPathMap(fileName, parseInt(lineNumber)), + lineNumber: lineNumber }; + } else if (type === 'UNBLOCK' && this.rendezvousBlocks[id]) { + // detected the completion of a rendezvous event + dataChanged = true; + let blockInfo = this.rendezvousBlocks[id]; + this.parseRendezvousLog(blockInfo, duration); + + // remove this event from pre history tracking + delete this.rendezvousBlocks[id]; } - - // how much time to add to the files total time - let timeToAdd = this.getTime(duration); - - // increment hit count and add to the total time for this file - this.rendezvousHistory.occurrences[blockInfo.fileName].hitCount++; - this.rendezvousHistory.hitCount++; - - // increment hit count and add to the total time for the history as a whole - this.rendezvousHistory.occurrences[blockInfo.fileName].totalTime += timeToAdd; - this.rendezvousHistory.totalTime += timeToAdd; - - if (timeToAdd === 0) { - this.rendezvousHistory.occurrences[blockInfo.fileName].zeroCostHitCount++; - this.rendezvousHistory.zeroCostHitCount++; - } - - // remove this event from pre history tracking - delete this.rendezvousBlocks[id]; } - + // still need to empty logs even if rendezvous tracking through ECP is enabled if (this.filterOutLogs) { lines.splice(i--, 1); } } } - if (dataChanged) { this.emit('rendezvous', this.rendezvousHistory); } @@ -148,6 +297,48 @@ export class RendezvousTracker { return lines.join('\n'); } + private parseRendezvousLog(blockInfo: { fileName: string; lineNumber: string }, duration: string) { + let clientLineNumber: string = this.clientPathsMap[blockInfo.fileName]?.clientLines[blockInfo.lineNumber].toString() ?? blockInfo.lineNumber; + if (this.rendezvousHistory.occurrences[blockInfo.fileName]) { + // file is in history + if (this.rendezvousHistory.occurrences[blockInfo.fileName].occurrences[clientLineNumber]) { + // line is in history, just update it + this.rendezvousHistory.occurrences[blockInfo.fileName].occurrences[clientLineNumber].totalTime += this.getTime(duration); + this.rendezvousHistory.occurrences[blockInfo.fileName].occurrences[clientLineNumber].hitCount++; + } else { + // new line to be added to a file in history + this.rendezvousHistory.occurrences[blockInfo.fileName].occurrences[clientLineNumber] = this.createLineObject(blockInfo.fileName, parseInt(clientLineNumber), duration); + } + } else { + // new file to be added to the history + this.rendezvousHistory.occurrences[blockInfo.fileName] = { + occurrences: { + [clientLineNumber]: this.createLineObject(blockInfo.fileName, parseInt(clientLineNumber), duration) + }, + hitCount: 0, + totalTime: 0, + type: 'fileInfo', + zeroCostHitCount: 0 + }; + } + + // how much time to add to the files total time + let timeToAdd = this.getTime(duration); + + // increment hit count and add to the total time for this file + this.rendezvousHistory.occurrences[blockInfo.fileName].hitCount++; + this.rendezvousHistory.hitCount++; + + // increment hit count and add to the total time for the history as a whole + this.rendezvousHistory.occurrences[blockInfo.fileName].totalTime += timeToAdd; + this.rendezvousHistory.totalTime += timeToAdd; + + if (timeToAdd === 0) { + this.rendezvousHistory.occurrences[blockInfo.fileName].zeroCostHitCount++; + this.rendezvousHistory.zeroCostHitCount++; + } + } + /** * Checks the client path map for existing path data and adds new data to the map if not found * @param fileName The filename or path parsed from the rendezvous output @@ -242,6 +433,25 @@ export class RendezvousTracker { private getTime(duration?: string): number { return duration ? parseFloat(duration) : 0.000; } + + /** + * Destroy/tear down this class + */ + public async destroy() { + this.emitter?.removeAllListeners(); + this.stopEcpPingTimer(); + //turn off ECP rendezvous tracking + if (this.doesHostSupportEcpRendezvousTracking) { + await this.toggleEcpRendezvousTracking('untrack'); + } + + //turn off telnet rendezvous tracking + try { + await this.runSGLogrendezvousCommand('off'); + } catch (e) { + this.logger.error('Failed to disable logrendezvous over 8080', e); + } + } } export interface RendezvousHistory { @@ -273,6 +483,19 @@ type RendezvousBlocks = Record; +interface EcpRendezvousData { + trackingEnabled: boolean; + items: EcpRendezvousItem[]; +} + +interface EcpRendezvousItem { + id: string; + startTime: string; + endTime: string; + lineNumber: string; + file: string; +} + type ElementType = 'fileInfo' | 'historyInfo' | 'lineInfo'; type RendezvousClientPathMap = Record { this.emit('chanperf', output); }); - - // watch for rendezvous events - this.rendezvousTracker.on('rendezvous', (output) => { - this.emit('rendezvous', output); - }); } private logger = logger.createLogger(`[padapter]`); @@ -65,7 +59,6 @@ export class DebugProtocolAdapter { private compileErrorProcessor: CompileErrorProcessor; private emitter: EventEmitter; private chanperfTracker: ChanperfTracker; - private rendezvousTracker: RendezvousTracker; private socketDebugger: DebugProtocolClient; private nextFrameId = 1; @@ -121,7 +114,6 @@ export class DebugProtocolAdapter { public on(eventName: 'connected', handler: (params: boolean) => void); public on(eventname: 'console-output', handler: (output: string) => void); // TODO: might be able to remove this at some point. public on(eventname: 'protocol-version', handler: (output: ProtocolVersionDetails) => void); - public on(eventname: 'rendezvous', handler: (output: RendezvousHistory) => void); public on(eventName: 'runtime-error', handler: (error: BrightScriptRuntimeError) => void); public on(eventName: 'suspend', handler: () => void); public on(eventName: 'start', handler: () => void); @@ -729,14 +721,6 @@ export class DebugProtocolAdapter { this.emitter = undefined; } - // #region Rendezvous Tracker pass though functions - /** - * Passes the debug functions used to locate the client files and lines to the RendezvousTracker - */ - public registerSourceLocator(sourceLocator: (debuggerPath: string, lineNumber: number) => Promise) { - this.rendezvousTracker.registerSourceLocator(sourceLocator); - } - /** * Passes the log level down to the RendezvousTracker and ChanperfTracker * @param outputLevel the consoleOutput from the launch config @@ -759,7 +743,6 @@ export class DebugProtocolAdapter { public clearChanperfHistory() { this.chanperfTracker.clearHistory(); } - // #endregion public async syncBreakpoints() { //we can't send breakpoints unless we're stopped (or in a protocol version that supports sending them while running). diff --git a/src/adapters/TelnetAdapter.spec.ts b/src/adapters/TelnetAdapter.spec.ts index 2264e6a6..a40a6281 100644 --- a/src/adapters/TelnetAdapter.spec.ts +++ b/src/adapters/TelnetAdapter.spec.ts @@ -3,14 +3,24 @@ import type { EvaluateContainer } from './TelnetAdapter'; import { TelnetAdapter } from './TelnetAdapter'; import * as dedent from 'dedent'; import { HighLevelType } from '../interfaces'; +import { RendezvousTracker } from '../RendezvousTracker'; describe('TelnetAdapter ', () => { let adapter: TelnetAdapter; + let deviceInfo = { + 'software-version': '11.5.0', + 'host': '192.168.1.5', + 'remotePort': 8060 + }; + let rendezvousTracker = new RendezvousTracker(deviceInfo); beforeEach(() => { - adapter = new TelnetAdapter({ - host: '127.0.0.1' - }); + adapter = new TelnetAdapter( + { + host: '127.0.0.1' + }, + rendezvousTracker + ); }); describe('getHighLevelTypeDetails', () => { diff --git a/src/adapters/TelnetAdapter.ts b/src/adapters/TelnetAdapter.ts index b8c391d6..c9c03a65 100644 --- a/src/adapters/TelnetAdapter.ts +++ b/src/adapters/TelnetAdapter.ts @@ -5,8 +5,7 @@ import { rokuDeploy } from 'roku-deploy'; import { PrintedObjectParser } from '../PrintedObjectParser'; import type { BSDebugDiagnostic } from '../CompileErrorProcessor'; import { CompileErrorProcessor } from '../CompileErrorProcessor'; -import type { RendezvousHistory } from '../RendezvousTracker'; -import { RendezvousTracker } from '../RendezvousTracker'; +import type { RendezvousHistory, RendezvousTracker } from '../RendezvousTracker'; import type { ChanperfData } from '../ChanperfTracker'; import { ChanperfTracker } from '../ChanperfTracker'; import type { SourceLocation } from '../managers/LocationManager'; @@ -24,7 +23,8 @@ export class TelnetAdapter { constructor( private options: AdapterOptions & { enableDebuggerAutoRecovery?: boolean; - } + }, + private rendezvousTracker: RendezvousTracker ) { util.normalizeAdapterOptions(this.options); this.options.enableDebuggerAutoRecovery ??= false; @@ -34,19 +34,12 @@ export class TelnetAdapter { this.debugStartRegex = /BrightScript Micro Debugger\./ig; this.debugEndRegex = /Brightscript Debugger>/ig; this.chanperfTracker = new ChanperfTracker(); - this.rendezvousTracker = new RendezvousTracker(); this.compileErrorProcessor = new CompileErrorProcessor(); - // watch for chanperf events this.chanperfTracker.on('chanperf', (output) => { this.emit('chanperf', output); }); - - // watch for rendezvous events - this.rendezvousTracker.on('rendezvous', (output) => { - this.emit('rendezvous', output); - }); } public logger = logger.createLogger(`[tadapter]`); @@ -63,7 +56,6 @@ export class TelnetAdapter { private debugStartRegex: RegExp; private debugEndRegex: RegExp; private chanperfTracker: ChanperfTracker; - private rendezvousTracker: RendezvousTracker; private cache = {}; @@ -86,7 +78,6 @@ export class TelnetAdapter { public on(eventName: 'diagnostics', handler: (params: BSDebugDiagnostic[]) => void); public on(eventName: 'connected', handler: (params: boolean) => void); public on(eventname: 'console-output', handler: (output: string) => void); - public on(eventname: 'rendezvous', handler: (output: RendezvousHistory) => void); public on(eventName: 'runtime-error', handler: (error: BrightScriptRuntimeError) => void); public on(eventName: 'suspend', handler: () => void); public on(eventName: 'start', handler: () => void); @@ -1034,14 +1025,6 @@ export class TelnetAdapter { return Promise.resolve(); } - // #region Rendezvous Tracker pass though functions - /** - * Passes the debug functions used to locate the client files and lines to the RendezvousTracker - */ - public registerSourceLocator(sourceLocator: (debuggerPath: string, lineNumber: number) => Promise) { - this.rendezvousTracker.registerSourceLocator(sourceLocator); - } - /** * Passes the log level down to the RendezvousTracker and ChanperfTracker * @param outputLevel the consoleOutput from the launch config @@ -1064,7 +1047,6 @@ export class TelnetAdapter { public clearChanperfHistory() { this.chanperfTracker.clearHistory(); } - // #endregion public async syncBreakpoints() { //we can't send dynamic breakpoints to the server...so just do nothing diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 8e0fab93..5a1df704 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -260,8 +260,8 @@ export class DebugProtocolClient { this.controlSocket = await this.establishControlConnection(); this.controlSocket.on('data', (data) => { + this.writeToBufferLog('server-to-client', data); this.emit('data', data); - this.logger.info('received server-to-client data\n', { type: 'server-to-client', data: data.toJSON() }); //queue up processing the new data, chunk by chunk void this.bufferQueue.run(async () => { this.buffer = Buffer.concat([this.buffer, data]); @@ -310,6 +310,18 @@ export class DebugProtocolClient { return this.sendRequest(request); } + /** + * Write a specific buffer log entry to the logger, which, when file logging is enabled + * can be extracted and processed through the DebugProtocolClientReplaySession + */ + private writeToBufferLog(type: 'server-to-client' | 'client-to-server' | 'io', buffer: Buffer) { + this.logger.log('[[bufferLog]]:', JSON.stringify({ + type: type, + timestamp: new Date().toISOString(), + buffer: buffer.toJSON() + })); + } + public continue() { return this.processContinueRequest( ContinueRequest.fromJson({ @@ -754,7 +766,8 @@ export class DebugProtocolClient { this.logEvent(request); if (this.controlSocket) { const buffer = request.toBuffer(); - this.logger.info('received client-to-server data\n', { type: 'client-to-server', data: buffer.toJSON() }); + this.writeToBufferLog('client-to-server', buffer); + this.controlSocket.write(buffer); void this.plugins.emit('afterSendRequest', { client: this, @@ -1066,7 +1079,7 @@ export class DebugProtocolClient { let lastPartialLine = ''; this.ioSocket.on('data', (buffer) => { - this.logger.info('received IO data\n', { type: 'io', data: buffer.toJSON() }); + this.writeToBufferLog('io', buffer); let responseText = buffer.toString(); if (!responseText.endsWith('\n')) { // buffer was split, save the partial line diff --git a/src/debugSession/BrightScriptDebugSession.spec.ts b/src/debugSession/BrightScriptDebugSession.spec.ts index 4977c7de..f993a7ed 100644 --- a/src/debugSession/BrightScriptDebugSession.spec.ts +++ b/src/debugSession/BrightScriptDebugSession.spec.ts @@ -10,13 +10,14 @@ import { fileUtils } from '../FileUtils'; import type { EvaluateContainer, StackFrame, TelnetAdapter } from '../adapters/TelnetAdapter'; import { PrimativeType } from '../adapters/TelnetAdapter'; import { defer } from '../util'; -import { HighLevelType, RokuAdapterEvaluateResponse } from '../interfaces'; +import { HighLevelType } from '../interfaces'; import type { LaunchConfiguration } from '../LaunchConfiguration'; import type { SinonStub } from 'sinon'; import { util as bscUtil, standardizePath as s } from 'brighterscript'; import { DefaultFiles } from 'roku-deploy'; import type { AddProjectParams, ComponentLibraryConstructorParams } from '../managers/ProjectManager'; import { ComponentLibraryProject, Project } from '../managers/ProjectManager'; +import { RendezvousTracker } from '../RendezvousTracker'; const sinon = sinonActual.createSandbox(); const tempDir = s`${__dirname}/../../.tmp`; @@ -30,6 +31,7 @@ describe('BrightScriptDebugSession', () => { let responses = []; afterEach(() => { + fsExtra.emptydirSync(tempDir); fsExtra.removeSync(outDir); sinon.restore(); }); @@ -43,8 +45,12 @@ describe('BrightScriptDebugSession', () => { let errorSpy: sinon.SinonSpy; beforeEach(() => { + fsExtra.emptydirSync(tempDir); sinon.restore(); + //prevent calling DebugSession.shutdown() because that calls process.kill(), which would kill the test session + sinon.stub(DebugSession.prototype, 'shutdown').returns(null); + try { session = new BrightScriptDebugSession(); } catch (e) { @@ -542,6 +548,33 @@ describe('BrightScriptDebugSession', () => { }); }); + describe('initRendezvousTracking', () => { + it('clears history when disabled', async () => { + const stub = sinon.stub(session, 'sendEvent'); + const activateStub = sinon.stub(RendezvousTracker.prototype, 'activate'); + const clearHistoryStub = sinon.stub(RendezvousTracker.prototype, 'clearHistory'); + + session['launchConfiguration'].rendezvousTracking = false; + + await session['initRendezvousTracking'](); + expect(clearHistoryStub.called).to.be.true; + expect(activateStub.called).to.be.false; + }); + + it('activates when not disabled', async () => { + const stub = sinon.stub(session, 'sendEvent'); + const activateStub = sinon.stub(RendezvousTracker.prototype, 'activate'); + const clearHistoryStub = sinon.stub(RendezvousTracker.prototype, 'clearHistory'); + + session['launchConfiguration'].rendezvousTracking = undefined; + + await session['initRendezvousTracking'](); + expect(clearHistoryStub.called).to.be.true; + expect(activateStub.called).to.be.true; + + }); + }); + describe('setBreakPointsRequest', () => { let response; let args: DebugProtocol.SetBreakpointsArguments; @@ -646,8 +679,6 @@ describe('BrightScriptDebugSession', () => { (session as any).launchConfiguration = { retainStagingFolder: false }; - //stub the super shutdown call so it doesn't kill the test session - sinon.stub(DebugSession.prototype, 'shutdown').returns(null); await session.shutdown(); expect(stub.callCount).to.equal(2); diff --git a/src/debugSession/BrightScriptDebugSession.ts b/src/debugSession/BrightScriptDebugSession.ts index 7b196590..f2362966 100644 --- a/src/debugSession/BrightScriptDebugSession.ts +++ b/src/debugSession/BrightScriptDebugSession.ts @@ -1,7 +1,6 @@ import * as fsExtra from 'fs-extra'; import { orderBy } from 'natural-orderby'; import * as path from 'path'; -import * as request from 'request'; import { rokuDeploy, CompileError } from 'roku-deploy'; import type { RokuDeploy, RokuDeployOptions } from 'roku-deploy'; import { @@ -31,6 +30,7 @@ import type { EvaluateContainer } from '../adapters/DebugProtocolAdapter'; import { isDebugProtocolAdapter, DebugProtocolAdapter } from '../adapters/DebugProtocolAdapter'; import { TelnetAdapter } from '../adapters/TelnetAdapter'; import type { BSDebugDiagnostic } from '../CompileErrorProcessor'; +import { RendezvousTracker } from '../RendezvousTracker'; import { LaunchStartEvent, LogOutputEvent, @@ -49,10 +49,9 @@ import { LocationManager } from '../managers/LocationManager'; import type { AugmentedSourceBreakpoint } from '../managers/BreakpointManager'; import { BreakpointManager } from '../managers/BreakpointManager'; import type { LogMessage } from '../logging'; -import { logger, debugServerLogOutputEventTransport } from '../logging'; +import { logger, FileLoggingManager, debugServerLogOutputEventTransport } from '../logging'; import { VariableType } from '../debugProtocol/events/responses/VariablesResponse'; import type { DeviceInfo } from '../DeviceInfo'; -import axios from 'axios'; import * as xml2js from 'xml2js'; export class BrightScriptDebugSession extends BaseDebugSession { @@ -72,6 +71,7 @@ export class BrightScriptDebugSession extends BaseDebugSession { //send newly-verified breakpoints to vscode this.breakpointManager.on('breakpoints-verified', (data) => this.onDeviceVerifiedBreakpoints(data)); this.projectManager = new ProjectManager(this.breakpointManager, this.locationManager); + this.fileLoggingManager = new FileLoggingManager(); } private onDeviceVerifiedBreakpoints(data: { breakpoints: AugmentedSourceBreakpoint[] }) { @@ -102,6 +102,8 @@ export class BrightScriptDebugSession extends BaseDebugSession { public projectManager: ProjectManager; + public fileLoggingManager: FileLoggingManager; + public breakpointManager: BreakpointManager; public locationManager: LocationManager; @@ -128,6 +130,8 @@ export class BrightScriptDebugSession extends BaseDebugSession { private rokuAdapter: DebugProtocolAdapter | TelnetAdapter; + private rendezvousTracker: RendezvousTracker; + public tempVarPrefix = '__rokudebug__'; private get enableDebugProtocol() { @@ -190,8 +194,15 @@ export class BrightScriptDebugSession extends BaseDebugSession { } private showPopupMessage(message: string, severity: 'error' | 'warn' | 'info') { + this.logger.trace('[showPopupMessage]', severity, message); this.sendEvent(new PopupMessageEvent(message, severity)); } + /** + * Get the cwd from the launchConfiguration, or default to process.cwd() + */ + private get cwd() { + return this.launchConfiguration?.cwd ?? process.cwd(); + } public async fetchDeviceInfo(host: string, remotePort: number) { @@ -199,8 +210,8 @@ export class BrightScriptDebugSession extends BaseDebugSession { const url = `http://${host}:${remotePort}/query/device-info`; try { // concatenates the url string using template literals - const res = await axios.get(url); - let xml = res.data; + const ressponse = await util.httpGet(url); + const xml = ressponse.body; // parses the xml data to JSON object const result = (await xml2js.parseStringPromise(xml))['device-info']; @@ -215,6 +226,9 @@ export class BrightScriptDebugSession extends BaseDebugSession { } } + result.host = this.launchConfiguration.host; + result.remotePort = this.launchConfiguration.remotePort; + // parses string value to int for the following fields result['software-build'] = parseInt(result['software-build'] as string); result.uptime = parseInt(result.uptime as string); @@ -256,6 +270,9 @@ export class BrightScriptDebugSession extends BaseDebugSession { return this.shutdown(`Developer mode is not enabled for host '${this.launchConfiguration.host}'.`); } + //initialize all file logging (rokuDevice, debugger, etc) + this.fileLoggingManager.activate(this.launchConfiguration?.fileLogging, this.cwd); + this.projectManager.launchConfiguration = this.launchConfiguration; this.breakpointManager.launchConfiguration = this.launchConfiguration; @@ -274,7 +291,9 @@ export class BrightScriptDebugSession extends BaseDebugSession { util.log(`Connecting to Roku via ${this.enableDebugProtocol ? 'the BrightScript debug protocol' : 'telnet'} at ${this.launchConfiguration.host}`); - this.createRokuAdapter(this.launchConfiguration.host); + await this.initRendezvousTracking(); + + this.createRokuAdapter(this.launchConfiguration.host, this.rendezvousTracker); if (!this.enableDebugProtocol) { //connect to the roku debug via telnet if (!this.rokuAdapter.connected) { @@ -289,11 +308,6 @@ export class BrightScriptDebugSession extends BaseDebugSession { //press the home button to ensure we're at the home screen await this.rokuDeploy.pressHomeButton(this.launchConfiguration.host, this.launchConfiguration.remotePort); - //pass the debug functions used to locate the client files and lines thought the adapter to the RendezvousTracker - this.rokuAdapter.registerSourceLocator(async (debuggerPath: string, lineNumber: number) => { - return this.projectManager.getSourceLocation(debuggerPath, lineNumber); - }); - //pass the log level down thought the adapter to the RendezvousTracker and ChanperfTracker this.rokuAdapter.setConsoleOutput(this.launchConfiguration.consoleOutput); @@ -313,11 +327,6 @@ export class BrightScriptDebugSession extends BaseDebugSession { this.sendEvent(new ChanperfEvent(output)); }); - // Send rendezvous events to the extension - this.rokuAdapter.on('rendezvous', (output) => { - this.sendEvent(new RendezvousEvent(output)); - }); - //listen for a closed connection (shut down when received) this.rokuAdapter.on('close', (reason = '') => { if (reason === 'compileErrors') { @@ -405,16 +414,53 @@ export class BrightScriptDebugSession extends BaseDebugSession { //convert a hostname to an ip address const deepLinkUrl = await util.resolveUrl(this.launchConfiguration.deepLinkUrl); //send the deep link http request - await new Promise((resolve, reject) => { - request.post(deepLinkUrl, (err, response) => { - return err ? reject(err) : resolve(response); - }); - }); + await util.httpPost(deepLinkUrl); } } /** - * Anytime a roku adapter emits diagnostics, this methid is called to handle it. + * Activate rendezvous tracking (IF enabled in the LaunchConfig) + */ + public async initRendezvousTracking() { + const timeout = 5000; + let initCompleted = false; + await Promise.race([ + util.sleep(timeout), + this._initRendezvousTracking().finally(() => { + initCompleted = true; + }) + ]); + + if (initCompleted === false) { + this.showPopupMessage(`Rendezvous tracking timed out after ${timeout}ms. Consider setting "rendezvousTracking": false in launch.json`, 'warn'); + } + } + + private async _initRendezvousTracking() { + this.rendezvousTracker = new RendezvousTracker(this.deviceInfo); + + //pass the debug functions used to locate the client files and lines thought the adapter to the RendezvousTracker + this.rendezvousTracker.registerSourceLocator(async (debuggerPath: string, lineNumber: number) => { + return this.projectManager.getSourceLocation(debuggerPath, lineNumber); + }); + + // Send rendezvous events to the debug protocol client + this.rendezvousTracker.on('rendezvous', (output) => { + this.sendEvent(new RendezvousEvent(output)); + }); + + //clear the history so the user doesn't have leftover rendezvous data from a previous session + this.rendezvousTracker.clearHistory(); + + //if rendezvous tracking is enabled, then enable it on the device + if (this.launchConfiguration.rendezvousTracking !== false) { + // start ECP rendezvous tracking (if possible) + await this.rendezvousTracker.activate(); + } + } + + /** + * Anytime a roku adapter emits diagnostics, this method is called to handle it. */ private async handleDiagnostics(diagnostics: BSDebugDiagnostic[]) { // Roku device and sourcemap work with 1-based line numbers, VSCode expects 0-based lines. @@ -445,6 +491,14 @@ export class BrightScriptDebugSession extends BaseDebugSession { this.logger.log('Uploading zip'); const start = Date.now(); let packageIsPublished = false; + + //delete any currently installed dev channel (if enabled to do so) + if (this.launchConfiguration.deleteDevChannelBeforeInstall === true) { + await this.rokuDeploy.deleteInstalledChannel({ + ...this.launchConfiguration + } as any as RokuDeployOptions); + } + //publish the package to the target Roku const publishPromise = this.rokuDeploy.publish({ ...this.launchConfiguration, @@ -475,6 +529,7 @@ export class BrightScriptDebugSession extends BaseDebugSession { * @param logOutput */ private sendLogOutput(logOutput: string) { + this.fileLoggingManager.writeRokuDeviceLog(logOutput); const lines = logOutput.split(/\r?\n/g); for (let line of lines) { line += '\n'; @@ -1078,11 +1133,11 @@ export class BrightScriptDebugSession extends BaseDebugSession { this.sendResponse(response); } - private createRokuAdapter(host: string) { + private createRokuAdapter(host: string, rendezvousTracker: RendezvousTracker) { if (this.enableDebugProtocol) { - this.rokuAdapter = new DebugProtocolAdapter(this.launchConfiguration, this.projectManager, this.breakpointManager); + this.rokuAdapter = new DebugProtocolAdapter(this.launchConfiguration, this.projectManager, this.breakpointManager, rendezvousTracker); } else { - this.rokuAdapter = new TelnetAdapter(this.launchConfiguration); + this.rokuAdapter = new TelnetAdapter(this.launchConfiguration, rendezvousTracker); } } @@ -1293,38 +1348,70 @@ export class BrightScriptDebugSession extends BaseDebugSession { } } + private shutdownPromise: Promise | undefined = undefined; + /** - * Called when the debugger is terminated + * Called when the debugger is terminated. Feel free to call this as frequently as you want; we'll only run the shutdown process the first time, and return + * the same promise on subsequent calls */ - public async shutdown(errorMessage?: string) { - //if configured, delete the staging directory - if (!this.launchConfiguration.retainStagingFolder) { - for (let stagingFolderPath of this.projectManager?.getStagingFolderPaths() ?? []) { + public async shutdown(errorMessage?: string): Promise { + if (this.shutdownPromise === undefined) { + this.logger.log('[shutdown] Beginning shutdown sequence', errorMessage); + this.shutdownPromise = this._shutdown(errorMessage); + } else { + this.logger.log('[shutdown] Tried to call `.shutdown()` again. Returning the same promise'); + } + return this.shutdownPromise; + } + + private async _shutdown(errorMessage?: string): Promise { + try { + // + this.rendezvousTracker?.destroy?.(); + + //if configured, delete the staging directory + if (!this.launchConfiguration.retainStagingFolder) { + const stagingFolders = this.projectManager?.getStagingFolderPaths() ?? []; + this.logger.info('deleting staging folders', stagingFolders); + for (let stagingFolderPath of stagingFolders) { + try { + fsExtra.removeSync(stagingFolderPath); + } catch (e) { + this.logger.error(e); + util.log(`Error removing staging directory '${stagingFolderPath}': ${JSON.stringify(e)}`); + } + } + } + + //if there was an error message, display it to the user + if (errorMessage) { + this.logger.error(errorMessage); + this.showPopupMessage(errorMessage, 'error'); + } + + if (this.launchConfiguration.stopDebuggerOnAppExit !== false) { + this.logger.log('Destroy rokuAdapter'); + await this.rokuAdapter?.destroy?.(); + //press the home button to return to the home screen try { - fsExtra.removeSync(stagingFolderPath); + this.logger.log('Press home button'); + await this.rokuDeploy.pressHomeButton(this.launchConfiguration.host, this.launchConfiguration.remotePort); } catch (e) { - util.log(`Error removing staging directory '${stagingFolderPath}': ${JSON.stringify(e)}`); + console.error(e); + this.logger.error(e); } } - } - //if there was an error message, display it to the user - if (errorMessage) { - this.logger.error(errorMessage); - this.showPopupMessage(errorMessage, 'error'); - } + this.logger.log('Send terminated event'); + this.sendEvent(new TerminatedEvent()); - if (this.launchConfiguration.stopDebuggerOnAppExit !== false) { - await this.rokuAdapter?.destroy?.(); - //press the home button to return to the home screen - try { - await this.rokuDeploy.pressHomeButton(this.launchConfiguration.host, this.launchConfiguration.remotePort); - } catch (e) { - console.error(e); - } + //shut down the process + this.logger.log('super.shutdown()'); + super.shutdown(); + this.logger.log('shutdown complete'); + } catch (e) { + this.logger.error(e); } - - this.sendEvent(new TerminatedEvent()); } } diff --git a/src/logging.spec.ts b/src/logging.spec.ts new file mode 100644 index 00000000..859dff55 --- /dev/null +++ b/src/logging.spec.ts @@ -0,0 +1,248 @@ +import { expect } from 'chai'; +import * as fsExtra from 'fs-extra'; +import * as sinonActual from 'sinon'; +import type { LaunchConfiguration } from './LaunchConfiguration'; +import { standardizePath as s } from 'brighterscript'; +import { FileLoggingManager, fileTransport } from './logging'; + +const sinon = sinonActual.createSandbox(); +const tempDir = s`${__dirname}/../../.tmp`; + +describe('LoggingManager', () => { + let manager: FileLoggingManager; + + beforeEach(() => { + fsExtra.emptydirSync(tempDir); + sinon.restore(); + manager = new FileLoggingManager(); + //register a writer that discards all log output + fileTransport.setWriter(() => { }); + }); + + afterEach(() => { + fsExtra.emptydirSync(tempDir); + sinon.restore(); + }); + + describe('configure', () => { + function configure(config: Partial, cwd?: string) { + manager.activate(config as any, cwd); + } + it('disables when not specified', () => { + configure(undefined); + expect(manager['fileLogging'].rokuDevice.enabled).to.be.false; + expect(manager['fileLogging'].debugger.enabled).to.be.false; + }); + + it('disables when set to disabled', () => { + configure({ + enabled: false + }); + expect(manager['fileLogging'].rokuDevice.enabled).to.be.false; + expect(manager['fileLogging'].debugger.enabled).to.be.false; + }); + + it('enables both when set to true', () => { + configure({ + enabled: true + }); + expect(manager['fileLogging'].rokuDevice.enabled).to.be.true; + expect(manager['fileLogging'].debugger.enabled).to.be.true; + }); + + it('disables one when explicitly disabled', () => { + configure({ + rokuDevice: false + }); + expect(manager['fileLogging'].rokuDevice.enabled).to.be.false; + expect(manager['fileLogging'].debugger.enabled).to.be.true; + + configure({ + debugger: false + }); + expect(manager['fileLogging'].rokuDevice.enabled).to.be.true; + expect(manager['fileLogging'].debugger.enabled).to.be.false; + }); + + it('uses logfile path when specified and mode="append"', () => { + configure({ + rokuDevice: { + filename: 'telnet.log', + mode: 'append' + }, + debugger: { + filename: 'dbg.log', + mode: 'append' + } + }, tempDir); + expect( + manager['fileLogging'].rokuDevice.filePath + ).to.eql( + s`${tempDir}/logs/telnet.log` + ); + expect( + manager['fileLogging'].debugger.filePath + ).to.eql( + s`${tempDir}/logs/dbg.log` + ); + }); + + it('generates a rolling logfile when specified as session', () => { + let dateText = manager['getLogDate'](new Date()); + sinon.stub(manager as any, 'getLogDate').callsFake((...args) => { + return dateText; + }); + configure({ + rokuDevice: true + }, tempDir); + expect( + manager['fileLogging'].rokuDevice.filePath + ).to.eql( + s`${tempDir}/logs/${dateText}-rokuDevice.log` + ); + }); + + it('uses default dir when not specified', () => { + let dateText = manager['getLogDate'](new Date()); + sinon.stub(manager as any, 'getLogDate').callsFake((...args) => { + return dateText; + }); + configure(true, tempDir); + expect( + manager['fileLogging'].rokuDevice.filePath + ).to.eql( + s`${tempDir}/logs/${dateText}-rokuDevice.log` + ); + expect( + manager['fileLogging'].debugger.filePath + ).to.eql( + s`${tempDir}/logs/${dateText}-debugger.log` + ); + }); + + it('uses root-level dir when specified', () => { + let dateText = manager['getLogDate'](new Date()); + sinon.stub(manager as any, 'getLogDate').callsFake((...args) => { + return dateText; + }); + configure({ + dir: s`${tempDir}/logs2` + }, tempDir); + expect( + manager['fileLogging'].rokuDevice.filePath + ).to.eql( + s`${tempDir}/logs2/${dateText}-rokuDevice.log` + ); + expect( + manager['fileLogging'].debugger.filePath + ).to.eql( + s`${tempDir}/logs2/${dateText}-debugger.log` + ); + }); + + it('uses log-type level dir when specified', () => { + let dateText = manager['getLogDate'](new Date()); + sinon.stub(manager as any, 'getLogDate').callsFake((...args) => { + return dateText; + }); + configure({ + rokuDevice: { + dir: s`${tempDir}/one` + }, + debugger: { + dir: s`${tempDir}/two` + } + }, tempDir); + expect( + manager['fileLogging'].rokuDevice.filePath + ).to.eql( + s`${tempDir}/one/${dateText}-rokuDevice.log` + ); + expect( + manager['fileLogging'].debugger.filePath + ).to.eql( + s`${tempDir}/two/${dateText}-debugger.log` + ); + }); + + it('uses log-type level dir when specified', () => { + let dateText = manager['getLogDate'](new Date()); + sinon.stub(manager as any, 'getLogDate').callsFake((...args) => { + return dateText; + }); + configure({ + rokuDevice: { + dir: s`${tempDir}/one` + }, + debugger: { + dir: s`${tempDir}/two` + } + }, tempDir); + expect( + manager['fileLogging'].rokuDevice.filePath + ).to.eql( + s`${tempDir}/one/${dateText}-rokuDevice.log` + ); + expect( + manager['fileLogging'].debugger.filePath + ).to.eql( + s`${tempDir}/two/${dateText}-debugger.log` + ); + }); + }); + + describe('pruneLogDir', () => { + let logsDir = s`${tempDir}/logs`; + beforeEach(() => { + fsExtra.ensureDirSync(logsDir); + }); + + function writeLogs(dir, filename: string, count: number) { + const paths: string[] = []; + let startDate = new Date(); + for (let i = 0; i < count; i++) { + startDate.setSeconds(startDate.getSeconds() + 1); + + paths.push( + s`${dir}/${manager['getLogDate'](startDate)}-${filename}` + ); + fsExtra.writeFileSync(paths[paths.length - 1], ''); + } + return paths; + } + + it('does not crash when no files were found', () => { + expect( + manager['pruneLogDir'](logsDir, 'log.log', 100) + ).to.eql([]); + }); + + it('does not delete matching files when under the max', () => { + const paths = writeLogs(logsDir, 'rokuDevice.log', 5); + expect( + manager['pruneLogDir'](logsDir, 'rokuDevice.log', 10) + //empty array means no files were deleted + ).to.eql([]); + }); + + it('prunes the oldest files when over the max', () => { + const paths = writeLogs(logsDir, 'rokuDevice.log', 5); + expect( + manager['pruneLogDir'](logsDir, 'rokuDevice.log', 2) + ).to.eql([ + paths[0], + paths[1] + ]); + expect(fsExtra.pathExistsSync(paths[0])).to.be.false; + expect(fsExtra.pathExistsSync(paths[1])).to.be.false; + }); + + it('does not prune when having exactly max number', () => { + const paths = writeLogs(logsDir, 'rokuDevice.log', 5); + expect( + manager['pruneLogDir'](logsDir, 'rokuDevice.log', 5) + //empty array means no files were deleted + ).to.eql([]); + }); + }); +}); diff --git a/src/logging.ts b/src/logging.ts index 05ec5066..23e61c37 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -1,6 +1,14 @@ import { default as defaultLogger } from '@rokucommunity/logger'; import type { Logger } from '@rokucommunity/logger'; import { QueuedTransport } from '@rokucommunity/logger/dist/transports/QueuedTransport'; +import { FileTransport } from '@rokucommunity/logger/dist/transports/FileTransport'; +import type { LaunchConfiguration } from './LaunchConfiguration'; +import * as path from 'path'; +import { util } from './util'; +import * as fsExtra from 'fs-extra'; +import * as dateformat from 'dateformat'; +import { standardizePath as s } from './FileUtils'; + const logger = defaultLogger.createLogger('[dap]'); //disable colors @@ -9,11 +17,148 @@ logger.enableColor = false; logger.consistentLogLevelWidth = true; export const debugServerLogOutputEventTransport = new QueuedTransport(); +/** + * A transport for logging that allows us to write all log output to a file. This should only be activated if the user has enabled 'debugger' file logging + */ +export const fileTransport = new FileTransport(); //add transport immediately so we can queue log entries logger.addTransport(debugServerLogOutputEventTransport); +logger.addTransport(fileTransport); logger.logLevel = 'log'; const createLogger = logger.createLogger.bind(logger) as typeof Logger.prototype.createLogger; export { logger, createLogger }; export type { Logger, LogMessage, LogLevel } from '@rokucommunity/logger'; + +export class FileLoggingManager { + + private fileLogging = { + rokuDevice: { + enabled: false, + filePath: undefined as string + }, + debugger: { + enabled: false, + filePath: undefined as string + } + }; + + /** + * Activate this manager and start processing log data + */ + public activate(config: LaunchConfiguration['fileLogging'], cwd: string) { + cwd ??= process.cwd(); + this.fileLogging = { + rokuDevice: { + enabled: false, + filePath: undefined + }, + debugger: { + enabled: false, + filePath: undefined + } + }; + //diisable all file logging if top-level config is omitted or set to false + if (!config || (typeof config === 'object' && config?.enabled === false)) { + return; + } + let fileLogging = typeof config === 'object' ? { ...config } : {}; + + let defaultDir = path.resolve( + cwd, + fileLogging.dir ?? './logs' + ); + let defaultLogLimit = fileLogging.logLimit ?? Number.MAX_SAFE_INTEGER; + + for (const logType of ['rokuDevice', 'debugger'] as Array<'rokuDevice' | 'debugger'>) { + //rokuDevice log stuff + if (util.isNullish(fileLogging[logType]) || fileLogging[logType] === true) { + fileLogging[logType] = { + enabled: true + }; + } + const logObj = fileLogging[logType]; + if (typeof logObj === 'object') { + //enabled unless explicitly disabled + this.fileLogging[logType].enabled = logObj?.enabled === false ? false : true; + const logLimit = logObj.logLimit ?? defaultLogLimit; + let filename = logObj.filename ?? `${logType}.log`; + let mode = logObj.mode ?? 'session'; + const dir = path.resolve( + cwd, + logObj.dir ?? defaultDir + ); + if (mode === 'session') { + filename = `${this.getLogDate(new Date())}-${filename}`; + //discard the excess session logs that match this filename + this.pruneLogDir(dir, filename, logLimit); + } + + this.fileLogging[logType].filePath = path.resolve( + logObj.dir ?? defaultDir, + filename + ); + } + } + + //if debugger logging is enabled, register the file path which will flush the logs and write all future logs + if (this.fileLogging.debugger.enabled) { + fileTransport.setLogFilePath(this.fileLogging.debugger.filePath); + + //debugger logging is disabled. remove the transport so we don't waste memory queueing log data indefinitely + } else { + logger.removeTransport(fileTransport); + } + } + + /** + * Delete excess log files matching the given filename (and preceeding timestamp) + */ + private pruneLogDir(dir: string, filename: string, max: number) { + const regexp = new RegExp(`\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d∶\\d\\d∶\\d\\d-${filename}`, 'i'); + let files: string[] = []; + try { + //get all the files from this dir + files = fsExtra.readdirSync(dir) + //keep only the file paths that match our filename pattern + .filter(x => regexp.test(x)) + .map(x => s`${dir}/${x}`) + //sort alphabetically + .sort(); + } catch { } + + if (files.length > max) { + let filesToDelete = files.splice(0, files.length - max - 1); + for (const file of filesToDelete) { + fsExtra.removeSync(file); + } + return filesToDelete; + //discard the keepers in order to get the list of files to delete + } else { + return []; + } + } + + /** + * Generate a date string used for log filenames + */ + private getLogDate(date: Date) { + return `${dateformat(date, 'yyyy-mm-dd"T"HH∶MM∶ss')}`; + } + + /** + * Write output from telnet/IO port from the roku device to the file log (if enabled). + */ + public writeRokuDeviceLog(logOutput: string) { + try { + if (this.fileLogging.rokuDevice.enabled) { + fsExtra.appendFileSync(this.fileLogging.rokuDevice.filePath, logOutput); + } + } catch (e) { + console.error(e); + } + } +} + +export type FileLoggingType = 'rokuDevice' | 'debugger'; diff --git a/src/util.ts b/src/util.ts index eca5db89..ad8be3d4 100644 --- a/src/util.ts +++ b/src/util.ts @@ -10,6 +10,10 @@ import { isDottedSetStatement, isIndexedSetStatement, Expression, DiagnosticSeve import { serializeError } from 'serialize-error'; import * as dns from 'dns'; import type { AdapterOptions } from './interfaces'; +import * as r from 'postman-request'; +import type { Response } from 'request'; +import type * as requestType from 'request'; +const request = r as typeof requestType; class Util { /** @@ -406,11 +410,30 @@ class Util { return cancel; } + public isNullish(item: any) { + return item === undefined || item === null; + } + + /** + * Do an http GET request + */ + public httpGet(url: string) { + return new Promise((resolve, reject) => { + request.get(url, (err, response) => { + return err ? reject(err) : resolve(response); + }); + }); + } + /** - * Is the given value null or undefined + * Do an http POST request */ - public isNullish(value: any) { - return value === undefined || value === null; + public httpPost(url: string, options?: requestType.CoreOptions) { + return new Promise((resolve, reject) => { + request.post(url, options, (err, response) => { + return err ? reject(err) : resolve(response); + }); + }); } /** From f1b16f7b6ca8daaa3582dd023e36466cf64047d6 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 20 Sep 2023 10:48:11 -0400 Subject: [PATCH 72/74] Fix breakpoint sync issues (#143) * support marking breakpoints as `pending` * Implement `deleteBreakpoint` for bp manager * retain bp "pending" status if bp is deleted * Fix breakpoint deviceId issues * Only run single breakpoint sync at a time * BreakpointRef support * Fix failing test * Prevent tests from stalling out * Resurrect breakpoints when device failed to remove * Fix bp resurrection * Make the bp events more generic * Add unit test for removing failed add breakpoints * Update changelog for v0.18.9 * 0.18.9 * Update changelog for v0.18.10 * 0.18.10 * Fix crash by using postman-request (#151) * Update changelog for v0.18.11 * 0.18.11 * Fix `file already exists` error and hung process (#152) * Remove axios in favor of postman-request (#153) * Update changelog for v0.18.12 * 0.18.12 * File logging (#155) * Adds FileLoggingManager * Fix missing cwd * Update changelog for v0.19.0 * 0.19.0 * Merge branch 'master' of https://github.com/rokucommunity/roku-debug into DebugProtocolServer * Simplified the relay session test * Move @types/request to deps to fix d.bs files * Update changelog for v0.19.1 * 0.19.1 * Support sgrendezvous through ecp (#150) * A lot of foundational work * Update testing * Push some of the more foundational functions * Add minversion function and await to an async function * Ending curly bracket * Add types * Add add launch config info to device info * Capture rendezvous * Wrap up rendezvous support * delete log * Make code more easily testable * Change a few things around how to handle the rendezvous data * test pingEcpRendezvous * Fix bug with telnet and ecp mismatch * Drive usage based on launch config setting instead of device. Move rendezvous events out of adapters. --------- Co-authored-by: Bronley Plumb * Update changelog for v0.20.0 * 0.20.0 * Fix rendezvous crash (#156) * Fix timing bugs during rendezvous tracking startup * Only emit rendezvous data if new data was received * Update changelog for v0.20.1 * 0.20.1 * Bump word-wrap from 1.2.3 to 1.2.4 (#157) Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4. - [Release notes](https://github.com/jonschlinkert/word-wrap/releases) - [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4) --- updated-dependencies: - dependency-name: word-wrap dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update changelog for v0.20.2 * 0.20.2 * Add option to delete dev channel before install (#158) * Update changelog for v0.20.3 * 0.20.3 * When a breakpoint fails to delete because of error NOT_STOPPED. Store the breakpoints and delete later. * Store the srcHash and destHash as different hashes Create a mapping of destHash to breakpoint deviceId * Unit test fixes * Fix another test * Remove more bp resurrection tests * Always set deviceId for bps, even on error * Remove unnecessary cache and resurrection references --------- Signed-off-by: dependabot[bot] Co-authored-by: Milap Naik Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Christian Holbrook --- package-lock.json | 23 +- package.json | 7 +- src/adapters/DebugProtocolAdapter.spec.ts | 159 ++++++++- src/adapters/DebugProtocolAdapter.ts | 97 +++-- .../DebugProtocolClientReplaySession.spec.ts | 337 +----------------- .../client/DebugProtocolClient.spec.ts | 10 +- .../client/DebugProtocolClient.ts | 53 ++- .../server/DebugProtocolServer.ts | 1 - .../BrightScriptDebugSession.spec.ts | 6 + src/debugSession/BrightScriptDebugSession.ts | 32 +- src/managers/ActionQueue.ts | 5 + src/managers/BreakpointManager.spec.ts | 94 ++++- src/managers/BreakpointManager.ts | 283 +++++++++++---- src/managers/LogManager.ts | 0 src/managers/ProjectManager.ts | 75 ++-- 15 files changed, 652 insertions(+), 530 deletions(-) create mode 100644 src/managers/LogManager.ts diff --git a/package-lock.json b/package-lock.json index 8e0690e3..7febbdb1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@rokucommunity/logger": "^0.3.3", "@types/request": "^2.48.8", - "brighterscript": "^0.65.4", + "brighterscript": "^0.65.0", "dateformat": "^4.6.3", "debounce": "^1.2.1", "eol": "^0.9.1", @@ -45,6 +45,7 @@ "@types/glob": "^7.2.0", "@types/mocha": "^9.0.0", "@types/node": "^16.11.6", + "@types/request": "^2.48.8", "@types/semver": "^7.3.9", "@types/sinon": "^10.0.6", "@types/vscode": "^1.61.0", @@ -749,7 +750,8 @@ "node_modules/@types/caseless": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", - "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", + "dev": true }, "node_modules/@types/chai": { "version": "4.2.22", @@ -829,12 +831,14 @@ }, "node_modules/@types/node": { "version": "16.11.6", + "dev": true, "license": "MIT" }, "node_modules/@types/request": { "version": "2.48.8", "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.8.tgz", "integrity": "sha512-whjk1EDJPcAR2kYHRbFl/lKeeKYTi05A15K9bnLInCVroNDCtXce57xKdI0/rQaA3K+6q0eFyUBPmqfSndUZdQ==", + "dev": true, "dependencies": { "@types/caseless": "*", "@types/node": "*", @@ -860,7 +864,8 @@ "node_modules/@types/tough-cookie": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", - "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==" + "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", + "dev": true }, "node_modules/@types/vscode": { "version": "1.61.0", @@ -2845,6 +2850,7 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -6599,7 +6605,8 @@ "@types/caseless": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", - "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", + "dev": true }, "@types/chai": { "version": "4.2.22", @@ -6678,12 +6685,14 @@ "dev": true }, "@types/node": { - "version": "16.11.6" + "version": "16.11.6", + "dev": true }, "@types/request": { "version": "2.48.8", "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.8.tgz", "integrity": "sha512-whjk1EDJPcAR2kYHRbFl/lKeeKYTi05A15K9bnLInCVroNDCtXce57xKdI0/rQaA3K+6q0eFyUBPmqfSndUZdQ==", + "dev": true, "requires": { "@types/caseless": "*", "@types/node": "*", @@ -6709,7 +6718,8 @@ "@types/tough-cookie": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", - "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==" + "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", + "dev": true }, "@types/vscode": { "version": "1.61.0", @@ -8218,6 +8228,7 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", diff --git a/package.json b/package.json index f518b1cc..048f9461 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "watchFiles": [ "src/**/*" ], - "timeout": "2000", + "timeout": 2000, "fullTrace": true, "watchExtensions": [ "ts" @@ -60,8 +60,8 @@ }, "devDependencies": { "@types/chai": "^4.2.22", - "@types/debounce": "^1.2.1", "@types/dateformat": "~3", + "@types/debounce": "^1.2.1", "@types/decompress": "^4.2.4", "@types/dedent": "^0.7.0", "@types/find-in-files": "^0.5.1", @@ -69,6 +69,7 @@ "@types/glob": "^7.2.0", "@types/mocha": "^9.0.0", "@types/node": "^16.11.6", + "@types/request": "^2.48.8", "@types/semver": "^7.3.9", "@types/sinon": "^10.0.6", "@types/vscode": "^1.61.0", @@ -96,7 +97,7 @@ }, "dependencies": { "@rokucommunity/logger": "^0.3.3", - "brighterscript": "^0.65.4", + "brighterscript": "^0.65.0", "dateformat": "^4.6.3", "debounce": "^1.2.1", "@types/request": "^2.48.8", diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index e4eb6785..d3f3d8b7 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -25,6 +25,7 @@ import { RemoveBreakpointsResponse } from '../debugProtocol/events/responses/Rem import { BreakpointVerifiedUpdate } from '../debugProtocol/events/updates/BreakpointVerifiedUpdate'; import { RemoveBreakpointsRequest } from '../debugProtocol/events/requests/RemoveBreakpointsRequest'; import type { AfterSendRequestEvent } from '../debugProtocol/client/DebugProtocolClientPlugin'; +import { GenericV3Response } from '../debugProtocol/events/responses/GenericV3Response'; import { RendezvousTracker } from '../RendezvousTracker'; const sinon = createSandbox(); @@ -45,6 +46,13 @@ describe('DebugProtocolAdapter', function() { let client: DebugProtocolClient; let plugin: DebugProtocolServerTestPlugin; let breakpointManager: BreakpointManager; + let projectManager: ProjectManager; + let deviceInfo = { + 'software-version': '11.5.0', + 'host': '192.168.1.5', + 'remotePort': 8060 + }; + let rendezvousTracker = new RendezvousTracker(deviceInfo); beforeEach(async () => { sinon.stub(console, 'log').callsFake((...args) => { }); @@ -56,7 +64,7 @@ describe('DebugProtocolAdapter', function() { const locationManager = new LocationManager(sourcemapManager); const rendezvousTracker = new RendezvousTracker({}); breakpointManager = new BreakpointManager(sourcemapManager, locationManager); - const projectManager = new ProjectManager(breakpointManager, locationManager); + projectManager = new ProjectManager(breakpointManager, locationManager); projectManager.mainProject = new Project({ rootDir: rootDir, files: [], @@ -74,7 +82,7 @@ describe('DebugProtocolAdapter', function() { afterEach(async () => { sinon.restore(); - client?.destroy(); + client?.destroy(true); //shut down and destroy the server after each test await server?.stop(); await util.sleep(10); @@ -86,6 +94,8 @@ describe('DebugProtocolAdapter', function() { async function initialize() { await adapter.connect(); client = adapter['socketDebugger']; + client['options'].shutdownTimeout = 100; + client['options'].exitChannelTimeout = 100; //disable logging for tests because they clutter the test output client['logger'].logLevel = 'off'; await Promise.all([ @@ -140,6 +150,136 @@ describe('DebugProtocolAdapter', function() { }); describe('syncBreakpoints', () => { + it('retries at next sync() to delete breakpoints if first request failed', async () => { + await initialize(); + //disable auto breakpoint verification + client.protocolVersion = '3.2.0'; + + //add a single breakpoint first and do a diff to lock in the diff + const bp2 = breakpointManager.setBreakpoint(srcPath, { line: 2 }); + + await breakpointManager.getDiff(projectManager.getAllProjects()); + + //add breakpoints + const [bp1, bp3] = breakpointManager.replaceBreakpoints(srcPath, [ + { line: 1 }, + { line: 3 } + ]); + + //sync the breakpoints so they get added + plugin.pushResponse(AddBreakpointsResponse.fromJson({ + breakpoints: [{ + id: 8, + errorCode: ErrorCode.OK, + ignoreCount: 0 + }, { + id: 9, + errorCode: ErrorCode.OK, + ignoreCount: 0 + }], + requestId: 1 + })); + + //sync the breakpoints. this request will fail, so try deleting the breakpoints again later + await adapter.syncBreakpoints(); + + //now try to delete the breakpoints + breakpointManager.deleteBreakpoints([bp1, bp3]); + + //complete request failure because debugger not stopped + plugin.pushResponse(GenericV3Response.fromJson({ + errorCode: ErrorCode.NOT_STOPPED, + requestId: 1 + })); + + //sync the breakpoints again. ask to delete the breakpoints, but it fails. + await adapter.syncBreakpoints(); + + expect( + [...breakpointManager.failedDeletions.values()].map(x => x.deviceId) + ).to.eql([8, 9]); + + plugin.pushResponse(RemoveBreakpointsResponse.fromJson({ + breakpoints: [{ + id: 8, + errorCode: ErrorCode.OK, + ignoreCount: 0 + }, { + id: 9, + errorCode: ErrorCode.OK, + ignoreCount: 0 + }], + requestId: 1 + })); + + await adapter.syncBreakpoints(); + expect(plugin.getLatestRequest().data.breakpointIds).to.eql([8, 9]); + }); + + it('removes any newly-added breakpoints that have errors', async () => { + await initialize(); + + const [bp1, bp2] = breakpointManager.replaceBreakpoints(srcPath, [ + { line: 1 }, + { line: 2 } + ]); + + plugin.pushResponse(AddBreakpointsResponse.fromJson({ + breakpoints: [{ + id: 3, + errorCode: ErrorCode.OK, + ignoreCount: 0 + }, { + id: 4, + errorCode: ErrorCode.INVALID_ARGS, + ignoreCount: 0 + }], + requestId: 1 + })); + + //sync breakpoints + await adapter.syncBreakpoints(); + + //the bad breakpoint (id=2) should now be removed + expect(breakpointManager.getBreakpoints([bp1, bp2])).to.eql([bp1]); + }); + + it('only allows one to run at a time', async () => { + let concurrentCount = 0; + let maxConcurrentCount = 0; + + sinon.stub(adapter, '_syncBreakpoints').callsFake(async () => { + console.log('_syncBreakpoints'); + concurrentCount++; + maxConcurrentCount = Math.max(0, concurrentCount); + //several nextticks here to give other promises a chance to run + await util.sleep(0); + maxConcurrentCount = Math.max(0, concurrentCount); + await util.sleep(0); + maxConcurrentCount = Math.max(0, concurrentCount); + await util.sleep(0); + maxConcurrentCount = Math.max(0, concurrentCount); + await util.sleep(0); + maxConcurrentCount = Math.max(0, concurrentCount); + concurrentCount--; + }); + + await Promise.all([ + adapter.syncBreakpoints(), + adapter.syncBreakpoints(), + adapter.syncBreakpoints(), + adapter.syncBreakpoints(), + adapter.syncBreakpoints(), + adapter.syncBreakpoints(), + adapter.syncBreakpoints(), + adapter.syncBreakpoints(), + adapter.syncBreakpoints(), + adapter.syncBreakpoints(), + adapter.syncBreakpoints() + ]); + expect(maxConcurrentCount).to.eql(1); + }); + it('removes "added" breakpoints that show up after a breakpoint was already removed', async () => { const bpId = 123; const bpLine = 12; @@ -188,6 +328,9 @@ describe('DebugProtocolAdapter', function() { //delete the breakpoint (before we ever got the deviceId from the server) breakpointManager.replaceBreakpoints(srcPath, []); + //run another breakpoint diff to simulate the breakpoint being deleted before the device responded with the device IDs + await breakpointManager.getDiff(projectManager.getAllProjects()); + //sync the breakpoints again, forcing the bp to be fully deleted let syncPromise = adapter.syncBreakpoints(); //since the breakpoints were deleted before getting deviceIDs, there should be no request sent @@ -243,14 +386,14 @@ describe('DebugProtocolAdapter', function() { //sync the breakpoints to mark this one as "sent to device" await adapter.syncBreakpoints(); - //replace the breakpoints before they were verified - adapter['breakpointManager'].replaceBreakpoints(`${rootDir}/source/main.brs`, []); - breakpoint.deviceId = undefined; + // //replace the breakpoints before they were verified + // adapter['breakpointManager'].replaceBreakpoints(`${rootDir}/source/main.brs`, []); + // breakpoint.deviceId = undefined; - //sync the breakpoints again. Since the breakpoint doesn't have an ID, we shouldn't send any request - await adapter.syncBreakpoints(); + // //sync the breakpoints again. Since the breakpoint doesn't have an ID, we shouldn't send any request + // await adapter.syncBreakpoints(); - expect(plugin.latestRequest?.constructor.name).not.to.eql(RemoveBreakpointsResponse.name); + // expect(plugin.latestRequest?.constructor.name).not.to.eql(RemoveBreakpointsResponse.name); }); it('skips sending AddBreakpoints and AddConditionalBreakpoints command when there are no breakpoints', async () => { diff --git a/src/adapters/DebugProtocolAdapter.ts b/src/adapters/DebugProtocolAdapter.ts index 3ec91d1b..645ca736 100644 --- a/src/adapters/DebugProtocolAdapter.ts +++ b/src/adapters/DebugProtocolAdapter.ts @@ -668,7 +668,7 @@ export class DebugProtocolAdapter { let threads: Thread[] = []; let threadsResponse = await this.socketDebugger.threads(); - for (let i = 0; i < threadsResponse.data.threads.length; i++) { + for (let i = 0; i < threadsResponse.data?.threads?.length ?? 0; i++) { let threadInfo = threadsResponse.data.threads[i]; let thread = { // NOTE: On THREAD_ATTACHED events the threads request is marking the wrong thread as primary. @@ -744,7 +744,20 @@ export class DebugProtocolAdapter { this.chanperfTracker.clearHistory(); } + private syncBreakpointsPromise = Promise.resolve(); public async syncBreakpoints() { + //wait for the previous sync to finish + this.syncBreakpointsPromise = this.syncBreakpointsPromise + //ignore any errors + .catch(() => { }) + //run the next sync + .then(() => this._syncBreakpoints()); + + //return the new promise, which will resolve once our latest `syncBreakpoints()` call is finished + return this.syncBreakpointsPromise; + } + + public async _syncBreakpoints() { //we can't send breakpoints unless we're stopped (or in a protocol version that supports sending them while running). //So...if we're not stopped, quit now. (we'll get called again when the stop event happens) if (!this.socketDebugger?.supportsBreakpointRegistrationWhileRunning && !this.isAtDebuggerPrompt) { @@ -754,16 +767,16 @@ export class DebugProtocolAdapter { //compute breakpoint changes since last sync const diff = await this.breakpointManager.getDiff(this.projectManager.getAllProjects()); - //delete these breakpoints + // REMOVE breakpoints (delete these breakpoints from the device) if (diff.removed.length > 0) { - await this.actionQueue.run(async () => { - const response = await this.socketDebugger.removeBreakpoints( - //TODO handle retrying to remove unverified breakpoints that might get verified in the future AFTER we've removed them (that's hard...) - diff.removed.map(x => x.deviceId).filter(x => typeof x === 'number') - ); - //return true to mark this action as complete, or false to retry the task again in the future - return response.success && response.data.errorCode === ErrorCode.OK; - }, 10); + const response = await this.socketDebugger.removeBreakpoints( + //TODO handle retrying to remove breakpoints that don't have deviceIds yet but might get one in the future + diff.removed.map(x => x.deviceId).filter(x => typeof x === 'number') + ); + + if (response.data?.errorCode === ErrorCode.NOT_STOPPED) { + this.breakpointManager.failedDeletions.push(...diff.removed); + } } if (diff.added.length > 0) { @@ -774,49 +787,55 @@ export class DebugProtocolAdapter { lineNumber: breakpoint.line, hitCount: !isNaN(hitCount) ? hitCount : undefined, conditionalExpression: breakpoint.condition, - key: breakpoint.hash, + srcHash: breakpoint.srcHash, + destHash: breakpoint.destHash, componentLibraryName: breakpoint.componentLibraryName }; }); - //send these new breakpoints to the device - await this.actionQueue.run(async () => { - //split the list into conditional and non-conditional breakpoints. - //(TODO we can eliminate this splitting logic once the conditional breakpoints "continue" bug in protocol is fixed) - const standardBreakpoints: typeof breakpointsToSendToDevice = []; - const conditionalBreakpoints: typeof breakpointsToSendToDevice = []; - for (const breakpoint of breakpointsToSendToDevice) { - if (breakpoint?.conditionalExpression?.trim()) { - conditionalBreakpoints.push(breakpoint); - } else { - standardBreakpoints.push(breakpoint); - } + //split the list into conditional and non-conditional breakpoints. + //(TODO we can eliminate this splitting logic once the conditional breakpoints "continue" bug in protocol is fixed) + const standardBreakpoints: typeof breakpointsToSendToDevice = []; + const conditionalBreakpoints: typeof breakpointsToSendToDevice = []; + for (const breakpoint of breakpointsToSendToDevice) { + if (breakpoint?.conditionalExpression?.trim()) { + conditionalBreakpoints.push(breakpoint); + } else { + standardBreakpoints.push(breakpoint); } - let success = true; - for (const breakpoints of [standardBreakpoints, conditionalBreakpoints]) { - const response = await this.socketDebugger.addBreakpoints(breakpoints); - if (response.data.errorCode === ErrorCode.OK) { - for (let i = 0; i < response?.data?.breakpoints?.length ?? 0; i++) { - const deviceBreakpoint = response.data.breakpoints[i]; + } + for (const breakpoints of [standardBreakpoints, conditionalBreakpoints]) { + const response = await this.socketDebugger.addBreakpoints(breakpoints); + + //if the response was successful, and we have the correct number of breakpoints in the response + if (response.data.errorCode === ErrorCode.OK && response?.data?.breakpoints?.length === breakpoints.length) { + for (let i = 0; i < response?.data?.breakpoints?.length ?? 0; i++) { + const deviceBreakpoint = response.data.breakpoints[i]; + + if (typeof deviceBreakpoint?.id === 'number') { //sync this breakpoint's deviceId with the roku-assigned breakpoint ID this.breakpointManager.setBreakpointDeviceId( - breakpoints[i].key, + breakpoints[i].srcHash, + breakpoints[i].destHash, deviceBreakpoint.id ); } - //return true to mark this action as complete - success &&= true; - } else { - //this action is not yet complete. it should be retried - success &&= false; + + //this breakpoint had an issue. remove it from the client + if (deviceBreakpoint.errorCode !== ErrorCode.OK) { + this.breakpointManager.deleteBreakpoint(breakpoints[i].srcHash); + } } + //the entire response was bad. delete these breakpoints from the client + } else { + this.breakpointManager.deleteBreakpoints( + breakpoints.map(x => x.srcHash) + ); } - return success; - }); + + } } } - - private actionQueue = new ActionQueue(); } export interface StackFrame { diff --git a/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts b/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts index baa6fb63..ee6f83bf 100644 --- a/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts +++ b/src/debugProtocol/DebugProtocolClientReplaySession.spec.ts @@ -1,21 +1,6 @@ import { expect } from 'chai'; import { DebugProtocolClientReplaySession } from './DebugProtocolClientReplaySession'; import type { ProtocolRequest, ProtocolResponse, ProtocolUpdate } from './events/ProtocolEvent'; -import { AddBreakpointsRequest } from './events/requests/AddBreakpointsRequest'; -import { ContinueRequest } from './events/requests/ContinueRequest'; -import { HandshakeRequest } from './events/requests/HandshakeRequest'; -import { StackTraceRequest } from './events/requests/StackTraceRequest'; -import { ThreadsRequest } from './events/requests/ThreadsRequest'; -import { VariablesRequest } from './events/requests/VariablesRequest'; -import { GenericV3Response } from './events/responses/GenericV3Response'; -import { HandshakeV3Response } from './events/responses/HandshakeV3Response'; -import { ListBreakpointsResponse } from './events/responses/ListBreakpointsResponse'; -import { StackTraceV3Response } from './events/responses/StackTraceV3Response'; -import { ThreadsResponse } from './events/responses/ThreadsResponse'; -import { VariablesResponse } from './events/responses/VariablesResponse'; -import { AllThreadsStoppedUpdate } from './events/updates/AllThreadsStoppedUpdate'; -import { IOPortOpenedUpdate } from './events/updates/IOPortOpenedUpdate'; -import { ThreadAttachedUpdate } from './events/updates/ThreadAttachedUpdate'; import * as fsExtra from 'fs-extra'; describe(DebugProtocolClientReplaySession.name, () => { @@ -25,327 +10,11 @@ describe(DebugProtocolClientReplaySession.name, () => { await session.destroy(); }); - it.skip('works for the failing breakpoint flow in fubo', async function test() { + it.skip('debug this debugger.log file', async function test() { this.timeout(10000000000); - const text = ` - {"type":"client-to-server","timestamp":"2023-02-10T15:02:34.732Z","buffer":{"type":"Buffer","data":[98,115,100,101,98,117,103,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:02:34.733Z","buffer":{"type":"Buffer","data":[98,115,100,101,98,117,103,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:02:34.734Z","buffer":{"type":"Buffer","data":[3,0,0,0,2,0,0,0,0,0,0,0,12,0,0,0,131,224,122,24,131,1,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:02:34.796Z","buffer":{"type":"Buffer","data":[20,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,146,31,0,0,27,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,4,66,82,69,65,75,0]}} - {"type":"client-to-server","timestamp":"2023-02-10T15:02:35.164Z","buffer":{"type":"Buffer","data":[12,0,0,0,1,0,0,0,3,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:02:35.736Z","buffer":{"type":"Buffer","data":[91,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:02:35.791Z","buffer":{"type":"Buffer","data":[1,0,0,0,0,0,0,0,1,0,0,0,1,4,0,0,0,66,82,69,65,75,0,1,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,115,117,98,77,97,105,110,40,105,110,112,117,116,65,114,103,117,109,101,110,116,115,32,97,115,32,111,98,106,101,99,116,41,0]}} - {"type":"client-to-server","timestamp":"2023-02-10T15:02:35.806Z","buffer":{"type":"Buffer","data":[16,0,0,0,2,0,0,0,4,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:02:35.812Z","buffer":{"type":"Buffer","data":[46,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:02:35.868Z","buffer":{"type":"Buffer","data":[2,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0]}} - {"type":"client-to-server","timestamp":"2023-02-10T15:02:36.094Z","buffer":{"type":"Buffer","data":[12,0,0,0,3,0,0,0,2,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:02:36.101Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:02:36.147Z","buffer":{"type":"Buffer","data":[3,0,0,0,0,0,0,0]}} - {"type":"io","timestamp":"2023-02-10T15:02:37.058Z","buffer":{"type":"Buffer","data":[48]}} - {"type":"io","timestamp":"2023-02-10T15:02:37.111Z","buffer":{"type":"Buffer","data":[50,45,49,48,32,49,48,58,48,50,58,51,57,46,49,56,51,32,91,82,84,65,93,91,73,78,70,79,93,32,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,32,105,110,105,116,10]}} - {"type":"io","timestamp":"2023-02-10T15:02:43.339Z","buffer":{"type":"Buffer","data":[48]}} - {"type":"io","timestamp":"2023-02-10T15:02:43.380Z","buffer":{"type":"Buffer","data":[50,45,49,48,32,49,53,58,48,50,58,52,53,46,52,54,53,32,91,115,99,114,112,116,46,99,109,112,108,93,32,67,111,109,112,105,108,105,110,103,32,39,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,39,44,32,105,100,32,39,82,83,71,95,66,65,65,65,65,65,89,114,48,104,114,83,39,10]}} - {"type":"io","timestamp":"2023-02-10T15:02:43.435Z","buffer":{"type":"Buffer","data":[48]}} - {"type":"io","timestamp":"2023-02-10T15:02:43.489Z","buffer":{"type":"Buffer","data":[50,45,49,48,32,49,53,58,48,50,58,52,53,46,53,54,49,32,91,115,99,114,112,116,46,108,111,97,100,46,109,107,117,112,93,32,76,111,97,100,105,110,103,32,109,97,114,107,117,112,32,82,83,71,95,66,65,65,65,65,65,89,114,48,104,114,83,32,39,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,39,10]}} - {"type":"io","timestamp":"2023-02-10T15:02:43.536Z","buffer":{"type":"Buffer","data":[48,50,45,49,48,32,49,53,58,48,50,58,52,53,46,54,50,52,32,91,115,99,114,112,116,46,117,110,108,111,97,100,46,109,107,117,112,93,32,85,110,108,111,97,100,105,110,103,32,109,97,114,107,117,112,32,82,83,71,95,66,65,65,65,65,65,89,114,48,104,114,83,32,39,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,39,10]}} - {"type":"io","timestamp":"2023-02-10T15:02:43.627Z","buffer":{"type":"Buffer","data":[48]}} - {"type":"io","timestamp":"2023-02-10T15:02:43.676Z","buffer":{"type":"Buffer","data":[50,45,49,48,32,49,53,58,48,50,58,52,53,46,55,53,51,32,91,115,99,114,112,116,46,112,97,114,115,101,46,109,107,117,112,46,116,105,109,101,93,32,80,97,114,115,101,100,32,109,97,114,107,117,112,32,82,83,71,95,66,65,65,65,65,65,89,114,48,104,114,83,32,39,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,39,32,105,110,32,49,57,50,32,109,105,108,108,105,115,101,99,111,110,100,115,10]}} - {"type":"io","timestamp":"2023-02-10T15:02:48.681Z","buffer":{"type":"Buffer","data":[48]}} - {"type":"io","timestamp":"2023-02-10T15:02:48.722Z","buffer":{"type":"Buffer","data":[50,45,49,48,32,49,53,58,48,50,58,53,48,46,56,48,55,32,91,115,99,114,112,116,46,112,114,111,99,46,109,107,117,112,46,116,105,109,101,93,32,80,114,111,99,101,115,115,101,100,32,109,97,114,107,117,112,32,82,83,71,95,66,65,65,65,65,65,89,114,48,104,114,83,32,39,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,39,32,105,110,32,49,32,109,105,108,108,105,115,101,99,111,110,100,115,10,73,110,105,116,105,97,108,105,122,97,116,105,111,110,32,32,65,108,108,32,108,105,98,114,97,114,105,101,115,32,97,110,100,32,100,101,112,101,110,100,101,110,99,105,101,115,32,97,114,101,32,108,111,97,100,101,100,32,97,110,100,32,119,101,39,114,101,32,114,101,97,100,121,32,116,111,32,103,111,46,10,73,110,105,116,105,97,108,105,122,97,116,105,111,110,32,32,67,111,109,112,111,110,101,110,116,32,108,105,98,114,97,114,121,32,115,104,111,117,108,100,32,110,111,119,32,104,97,118,101,32,99,111,110,116,114,111,108,46,32,32,32,32,32,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,32,61,10,123,10,32,32,32,32,108,111,97,100,116,105,109,101,58,32,49,49,49,51,50,10,32,32,32,32,115,116,97,116,117,115,58,32,34,115,117,99,99,101,115,115,34,10,32,32,32,32,117,114,105,58,32,34,104,116,116,112,58,47,47,49,57,50,46,49,54,56,46,49,46,50,50,58,56,48,56,48,47,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,46,122,105,112,34,10,125,10,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,10]}} - {"type":"io","timestamp":"2023-02-10T15:02:48.865Z","buffer":{"type":"Buffer","data":[49]}} - {"type":"io","timestamp":"2023-02-10T15:02:48.907Z","buffer":{"type":"Buffer","data":[53,58,48,50,32,124,32,91,76,79,71,93,32,32,32,124,32,80,108,97,121,101,114,32,83,101,116,116,105,110,103,32,85,112,32,82,65,70,32,10,49,53,58,48,50,32,124,32,91,76,79,71,93,32,32,32,124,32,77,97,105,110,83,99,101,110,101,32,62,62,62,32,77,65,73,78,32,83,67,69,78,69,32,40,67,79,82,69,41,32,72,65,83,32,66,69,69,78,32,67,82,69,65,84,69,68,33,32,67,76,73,69,78,84,32,86,69,82,83,73,79,78,32,53,46,48,46,48,32,10]}} - {"type":"io","timestamp":"2023-02-10T15:02:48.954Z","buffer":{"type":"Buffer","data":[49,53,58,48,50,32,124,32,91,73,78,70,79,93,32,32,124,32,78,101,116,119,111,114,107,83,101,114,118,105,99,101,32,114,117,110,110,105,110,103,58,32,116,114,117,101,32,10,49,53,58,48,50,32,124,32,91,73,78,70,79,93,32,32,124,32,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,32,114,117,110,110,105,110,103,58,32,116,114,117,101,32,10,10,91,82,65,70,93,32,115,101,116,68,101,98,117,103,79,117,116,112,117,116,40,116,114,117,101,41,10,10,91,82,65,70,93,32,115,101,116,68,101,98,117,103,79,117,116,112,117,116,40,116,114,117,101,41,10]}} - {"type":"io","timestamp":"2023-02-10T15:02:49.135Z","buffer":{"type":"Buffer","data":[10]}} - {"type":"io","timestamp":"2023-02-10T15:02:49.185Z","buffer":{"type":"Buffer","data":[91,82,65,70,93,32,82,111,107,117,95,65,100,115,32,70,114,97,109,101,119,111,114,107,32,118,101,114,115,105,111,110,32,51,46,48,50,51,52,10,10,91,82,65,70,93,32,115,101,116,68,101,98,117,103,79,117,116,112,117,116,40,102,97,108,115,101,41,10,10,91,82,65,70,93,32,82,111,107,117,95,65,100,115,32,70,114,97,109,101,119,111,114,107,32,118,101,114,115,105,111,110,32,51,46,48,50,51,52,10]}} - {"type":"io","timestamp":"2023-02-10T15:02:49.231Z","buffer":{"type":"Buffer","data":[10,91,82,65,70,93,32,115,101,116,68,101,98,117,103,79,117,116,112,117,116,40,102,97,108,115,101,41,10,49,53,58,48,50,32,124,32,91,73,78,70,79,93,32,32,124,32,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,32,114,117,110,110,105,110,103,58,32,116,114,117,101,32,10,49,53,58,48,50,32,124,32,91,76,79,71,93,32,32,32,124,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,114,114,97,121,62,32,61,10,91,10,32,32,32,32,34,65,99,116,105,111,110,34,10,32,32,32,32,34,65,112,112,108,105,99,97,116,105,111,110,83,116,97,116,101,34,10,32,32,32,32,34,65,117,116,104,34,10,32,32,32,32,34,67,111,110,116,101,120,116,77,101,110,117,34,10,32,32,32,32,34,68,101,118,101,108,111,112,101,114,34,10,32,32,32,32,34,68,105,97,108,111,103,34,10,32,32,32,32,34,80,114,111,102,105,108,101,34,10,32,32,32,32,34,80,114,111,102,105,108,101,82,101,103,105,115,116,114,121,34,10,32,32,32,32,34,82,101,103,105,115,116,114,121,34,10,32,32,32,32,34,82,111,119,73,116,101,109,34,10,32,32,32,32,34,83,99,114,101,101,110,34,10,32,32,32,32,34,84,111,97,115,116,34,10,32,32,32,32,34,84,111,111,108,116,105,112,34,10,93,32,10]}} - {"type":"io","timestamp":"2023-02-10T15:02:50.071Z","buffer":{"type":"Buffer","data":[49]}} - {"type":"io","timestamp":"2023-02-10T15:02:50.113Z","buffer":{"type":"Buffer","data":[53,58,48,50,32,124,32,91,73,78,70,79,93,32,32,124,32,115,97,118,101,65,99,99,101,115,115,84,111,107,101,110,32,84,111,107,101,110,32,114,101,102,114,101,115,104,105,110,103,32,105,110,58,32,32,50,56,48,32,32,32,115,101,99,111,110,100,115,32,10]}} - {"type":"io","timestamp":"2023-02-10T15:02:50.181Z","buffer":{"type":"Buffer","data":[49]}} - {"type":"io","timestamp":"2023-02-10T15:02:50.236Z","buffer":{"type":"Buffer","data":[53,58,48,50,32,124,32,91,73,78,70,79,93,32,32,124,32,65,99,116,105,111,110,77,97,110,97,103,101,114,32,101,120,101,99,117,116,101,65,99,116,105,111,110,32,45,32,110,97,118,105,103,97,116,105,111,110,32,10]}} - {"type":"io","timestamp":"2023-02-10T15:02:50.337Z","buffer":{"type":"Buffer","data":[49]}} - {"type":"io","timestamp":"2023-02-10T15:02:50.391Z","buffer":{"type":"Buffer","data":[53,58,48,50,32,124,32,91,73,78,70,79,93,32,32,124,32,80,114,111,102,105,108,101,83,99,114,101,101,110,95,102,57,57,56,53,53,55,57,45,101,100,100,98,45,52,56,51,48,45,56,55,50,101,45,52,55,56,49,52,56,56,50,56,101,101,98,32,111,110,83,99,114,101,101,110,83,104,111,119,110,32,10,48,50,45,49,48,32,49,53,58,48,50,58,53,50,46,52,54,51,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,68,105,97,108,111,103,73,110,105,116,105,97,116,101,32,45,45,45,45,45,45,45,45,45,62,32,84,105,109,101,66,97,115,101,40,50,49,48,48,54,32,109,115,41,10]}} - {"type":"io","timestamp":"2023-02-10T15:07:30.439Z","buffer":{"type":"Buffer","data":[49]}} - {"type":"io","timestamp":"2023-02-10T15:07:30.480Z","buffer":{"type":"Buffer","data":[53,58,48,55,32,124,32,91,73,78,70,79,93,32,32,124,32,115,97,118,101,65,99,99,101,115,115,84,111,107,101,110,32,84,111,107,101,110,32,114,101,102,114,101,115,104,105,110,103,32,105,110,58,32,32,50,56,48,32,32,32,115,101,99,111,110,100,115,32,10]}} - {"type":"io","timestamp":"2023-02-10T15:08:02.106Z","buffer":{"type":"Buffer","data":[49]}} - {"type":"io","timestamp":"2023-02-10T15:08:02.155Z","buffer":{"type":"Buffer","data":[53,58,48,56,32,124,32,91,73,78,70,79,93,32,32,124,32,78,101,119,32,112,114,111,102,105,108,101,32,115,101,108,101,99,116,101,100,58,32,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,83,71,78,111,100,101,58,67,111,110,116,101,110,116,78,111,100,101,62,32,61,10,123,10,32,32,32,32,99,104,97,110,103,101,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,102,111,99,117,115,97,98,108,101,58,32,102,97,108,115,101,10,32,32,32,32,102,111,99,117,115,101,100,67,104,105,108,100,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,73,110,118,97,108,105,100,62,10,32,32,32,32,105,100,58,32,34,53,50,98,54,97,100,100,48,45,54,54,55,57,45,52,50,98,49,45,56,48,49,102,45,51,53,100,100,97,99,99,101,98,102,55,101,34,10,32,32,32,32,97,118,97,116,97,114,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,110,97,109,101,58,32,34,66,114,111,110,108,101,121,34,10,32,32,32,32,116,121,112,101,58,32,34,112,114,111,102,105,108,101,34,10,125,32,10,49,53,58,48,56,32,124,32,91,73,78,70,79,93,32,32,124,32,65,99,116,105,111,110,77,97,110,97,103,101,114,32,101,120,101,99,117,116,101,65,99,116,105,111,110,32,45,32,110,97,116,105,118,101,95,112,114,111,102,105,108,101,95,115,101,108,101,99,116,32,10]}} - {"type":"io","timestamp":"2023-02-10T15:08:02.264Z","buffer":{"type":"Buffer","data":[49]}} - {"type":"io","timestamp":"2023-02-10T15:08:02.309Z","buffer":{"type":"Buffer","data":[53,58,48,56,32,124,32,91,73,78,70,79,93,32,32,124,32,65,99,116,105,111,110,77,97,110,97,103,101,114,32,101,120,101,99,117,116,101,65,99,116,105,111,110,32,45,32,110,97,116,105,118,101,95,110,97,118,105,103,97,116,101,95,116,111,95,108,97,110,100,105,110,103,32,10,49,53,58,48,56,32,124,32,91,73,78,70,79,93,32,32,124,32,65,99,116,105,111,110,77,97,110,97,103,101,114,32,101,120,101,99,117,116,101,65,99,116,105,111,110,32,45,32,110,97,118,105,103,97,116,105,111,110,32,10]}} - {"type":"io","timestamp":"2023-02-10T15:08:02.728Z","buffer":{"type":"Buffer","data":[49]}} - {"type":"io","timestamp":"2023-02-10T15:08:02.771Z","buffer":{"type":"Buffer","data":[53,58,48,56,32,124,32,91,87,65,82,78,93,32,32,124,32,112,97,114,115,101,83,101,99,116,105,111,110,115,32,78,111,32,99,104,105,108,100,32,99,111,109,112,111,110,101,110,116,115,32,116,111,32,114,101,110,100,101,114,46,32,67,97,114,111,117,115,101,108,32,116,104,114,111,119,110,32,111,117,116,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,32,61,10,123,10,32,32,32,32,99,111,109,112,111,110,101,110,116,95,116,121,112,101,58,32,34,99,97,114,100,45,119,105,100,101,34,10,32,32,32,32,99,111,110,116,101,120,116,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,102,111,99,117,115,101,100,95,101,108,101,109,101,110,116,58,32,102,97,108,115,101,10,32,32,32,32,115,105,122,101,58,32,34,109,34,10,32,32,32,32,115,108,117,103,58,32,34,100,118,114,45,99,111,110,116,101,110,116,45,99,97,114,111,117,115,101,108,34,10,32,32,32,32,116,105,116,108,101,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,116,121,112,101,58,32,34,99,97,114,111,117,115,101,108,34,10,125,32,10,49,53,58,48,56,32,124,32,91,87,65,82,78,93,32,32,124,32,112,97,114,115,101,83,101,99,116,105,111,110,115,32,78,111,32,99,104,105,108,100,32,99,111,109,112,111,110,101,110,116,115,32,116,111,32,114,101,110,100,101,114,46,32,67,97,114,111,117,115,101,108,32,116,104,114,111,119,110,32,111,117,116,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,32,61,10,123,10,32,32,32,32,99,111,109,112,111,110,101,110,116,95,116,121,112,101,58,32,34,99,97,114,100,45,119,105,100,101,34,10,32,32,32,32,99,111,110,116,101,120,116,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,102,111,99,117,115,101,100,95,101,108,101,109,101,110,116,58,32,102,97,108,115,101,10,32,32,32,32,115,105,122,101,58,32,34,109,34,10,32,32,32,32,115,108,117,103,58,32,34,118,111,100,45,99,111,110,116,101,110,116,45,99,97,114,111,117,115,101,108,34,10,32,32,32,32,116,105,116,108,101,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,116,121,112,101,58,32,34,99,97,114,111,117,115,101,108,34,10,125,32,10]}} - {"type":"io","timestamp":"2023-02-10T15:08:02.851Z","buffer":{"type":"Buffer","data":[48]}} - {"type":"io","timestamp":"2023-02-10T15:08:02.893Z","buffer":{"type":"Buffer","data":[50,45,49,48,32,49,53,58,48,56,58,48,52,46,57,57,50,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,68,105,97,108,111,103,67,111,109,112,108,101,116,101,32,45,45,45,45,45,45,45,45,45,62,32,68,117,114,97,116,105,111,110,40,51,49,50,53,50,57,32,109,115,41,10,49,53,58,48,56,32,124,32,91,73,78,70,79,93,32,32,124,32,80,114,111,102,105,108,101,83,99,114,101,101,110,95,102,57,57,56,53,53,55,57,45,101,100,100,98,45,52,56,51,48,45,56,55,50,101,45,52,55,56,49,52,56,56,50,56,101,101,98,32,111,110,83,99,114,101,101,110,72,105,100,100,101,110,32,10,48,50,45,49,48,32,49,53,58,48,56,58,48,52,46,57,57,54,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,68,105,97,108,111,103,73,110,105,116,105,97,116,101,32,45,45,45,45,45,45,45,45,45,62,32,84,105,109,101,66,97,115,101,40,51,51,51,53,51,57,32,109,115,41,10,49,53,58,48,56,32,124,32,91,73,78,70,79,93,32,32,124,32,83,101,99,116,105,111,110,115,32,115,101,99,116,105,111,110,115,32,108,111,97,100,101,100,58,32,32,55,32,10]}} - {"type":"io","timestamp":"2023-02-10T15:08:03.749Z","buffer":{"type":"Buffer","data":[48]}} - {"type":"io","timestamp":"2023-02-10T15:08:03.793Z","buffer":{"type":"Buffer","data":[50,45,49,48,32,49,53,58,48,56,58,48,53,46,56,57,48,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,68,105,97,108,111,103,67,111,109,112,108,101,116,101,32,45,45,45,45,45,45,45,45,45,62,32,68,117,114,97,116,105,111,110,40,56,57,52,32,109,115,41,10,48,50,45,49,48,32,49,53,58,48,56,58,48,53,46,56,57,48,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,76,97,117,110,99,104,67,111,109,112,108,101,116,101,32,45,45,45,45,45,45,45,45,45,62,32,80,101,110,100,105,110,103,32,82,101,110,100,101,114,32,80,97,115,115,10]}} - {"type":"io","timestamp":"2023-02-10T15:08:03.898Z","buffer":{"type":"Buffer","data":[48]}} - {"type":"io","timestamp":"2023-02-10T15:08:03.948Z","buffer":{"type":"Buffer","data":[50,45,49,48,32,49,53,58,48,56,58,48,54,46,48,51,57,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,76,97,117,110,99,104,67,111,109,112,108,101,116,101,32,45,45,45,45,45,45,45,45,45,62,32,68,117,114,97,116,105,111,110,40,51,51,52,53,56,50,32,109,115,41,44,32,68,105,97,108,111,103,84,105,109,101,40,51,49,51,52,50,51,32,109,115,41,10]}} - {"type":"client-to-server","timestamp":"2023-02-10T15:08:06.804Z","buffer":{"type":"Buffer","data":[154,0,0,0,4,0,0,0,7,0,0,0,2,0,0,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,46,98,114,115,0,187,0,0,0,0,0,0,0,108,105,98,58,47,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,187,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:06.809Z","buffer":{"type":"Buffer","data":[40,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:06.852Z","buffer":{"type":"Buffer","data":[4,0,0,0,0,0,0,0,2,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:06.899Z","buffer":{"type":"Buffer","data":[1,0,0,0,0,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:06.976Z","buffer":{"type":"Buffer","data":[2,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:07.020Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:07.082Z","buffer":{"type":"Buffer","data":[28,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:07.128Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0]}} - {"type":"io","timestamp":"2023-02-10T15:08:10.120Z","buffer":{"type":"Buffer","data":[49]}} - {"type":"io","timestamp":"2023-02-10T15:08:10.174Z","buffer":{"type":"Buffer","data":[53,58,48,56,32,124,32,91,73,78,70,79,93,32,32,124,32,65,99,116,105,111,110,77,97,110,97,103,101,114,32,101,120,101,99,117,116,101,65,99,116,105,111,110,32,45,32,110,97,118,105,103,97,116,105,111,110,32,10]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:10.357Z","buffer":{"type":"Buffer","data":[28,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:10.409Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:11.357Z","buffer":{"type":"Buffer","data":[27,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:11.403Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,2,0,0,0,4,0,0,0,4,66,82,69,65,75,0]}} - {"type":"client-to-server","timestamp":"2023-02-10T15:08:11.751Z","buffer":{"type":"Buffer","data":[12,0,0,0,5,0,0,0,3,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:11.800Z","buffer":{"type":"Buffer","data":[55,3,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:11.843Z","buffer":{"type":"Buffer","data":[5,0,0,0,0,0,0,0,6,0,0,0,0,4,0,0,0,66,82,69,65,75,0,20,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,22,0,0,0,36,97,110,111,110,95,50,97,57,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,119,105,100,116,104,32,61,32,101,118,101,110,116,46,103,101,116,68,97,116,97,40,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,50,54,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,187,0,0,0,36,97,110,111,110,95,50,100,98,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,99,97,114,111,117,115,101,108,67,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,103,101,116,95,115,116,114,105,110,103,40,115,101,99,116,105,111,110,44,32,34,99,111,109,112,111,110,101,110,116,95,116,121,112,101,34,41,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} - {"type":"client-to-server","timestamp":"2023-02-10T15:08:11.860Z","buffer":{"type":"Buffer","data":[16,0,0,0,6,0,0,0,4,0,0,0,4,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-10T15:08:11.860Z","buffer":{"type":"Buffer","data":[16,0,0,0,7,0,0,0,4,0,0,0,0,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-10T15:08:11.860Z","buffer":{"type":"Buffer","data":[16,0,0,0,8,0,0,0,4,0,0,0,1,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-10T15:08:11.861Z","buffer":{"type":"Buffer","data":[16,0,0,0,9,0,0,0,4,0,0,0,2,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-10T15:08:11.861Z","buffer":{"type":"Buffer","data":[16,0,0,0,10,0,0,0,4,0,0,0,3,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-10T15:08:11.861Z","buffer":{"type":"Buffer","data":[16,0,0,0,11,0,0,0,4,0,0,0,5,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:11.875Z","buffer":{"type":"Buffer","data":[166,1,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:11.923Z","buffer":{"type":"Buffer","data":[6,0,0,0,0,0,0,0,5,0,0,0,187,0,0,0,36,97,110,111,110,95,50,100,98,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,14,0,0,0,36,97,110,111,110,95,50,100,98,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,99,114,101,101,110,115,47,67,97,116,97,108,111,103,83,99,114,101,101,110,47,67,97,116,97,108,111,103,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,71,0,0,0,36,97,110,111,110,95,50,100,98,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,80,97,114,115,101,114,115,95,95,108,105,98,48,46,98,114,115,0,16,0,0,0,36,97,110,111,110,95,50,100,98,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,238,0,0,0,36,97,110,111,110,95,50,100,98,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,66,97,115,101,95,95,108,105,98,48,46,98,114,115,0,46,0,0,0,7,0,0,0,0,0,0,0,1,0,0,0,20,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,129,0,0,0,8,0,0,0,0,0,0,0,1,0,0,0,22,0,0,0,36,97,110,111,110,95,50,97,57,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,104,0,0,0,9,0,0,0,0,0,0,0,1,0,0,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,110,0,0,0,10,0,0,0,0,0,0,0,1,0,0,0,19,0,0,0,32,50,50,54,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,116,0,0,0,11,0,0,0,0,0,0,0,1,0,0,0,21,0,0,0,32,50,50,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0]}} - {"type":"client-to-server","timestamp":"2023-02-10T15:08:12.021Z","buffer":{"type":"Buffer","data":[37,0,0,0,12,0,0,0,5,0,0,0,3,4,0,0,0,4,0,0,0,1,0,0,0,102,105,110,97,108,86,97,108,117,101,0,1]}} - {"type":"client-to-server","timestamp":"2023-02-10T15:08:12.022Z","buffer":{"type":"Buffer","data":[45,0,0,0,13,0,0,0,5,0,0,0,3,4,0,0,0,4,0,0,0,1,0,0,0,115,116,97,114,116,73,110,100,101,120,73,110,83,116,114,105,110,103,0,1]}} - {"type":"client-to-server","timestamp":"2023-02-10T15:08:12.023Z","buffer":{"type":"Buffer","data":[43,0,0,0,14,0,0,0,5,0,0,0,3,4,0,0,0,4,0,0,0,1,0,0,0,101,120,112,114,101,115,115,105,111,110,76,101,110,103,116,104,0,1]}} - {"type":"client-to-server","timestamp":"2023-02-10T15:08:12.023Z","buffer":{"type":"Buffer","data":[39,0,0,0,15,0,0,0,5,0,0,0,3,4,0,0,0,4,0,0,0,1,0,0,0,104,69,120,112,114,101,115,115,105,111,110,115,0,1]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:12.024Z","buffer":{"type":"Buffer","data":[20,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:12.026Z","buffer":{"type":"Buffer","data":[12,0,0,0,5,0,0,0,2,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:12.078Z","buffer":{"type":"Buffer","data":[20,0,0,0,13,0,0,0,5,0,0,0,2,0,0,0,0,0,0,0,20,0,0,0,14,0,0,0,5,0,0,0,2,0,0,0,0,0,0,0,20,0,0,0,15,0,0,0,5,0,0,0,2,0,0,0,0,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-10T15:08:12.373Z","buffer":{"type":"Buffer","data":[25,0,0,0,16,0,0,0,5,0,0,0,1,4,0,0,0,4,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:12.377Z","buffer":{"type":"Buffer","data":[222,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:12.419Z","buffer":{"type":"Buffer","data":[16,0,0,0,0,0,0,0,14,0,0,0,93,1,99,111,110,116,101,110,116,0,2,0,0,0,13,3,0,0,0,41,8,103,108,111,98,97,108,0,105,102,71,108,111,98,97,108,0,29,1,109,0,5,0,0,0,13,15,0,0,0,29,2,115,101,99,116,105,111,110,115,0,1,0,0,0,7,0,0,0,0,93,1,115,101,99,116,105,111,110,0,2,0,0,0,13,8,0,0,0,57,13,99,111,109,112,111,110,101,110,116,116,121,112,101,0,2,0,0,0,99,97,114,111,117,115,101,108,0,9,16,99,97,114,111,117,115,101,108,99,111,109,112,111,110,101,110,116,116,121,112,101,0,9,16,116,97,98,108,105,115,116,0,9,16,99,104,105,112,108,105,115,116,0,9,16,99,97,114,111,117,115,101,108,0,9,16,103,114,105,100,0,9,16,104,101,114,111,0,9,16,108,105,115,116,0,9,16,102,97,108,108,98,97,99,107,0]}} - {"type":"client-to-server","timestamp":"2023-02-10T15:08:12.434Z","buffer":{"type":"Buffer","data":[12,0,0,0,17,0,0,0,2,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:12.435Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:12.481Z","buffer":{"type":"Buffer","data":[17,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:14.376Z","buffer":{"type":"Buffer","data":[27,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:14.419Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,2,0,0,0,3,0,0,0,4,66,82,69,65,75,0,27,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,1,0,0,0,4,66,82,69,65,75,0]}} - {"type":"client-to-server","timestamp":"2023-02-10T15:08:14.751Z","buffer":{"type":"Buffer","data":[12,0,0,0,18,0,0,0,3,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:14.792Z","buffer":{"type":"Buffer","data":[55,3,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-10T15:08:14.840Z","buffer":{"type":"Buffer","data":[18,0,0,0,0,0,0,0,6,0,0,0,0,4,0,0,0,66,82,69,65,75,0,20,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,22,0,0,0,36,97,110,111,110,95,50,97,57,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,119,105,100,116,104,32,61,32,101,118,101,110,116,46,103,101,116,68,97,116,97,40,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,50,54,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,187,0,0,0,36,97,110,111,110,95,50,100,98,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,99,97,114,111,117,115,101,108,67,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,103,101,116,95,115,116,114,105,110,103,40,115,101,99,116,105,111,110,44,32,34,99,111,109,112,111,110,101,110,116,95,116,121,112,101,34,41,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,57,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} - `; + const logPath = 'C:/users/bronley/downloads/2023-06-01T12∶21∶04-debugger.log'; session = new DebugProtocolClientReplaySession({ - bufferLog: text - }); - - await session.run(); - expectClientReplayResult([ - HandshakeRequest, - HandshakeV3Response, - IOPortOpenedUpdate, - AllThreadsStoppedUpdate, - ThreadsRequest, - ThreadsResponse, - StackTraceRequest, - StackTraceV3Response, - ContinueRequest, - GenericV3Response, - AddBreakpointsRequest, - ListBreakpointsResponse, - AllThreadsStoppedUpdate, - ThreadsRequest, - ThreadsResponse, - StackTraceRequest, - StackTraceRequest, - StackTraceRequest, - StackTraceRequest, - StackTraceRequest, - StackTraceRequest, - StackTraceV3Response, - StackTraceV3Response, - StackTraceV3Response, - StackTraceV3Response, - StackTraceV3Response, - StackTraceV3Response, - VariablesRequest, - VariablesRequest, - VariablesRequest, - VariablesRequest, - GenericV3Response, - GenericV3Response, - GenericV3Response, - GenericV3Response, - VariablesRequest, - VariablesResponse, - ContinueRequest, - GenericV3Response, - AllThreadsStoppedUpdate, - ThreadAttachedUpdate, - ThreadsRequest - ], session.result); - console.log(session); - }); - - it.skip('works for this specific thing', async function test() { - this.timeout(10000000000); - const text = ` - {"type":"client-to-server","timestamp":"2023-02-06T16:47:49.370Z","buffer":{"type":"Buffer","data":[98,115,100,101,98,117,103,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:47:49.371Z","buffer":{"type":"Buffer","data":[98,115,100,101,98,117,103,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:47:49.374Z","buffer":{"type":"Buffer","data":[3,0,0,0,2,0,0,0,0,0,0,0,12,0,0,0,131,224,122,24,131,1,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:47:49.428Z","buffer":{"type":"Buffer","data":[20,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,146,31,0,0,27,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,4,66,82,69,65,75,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:47:49.433Z","buffer":{"type":"Buffer","data":[12,0,0,0,1,0,0,0,3,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:47:49.437Z","buffer":{"type":"Buffer","data":[91,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:47:49.489Z","buffer":{"type":"Buffer","data":[1,0,0,0,0,0,0,0,1,0,0,0,1,4,0,0,0,66,82,69,65,75,0,1,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,115,117,98,77,97,105,110,40,105,110,112,117,116,65,114,103,117,109,101,110,116,115,32,97,115,32,111,98,106,101,99,116,41,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:47:49.504Z","buffer":{"type":"Buffer","data":[16,0,0,0,2,0,0,0,4,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:47:49.506Z","buffer":{"type":"Buffer","data":[46,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:47:49.550Z","buffer":{"type":"Buffer","data":[2,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:47:49.552Z","buffer":{"type":"Buffer","data":[12,0,0,0,3,0,0,0,2,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:47:49.554Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:47:49.597Z","buffer":{"type":"Buffer","data":[3,0,0,0,0,0,0,0]}} - {"type":"io","timestamp":"2023-02-06T16:47:50.721Z","buffer":{"type":"Buffer","data":[48]}} - {"type":"io","timestamp":"2023-02-06T16:47:50.773Z","buffer":{"type":"Buffer","data":[50,45,48,54,32,49,49,58,52,55,58,53,50,46,56,54,50,32,91,82,84,65,93,91,73,78,70,79,93,32,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,32,105,110,105,116,10]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:47:57.260Z","buffer":{"type":"Buffer","data":[154,0,0,0,4,0,0,0,7,0,0,0,2,0,0,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,46,98,114,115,0,178,0,0,0,0,0,0,0,108,105,98,58,47,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,178,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:47:57.262Z","buffer":{"type":"Buffer","data":[40,0,0,0]}} - {"type":"io","timestamp":"2023-02-06T16:47:57.284Z","buffer":{"type":"Buffer","data":[48]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:47:57.314Z","buffer":{"type":"Buffer","data":[4,0,0,0,0,0,0,0,2,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0]}} - {"type":"io","timestamp":"2023-02-06T16:47:57.329Z","buffer":{"type":"Buffer","data":[50,45,48,54,32,49,54,58,52,55,58,53,57,46,52,50,54,32,91,115,99,114,112,116,46,99,109,112,108,93,32,67,111,109,112,105,108,105,110,103,32,39,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,39,44,32,105,100,32,39,82,83,71,95,66,65,65,65,65,65,68,48,83,82,101,112,39,10]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:47:57.360Z","buffer":{"type":"Buffer","data":[2,0,0,0,0,0,0,0,0,0,0,0]}} - {"type":"io","timestamp":"2023-02-06T16:47:57.386Z","buffer":{"type":"Buffer","data":[48]}} - {"type":"io","timestamp":"2023-02-06T16:47:57.436Z","buffer":{"type":"Buffer","data":[50,45,48,54,32,49,54,58,52,55,58,53,57,46,53,50,56,32,91,115,99,114,112,116,46,108,111,97,100,46,109,107,117,112,93,32,76,111,97,100,105,110,103,32,109,97,114,107,117,112,32,82,83,71,95,66,65,65,65,65,65,68,48,83,82,101,112,32,39,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,39,10]}} - {"type":"io","timestamp":"2023-02-06T16:47:57.482Z","buffer":{"type":"Buffer","data":[48,50,45,48,54,32,49,54,58,52,55,58,53,57,46,53,57,51,32,91,115,99,114,112,116,46,117,110,108,111,97,100,46,109,107,117,112,93,32,85,110,108,111,97,100,105,110,103,32,109,97,114,107,117,112,32,82,83,71,95,66,65,65,65,65,65,68,48,83,82,101,112,32,39,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,39,10]}} - {"type":"io","timestamp":"2023-02-06T16:47:57.581Z","buffer":{"type":"Buffer","data":[48]}} - {"type":"io","timestamp":"2023-02-06T16:47:57.638Z","buffer":{"type":"Buffer","data":[50,45,48,54,32,49,54,58,52,55,58,53,57,46,55,50,51,32,91,115,99,114,112,116,46,112,97,114,115,101,46,109,107,117,112,46,116,105,109,101,93,32,80,97,114,115,101,100,32,109,97,114,107,117,112,32,82,83,71,95,66,65,65,65,65,65,68,48,83,82,101,112,32,39,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,39,32,105,110,32,49,57,53,32,109,105,108,108,105,115,101,99,111,110,100,115,10]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:47:58.000Z","buffer":{"type":"Buffer","data":[28,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:47:58.054Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0]}} - {"type":"io","timestamp":"2023-02-06T16:48:02.781Z","buffer":{"type":"Buffer","data":[48]}} - {"type":"io","timestamp":"2023-02-06T16:48:02.829Z","buffer":{"type":"Buffer","data":[50,45,48,54,32,49,54,58,52,56,58,48,52,46,57,50,51,32,91,115,99,114,112,116,46,112,114,111,99,46,109,107,117,112,46,116,105,109,101,93,32,80,114,111,99,101,115,115,101,100,32,109,97,114,107,117,112,32,82,83,71,95,66,65,65,65,65,65,68,48,83,82,101,112,32,39,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,39,32,105,110,32,49,32,109,105,108,108,105,115,101,99,111,110,100,115,10,73,110,105,116,105,97,108,105,122,97,116,105,111,110,32,32,65,108,108,32,108,105,98,114,97,114,105,101,115,32,97,110,100,32,100,101,112,101,110,100,101,110,99,105,101,115,32,97,114,101,32,108,111,97,100,101,100,32,97,110,100,32,119,101,39,114,101,32,114,101,97,100,121,32,116,111,32,103,111,46,10,73,110,105,116,105,97,108,105,122,97,116,105,111,110,32,32,67,111,109,112,111,110,101,110,116,32,108,105,98,114,97,114,121,32,115,104,111,117,108,100,32,110,111,119,32,104,97,118,101,32,99,111,110,116,114,111,108,46,32,32,32,32,32,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,32,61,10,123,10,32,32,32,32,108,111,97,100,116,105,109,101,58,32,49,49,53,55,53,10,32,32,32,32,115,116,97,116,117,115,58,32,34,115,117,99,99,101,115,115,34,10,32,32,32,32,117,114,105,58,32,34,104,116,116,112,58,47,47,49,57,50,46,49,54,56,46,49,46,50,50,58,56,48,56,48,47,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,46,122,105,112,34,10,125,10]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:02.882Z","buffer":{"type":"Buffer","data":[28,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:02.935Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0]}} - {"type":"io","timestamp":"2023-02-06T16:48:02.975Z","buffer":{"type":"Buffer","data":[49]}} - {"type":"io","timestamp":"2023-02-06T16:48:03.028Z","buffer":{"type":"Buffer","data":[54,58,52,56,32,124,32,91,76,79,71,93,32,32,32,124,32,80,108,97,121,101,114,32,83,101,116,116,105,110,103,32,85,112,32,82,65,70,32,10,49,54,58,52,56,32,124,32,91,76,79,71,93,32,32,32,124,32,77,97,105,110,83,99,101,110,101,32,62,62,62,32,77,65,73,78,32,83,67,69,78,69,32,40,67,79,82,69,41,32,72,65,83,32,66,69,69,78,32,67,82,69,65,84,69,68,33,32,67,76,73,69,78,84,32,86,69,82,83,73,79,78,32,53,46,48,46,48,32,10]}} - {"type":"io","timestamp":"2023-02-06T16:48:03.080Z","buffer":{"type":"Buffer","data":[49]}} - {"type":"io","timestamp":"2023-02-06T16:48:03.136Z","buffer":{"type":"Buffer","data":[54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,32,114,117,110,110,105,110,103,58,32,116,114,117,101,32,10,49,54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,78,101,116,119,111,114,107,83,101,114,118,105,99,101,32,114,117,110,110,105,110,103,58,32,116,114,117,101,32,10,10,91,82,65,70,93,32,115,101,116,68,101,98,117,103,79,117,116,112,117,116,40,116,114,117,101,41,10,10,91,82,65,70,93,32,115,101,116,68,101,98,117,103,79,117,116,112,117,116,40,116,114,117,101,41,10]}} - {"type":"io","timestamp":"2023-02-06T16:48:03.322Z","buffer":{"type":"Buffer","data":[10]}} - {"type":"io","timestamp":"2023-02-06T16:48:03.366Z","buffer":{"type":"Buffer","data":[91,82,65,70,93,32,82,111,107,117,95,65,100,115,32,70,114,97,109,101,119,111,114,107,32,118,101,114,115,105,111,110,32,51,46,48,50,51,52,10,10,91,82,65,70,93,32,115,101,116,68,101,98,117,103,79,117,116,112,117,116,40,102,97,108,115,101,41,10,10,91,82,65,70,93,32,82,111,107,117,95,65,100,115,32,70,114,97,109,101,119,111,114,107,32,118,101,114,115,105,111,110,32,51,46,48,50,51,52,10]}} - {"type":"io","timestamp":"2023-02-06T16:48:03.412Z","buffer":{"type":"Buffer","data":[10,91,82,65,70,93,32,115,101,116,68,101,98,117,103,79,117,116,112,117,116,40,102,97,108,115,101,41,10,49,54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,32,114,117,110,110,105,110,103,58,32,116,114,117,101,32,10,49,54,58,52,56,32,124,32,91,76,79,71,93,32,32,32,124,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,114,114,97,121,62,32,61,10,91,10,32,32,32,32,34,65,99,116,105,111,110,34,10,32,32,32,32,34,65,112,112,108,105,99,97,116,105,111,110,83,116,97,116,101,34,10,32,32,32,32,34,65,117,116,104,34,10,32,32,32,32,34,67,111,110,116,101,120,116,77,101,110,117,34,10,32,32,32,32,34,68,101,118,101,108,111,112,101,114,34,10,32,32,32,32,34,68,105,97,108,111,103,34,10,32,32,32,32,34,80,114,111,102,105,108,101,34,10,32,32,32,32,34,80,114,111,102,105,108,101,82,101,103,105,115,116,114,121,34,10,32,32,32,32,34,82,101,103,105,115,116,114,121,34,10,32,32,32,32,34,82,111,119,73,116,101,109,34,10,32,32,32,32,34,83,99,114,101,101,110,34,10,32,32,32,32,34,84,111,97,115,116,34,10,32,32,32,32,34,84,111,111,108,116,105,112,34,10,93,32,10]}} - {"type":"io","timestamp":"2023-02-06T16:48:03.893Z","buffer":{"type":"Buffer","data":[49]}} - {"type":"io","timestamp":"2023-02-06T16:48:03.937Z","buffer":{"type":"Buffer","data":[54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,115,97,118,101,65,99,99,101,115,115,84,111,107,101,110,32,84,111,107,101,110,32,114,101,102,114,101,115,104,105,110,103,32,105,110,58,32,32,50,56,48,32,32,32,115,101,99,111,110,100,115,32,10]}} - {"type":"io","timestamp":"2023-02-06T16:48:04.003Z","buffer":{"type":"Buffer","data":[49]}} - {"type":"io","timestamp":"2023-02-06T16:48:04.044Z","buffer":{"type":"Buffer","data":[54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,65,99,116,105,111,110,77,97,110,97,103,101,114,32,101,120,101,99,117,116,101,65,99,116,105,111,110,32,45,32,110,97,118,105,103,97,116,105,111,110,32,10]}} - {"type":"io","timestamp":"2023-02-06T16:48:04.142Z","buffer":{"type":"Buffer","data":[48]}} - {"type":"io","timestamp":"2023-02-06T16:48:04.183Z","buffer":{"type":"Buffer","data":[50,45,48,54,32,49,54,58,52,56,58,48,54,46,50,56,52,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,68,105,97,108,111,103,73,110,105,116,105,97,116,101,32,45,45,45,45,45,45,45,45,45,62,32,84,105,109,101,66,97,115,101,40,50,48,51,51,57,32,109,115,41,10,49,54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,80,114,111,102,105,108,101,83,99,114,101,101,110,95,53,55,98,52,97,48,98,97,45,52,102,53,98,45,52,51,102,55,45,98,50,50,101,45,97,98,55,56,55,99,100,56,52,100,49,49,32,111,110,83,99,114,101,101,110,83,104,111,119,110,32,10]}} - {"type":"io","timestamp":"2023-02-06T16:48:05.795Z","buffer":{"type":"Buffer","data":[49]}} - {"type":"io","timestamp":"2023-02-06T16:48:05.930Z","buffer":{"type":"Buffer","data":[54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,78,101,119,32,112,114,111,102,105,108,101,32,115,101,108,101,99,116,101,100,58,32,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,83,71,78,111,100,101,58,67,111,110,116,101,110,116,78,111,100,101,62,32,61,10,123,10,32,32,32,32,99,104,97,110,103,101,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,102,111,99,117,115,97,98,108,101,58,32,102,97,108,115,101,10,32,32,32,32,102,111,99,117,115,101,100,67,104,105,108,100,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,73,110,118,97,108,105,100,62,10,32,32,32,32,105,100,58,32,34,53,50,98,54,97,100,100,48,45,54,54,55,57,45,52,50,98,49,45,56,48,49,102,45,51,53,100,100,97,99,99,101,98,102,55,101,34,10,32,32,32,32,97,118,97,116,97,114,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,110,97,109,101,58,32,34,66,114,111,110,108,101,121,34,10,32,32,32,32,116,121,112,101,58,32,34,112,114,111,102,105,108,101,34,10,125,32,10,49,54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,65,99,116,105,111,110,77,97,110,97,103,101,114,32,101,120,101,99,117,116,101,65,99,116,105,111,110,32,45,32,110,97,116,105,118,101,95,112,114,111,102,105,108,101,95,115,101,108,101,99,116,32,10]}} - {"type":"io","timestamp":"2023-02-06T16:48:06.091Z","buffer":{"type":"Buffer","data":[49]}} - {"type":"io","timestamp":"2023-02-06T16:48:06.187Z","buffer":{"type":"Buffer","data":[54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,65,99,116,105,111,110,77,97,110,97,103,101,114,32,101,120,101,99,117,116,101,65,99,116,105,111,110,32,45,32,110,97,116,105,118,101,95,110,97,118,105,103,97,116,101,95,116,111,95,108,97,110,100,105,110,103,32,10,49,54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,65,99,116,105,111,110,77,97,110,97,103,101,114,32,101,120,101,99,117,116,101,65,99,116,105,111,110,32,45,32,110,97,118,105,103,97,116,105,111,110,32,10]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:07.630Z","buffer":{"type":"Buffer","data":[27,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:07.679Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,2,0,0,0,4,0,0,0,4,66,82,69,65,75,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:07.825Z","buffer":{"type":"Buffer","data":[12,0,0,0,5,0,0,0,3,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:07.847Z","buffer":{"type":"Buffer","data":[228,2,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:07.898Z","buffer":{"type":"Buffer","data":[5,0,0,0,0,0,0,0,6,0,0,0,0,4,0,0,0,66,82,69,65,75,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,9,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,49,48,48,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,50,52,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,178,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,102,111,114,32,101,97,99,104,32,115,101,99,116,105,111,110,32,105,110,32,103,101,116,95,100,101,101,112,95,97,114,114,97,121,40,91,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,55,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:07.914Z","buffer":{"type":"Buffer","data":[16,0,0,0,6,0,0,0,4,0,0,0,4,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:07.914Z","buffer":{"type":"Buffer","data":[16,0,0,0,7,0,0,0,4,0,0,0,0,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:07.914Z","buffer":{"type":"Buffer","data":[16,0,0,0,8,0,0,0,4,0,0,0,1,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:07.915Z","buffer":{"type":"Buffer","data":[16,0,0,0,9,0,0,0,4,0,0,0,2,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:07.915Z","buffer":{"type":"Buffer","data":[16,0,0,0,10,0,0,0,4,0,0,0,3,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:07.915Z","buffer":{"type":"Buffer","data":[16,0,0,0,11,0,0,0,4,0,0,0,5,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:07.949Z","buffer":{"type":"Buffer","data":[166,1,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:07.950Z","buffer":{"type":"Buffer","data":[6,0,0,0,0,0,0,0,5,0,0,0,178,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,14,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,99,114,101,101,110,115,47,67,97,116,97,108,111,103,83,99,114,101,101,110,47,67,97,116,97,108,111,103,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,71,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,80,97,114,115,101,114,115,95,95,108,105,98,48,46,98,114,115,0,16,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,239,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,66,97,115,101,95,95,108,105,98,48,46,98,114,115,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:08.005Z","buffer":{"type":"Buffer","data":[46,0,0,0,7,0,0,0,0,0,0,0,1,0,0,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,80,0,0,0,8,0,0,0,0,0,0,0,1,0,0,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,104,0,0,0,9,0,0,0,0,0,0,0,1,0,0,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,110,0,0,0,10,0,0,0,0,0,0,0,1,0,0,0,19,0,0,0,32,50,50,52,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,116,0,0,0,11,0,0,0,0,0,0,0,1,0,0,0,21,0,0,0,32,50,50,55,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:08.441Z","buffer":{"type":"Buffer","data":[25,0,0,0,12,0,0,0,5,0,0,0,1,4,0,0,0,4,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:08.445Z","buffer":{"type":"Buffer","data":[200,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:08.487Z","buffer":{"type":"Buffer","data":[12,0,0,0,0,0,0,0,14,0,0,0,93,1,99,111,110,116,101,110,116,0,2,0,0,0,13,3,0,0,0,41,8,103,108,111,98,97,108,0,105,102,71,108,111,98,97,108,0,29,1,109,0,5,0,0,0,13,15,0,0,0,29,2,115,101,99,116,105,111,110,115,0,1,0,0,0,7,0,0,0,0,9,16,115,101,99,116,105,111,110,0,9,16,99,111,109,112,111,110,101,110,116,116,121,112,101,0,9,16,99,97,114,111,117,115,101,108,99,111,109,112,111,110,101,110,116,116,121,112,101,0,9,16,116,97,98,108,105,115,116,0,9,16,99,104,105,112,108,105,115,116,0,9,16,99,97,114,111,117,115,101,108,0,9,16,103,114,105,100,0,9,16,104,101,114,111,0,9,16,108,105,115,116,0,9,16,102,97,108,108,98,97,99,107,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:11.039Z","buffer":{"type":"Buffer","data":[24,0,0,0,13,0,0,0,9,0,0,0,2,0,0,0,2,0,0,0,2,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:11.042Z","buffer":{"type":"Buffer","data":[40,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:11.096Z","buffer":{"type":"Buffer","data":[13,0,0,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:11.821Z","buffer":{"type":"Buffer","data":[12,0,0,0,14,0,0,0,2,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:11.824Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:11.869Z","buffer":{"type":"Buffer","data":[14,0,0,0,0,0,0,0]}} - {"type":"io","timestamp":"2023-02-06T16:48:12.746Z","buffer":{"type":"Buffer","data":[49]}} - {"type":"io","timestamp":"2023-02-06T16:48:12.791Z","buffer":{"type":"Buffer","data":[54,58,52,56,32,124,32,91,87,65,82,78,93,32,32,124,32,112,97,114,115,101,83,101,99,116,105,111,110,115,32,78,111,32,99,104,105,108,100,32,99,111,109,112,111,110,101,110,116,115,32,116,111,32,114,101,110,100,101,114,46,32,67,97,114,111,117,115,101,108,32,116,104,114,111,119,110,32,111,117,116,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,32,61,10,123,10,32,32,32,32,99,111,109,112,111,110,101,110,116,95,116,121,112,101,58,32,34,99,97,114,100,45,119,105,100,101,34,10,32,32,32,32,99,111,110,116,101,120,116,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,102,111,99,117,115,101,100,95,101,108,101,109,101,110,116,58,32,102,97,108,115,101,10,32,32,32,32,115,105,122,101,58,32,34,109,34,10,32,32,32,32,115,108,117,103,58,32,34,100,118,114,45,99,111,110,116,101,110,116,45,99,97,114,111,117,115,101,108,34,10,32,32,32,32,116,105,116,108,101,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,116,121,112,101,58,32,34,99,97,114,111,117,115,101,108,34,10,125,32,10,49,54,58,52,56,32,124,32,91,87,65,82,78,93,32,32,124,32,112,97,114,115,101,83,101,99,116,105,111,110,115,32,78,111,32,99,104,105,108,100,32,99,111,109,112,111,110,101,110,116,115,32,116,111,32,114,101,110,100,101,114,46,32,67,97,114,111,117,115,101,108,32,116,104,114,111,119,110,32,111,117,116,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,32,61,10,123,10,32,32,32,32,99,111,109,112,111,110,101,110,116,95,116,121,112,101,58,32,34,99,97,114,100,45,119,105,100,101,34,10,32,32,32,32,99,111,110,116,101,120,116,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,102,111,99,117,115,101,100,95,101,108,101,109,101,110,116,58,32,102,97,108,115,101,10,32,32,32,32,115,105,122,101,58,32,34,109,34,10,32,32,32,32,115,108,117,103,58,32,34,118,111,100,45,99,111,110,116,101,110,116,45,99,97,114,111,117,115,101,108,34,10,32,32,32,32,116,105,116,108,101,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,116,121,112,101,58,32,34,99,97,114,111,117,115,101,108,34,10,125,32,10]}} - {"type":"io","timestamp":"2023-02-06T16:48:12.864Z","buffer":{"type":"Buffer","data":[49]}} - {"type":"io","timestamp":"2023-02-06T16:48:12.916Z","buffer":{"type":"Buffer","data":[54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,80,114,111,102,105,108,101,83,99,114,101,101,110,95,53,55,98,52,97,48,98,97,45,52,102,53,98,45,52,51,102,55,45,98,50,50,101,45,97,98,55,56,55,99,100,56,52,100,49,49,32,111,110,83,99,114,101,101,110,72,105,100,100,101,110,32,10,48,50,45,48,54,32,49,54,58,52,56,58,49,53,46,48,48,55,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,68,105,97,108,111,103,67,111,109,112,108,101,116,101,32,45,45,45,45,45,45,45,45,45,62,32,68,117,114,97,116,105,111,110,40,56,55,50,50,32,109,115,41,10,48,50,45,48,54,32,49,54,58,52,56,58,49,53,46,48,49,49,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,68,105,97,108,111,103,73,110,105,116,105,97,116,101,32,45,45,45,45,45,45,45,45,45,62,32,84,105,109,101,66,97,115,101,40,50,57,48,54,53,32,109,115,41,10,49,54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,83,101,99,116,105,111,110,115,32,115,101,99,116,105,111,110,115,32,108,111,97,100,101,100,58,32,32,55,32,10]}} - {"type":"io","timestamp":"2023-02-06T16:48:13.301Z","buffer":{"type":"Buffer","data":[48]}} - {"type":"io","timestamp":"2023-02-06T16:48:13.350Z","buffer":{"type":"Buffer","data":[50,45,48,54,32,49,54,58,52,56,58,49,53,46,52,52,52,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,68,105,97,108,111,103,67,111,109,112,108,101,116,101,32,45,45,45,45,45,45,45,45,45,62,32,68,117,114,97,116,105,111,110,40,52,51,51,32,109,115,41,10,48,50,45,48,54,32,49,54,58,52,56,58,49,53,46,52,52,52,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,76,97,117,110,99,104,67,111,109,112,108,101,116,101,32,45,45,45,45,45,45,45,45,45,62,32,80,101,110,100,105,110,103,32,82,101,110,100,101,114,32,80,97,115,115,10]}} - {"type":"io","timestamp":"2023-02-06T16:48:13.420Z","buffer":{"type":"Buffer","data":[48]}} - {"type":"io","timestamp":"2023-02-06T16:48:13.472Z","buffer":{"type":"Buffer","data":[50,45,48,54,32,49,54,58,52,56,58,49,53,46,53,54,51,32,91,98,101,97,99,111,110,46,115,105,103,110,97,108,93,32,124,65,112,112,76,97,117,110,99,104,67,111,109,112,108,101,116,101,32,45,45,45,45,45,45,45,45,45,62,32,68,117,114,97,116,105,111,110,40,50,57,54,49,48,32,109,115,41,44,32,68,105,97,108,111,103,84,105,109,101,40,57,49,53,53,32,109,115,41,10]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:18.062Z","buffer":{"type":"Buffer","data":[154,0,0,0,15,0,0,0,7,0,0,0,2,0,0,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,46,98,114,115,0,185,0,0,0,0,0,0,0,108,105,98,58,47,82,111,107,117,107,111,114,67,111,109,112,111,110,101,110,116,76,105,98,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,185,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:18.065Z","buffer":{"type":"Buffer","data":[40,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:18.116Z","buffer":{"type":"Buffer","data":[15,0,0,0,0,0,0,0,2,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:18.195Z","buffer":{"type":"Buffer","data":[3,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:18.240Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:18.308Z","buffer":{"type":"Buffer","data":[4,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:18.362Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:18.440Z","buffer":{"type":"Buffer","data":[28,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:18.486Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,1,0,0,0,4,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:18.629Z","buffer":{"type":"Buffer","data":[28,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:18.671Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,1,0,0,0,3,0,0,0]}} - {"type":"io","timestamp":"2023-02-06T16:48:22.015Z","buffer":{"type":"Buffer","data":[49]}} - {"type":"io","timestamp":"2023-02-06T16:48:22.070Z","buffer":{"type":"Buffer","data":[54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,65,99,116,105,111,110,77,97,110,97,103,101,114,32,101,120,101,99,117,116,101,65,99,116,105,111,110,32,45,32,110,97,118,105,103,97,116,105,111,110,32,10]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:23.294Z","buffer":{"type":"Buffer","data":[27,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:23.337Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,2,0,0,0,5,0,0,0,4,66,82,69,65,75,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:23.466Z","buffer":{"type":"Buffer","data":[12,0,0,0,16,0,0,0,3,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:23.516Z","buffer":{"type":"Buffer","data":[138,3,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:23.569Z","buffer":{"type":"Buffer","data":[16,0,0,0,0,0,0,0,7,0,0,0,0,4,0,0,0,66,82,69,65,75,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,22,0,0,0,36,97,110,111,110,95,50,97,55,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,119,105,100,116,104,32,61,32,101,118,101,110,116,46,103,101,116,68,97,116,97,40,41,0,0,4,0,0,0,66,82,69,65,75,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,9,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,49,48,48,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,50,52,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,185,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,99,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,103,101,116,95,115,116,114,105,110,103,40,115,101,99,116,105,111,110,44,32,34,116,121,112,101,34,41,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,55,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:23.586Z","buffer":{"type":"Buffer","data":[16,0,0,0,17,0,0,0,4,0,0,0,5,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:23.586Z","buffer":{"type":"Buffer","data":[16,0,0,0,18,0,0,0,4,0,0,0,0,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:23.586Z","buffer":{"type":"Buffer","data":[16,0,0,0,19,0,0,0,4,0,0,0,1,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:23.586Z","buffer":{"type":"Buffer","data":[16,0,0,0,20,0,0,0,4,0,0,0,2,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:23.587Z","buffer":{"type":"Buffer","data":[16,0,0,0,21,0,0,0,4,0,0,0,3,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:23.587Z","buffer":{"type":"Buffer","data":[16,0,0,0,22,0,0,0,4,0,0,0,4,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:23.587Z","buffer":{"type":"Buffer","data":[16,0,0,0,23,0,0,0,4,0,0,0,6,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:23.601Z","buffer":{"type":"Buffer","data":[166,1,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:23.647Z","buffer":{"type":"Buffer","data":[17,0,0,0,0,0,0,0,5,0,0,0,185,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,14,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,99,114,101,101,110,115,47,67,97,116,97,108,111,103,83,99,114,101,101,110,47,67,97,116,97,108,111,103,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,71,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,80,97,114,115,101,114,115,95,95,108,105,98,48,46,98,114,115,0,16,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,239,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,66,97,115,101,95,95,108,105,98,48,46,98,114,115,0,46,0,0,0,18,0,0,0,0,0,0,0,1,0,0,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,129,0,0,0,19,0,0,0,0,0,0,0,1,0,0,0,22,0,0,0,36,97,110,111,110,95,50,97,55,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,80,0,0,0,20,0,0,0,0,0,0,0,1,0,0,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,104,0,0,0,21,0,0,0,0,0,0,0,1,0,0,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,110,0,0,0,22,0,0,0,0,0,0,0,1,0,0,0,19,0,0,0,32,50,50,52,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,116,0,0,0,23,0,0,0,0,0,0,0,1,0,0,0,21,0,0,0,32,50,50,55,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:24.096Z","buffer":{"type":"Buffer","data":[25,0,0,0,24,0,0,0,5,0,0,0,1,5,0,0,0,4,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:24.108Z","buffer":{"type":"Buffer","data":[209,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:24.158Z","buffer":{"type":"Buffer","data":[24,0,0,0,0,0,0,0,14,0,0,0,93,1,99,111,110,116,101,110,116,0,2,0,0,0,13,3,0,0,0,41,8,103,108,111,98,97,108,0,105,102,71,108,111,98,97,108,0,29,1,109,0,5,0,0,0,13,15,0,0,0,29,2,115,101,99,116,105,111,110,115,0,1,0,0,0,7,0,0,0,0,93,1,115,101,99,116,105,111,110,0,2,0,0,0,13,8,0,0,0,9,16,99,111,109,112,111,110,101,110,116,116,121,112,101,0,9,16,99,97,114,111,117,115,101,108,99,111,109,112,111,110,101,110,116,116,121,112,101,0,9,16,116,97,98,108,105,115,116,0,9,16,99,104,105,112,108,105,115,116,0,9,16,99,97,114,111,117,115,101,108,0,9,16,103,114,105,100,0,9,16,104,101,114,111,0,9,16,108,105,115,116,0,9,16,102,97,108,108,98,97,99,107,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:25.329Z","buffer":{"type":"Buffer","data":[12,0,0,0,25,0,0,0,2,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:25.331Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:25.380Z","buffer":{"type":"Buffer","data":[25,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:27.334Z","buffer":{"type":"Buffer","data":[27,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:27.387Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,2,0,0,0,5,0,0,0,4,66,82,69,65,75,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:27.524Z","buffer":{"type":"Buffer","data":[12,0,0,0,26,0,0,0,3,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:27.560Z","buffer":{"type":"Buffer","data":[117,3,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:27.602Z","buffer":{"type":"Buffer","data":[26,0,0,0,0,0,0,0,7,0,0,0,0,4,0,0,0,66,82,69,65,75,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,27,0,0,0,36,97,110,111,110,95,50,97,55,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,125,41,0,0,4,0,0,0,66,82,69,65,75,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,9,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,49,48,48,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,50,52,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,185,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,99,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,103,101,116,95,115,116,114,105,110,103,40,115,101,99,116,105,111,110,44,32,34,116,121,112,101,34,41,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,55,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:27.619Z","buffer":{"type":"Buffer","data":[16,0,0,0,27,0,0,0,4,0,0,0,5,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:27.619Z","buffer":{"type":"Buffer","data":[16,0,0,0,28,0,0,0,4,0,0,0,0,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:27.619Z","buffer":{"type":"Buffer","data":[16,0,0,0,29,0,0,0,4,0,0,0,1,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:27.619Z","buffer":{"type":"Buffer","data":[16,0,0,0,30,0,0,0,4,0,0,0,2,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:27.620Z","buffer":{"type":"Buffer","data":[16,0,0,0,31,0,0,0,4,0,0,0,3,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:27.620Z","buffer":{"type":"Buffer","data":[16,0,0,0,32,0,0,0,4,0,0,0,4,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:27.620Z","buffer":{"type":"Buffer","data":[16,0,0,0,33,0,0,0,4,0,0,0,6,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:27.634Z","buffer":{"type":"Buffer","data":[166,1,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:27.635Z","buffer":{"type":"Buffer","data":[27,0,0,0,0,0,0,0,5,0,0,0,185,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,14,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,99,114,101,101,110,115,47,67,97,116,97,108,111,103,83,99,114,101,101,110,47,67,97,116,97,108,111,103,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,71,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,80,97,114,115,101,114,115,95,95,108,105,98,48,46,98,114,115,0,16,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,239,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,66,97,115,101,95,95,108,105,98,48,46,98,114,115,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:27.680Z","buffer":{"type":"Buffer","data":[46,0,0,0,28,0,0,0,0,0,0,0,1,0,0,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,129,0,0,0,29,0,0,0,0,0,0,0,1,0,0,0,27,0,0,0,36,97,110,111,110,95,50,97,55,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,80,0,0,0,30,0,0,0,0,0,0,0,1,0,0,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,104,0,0,0,31,0,0,0,0,0,0,0,1,0,0,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,110,0,0,0,32,0,0,0,0,0,0,0,1,0,0,0,19,0,0,0,32,50,50,52,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,116,0,0,0,33,0,0,0,0,0,0,0,1,0,0,0,21,0,0,0,32,50,50,55,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:28.104Z","buffer":{"type":"Buffer","data":[25,0,0,0,34,0,0,0,5,0,0,0,1,5,0,0,0,4,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:28.116Z","buffer":{"type":"Buffer","data":[245,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:28.163Z","buffer":{"type":"Buffer","data":[34,0,0,0,0,0,0,0,14,0,0,0,93,1,99,111,110,116,101,110,116,0,2,0,0,0,13,3,0,0,0,41,8,103,108,111,98,97,108,0,105,102,71,108,111,98,97,108,0,29,1,109,0,5,0,0,0,13,15,0,0,0,29,2,115,101,99,116,105,111,110,115,0,1,0,0,0,7,1,0,0,0,93,1,115,101,99,116,105,111,110,0,2,0,0,0,13,7,0,0,0,57,13,99,111,109,112,111,110,101,110,116,116,121,112,101,0,2,0,0,0,99,97,114,111,117,115,101,108,0,57,13,99,97,114,111,117,115,101,108,99,111,109,112,111,110,101,110,116,116,121,112,101,0,3,0,0,0,99,97,114,100,45,119,105,100,101,0,9,16,116,97,98,108,105,115,116,0,9,16,99,104,105,112,108,105,115,116,0,29,1,99,97,114,111,117,115,101,108,0,2,0,0,0,13,11,0,0,0,9,16,103,114,105,100,0,9,16,104,101,114,111,0,9,16,108,105,115,116,0,9,16,102,97,108,108,98,97,99,107,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:28.841Z","buffer":{"type":"Buffer","data":[17,0,0,0,35,0,0,0,6,0,0,0,5,0,0,0,3]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:28.845Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:28.892Z","buffer":{"type":"Buffer","data":[35,0,0,0,0,0,0,0]}} - {"type":"io","timestamp":"2023-02-06T16:48:29.335Z","buffer":{"type":"Buffer","data":[49]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:29.343Z","buffer":{"type":"Buffer","data":[27,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:29.386Z","buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,0,2,0,0,0,5,0,0,0,4,66,82,69,65,75,0]}} - {"type":"io","timestamp":"2023-02-06T16:48:29.387Z","buffer":{"type":"Buffer","data":[56,53,58,32,32,32,32,32,32,32,32,32,32,32,32,32,99,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,103,101,116,95,115,116,114,105,110,103,40,115,101,99,116,105,111,110,44,32,34,116,121,112,101,34,41,10]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:29.511Z","buffer":{"type":"Buffer","data":[12,0,0,0,36,0,0,0,3,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:29.546Z","buffer":{"type":"Buffer","data":[117,3,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:29.603Z","buffer":{"type":"Buffer","data":[36,0,0,0,0,0,0,0,7,0,0,0,0,4,0,0,0,66,82,69,65,75,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,27,0,0,0,36,97,110,111,110,95,50,97,55,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,125,41,0,0,4,0,0,0,66,82,69,65,75,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,9,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,49,48,48,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,115,103,32,61,32,119,97,105,116,40,48,44,32,109,46,112,111,114,116,41,0,0,4,0,0,0,66,82,69,65,75,0,19,0,0,0,32,50,50,52,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0,1,4,0,0,0,66,82,69,65,75,0,185,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,32,32,32,32,99,111,109,112,111,110,101,110,116,84,121,112,101,32,61,32,103,101,116,95,115,116,114,105,110,103,40,115,101,99,116,105,111,110,44,32,34,116,121,112,101,34,41,0,0,4,0,0,0,66,82,69,65,75,0,21,0,0,0,32,50,50,55,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,32,32,32,32,32,32,32,109,101,115,115,97,103,101,32,61,32,119,97,105,116,40,48,44,32,112,111,114,116,41,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:29.620Z","buffer":{"type":"Buffer","data":[16,0,0,0,37,0,0,0,4,0,0,0,5,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:29.620Z","buffer":{"type":"Buffer","data":[16,0,0,0,38,0,0,0,4,0,0,0,0,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:29.620Z","buffer":{"type":"Buffer","data":[16,0,0,0,39,0,0,0,4,0,0,0,1,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:29.620Z","buffer":{"type":"Buffer","data":[16,0,0,0,40,0,0,0,4,0,0,0,2,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:29.621Z","buffer":{"type":"Buffer","data":[16,0,0,0,41,0,0,0,4,0,0,0,3,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:29.621Z","buffer":{"type":"Buffer","data":[16,0,0,0,42,0,0,0,4,0,0,0,4,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:29.621Z","buffer":{"type":"Buffer","data":[16,0,0,0,43,0,0,0,4,0,0,0,6,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:29.635Z","buffer":{"type":"Buffer","data":[166,1,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:29.680Z","buffer":{"type":"Buffer","data":[37,0,0,0,0,0,0,0,5,0,0,0,185,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,66,97,115,101,84,101,109,112,108,97,116,101,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,14,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,99,114,101,101,110,115,47,67,97,116,97,108,111,103,83,99,114,101,101,110,47,67,97,116,97,108,111,103,80,97,114,115,101,114,95,95,108,105,98,48,46,98,114,115,0,71,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,80,97,114,115,101,114,115,95,95,108,105,98,48,46,98,114,115,0,16,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,239,0,0,0,36,97,110,111,110,95,50,100,99,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,78,101,116,119,111,114,107,66,97,115,101,95,95,108,105,98,48,46,98,114,115,0,46,0,0,0,38,0,0,0,0,0,0,0,1,0,0,0,16,0,0,0,109,97,105,110,0,112,107,103,58,47,115,111,117,114,99,101,47,109,97,105,110,46,98,114,115,0,129,0,0,0,39,0,0,0,0,0,0,0,1,0,0,0,27,0,0,0,36,97,110,111,110,95,50,97,55,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,68,101,115,105,103,110,83,121,115,116,101,109,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,47,67,117,115,116,111,109,80,114,111,103,114,101,115,115,66,97,114,95,95,108,105,98,48,46,98,114,115,0,80,0,0,0,40,0,0,0,0,0,0,0,1,0,0,0,69,0,0,0,114,117,110,116,97,115,107,116,104,114,101,97,100,0,112,107,103,58,47,99,111,109,112,111,110,101,110,116,115,47,82,84,65,95,79,110,68,101,118,105,99,101,67,111,109,112,111,110,101,110,116,84,97,115,107,46,98,114,115,0,104,0,0,0,41,0,0,0,0,0,0,0,1,0,0,0,51,0,0,0,114,117,110,116,97,115,107,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,109,111,100,117,108,101,115,47,80,108,97,121,101,114,47,99,111,109,112,111,110,101,110,116,115,47,82,65,70,84,97,115,107,95,95,108,105,98,48,46,98,114,115,0,110,0,0,0,42,0,0,0,0,0,0,0,1,0,0,0,19,0,0,0,32,50,50,52,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,73,110,112,117,116,69,118,101,110,116,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0,116,0,0,0,43,0,0,0,0,0,0,0,1,0,0,0,21,0,0,0,32,50,50,55,64,64,64,114,117,110,116,97,115,107,116,104,114,101,97,100,0,114,111,107,117,107,111,114,99,111,109,112,111,110,101,110,116,108,105,98,58,47,99,111,109,112,111,110,101,110,116,115,47,115,101,114,118,105,99,101,115,47,82,111,107,117,83,105,103,110,97,108,66,101,97,99,111,110,83,101,114,118,105,99,101,95,95,108,105,98,48,46,98,114,115,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:30.107Z","buffer":{"type":"Buffer","data":[25,0,0,0,44,0,0,0,5,0,0,0,1,5,0,0,0,4,0,0,0,0,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:30.119Z","buffer":{"type":"Buffer","data":[246,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:30.163Z","buffer":{"type":"Buffer","data":[44,0,0,0,0,0,0,0,14,0,0,0,93,1,99,111,110,116,101,110,116,0,2,0,0,0,13,3,0,0,0,41,8,103,108,111,98,97,108,0,105,102,71,108,111,98,97,108,0,29,1,109,0,5,0,0,0,13,15,0,0,0,29,2,115,101,99,116,105,111,110,115,0,1,0,0,0,7,1,0,0,0,93,1,115,101,99,116,105,111,110,0,2,0,0,0,13,7,0,0,0,57,13,99,111,109,112,111,110,101,110,116,116,121,112,101,0,2,0,0,0,99,104,105,112,45,108,105,115,116,0,57,13,99,97,114,111,117,115,101,108,99,111,109,112,111,110,101,110,116,116,121,112,101,0,3,0,0,0,99,97,114,100,45,119,105,100,101,0,9,16,116,97,98,108,105,115,116,0,9,16,99,104,105,112,108,105,115,116,0,29,1,99,97,114,111,117,115,101,108,0,2,0,0,0,13,11,0,0,0,9,16,103,114,105,100,0,9,16,104,101,114,111,0,9,16,108,105,115,116,0,9,16,102,97,108,108,98,97,99,107,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:30.906Z","buffer":{"type":"Buffer","data":[24,0,0,0,45,0,0,0,9,0,0,0,2,0,0,0,4,0,0,0,4,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:30.908Z","buffer":{"type":"Buffer","data":[40,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:30.956Z","buffer":{"type":"Buffer","data":[45,0,0,0,0,0,0,0,2,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:32.148Z","buffer":{"type":"Buffer","data":[12,0,0,0,46,0,0,0,2,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:32.151Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:32.196Z","buffer":{"type":"Buffer","data":[46,0,0,0,0,0,0,0]}} - {"type":"io","timestamp":"2023-02-06T16:48:32.419Z","buffer":{"type":"Buffer","data":[49]}} - {"type":"io","timestamp":"2023-02-06T16:48:32.474Z","buffer":{"type":"Buffer","data":[54,58,52,56,32,124,32,91,87,65,82,78,93,32,32,124,32,112,97,114,115,101,83,101,99,116,105,111,110,115,32,78,111,32,99,104,105,108,100,32,99,111,109,112,111,110,101,110,116,115,32,116,111,32,114,101,110,100,101,114,46,32,67,97,114,111,117,115,101,108,32,116,104,114,111,119,110,32,111,117,116,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,32,61,10,123,10,32,32,32,32,99,111,109,112,111,110,101,110,116,95,116,121,112,101,58,32,34,99,97,114,100,45,119,105,100,101,34,10,32,32,32,32,99,111,110,116,101,120,116,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,102,111,99,117,115,101,100,95,101,108,101,109,101,110,116,58,32,102,97,108,115,101,10,32,32,32,32,103,114,111,117,112,95,105,100,58,32,34,49,34,10,32,32,32,32,115,105,122,101,58,32,34,115,34,10,32,32,32,32,115,108,117,103,58,32,34,118,111,100,34,10,32,32,32,32,116,105,116,108,101,58,32,60,67,111,109,112,111,110,101,110,116,58,32,114,111,65,115,115,111,99,105,97,116,105,118,101,65,114,114,97,121,62,10,32,32,32,32,116,121,112,101,58,32,34,99,97,114,111,117,115,101,108,34,10,125,32,10,49,54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,47,104,111,109,101,32,111,110,83,99,114,101,101,110,72,105,100,100,101,110,32,10]}} - {"type":"io","timestamp":"2023-02-06T16:48:32.519Z","buffer":{"type":"Buffer","data":[49,54,58,52,56,32,124,32,91,73,78,70,79,93,32,32,124,32,83,101,99,116,105,111,110,115,32,115,101,99,116,105,111,110,115,32,108,111,97,100,101,100,58,32,32,52,32,10]}} - {"type":"client-to-server","timestamp":"2023-02-06T16:48:36.170Z","buffer":{"type":"Buffer","data":[12,0,0,0,47,0,0,0,122,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:36.171Z","buffer":{"type":"Buffer","data":[12,0,0,0]}} - {"type":"server-to-client","timestamp":"2023-02-06T16:48:36.176Z","buffer":{"type":"Buffer","data":[47,0,0,0,0,0,0,0]}} - - `; - session = new DebugProtocolClientReplaySession({ - bufferLog: text + bufferLog: fsExtra.readFileSync(logPath).toString() }); await session.run(); diff --git a/src/debugProtocol/client/DebugProtocolClient.spec.ts b/src/debugProtocol/client/DebugProtocolClient.spec.ts index 16c1e8c4..1a8e6127 100644 --- a/src/debugProtocol/client/DebugProtocolClient.spec.ts +++ b/src/debugProtocol/client/DebugProtocolClient.spec.ts @@ -36,7 +36,6 @@ import { StackTraceV3Response } from '../events/responses/StackTraceV3Response'; import { IOPortOpenedUpdate } from '../events/updates/IOPortOpenedUpdate'; import * as Net from 'net'; import { ThreadAttachedUpdate } from '../events/updates/ThreadAttachedUpdate'; -import { KeyType } from '../../adapters/DebugProtocolAdapter'; process.on('uncaughtException', (err) => console.log('node js process error\n', err)); const sinon = createSandbox(); @@ -50,6 +49,8 @@ describe('DebugProtocolClient', () => { */ async function connect() { await client.connect(); + client['options'].shutdownTimeout = 100; + client['options'].exitChannelTimeout = 100; //send the AllThreadsStopped event, and also wait for the client to suspend await Promise.all([ server.sendUpdate(AllThreadsStoppedUpdate.fromJson({ @@ -85,13 +86,12 @@ describe('DebugProtocolClient', () => { sinon.restore(); try { - client?.destroy(); + await client?.destroy(true); } catch (e) { } //shut down and destroy the server after each test try { await server?.destroy(); } catch (e) { } - await util.sleep(10); }); it('knows when to enable the thread hopping workaround', () => { @@ -371,10 +371,6 @@ describe('DebugProtocolClient', () => { expect(response.data.breakpoints).to.eql(responseBreakpoins); }); - it('skips sending command when there are zero conditional breakpoints', async () => { - - }); - it('sends AddBreakpointsRequest when conditional breakpoints are NOT supported', async () => { await connect(); client.protocolVersion = '2.0.0'; diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 5a1df704..6492bbe0 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -281,7 +281,8 @@ export class DebugProtocolClient { // Don't forget to catch error, for your own sake. this.controlSocket.once('error', (error) => { //the Roku closed the connection for some unknown reason... - console.error(`error on control port`, error); + this.logger.error(`error on control port`, error); + this.controlSocket?.destroy?.(); void this.shutdown('close'); }); @@ -767,7 +768,6 @@ export class DebugProtocolClient { if (this.controlSocket) { const buffer = request.toBuffer(); this.writeToBufferLog('client-to-server', buffer); - this.controlSocket.write(buffer); void this.plugins.emit('afterSendRequest', { client: this, @@ -1111,31 +1111,47 @@ export class DebugProtocolClient { return false; } - public async destroy() { - await this.shutdown('close'); + /** + * Destroy this instance, shutting down any sockets or other long-running items and cleaning up. + * @param immediate if true, all sockets are immediately closed and do not gracefully shut down + */ + public async destroy(immediate = false) { + await this.shutdown('close', immediate); } - private async shutdown(eventName: 'app-exit' | 'close') { + private async shutdown(eventName: 'app-exit' | 'close', immediate = false) { this.logger.log('Shutting down!'); + let exitChannelTimeout = this.options?.exitChannelTimeout ?? 30_000; + let shutdownTimeMax = this.options?.shutdownTimeout ?? 10_000; + //if immediate is true, this is an instant shutdown force. don't wait for anything + if (immediate) { + exitChannelTimeout = 0; + shutdownTimeMax = 0; + } + //tell the device to exit the channel try { //ask the device to terminate the debug session. We have to wait for this to come back. //The device might be running unstoppable code, so this might take a while. Wait for the device to send back //the response before we continue with the teardown process await Promise.race([ - this.exitChannel().finally(() => this.logger.log('exit channel completed')), + immediate + ? Promise.resolve(null) + : this.exitChannel().finally(() => this.logger.log('exit channel completed')), //if the exit channel request took this long to finish, something's terribly wrong - util.sleep(30_000) + util.sleep(exitChannelTimeout) ]); } finally { } - const maxTimeout = 10_000; await Promise.all([ - this.destroyControlSocket(maxTimeout), - this.destroyIOSocket(maxTimeout) + this.destroyControlSocket(shutdownTimeMax), + this.destroyIOSocket(shutdownTimeMax, immediate) ]); this.emit(eventName); + this.emitter?.removeAllListeners(); + this.buffer = Buffer.alloc(0); + this.bufferQueue.destroy(); } private isDestroyingControlSocket = false; @@ -1162,7 +1178,10 @@ export class DebugProtocolClient { private isDestroyingIOSocket = false; - private async destroyIOSocket(timeout: number) { + /** + * @param immediate if true, force close immediately instead of waiting for it to settle + */ + private async destroyIOSocket(timeout: number, immediate = false) { if (this.ioSocket && !this.isDestroyingIOSocket) { this.isDestroyingIOSocket = true; //wait for the ioSocket to be closed @@ -1172,7 +1191,7 @@ export class DebugProtocolClient { ]); //if the io socket is not closed, wait for it to at least settle - if (!this.ioSocketClosed.isCompleted) { + if (!this.ioSocketClosed.isCompleted && !immediate) { await new Promise((resolve) => { const callback = debounce(() => { resolve(); @@ -1244,6 +1263,16 @@ export interface ConstructorOptions { * This is here to prevent infinitely pinging the Roku device. */ controlConnectMaxTime?: number; + + /** + * The number of milliseconds that the client should wait during a shutdown request before forcefully terminating the sockets + */ + shutdownTimeout?: number; + + /** + * The max time the client will wait for the `exit channel` response before forcefully terminating the sockets + */ + exitChannelTimeout?: number; } /** diff --git a/src/debugProtocol/server/DebugProtocolServer.ts b/src/debugProtocol/server/DebugProtocolServer.ts index b48c0473..46cc4134 100644 --- a/src/debugProtocol/server/DebugProtocolServer.ts +++ b/src/debugProtocol/server/DebugProtocolServer.ts @@ -131,7 +131,6 @@ export class DebugProtocolServer { //close the client socket this.client?.destroy(); - //now close the server try { await new Promise((resolve, reject) => { diff --git a/src/debugSession/BrightScriptDebugSession.spec.ts b/src/debugSession/BrightScriptDebugSession.spec.ts index f993a7ed..2734d849 100644 --- a/src/debugSession/BrightScriptDebugSession.spec.ts +++ b/src/debugSession/BrightScriptDebugSession.spec.ts @@ -142,6 +142,12 @@ describe('BrightScriptDebugSession', () => { }); }); + afterEach(() => { + fsExtra.emptydirSync(tempDir); + fsExtra.removeSync(outDir); + sinon.restore(); + }); + describe('evaluateRequest', () => { it('resets local var counter on suspend', async () => { const stub = sinon.stub(session['rokuAdapter'], 'evaluate').callsFake(x => { diff --git a/src/debugSession/BrightScriptDebugSession.ts b/src/debugSession/BrightScriptDebugSession.ts index f2362966..8cc6e85b 100644 --- a/src/debugSession/BrightScriptDebugSession.ts +++ b/src/debugSession/BrightScriptDebugSession.ts @@ -50,9 +50,9 @@ import type { AugmentedSourceBreakpoint } from '../managers/BreakpointManager'; import { BreakpointManager } from '../managers/BreakpointManager'; import type { LogMessage } from '../logging'; import { logger, FileLoggingManager, debugServerLogOutputEventTransport } from '../logging'; -import { VariableType } from '../debugProtocol/events/responses/VariablesResponse'; import type { DeviceInfo } from '../DeviceInfo'; import * as xml2js from 'xml2js'; +import { VariableType } from '../debugProtocol/events/responses/VariablesResponse'; export class BrightScriptDebugSession extends BaseDebugSession { public constructor() { @@ -69,25 +69,25 @@ export class BrightScriptDebugSession extends BaseDebugSession { this.locationManager = new LocationManager(this.sourceMapManager); this.breakpointManager = new BreakpointManager(this.sourceMapManager, this.locationManager); //send newly-verified breakpoints to vscode - this.breakpointManager.on('breakpoints-verified', (data) => this.onDeviceVerifiedBreakpoints(data)); + this.breakpointManager.on('breakpoints-verified', (data) => this.onDeviceBreakpointsChanged('changed', data)); this.projectManager = new ProjectManager(this.breakpointManager, this.locationManager); this.fileLoggingManager = new FileLoggingManager(); } - private onDeviceVerifiedBreakpoints(data: { breakpoints: AugmentedSourceBreakpoint[] }) { + private onDeviceBreakpointsChanged(eventName: 'changed' | 'new', data: { breakpoints: AugmentedSourceBreakpoint[] }) { this.logger.info('Sending verified device breakpoints to client', data); //send all verified breakpoints to the client for (const breakpoint of data.breakpoints) { const event: DebugProtocol.Breakpoint = { line: breakpoint.line, column: breakpoint.column, - verified: true, + verified: breakpoint.verified, id: breakpoint.id, source: { path: breakpoint.srcPath } }; - this.sendEvent(new BreakpointEvent('changed', event)); + this.sendEvent(new BreakpointEvent(eventName, event)); } } @@ -1198,10 +1198,6 @@ export class BrightScriptDebugSession extends BaseDebugSession { //clear the index for storing evalutated expressions this.evaluateVarIndexByFrameId.clear(); - //sync breakpoints - await this.rokuAdapter?.syncBreakpoints(); - this.logger.info('received "suspend" event from adapter'); - const threads = await this.rokuAdapter.getThreads(); const activeThread = threads.find(x => x.isSelected); @@ -1218,6 +1214,23 @@ export class BrightScriptDebugSession extends BaseDebugSession { }) ); + outer: for (const bp of this.breakpointManager.failedDeletions) { + for (const thread of threads) { + let sourceLocation = await this.projectManager.getSourceLocation(thread.filePath, thread.lineNumber); + // This stop was due to a breakpoint that we tried to delete, but couldn't. + // Now that we are stopped, we can delete it. We won't stop here again unless you re-add the breakpoint. You're welcome. + if ((bp.srcPath === sourceLocation.filePath) && (bp.line === sourceLocation.lineNumber)) { + this.showPopupMessage(`Stopped at breakpoint that failed to delete. Deleting now, and should not cause future stops.`, 'info'); + this.logger.warn(`Stopped at breakpoint that failed to delete. Deleting now, and should not cause future stops`, bp, thread, sourceLocation); + break outer; + } + } + } + + //sync breakpoints + await this.rokuAdapter?.syncBreakpoints(); + this.logger.info('received "suspend" event from adapter'); + //if !stopOnEntry, and we haven't encountered a suspend yet, THIS is the entry breakpoint. auto-continue if (!this.entryBreakpointWasHandled && !this.launchConfiguration.stopOnEntry) { this.entryBreakpointWasHandled = true; @@ -1366,7 +1379,6 @@ export class BrightScriptDebugSession extends BaseDebugSession { private async _shutdown(errorMessage?: string): Promise { try { - // this.rendezvousTracker?.destroy?.(); //if configured, delete the staging directory diff --git a/src/managers/ActionQueue.ts b/src/managers/ActionQueue.ts index 9559c40c..94d31f73 100644 --- a/src/managers/ActionQueue.ts +++ b/src/managers/ActionQueue.ts @@ -58,4 +58,9 @@ export class ActionQueue { } this.isRunning = false; } + + public destroy() { + this.isRunning = false; + this.queueItems = []; + } } diff --git a/src/managers/BreakpointManager.spec.ts b/src/managers/BreakpointManager.spec.ts index 527b7cb7..746a611b 100644 --- a/src/managers/BreakpointManager.spec.ts +++ b/src/managers/BreakpointManager.spec.ts @@ -72,6 +72,76 @@ describe('BreakpointManager', () => { fsExtra.removeSync(tmpDir); }); + describe('pending breakpoints', () => { + it('marks existing breakpoints as pending', () => { + const breakpoints = bpManager.replaceBreakpoints(srcPath, [ + { line: 1 }, + { line: 2 }, + { line: 3 }, + { line: 4 } + ]); + bpManager.setPending(srcPath, breakpoints, false); + expect(breakpoints.map(x => bpManager.isPending(x.srcHash))).to.eql([false, false, false, false]); + + bpManager.setPending(srcPath, [{ line: 1 }, { line: 3 }], true); + + expect(breakpoints.map(x => bpManager.isPending(x.srcHash))).to.eql([true, false, true, false]); + }); + + it('marks existing breakpoints as not pending', () => { + const breakpoints = bpManager.replaceBreakpoints(srcPath, [ + { line: 1 }, + { line: 2 }, + { line: 3 }, + { line: 4 } + ]); + bpManager.setPending(srcPath, breakpoints, true); + expect(breakpoints.map(x => bpManager.isPending(x.srcHash))).to.eql([true, true, true, true]); + + bpManager.setPending(srcPath, [{ line: 1 }, { line: 3 }], false); + + expect(breakpoints.map(x => bpManager.isPending(x.srcHash))).to.eql([false, true, false, true]); + }); + + it('ignores not-found breakpoints', () => { + const breakpoints = bpManager.replaceBreakpoints(srcPath, [ + { line: 1 }, + { line: 2 }, + { line: 3 }, + { line: 4 } + ]); + bpManager.setPending(srcPath, breakpoints, true); + expect(breakpoints.map(x => bpManager.isPending(x.srcHash))).to.eql([true, true, true, true]); + + bpManager.setPending(srcPath, [{ line: 5 }], false); + + expect(breakpoints.map(x => bpManager.isPending(x.srcHash))).to.eql([true, true, true, true]); + }); + + it('remembers a breakpoint pending status through delete and add', () => { + let breakpoints = bpManager.replaceBreakpoints(srcPath, [ + { line: 1 } + ]); + //mark the breakpoint as pending + bpManager.setPending(srcPath, breakpoints, true); + expect(bpManager.isPending(srcPath, breakpoints[0])).to.be.true; + + //delete the breakpoint + bpManager.deleteBreakpoint(srcPath, { line: 1 }); + + //mark the breakpoint as pending (even though it's not there anymore) + bpManager.setPending(srcPath, [{ line: 5 }], true); + + //add the breakpoint again + breakpoints = bpManager.replaceBreakpoints(srcPath, [ + { line: 1 } + ]); + + //the breakpoint should be pending even though this is a new instance of the breakpoint + expect(bpManager.isPending(srcPath, breakpoints[0])).to.be.true; + }); + }); + describe('sanitizeSourceFilePath', () => { it('returns the original string when no key was found', () => { expect(bpManager.sanitizeSourceFilePath('a/b/c')).to.equal(s`a/b/c`); @@ -773,7 +843,7 @@ describe('BreakpointManager', () => { }, { line: 6, logMessage: 'hello world' - }]).map(x => x.hash).sort() + }]).map(x => x.srcHash).sort() ).to.eql([ s`${rootDir}/source/main.brs:2:0-standard`, s`${rootDir}/source/main.brs:3:0-condition=true`, @@ -792,7 +862,7 @@ describe('BreakpointManager', () => { line: 2 }); expect( - bpManager['getBreakpointsForFile'](pkgPath).map(x => x.hash) + bpManager['getBreakpointsForFile'](pkgPath).map(x => x.srcHash) ).to.eql([ s`${pkgPath}:2:0-standard` ]); @@ -805,7 +875,7 @@ describe('BreakpointManager', () => { line: 2 }); expect( - bpManager['getBreakpointsForFile'](pkgPath).map(x => x.hash) + bpManager['getBreakpointsForFile'](pkgPath).map(x => x.srcHash) ).to.eql([ s`${pkgPath}:2:0-standard` ]); @@ -815,7 +885,7 @@ describe('BreakpointManager', () => { condition: 'true' }); expect( - bpManager['getBreakpointsForFile'](pkgPath).map(x => x.hash) + bpManager['getBreakpointsForFile'](pkgPath).map(x => x.srcHash) ).to.eql([ s`${pkgPath}:2:0-condition=true` ]); @@ -825,7 +895,7 @@ describe('BreakpointManager', () => { hitCondition: '4' }); expect( - bpManager['getBreakpointsForFile'](pkgPath).map(x => x.hash) + bpManager['getBreakpointsForFile'](pkgPath).map(x => x.srcHash) ).to.eql([ s`${pkgPath}:2:0-hitCondition=4` ]); @@ -1041,5 +1111,19 @@ describe('BreakpointManager', () => { }); }); + it('includes the deviceId in all breakpoints when possible', async () => { + const bp = bpManager.setBreakpoint(srcPath, { line: 1 }); + + let diff = await bpManager.getDiff(projectManager.getAllProjects()); + expect(diff.added[0].deviceId).not.to.exist; + + bpManager.setBreakpointDeviceId(bp.srcHash, diff.added[0].destHash, 3); + + bpManager.deleteBreakpoint(srcPath, bp); + + diff = await bpManager.getDiff(projectManager.getAllProjects()); + + expect(diff.removed[0].deviceId).to.eql(3); + }); }); }); diff --git a/src/managers/BreakpointManager.ts b/src/managers/BreakpointManager.ts index d96fdd5b..e0b195f3 100644 --- a/src/managers/BreakpointManager.ts +++ b/src/managers/BreakpointManager.ts @@ -45,6 +45,19 @@ export class BreakpointManager { }; } + /** + * Get a promise that resolves the next time the specified event occurs + */ + public once(eventName: 'breakpoints-verified'): Promise<{ breakpoints: AugmentedSourceBreakpoint[] }>; + public once(eventName: string): Promise { + return new Promise((resolve) => { + const disconnect = this.on(eventName as 'breakpoints-verified', (data) => { + disconnect(); + resolve(data); + }); + }); + } + /** * A map of breakpoints by what file they were set in. * This does not handle any source-to-dest mapping...these breakpoints are stored in the file they were set in. @@ -53,6 +66,11 @@ export class BreakpointManager { */ private breakpointsByFilePath = new Map(); + /** + * A list of breakpoints that failed to delete and will be deleted as soon as possible + */ + public failedDeletions = [] as BreakpointWorkItem[]; + /** * A sequence used to generate unique client breakpoint IDs */ @@ -95,8 +113,8 @@ export class BreakpointManager { ...breakpoint, srcPath: srcPath, //assign a hash-like key to this breakpoint (so we can match against other similar breakpoints in the future) - hash: this.getBreakpointKey(srcPath, breakpoint) - }) as AugmentedSourceBreakpoint; + srcHash: this.getBreakpointSrcHash(srcPath, breakpoint) + } as AugmentedSourceBreakpoint); //generate a new id for this breakpoint if one does not exist bp.id ??= this.breakpointIdSequence++; @@ -105,7 +123,7 @@ export class BreakpointManager { bp.verified ??= false; //if the breakpoint hash changed, mark the breakpoint as unverified - if (existingBreakpoint?.hash !== bp.hash) { + if (existingBreakpoint?.srcHash !== bp.srcHash) { bp.verified = false; } @@ -115,69 +133,108 @@ export class BreakpointManager { } //if this is one of the permanent breakpoints, mark it as verified immediately (only applicable to telnet sessions) - if (this.getPermanentBreakpoint(bp.hash)) { - this.setBreakpointDeviceId(bp.hash, bp.id); + if (this.getPermanentBreakpoint(bp.srcHash)) { + this.setBreakpointDeviceId(bp.srcHash, bp.srcHash, bp.id); this.verifyBreakpoint(bp.id, true); } return bp; } /** - * Find a breakpoint by its hash - * @returns the breakpoint, or undefined if not found + * Delete a breakpoint */ - private getBreakpointByHash(hash: string) { - return this.getBreakpointsByHashes([hash])[0]; + public deleteBreakpoint(hash: string); + public deleteBreakpoint(breakpoint: AugmentedSourceBreakpoint); + public deleteBreakpoint(srcPath: string, breakpoint: Breakpoint); + public deleteBreakpoint(...args: [string] | [AugmentedSourceBreakpoint] | [string, Breakpoint]) { + this.deleteBreakpoints([ + this.getBreakpoint(...args as [string]) + ]); } /** - * Find a list of breakpoints by their hashes - * @returns the breakpoint, or undefined if not found + * Delete a set of breakpoints */ - private getBreakpointsByHashes(hashes: string[]) { - const result = [] as AugmentedSourceBreakpoint[]; - for (const [, breakpoints] of this.breakpointsByFilePath) { - for (const breakpoint of breakpoints) { - if (hashes.includes(breakpoint.hash)) { - result.push(breakpoint); - } + public deleteBreakpoints(args: BreakpointRef[]) { + for (const breakpoint of this.getBreakpoints(args)) { + const actualBreakpoint = this.getBreakpoint(breakpoint); + if (actualBreakpoint) { + const breakpoints = new Set(this.getBreakpointsForFile(actualBreakpoint.srcPath)); + breakpoints.delete(actualBreakpoint); + this.replaceBreakpoints(actualBreakpoint.srcPath, [...breakpoints]); } } - return result; } /** - * Find a breakpoint by its deviceId - * @returns the breakpoint, or undefined if not found - */ - private getBreakpointByDeviceId(deviceId: number) { - return this.getBreakpointsByDeviceIds([deviceId])[0]; + * Get a breakpoint by providing the data you have available + */ + public getBreakpoint(hash: BreakpointRef): AugmentedSourceBreakpoint; + public getBreakpoint(srcPath: string, breakpoint: Breakpoint): AugmentedSourceBreakpoint; + public getBreakpoint(...args: [BreakpointRef] | [string, Breakpoint]): AugmentedSourceBreakpoint { + let ref: BreakpointRef; + if (typeof args[0] === 'string' && typeof args[1] === 'object') { + ref = this.getBreakpointSrcHash(args[0], args[1]); + } else { + ref = args[0]; + } + return this.getBreakpoints([ref])[0]; } /** - * Find a list of breakpoints by their deviceIds - * @returns the breakpoints, or undefined if not found + * Given a breakpoint ref, turn it into a hash */ - private getBreakpointsByDeviceIds(deviceIds: number[]) { - const result = [] as AugmentedSourceBreakpoint[]; - for (const [, breakpoints] of this.breakpointsByFilePath) { - for (const breakpoint of breakpoints) { - if (deviceIds.includes(breakpoint.deviceId)) { - result.push(breakpoint); - } - } + private refToHash(ref: BreakpointRef): string { + if (!ref) { + return; } - return result; + //hash + if (typeof ref === 'string') { + return ref; + } + //object with a .hash key + if ('srcHash' in ref) { + return ref.srcHash; + } + //breakpoint with srcPath + if (ref?.srcPath) { + return this.getBreakpointSrcHash(ref.srcPath, ref); + } + } + + /** + * Get breakpoints by providing a list of breakpoint refs + * @param refs a list of breakpoint refs for breakpoints to get + * @param includeHistoric if true, will also look through historic breakpoints for a match. + */ + public getBreakpoints(refs: BreakpointRef[]): AugmentedSourceBreakpoint[] { + //convert all refs into a hash + const refHashes = new Set(refs.map(x => this.refToHash(x))); + + //find all the breakpoints that match one of the specified refs + return [...this.breakpointsByFilePath].map(x => x[1]).flat().filter((x) => { + return refHashes.has(x.srcHash); + }); + } + + /** + * Find a breakpoint by its deviceId + * @returns the breakpoint, or undefined if not found + */ + private getBreakpointByDeviceId(deviceId: number) { + const bpRef = [...this.deviceIdByDestHash.values()].find(x => { + return x.deviceId === deviceId; + }); + return this.getBreakpoint(bpRef?.srcHash); } + private deviceIdByDestHash = new Map(); + /** * Set the deviceId of a breakpoint */ - public setBreakpointDeviceId(hash: string, deviceId: number) { - const breakpoint = this.getBreakpointByHash(hash); - if (breakpoint) { - breakpoint.deviceId = deviceId; - } + public setBreakpointDeviceId(srcHash: string, destHast: string, deviceId: number) { + this.deviceIdByDestHash.set(destHast, { srcHash: srcHash, deviceId: deviceId }); } /** @@ -187,7 +244,8 @@ export class BreakpointManager { const breakpoint = this.getBreakpointByDeviceId(deviceId); if (breakpoint) { breakpoint.verified = isVerified; - this.queueVerifyEvent(breakpoint.hash); + + this.queueEvent('breakpoints-verified', breakpoint.srcHash); return true; } else { //couldn't find the breakpoint. return false so the caller can handle that properly @@ -195,36 +253,39 @@ export class BreakpointManager { } } + private queueEventStates = new Map(); + + /** - * Whenever breakpoints get verified, they need to be synced back to vscode. - * This queues up a future function that will emit a batch of all verified breakpoints. + * Queue future events to be fired when data settles. Typically this is data that needs synced back to vscode. + * This queues up a future function that will emit a batch of all the specified breakpoints. * @param hash the breakpoint hash that identifies this specific breakpoint based on its features */ - private queueVerifyEvent(hash: string) { - this.verifiedBreakpointKeys.push(hash); - if (!this.isVerifyEventQueued) { - this.isVerifyEventQueued = true; + private queueEvent(event: 'breakpoints-verified', ref: BreakpointRef) { + //get the state (or create a new one) + const state = this.queueEventStates.get(event) ?? (this.queueEventStates.set(event, { pendingRefs: [], isQueued: false }).get(event)); + + this.queueEventStates.set(event, state); + state.pendingRefs.push(ref); + if (!state.isQueued) { + state.isQueued = true; process.nextTick(() => { - this.isVerifyEventQueued = false; - const breakpoints = this.getBreakpointsByHashes( - this.verifiedBreakpointKeys.map(x => x) - ); - this.verifiedBreakpointKeys = []; - this.emit('breakpoints-verified', { + state.isQueued = false; + const breakpoints = this.getBreakpoints(state.pendingRefs); + state.pendingRefs = []; + this.emit(event as Parameters[0], { breakpoints: breakpoints }); }); } } - private verifiedBreakpointKeys: string[] = []; - private isVerifyEventQueued = false; /** - * Generate a key based on the features of the breakpoint. Every breakpoint that exists at the same location - * and has the same features should have the same key. + * Generate a hash based on the features of the breakpoint. Every breakpoint that exists at the same location + * and has the same features should have the same hash. */ - public getBreakpointKey(filePath: string, breakpoint: DebugProtocol.SourceBreakpoint | AugmentedSourceBreakpoint) { + private getBreakpointSrcHash(filePath: string, breakpoint: DebugProtocol.SourceBreakpoint | AugmentedSourceBreakpoint) { const key = `${standardizePath(filePath)}:${breakpoint.line}:${breakpoint.column ?? 0}`; const condition = breakpoint.condition?.trim(); @@ -244,6 +305,30 @@ export class BreakpointManager { return `${key}-standard`; } + /** + * Generate a hash based on the features of the breakpoint. Every breakpoint that exists at the same location + * and has the same features should have the same hash. + */ + private getBreakpointDestHash(breakpoint: BreakpointWorkItem) { + const key = `${standardizePath(breakpoint.stagingFilePath)}:${breakpoint.line}:${breakpoint.column ?? 0}`; + + const condition = breakpoint.condition?.trim(); + if (condition) { + return `${key}-condition=${condition}`; + } + + const hitCondition = parseInt(breakpoint.hitCondition?.trim()); + if (!isNaN(hitCondition)) { + return `${key}-hitCondition=${hitCondition}`; + } + + if (breakpoint.logMessage) { + return `${key}-logMessage=${breakpoint.logMessage}`; + } + + return `${key}-standard`; + } + /** * Set/replace/delete the list of breakpoints for this file. * @param srcPath @@ -313,6 +398,7 @@ export class BreakpointManager { ...breakpoint, //add additional info srcPath: sourceFilePath, + destHash: undefined, rootDirFilePath: s`${project.rootDir}/${relativeStagingPath}`, line: stagingLocation.lineNumber, column: stagingLocation.columnIndex, @@ -321,6 +407,9 @@ export class BreakpointManager { pkgPath: pkgPath, componentLibraryName: (project as ComponentLibraryProject).name }; + obj.destHash = this.getBreakpointDestHash(obj); + obj.deviceId = this.deviceIdByDestHash.get(obj.destHash)?.deviceId; + if (!result[stagingLocation.filePath]) { result[stagingLocation.filePath] = []; } @@ -365,7 +454,7 @@ export class BreakpointManager { promises.push(this.writeBreakpointsToFile(stagingFilePath, breakpoints)); for (const breakpoint of breakpoints) { //mark this breakpoint as verified - this.setBreakpointDeviceId(breakpoint.hash, breakpoint.id); + this.setBreakpointDeviceId(breakpoint.srcHash, breakpoint.destHash, breakpoint.id); this.verifyBreakpoint(breakpoint.id, true); //add this breakpoint to the list of "permanent" breakpoints this.registerPermanentBreakpoint(breakpoint); @@ -548,7 +637,7 @@ export class BreakpointManager { public getPermanentBreakpoint(hash: string) { for (const [, breakpoints] of this.permanentBreakpointsBySrcPath) { for (const breakpoint of breakpoints) { - if (breakpoint.hash === hash) { + if (breakpoint.srcHash === hash) { return breakpoint; } } @@ -602,7 +691,6 @@ export class BreakpointManager { } } - /** * Get a diff of all breakpoints that have changed since the last time the diff was retrieved. * Sets the new baseline to the current state, so the next diff will be based on this new baseline. @@ -640,7 +728,7 @@ export class BreakpointManager { bp.logMessage ].join('--'); //clone the breakpoint and then add it to the current state - currentState.set(key, { ...bp }); + currentState.set(key, { ...bp, deviceId: this.deviceIdByDestHash.get(bp.destHash)?.deviceId }); } } }) @@ -666,15 +754,59 @@ export class BreakpointManager { } } this.lastState = currentState; - return { + + const result = { added: [...added.values()], - removed: [...removed.values()], + removed: [...removed.values(), ...this.failedDeletions], unchanged: [...unchanged.values()] }; + this.failedDeletions = []; + //hydrate the breakpoints with any available deviceIds + for (const breakpoint of [...result.added, ...result.removed, ...result.unchanged]) { + breakpoint.deviceId = this.deviceIdByDestHash.get(breakpoint.destHash)?.deviceId; + } + return result; } finally { this.isGetDiffRunning = false; } } + + /** + * Set the pending status of the given list of breakpoints. + * + * Whenever the breakpoint is currently being handled by an adapter (i.e. add/update/delete), it should + * be marked "pending". Then, when the response comes back (success or fail), "pending" should be set to false. + * In this way, we can ensure that all breakpoints can be synchronized with the device + */ + public setPending(srcPath: string, breakpoints: Breakpoint[], isPending: boolean) { + for (const breakpoint of breakpoints) { + if (breakpoint) { + const hash = this.getBreakpointSrcHash(srcPath, breakpoint); + this.breakpointPendingStatus.set(hash, isPending); + } + } + } + + /** + * Determine whether the current breakpoint is pending or not + */ + public isPending(srcPath: string, breakpoint: Breakpoint); + public isPending(hash: string); + public isPending(...args: [string] | [string, Breakpoint]) { + let hash: string; + if (args[1]) { + hash = this.getBreakpointSrcHash(args[0], args[1]); + } else { + hash = args[0]; + } + return this.breakpointPendingStatus.get(hash) ?? false; + } + + /** + * A map of breakpoint hashes, and whether that breakpoint is currently pending or not. + */ + private breakpointPendingStatus = new Map(); + /** * Flag indicating whether a `getDiff` function is currently running */ @@ -696,11 +828,7 @@ export interface AugmentedSourceBreakpoint extends DebugProtocol.SourceBreakpoin /** * A unique hash generated for the breakpoint at this exact file/line/column/feature. Every breakpoint with these same features should get the same hash */ - hash: string; - /** - * The device-provided breakpoint id. A missing ID means this breakpoint has not yet been verified by the device. - */ - deviceId?: number; + srcHash: string; /** * A unique ID the debug adapter generates to help send updates to the client about this breakpoint */ @@ -743,7 +871,11 @@ export interface BreakpointWorkItem { /** * A unique hash generated for the breakpoint at this exact file/line/column/feature. Every breakpoint with these same features should get the same hash */ - hash: string; + srcHash: string; + /** + * + */ + destHash: string; /** * The 0-based column index */ @@ -771,3 +903,14 @@ export interface BreakpointWorkItem { */ type: 'fileMap' | 'sourceDirs' | 'sourceMap'; } + +export type Breakpoint = DebugProtocol.SourceBreakpoint | AugmentedSourceBreakpoint; + +/** + * A way to reference a breakpoint. + * - `string` - a hash + * - `AugmentedSourceBreakpoint` an actual breakpoint + * - `{hash: string}` - an object containing a breakpoint hash + * - `Breakpoint & {srcPath: string}` - an object with all the properties of a breakpoint _and_ an explicitly defined `srcPath` + */ +export type BreakpointRef = string | AugmentedSourceBreakpoint | { srcHash: string } | (Breakpoint & { srcPath: string }); diff --git a/src/managers/LogManager.ts b/src/managers/LogManager.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/managers/ProjectManager.ts b/src/managers/ProjectManager.ts index 61910864..315212b3 100644 --- a/src/managers/ProjectManager.ts +++ b/src/managers/ProjectManager.ts @@ -11,6 +11,7 @@ import { fileUtils, standardizePath as s } from '../FileUtils'; import type { LocationManager, SourceLocation } from './LocationManager'; import { util } from '../util'; import { logger } from '../logging'; +import { Cache } from 'brighterscript/dist/Cache'; // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports const replaceInFile = require('replace-in-file'); @@ -96,50 +97,54 @@ export class ProjectManager { return sourceLineByDebuggerLine[debuggerLineNumber]; } + public sourceLocationCache = new Cache>(); + /** * @param debuggerPath * @param debuggerLineNumber - the 1-based line number from the debugger */ public async getSourceLocation(debuggerPath: string, debuggerLineNumber: number) { - //get source location using - let stagingFileInfo = await this.getStagingFileInfo(debuggerPath); - if (!stagingFileInfo) { - return; - } - let project = stagingFileInfo.project; + return this.sourceLocationCache.getOrAdd(`${debuggerPath}-${debuggerLineNumber}`, async () => { + //get source location using + let stagingFileInfo = await this.getStagingFileInfo(debuggerPath); + if (!stagingFileInfo) { + return; + } + let project = stagingFileInfo.project; - //remove the component library postfix if present - if (project instanceof ComponentLibraryProject) { - stagingFileInfo.absolutePath = fileUtils.unPostfixFilePath(stagingFileInfo.absolutePath, project.postfix); - stagingFileInfo.relativePath = fileUtils.unPostfixFilePath(stagingFileInfo.relativePath, project.postfix); - } + //remove the component library postfix if present + if (project instanceof ComponentLibraryProject) { + stagingFileInfo.absolutePath = fileUtils.unPostfixFilePath(stagingFileInfo.absolutePath, project.postfix); + stagingFileInfo.relativePath = fileUtils.unPostfixFilePath(stagingFileInfo.relativePath, project.postfix); + } - let sourceLocation = await this.locationManager.getSourceLocation({ - lineNumber: debuggerLineNumber, - columnIndex: 0, - fileMappings: project.fileMappings, - rootDir: project.rootDir, - stagingFilePath: stagingFileInfo.absolutePath, - stagingFolderPath: project.stagingFolderPath, - sourceDirs: project.sourceDirs, - enableSourceMaps: this.launchConfiguration?.enableSourceMaps ?? true - }); + let sourceLocation = await this.locationManager.getSourceLocation({ + lineNumber: debuggerLineNumber, + columnIndex: 0, + fileMappings: project.fileMappings, + rootDir: project.rootDir, + stagingFilePath: stagingFileInfo.absolutePath, + stagingFolderPath: project.stagingFolderPath, + sourceDirs: project.sourceDirs, + enableSourceMaps: this.launchConfiguration?.enableSourceMaps ?? true + }); - //if sourcemaps are disabled, and this is a telnet debug dession, account for breakpoint offsets - if (sourceLocation && this.launchConfiguration?.enableSourceMaps === false && !this.launchConfiguration.enableDebugProtocol) { - sourceLocation.lineNumber = this.getLineNumberOffsetByBreakpoints(sourceLocation.filePath, sourceLocation.lineNumber); - } + //if sourcemaps are disabled, and this is a telnet debug dession, account for breakpoint offsets + if (sourceLocation && this.launchConfiguration?.enableSourceMaps === false && !this.launchConfiguration.enableDebugProtocol) { + sourceLocation.lineNumber = this.getLineNumberOffsetByBreakpoints(sourceLocation.filePath, sourceLocation.lineNumber); + } - if (!sourceLocation?.filePath) { - //couldn't find a source location. At least send back the staging file information so the user can still debug - return { - filePath: stagingFileInfo.absolutePath, - lineNumber: sourceLocation?.lineNumber || debuggerLineNumber, - columnIndex: 0 - } as SourceLocation; - } else { - return sourceLocation; - } + if (!sourceLocation?.filePath) { + //couldn't find a source location. At least send back the staging file information so the user can still debug + return { + filePath: stagingFileInfo.absolutePath, + lineNumber: sourceLocation?.lineNumber || debuggerLineNumber, + columnIndex: 0 + } as SourceLocation; + } else { + return sourceLocation; + } + }); } /** From 77be661333ffc8d5841c29d1cf50584782af71bf Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 4 Oct 2023 13:08:01 -0400 Subject: [PATCH 73/74] Add some breakpoint logging --- src/adapters/DebugProtocolAdapter.ts | 2 ++ src/debugProtocol/client/DebugProtocolClient.ts | 6 ++++-- src/debugSession/BrightScriptDebugSession.ts | 4 +++- src/managers/ProjectManager.ts | 3 +++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/adapters/DebugProtocolAdapter.ts b/src/adapters/DebugProtocolAdapter.ts index 645ca736..e46c0fdf 100644 --- a/src/adapters/DebugProtocolAdapter.ts +++ b/src/adapters/DebugProtocolAdapter.ts @@ -746,6 +746,7 @@ export class DebugProtocolAdapter { private syncBreakpointsPromise = Promise.resolve(); public async syncBreakpoints() { + this.logger.log('syncBreakpoints()'); //wait for the previous sync to finish this.syncBreakpointsPromise = this.syncBreakpointsPromise //ignore any errors @@ -766,6 +767,7 @@ export class DebugProtocolAdapter { //compute breakpoint changes since last sync const diff = await this.breakpointManager.getDiff(this.projectManager.getAllProjects()); + this.logger.log('Syncing breakpoints', diff); // REMOVE breakpoints (delete these breakpoints from the device) if (diff.removed.length > 0) { diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 6492bbe0..66f4e0ea 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -755,7 +755,7 @@ export class DebugProtocolClient { this.activeRequests.set(request.data.requestId, request); - return new Promise((resolve) => { + return new Promise((resolve, reject) => { let unsubscribe = this.on('response', (response) => { if (response.data.requestId === request.data.requestId) { unsubscribe(); @@ -774,7 +774,9 @@ export class DebugProtocolClient { request: request }); } else { - throw new Error(`Control socket was closed - Command: ${Command[request.data.command]}`); + reject( + new Error(`Control socket was closed - Command: ${Command[request.data.command]}`) + ); } }); } diff --git a/src/debugSession/BrightScriptDebugSession.ts b/src/debugSession/BrightScriptDebugSession.ts index 8cc6e85b..6cd9823e 100644 --- a/src/debugSession/BrightScriptDebugSession.ts +++ b/src/debugSession/BrightScriptDebugSession.ts @@ -398,7 +398,7 @@ export class BrightScriptDebugSession extends BaseDebugSession { } //send any compile errors to the client - await this.rokuAdapter.sendErrors(); + await this.rokuAdapter?.sendErrors(); this.logger.error('Error. Shutting down.', e); return this.shutdown(); } @@ -720,6 +720,7 @@ export class BrightScriptDebugSession extends BaseDebugSession { * Called every time a breakpoint is created, modified, or deleted, for each file. This receives the entire list of breakpoints every time. */ public async setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments) { + this.logger.log('setBreakpointsRequest'); let sanitizedBreakpoints = this.breakpointManager.replaceBreakpoints(args.source.path, args.breakpoints); //sort the breakpoints let sortedAndFilteredBreakpoints = orderBy(sanitizedBreakpoints, [x => x.line, x => x.column]); @@ -729,6 +730,7 @@ export class BrightScriptDebugSession extends BaseDebugSession { }; this.sendResponse(response); + this.logger.debug('[setBreakpointsRequest] syncBreakpoints()', args); await this.rokuAdapter?.syncBreakpoints(); } diff --git a/src/managers/ProjectManager.ts b/src/managers/ProjectManager.ts index 315212b3..a16d7156 100644 --- a/src/managers/ProjectManager.ts +++ b/src/managers/ProjectManager.ts @@ -39,6 +39,8 @@ export class ProjectManager { enableDebugProtocol?: boolean; }; + public logger = logger.createLogger('[ProjectManager]'); + public mainProject: Project; public componentLibraryProjects = [] as ComponentLibraryProject[]; @@ -158,6 +160,7 @@ export class ProjectManager { //convert entry point staging location to source location let sourceLocation = await this.getSourceLocation(entryPoint.relativePath, entryPoint.lineNumber); + this.logger.info(`Registering entry breakpoint at ${sourceLocation.filePath}:${sourceLocation.lineNumber} (${entryPoint.pathAbsolute}:${entryPoint.lineNumber})`); //register the entry breakpoint this.breakpointManager.setBreakpoint(sourceLocation.filePath, { //+1 to select the first line of the function From f829f818804c61d56296281850333143cafa4707 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Wed, 11 Oct 2023 09:33:30 -0400 Subject: [PATCH 74/74] DebugProtocol compile-error handling (#161) * support marking breakpoints as `pending` * Implement `deleteBreakpoint` for bp manager * retain bp "pending" status if bp is deleted * Fix breakpoint deviceId issues * Only run single breakpoint sync at a time * BreakpointRef support * Fix failing test * Prevent tests from stalling out * Resurrect breakpoints when device failed to remove * Fix bp resurrection * Make the bp events more generic * Add unit test for removing failed add breakpoints * Merge branch 'master' of https://github.com/rokucommunity/roku-debug into DebugProtocolServer * Simplified the relay session test * When a breakpoint fails to delete because of error NOT_STOPPED. Store the breakpoints and delete later. * Store the srcHash and destHash as different hashes Create a mapping of destHash to breakpoint deviceId * Unit test fixes * Fix another test * Remove more bp resurrection tests * Always set deviceId for bps, even on error * Add better suppport for compile error output * Changes that need to be verified * Small comments, fix leftover merge items * Small stability tweaks * Properly shut down the adapter on debugger close * Fix telnet complib error detection * Shut down if compile error exists when using debug nav commands * send responses earlier to feel more responsive * Dedupe compile errors. add diagnostic source * better diagnostic source handling * Fix tests * Simplify the response sending logic --------- Co-authored-by: Christian Holbrook --- .vscode/launch.json | 2 +- src/CompileErrorProcessor.spec.ts | 236 ++++++++++++++--- src/CompileErrorProcessor.ts | 139 +++++----- src/DeviceInfo.ts | 1 + src/adapters/DebugProtocolAdapter.spec.ts | 2 +- src/adapters/DebugProtocolAdapter.ts | 52 +++- src/adapters/TelnetAdapter.ts | 7 + .../client/DebugProtocolClient.ts | 125 ++++++--- .../events/updates/CompileErrorUpdate.spec.ts | 8 +- .../events/updates/CompileErrorUpdate.ts | 4 +- .../BrightScriptDebugSession.spec.ts | 9 +- src/debugSession/BrightScriptDebugSession.ts | 247 ++++++++++++------ src/logging.ts | 1 + src/managers/BreakpointManager.ts | 5 +- 14 files changed, 577 insertions(+), 261 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index fff84015..0ca8a376 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -34,4 +34,4 @@ "internalConsoleOptions": "openOnSessionStart" } ] -} \ No newline at end of file +} diff --git a/src/CompileErrorProcessor.spec.ts b/src/CompileErrorProcessor.spec.ts index 1caabf12..c8b8e5c7 100644 --- a/src/CompileErrorProcessor.spec.ts +++ b/src/CompileErrorProcessor.spec.ts @@ -3,7 +3,8 @@ import { CompileErrorProcessor, CompileStatus } from './CompileErrorProcessor'; import { expect } from 'chai'; import type { SinonFakeTimers } from 'sinon'; import { createSandbox } from 'sinon'; -import { util as bscUtil } from 'brighterscript'; +import { DiagnosticSeverity, util as bscUtil } from 'brighterscript'; +import dedent = require('dedent'); const sinon = createSandbox(); describe('CompileErrorProcessor', () => { @@ -73,7 +74,26 @@ describe('CompileErrorProcessor', () => { describe('sendErrors', () => { it('emits the errors', async () => { - compiler.processUnhandledLines(`-------> Error parsing XML component SimpleButton.xml`); + compiler.processUnhandledLines(dedent` + 10-05 18:03:33.677 [beacon.signal] |AppCompileInitiate --------> TimeBase(0 ms) + 10-05 18:03:33.679 [scrpt.cmpl] Compiling 'app', id 'dev' + 10-05 18:03:33.681 [scrpt.load.mkup] Loading markup dev 'app' + 10-05 18:03:33.681 [scrpt.unload.mkup] Unloading markup dev 'app' + 10-05 18:03:33.683 [scrpt.parse.mkup.time] Parsed markup dev 'app' in 1 milliseconds + + ------ Compiling dev 'app' ------ + + ================================================================= + Found 1 compile error + --- Syntax Error. (compile error &h02) in pkg:/components/MainScene.brs(3) + *** ERROR compiling MainScene: + + + ================================================================= + An error occurred while attempting to compile the application's components: + -------> Compilation Failed. + MainScene + `); let callCount = 0; compiler.on('diagnostics', () => { callCount++; @@ -113,7 +133,8 @@ describe('CompileErrorProcessor', () => { path: 'SimpleButton.xml', range: bscUtil.createRange(0, 0, 0, 999), message: `Error parsing XML component`, - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }]); }); @@ -124,7 +145,8 @@ describe('CompileErrorProcessor', () => { path: 'pkg:/components/SimpleButton.xml', range: bscUtil.createRange(0, 0, 0, 999), message: `Error parsing XML component`, - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }]); }); }); @@ -140,7 +162,8 @@ describe('CompileErrorProcessor', () => { range: bscUtil.createRange(0, 0, 0, 999), message: 'Error in XML component RedButton', path: 'pkg:/components/RedButton.xml', - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }]); }); @@ -152,7 +175,8 @@ describe('CompileErrorProcessor', () => { path: 'Parsers.brs', range: bscUtil.createRange(18, 0, 18, 999), message: `Invalid #If/#ElseIf expression ( not defined) 'BAD_BS_CONST'`, - code: '&h92' + code: '&h92', + severity: DiagnosticSeverity.Error }]); }); @@ -163,7 +187,8 @@ describe('CompileErrorProcessor', () => { path: 'Parsers.brs', range: bscUtil.createRange(18, 0, 18, 999), message: `Invalid #If/#ElseIf expression ( not defined) 'BAD_BS_CONST'`, - code: '&h92' + code: '&h92', + severity: DiagnosticSeverity.Error }]); }); @@ -174,7 +199,8 @@ describe('CompileErrorProcessor', () => { path: 'Parsers.brs', range: bscUtil.createRange(18, 0, 18, 999), message: `Invalid #If/#ElseIf expression ( not defined)`, - code: '&h92' + code: '&h92', + severity: DiagnosticSeverity.Error }]); }); @@ -185,7 +211,8 @@ describe('CompileErrorProcessor', () => { path: 'Parsers.brs', range: bscUtil.createRange(0, 0, 0, 999), message: `Invalid #If/#ElseIf expression ( not defined) 'BAD_BS_CONST'`, - code: '&h92' + code: '&h92', + severity: DiagnosticSeverity.Error }]); }); @@ -196,7 +223,8 @@ describe('CompileErrorProcessor', () => { path: 'Parsers.brs', range: bscUtil.createRange(18, 0, 18, 999), message: `Invalid #If/#ElseIf expression ( not defined) 'BAD_BS_CONST'`, - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }]); }); }); @@ -209,7 +237,8 @@ describe('CompileErrorProcessor', () => { path: 'SimpleEntitlements.xml', range: bscUtil.createRange(0, 0, 0, 999), message: `Error parsing XML component`, - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }]); }); @@ -220,12 +249,14 @@ describe('CompileErrorProcessor', () => { path: 'SimpleEntitlements.xml', range: bscUtil.createRange(0, 0, 0, 999), message: `Error parsing XML component`, - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }, { path: 'Otherfile.xml', range: bscUtil.createRange(0, 0, 0, 999), message: `Error parsing XML component`, - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }]); }); @@ -236,7 +267,8 @@ describe('CompileErrorProcessor', () => { path: 'pkg:/components/SimpleEntitlements.xml', range: bscUtil.createRange(0, 0, 0, 999), message: `Error parsing XML component`, - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }]); }); @@ -247,12 +279,14 @@ describe('CompileErrorProcessor', () => { path: 'pkg:/components/SimpleEntitlements.xml', range: bscUtil.createRange(0, 0, 0, 999), message: `Error parsing XML component`, - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }, { path: 'pkg:/components/Otherfile.xml', range: bscUtil.createRange(0, 0, 0, 999), message: `Error parsing XML component`, - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }]); }); @@ -268,12 +302,14 @@ describe('CompileErrorProcessor', () => { path: 'SimpleEntitlements.xml', range: bscUtil.createRange(0, 0, 0, 999), message: `Error parsing XML component`, - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }, { path: 'Otherfile.xml', range: bscUtil.createRange(0, 0, 0, 999), message: `Error parsing XML component`, - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }]); }); }); @@ -292,12 +328,13 @@ describe('CompileErrorProcessor', () => { range: bscUtil.createRange(0, 0, 0, 999), message: 'Error loading file', path: 'pkg:/components/Scene/MainScene.GetConfigurationw.brs', - code: '&hb9' + code: '&hb9', + severity: DiagnosticSeverity.Error }]); }); describe('processUnhandledLines', () => { - async function runTest(lines: string[], expectedStatus: CompileStatus, expectedErrors?: BSDebugDiagnostic[]) { + async function runTest(lines: string | string[], expectedStatus: CompileStatus, expectedErrors?: BSDebugDiagnostic[]) { let compileErrors: BSDebugDiagnostic[]; let promise: Promise; if (expectedErrors) { @@ -309,9 +346,13 @@ describe('CompileErrorProcessor', () => { }); } - lines.forEach((line) => { - compiler.processUnhandledLines(line); - }); + if (typeof lines === 'string') { + compiler.processUnhandledLines(lines); + } else { + for (const line of lines) { + compiler.processUnhandledLines(line); + } + } if (expectedErrors) { //wait for the compiler-errors event @@ -321,6 +362,94 @@ describe('CompileErrorProcessor', () => { expect(compiler.status).to.eql(expectedStatus); } + it('handles the data in large chunks', async () => { + await runTest(dedent` + 10-05 18:03:33.677 [beacon.signal] |AppCompileInitiate --------> TimeBase(0 ms) + 10-05 18:03:33.679 [scrpt.cmpl] Compiling 'app', id 'dev' + 10-05 18:03:33.681 [scrpt.load.mkup] Loading markup dev 'app' + 10-05 18:03:33.681 [scrpt.unload.mkup] Unloading markup dev 'app' + 10-05 18:03:33.683 [scrpt.parse.mkup.time] Parsed markup dev 'app' in 1 milliseconds + + ------ Compiling dev 'app' ------ + + ================================================================= + Found 1 compile error + --- Syntax Error. (compile error &h02) in pkg:/components/MainScene.brs(3) + *** ERROR compiling MainScene: + + + ================================================================= + An error occurred while attempting to compile the application's components: + -------> Compilation Failed. + MainScene + `, CompileStatus.compileError, [{ + range: bscUtil.createRange(2, 0, 2, 999), + message: 'Syntax Error', + path: 'pkg:/components/MainScene.brs', + code: '&h02', + severity: DiagnosticSeverity.Error + }]); + }); + + it('emits component library errors after initial compile is complete', async () => { + await runTest(dedent` + 10-06 19:37:12.462 [beacon.signal] |AppLaunchInitiate ---------> TimeBase(0 ms) + 10-06 19:37:12.463 [beacon.signal] |AppCompileInitiate --------> TimeBase(0 ms) + 10-06 19:37:12.465 [scrpt.cmpl] Compiling 'app', id 'dev' + 10-06 19:37:12.466 [scrpt.load.mkup] Loading markup dev 'app' + 10-06 19:37:12.467 [scrpt.unload.mkup] Unloading markup dev 'app' + 10-06 19:37:12.468 [scrpt.parse.mkup.time] Parsed markup dev 'app' in 1 milliseconds + + ------ Compiling dev 'app' ------ + 10-06 19:37:12.471 [scrpt.ctx.cmpl.time] Compiled 'app', id 'dev' in 2 milliseconds (BCVer:0) + 10-06 19:37:12.471 [scrpt.proc.mkup.time] Processed markup dev 'app' in 0 milliseconds + 10-06 19:37:12.481 [beacon.signal] |AppCompileComplete --------> Duration(18 ms) + 10-06 19:37:12.498 [beacon.signal] |AppLaunchInitiate ---------> TimeBase(0 ms) + 10-06 19:37:12.508 [beacon.signal] |AppSplashInitiate ---------> TimeBase(9 ms) + 10-06 19:37:13.198 [beacon.signal] |AppSplashComplete ---------> Duration(690 ms) + 10-06 19:37:13.370 [beacon.signal] |AppLaunchInitiate ---------> TimeBase(0 ms) + 10-06 19:37:13.384 [scrpt.cmpl] Compiling 'app', id 'dev' + 10-06 19:37:13.391 [scrpt.load.mkup] Loading markup dev 'app' + 10-06 19:37:13.392 [scrpt.unload.mkup] Unloading markup dev 'app' + 10-06 19:37:13.394 [scrpt.parse.mkup.time] Parsed markup dev 'app' in 2 milliseconds + + ------ Compiling dev 'app' ------ + 10-06 19:37:13.399 [scrpt.ctx.cmpl.time] Compiled 'app', id 'dev' in 4 milliseconds (BCVer:0) + 10-06 19:37:13.399 [scrpt.proc.mkup.time] Processed markup dev 'app' in 0 milliseconds + 10-06 19:37:13.400 [beacon.signal] |AppCompileComplete --------> Duration(28 ms) + + ------ Running dev 'app' main ------ + 10-06 19:37:14.005 [scrpt.ctx.run.enter] UI: Entering 'app', id 'dev' + Complib loadStatus: loading + 10-06 19:37:14.212 [scrpt.cmpl] Compiling '', id 'RSG_BAAAAAJlSIgm' + 10-06 19:37:14.214 [scrpt.load.mkup] Loading markup RSG_BAAAAAJlSIgm '' + 10-06 19:37:14.215 [scrpt.unload.mkup] Unloading markup RSG_BAAAAAJlSIgm '' + 10-06 19:37:14.218 [scrpt.parse.mkup.time] Parsed markup RSG_BAAAAAJlSIgm '' in 4 milliseconds + + ================================================================= + Found 1 compile error + contained in ComponentLibrary package with uri + http://192.168.1.22:8080/complib.zip + --- Syntax Error. (compile error &h02) in pkg:/components/RedditViewer__lib0.brs(4) + *** ERROR compiling RedditViewer: + 10-06 19:37:14.505 [bs.ndk.proc.exit] plugin=dev pid=8755 status=signal retval=11 user requested=0 process name='SdkLauncher' exit code=EXIT_SYSTEM_KILL + 10-06 19:37:14.512 [beacon.signal] |AppExitInitiate -----------> TimeBase(2014 ms) + 10-06 19:37:14.514 [beacon.header] __________________________________________ + 10-06 19:37:14.514 [beacon.report] |AppLaunchInitiate ---------> TimeBase(0 ms), InstantOn + 10-06 19:37:14.515 [beacon.report] |AppSplashInitiate ---------> TimeBase(9 ms) + 10-06 19:37:14.515 [beacon.report] |AppSplashComplete ---------> Duration(690 ms) + 10-06 19:37:14.515 [beacon.report] |AppExitInitiate -----------> TimeBase(2014 ms) + 10-06 19:37:14.515 [beacon.report] |AppExitComplete -----------> Duration(2 ms) + 10-06 19:37:14.515 [beacon.footer] __________________________________________ + `, CompileStatus.compileError, [{ + range: bscUtil.createRange(3, 0, 3, 999), + message: 'Syntax Error', + path: 'pkg:/components/RedditViewer__lib0.brs', + code: '&h02', + severity: DiagnosticSeverity.Error + }]); + }); + it('detects No errors', async () => { let lines = [ `03-26 23:57:28.111 [beacon.signal] |AppLaunchInitiate ---------> TimeBase(0)`, @@ -368,7 +497,8 @@ describe('CompileErrorProcessor', () => { range: bscUtil.createRange(0, 0, 0, 999), message: 'Error loading file', path: 'pkg:/components/Scene/MainScene.GetConfigurationw.brs', - code: '&hb9' + code: '&hb9', + severity: DiagnosticSeverity.Error }]); }); @@ -398,22 +528,26 @@ describe('CompileErrorProcessor', () => { range: bscUtil.createRange(1, 0, 1, 999), message: 'Unexpected data found inside a element (first 10 characters are "aaa")', path: 'Foo.xml', - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }, { range: bscUtil.createRange(2, 0, 2, 999), message: 'Some unique error message', path: 'Foo.xml', - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }, { range: bscUtil.createRange(4, 0, 4, 999), message: 'message with Line 4 inside it', path: 'Foo.xml', - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }, { range: bscUtil.createRange(0, 0, 0, 999), message: 'Error parsing XML component', path: 'Foo.xml', - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }]); }); @@ -445,12 +579,14 @@ describe('CompileErrorProcessor', () => { range: bscUtil.createRange(2, 0, 2, 999), message: 'XML syntax error found ---> not well-formed (invalid token)', path: 'SampleScreen.xml', - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }, { range: bscUtil.createRange(0, 0, 0, 999), message: 'Error parsing XML component', path: 'SampleScreen.xml', - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error } ]); }); @@ -479,27 +615,32 @@ describe('CompileErrorProcessor', () => { range: bscUtil.createRange(595 - 1, 0, 595 - 1, 999), code: '&h02', message: 'Syntax Error', - path: 'pkg:/components/Services/Network/Parsers.brs' + path: 'pkg:/components/Services/Network/Parsers.brs', + severity: DiagnosticSeverity.Error }, { range: bscUtil.createRange(598 - 1, 0, 598 - 1, 999), code: '&h02', message: 'Syntax Error', - path: 'pkg:/components/Services/Network/Parsers.brs' + path: 'pkg:/components/Services/Network/Parsers.brs', + severity: DiagnosticSeverity.Error }, { range: bscUtil.createRange(732 - 1, 0, 732 - 1, 999), code: '&h02', message: 'Syntax Error', - path: 'pkg:/components/Services/Network/Parsers.brs' + path: 'pkg:/components/Services/Network/Parsers.brs', + severity: DiagnosticSeverity.Error }, { range: bscUtil.createRange(733 - 1, 0, 733 - 1, 999), code: '&h02', message: 'Syntax Error', - path: 'pkg:/components/Services/Network/Parsers.brs' + path: 'pkg:/components/Services/Network/Parsers.brs', + severity: DiagnosticSeverity.Error }, { range: bscUtil.createRange(734 - 1, 0, 734 - 1, 999), code: '&h02', message: 'Syntax Error', - path: 'pkg:/components/Services/Network/Parsers.brs' + path: 'pkg:/components/Services/Network/Parsers.brs', + severity: DiagnosticSeverity.Error } ]); }); @@ -539,32 +680,38 @@ describe('CompileErrorProcessor', () => { range: bscUtil.createRange(2, 0, 2, 999), message: 'XML syntax error found ---> not well-formed (invalid token)', path: 'SampleScreen.xml', - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }, { range: bscUtil.createRange(0, 0, 0, 999), message: 'Extends type does not exist: "ColoredButton"', path: 'pkg:/components/RedButton.xml', - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }, { range: bscUtil.createRange(8, 0, 8, 999), message: 'XML syntax error found ---> not well-formed (invalid token)', path: 'ChannelItemComponent.xml', - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }, { range: bscUtil.createRange(0, 0, 0, 999), message: 'Error parsing XML component', path: 'SampleScreen.xml', - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }, { range: bscUtil.createRange(0, 0, 0, 999), message: 'Error parsing XML component', path: 'ChannelItemComponent.xml', - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }, { range: bscUtil.createRange(0, 0, 0, 999), message: 'Error parsing XML component', path: 'RedButton.xml', - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error } ]); }); @@ -590,7 +737,8 @@ describe('CompileErrorProcessor', () => { code: '&h92', range: bscUtil.createRange(19 - 1, 0, 19 - 1, 999), message: `Invalid #If/#ElseIf expression ( not defined) 'BAD_BS_CONST'`, - path: 'Parsers.brs' + path: 'Parsers.brs', + severity: DiagnosticSeverity.Error } ]); }); @@ -614,7 +762,9 @@ describe('CompileErrorProcessor', () => { { range: bscUtil.createRange(0, 0, 0, 999), message: 'No manifest. Invalid package', - path: 'pkg:/manifest' + path: 'pkg:/manifest', + severity: DiagnosticSeverity.Error, + code: undefined } ]); }); diff --git a/src/CompileErrorProcessor.ts b/src/CompileErrorProcessor.ts index 0c4b23db..2d86ccdd 100644 --- a/src/CompileErrorProcessor.ts +++ b/src/CompileErrorProcessor.ts @@ -1,7 +1,7 @@ import { EventEmitter } from 'events'; import type { Diagnostic } from 'vscode-languageserver-protocol/node'; import { logger } from './logging'; -import { util as bscUtil } from 'brighterscript'; +import { DiagnosticSeverity, util as bscUtil } from 'brighterscript'; export class CompileErrorProcessor { @@ -28,52 +28,45 @@ export class CompileErrorProcessor { //emit these events on next tick, otherwise they will be processed immediately which could cause issues setTimeout(() => { //in rare cases, this event is fired after the debugger has closed, so make sure the event emitter still exists - if (this.emitter) { - this.emitter.emit(eventName, data); - } + this.emitter?.emit?.(eventName, data); }, 0); } public processUnhandledLines(responseText: string) { - if (this.status === CompileStatus.running) { - return; - } - - let newLines = responseText.split(/\r?\n/g); - switch (this.status) { - case CompileStatus.compiling: - case CompileStatus.compileError: - this.endCompilingLine = this.getEndCompilingLine(newLines); - if (this.endCompilingLine !== -1) { - this.logger.debug('[processUnhandledLines] entering state CompileStatus.running'); - this.status = CompileStatus.running; - this.resetCompileErrorTimer(false); - } else { - this.compilingLines = this.compilingLines.concat(newLines); - if (this.status === CompileStatus.compiling) { - //check to see if we've entered an error scenario - let hasError = /\berror\b/gi.test(responseText); - if (hasError) { - this.logger.debug('[processUnhandledLines] entering state CompileStatus.compileError'); - this.status = CompileStatus.compileError; + let lines = responseText.split(/\r?\n/g); + for (const line of lines) { + switch (this.status) { + case CompileStatus.compiling: + case CompileStatus.compileError: + if (this.isEndCompilingLine(line)) { + this.logger.debug('[processUnhandledLines] entering state CompileStatus.running'); + this.status = CompileStatus.running; + this.resetCompileErrorTimer(false); + } else { + this.compilingLines.push(line); + if (this.status === CompileStatus.compiling) { + //check to see if we've entered an error scenario + let hasError = /\berror\b/gi.test(line); + if (hasError) { + this.logger.debug('[processUnhandledLines] entering state CompileStatus.compileError'); + this.status = CompileStatus.compileError; + } + } + if (this.status === CompileStatus.compileError) { + //every input line while in error status will reset the stale timer, so we can wait for more errors to roll in. + this.resetCompileErrorTimer(true); } } - if (this.status === CompileStatus.compileError) { - //every input line while in error status will reset the stale timer, so we can wait for more errors to roll in. + break; + case CompileStatus.none: + case CompileStatus.running: + if (this.isStartingCompilingLine(line)) { + this.logger.debug('[processUnhandledLines] entering state CompileStatus.compiling'); + this.status = CompileStatus.compiling; this.resetCompileErrorTimer(true); } - } - break; - case CompileStatus.none: - this.startCompilingLine = this.getStartingCompilingLine(newLines); - this.compilingLines = this.compilingLines.concat(newLines); - if (this.startCompilingLine !== -1) { - this.logger.debug('[processUnhandledLines] entering state CompileStatus.compiling'); - newLines.splice(0, this.startCompilingLine); - this.status = CompileStatus.compiling; - this.resetCompileErrorTimer(true); - } - break; + break; + } } } @@ -88,7 +81,7 @@ export class CompileErrorProcessor { } public getErrors(lines: string[]) { - const result: BSDebugDiagnostic[] = []; + let diagnostics: BSDebugDiagnostic[] = []; //clone the lines so the parsers can manipulate them lines = [...lines]; while (lines.length > 0) { @@ -96,7 +89,7 @@ export class CompileErrorProcessor { const line = lines[0]; if (line) { - result.push( + diagnostics.push( ...[ this.processMultiLineErrors(lines), this.parseComponentDefinedInFileError(lines), @@ -111,10 +104,17 @@ export class CompileErrorProcessor { lines.shift(); } } - return result.filter(x => { - //throw out $livecompile errors (those are generated by REPL/eval code) + //throw out $livecompile errors (those are generated by REPL/eval code) + const result = diagnostics.filter(x => { return x.path && !x.path.toLowerCase().includes('$livecompile'); - }); + + //dedupe compile errors that have the same information + }).reduce((map, d) => { + map.set(`${d.path}:${d.range?.start.line}:${d.range?.start.character}-${d.message}-${d.severity}-${d.source}`, d); + return map; + }, new Map()); + + return [...result].map(x => x[1]); } /** @@ -137,7 +137,8 @@ export class CompileErrorProcessor { path: this.sanitizeCompilePath(filePath), range: bscUtil.createRange(0, 0, 0, 999), message: this.buildMessage(message), - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error })) .filter(x => !!x); } @@ -165,7 +166,8 @@ export class CompileErrorProcessor { path: this.sanitizeCompilePath(filePath), message: this.buildMessage(message, context), range: this.getRange(lineNumber), //lineNumber is 1-based - code: code + code: code, + severity: DiagnosticSeverity.Error }]; } } @@ -201,7 +203,8 @@ export class CompileErrorProcessor { path: filePath, range: this.getRange(lineNumber), //lineNumber is 1-based message: this.buildMessage(message), - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }); } else { //assume there are no more errors for this file @@ -235,7 +238,8 @@ export class CompileErrorProcessor { message: this.buildMessage(message), path: this.sanitizeCompilePath(filePath), range: this.getRange(), - code: undefined + code: undefined, + severity: DiagnosticSeverity.Error }]; } } @@ -257,7 +261,9 @@ export class CompileErrorProcessor { return [{ path: 'pkg:/manifest', range: bscUtil.createRange(0, 0, 0, 999), - message: this.buildMessage(message) + message: this.buildMessage(message), + code: undefined, + severity: DiagnosticSeverity.Error }]; } } @@ -304,7 +310,7 @@ export class CompileErrorProcessor { public resetCompileErrorTimer(isRunning): any { if (this.compileErrorTimer) { - clearInterval(this.compileErrorTimer); + clearTimeout(this.compileErrorTimer); this.compileErrorTimer = undefined; } @@ -323,29 +329,16 @@ export class CompileErrorProcessor { this.reportErrors(); } - private getStartingCompilingLine(lines: string[]): number { - let lastIndex = -1; - for (let i = 0; i < lines.length; i++) { - let line = lines[i]; - //if this line looks like the compiling line - if (/------\s+compiling.*------/i.exec(line)) { - lastIndex = i; - } - } - return lastIndex; + private isStartingCompilingLine(line: string): boolean { + //https://regex101.com/r/8W2wuZ/1 + // We need to start scanning for compile errors earlier than the ---compiling--- message, so look for the [scrpt.cmpl] message. + // keep the ---compiling--- as well, since it doesn't hurt to remain in compile mode + return /(------\s+compiling.*------)|(\[scrpt.cmpl]\s+compiling\s+'.*?'\s*,\s*id\s*'.*?')/i.test(line); } - private getEndCompilingLine(lines: string[]): number { - let lastIndex = -1; - for (let i = 0; i < lines.length; i++) { - let line = lines[i]; - // if this line looks like the compiling line - if (/------\s+Running.*------/i.exec(line)) { - lastIndex = i; - } - } - return lastIndex; - + private isEndCompilingLine(line: string): boolean { + // if this line looks like the compiling line + return /------\s+Running.*------/i.test(line); } /** @@ -377,6 +370,10 @@ export interface BSDebugDiagnostic extends Diagnostic { * main app. */ componentLibraryName?: string; + /** + * The diagnostic's severity. + */ + severity: DiagnosticSeverity; } export enum CompileStatus { diff --git a/src/DeviceInfo.ts b/src/DeviceInfo.ts index b52f90af..967d02cc 100644 --- a/src/DeviceInfo.ts +++ b/src/DeviceInfo.ts @@ -66,6 +66,7 @@ export interface DeviceInfo { 'trc-channel-version'?: string; 'davinci-version'?: string; 'av-sync-calibration-enabled'?: number; + 'brightscript-debugger-version'?: string; // Anything new they might add that we do not know about [key: string]: any; } diff --git a/src/adapters/DebugProtocolAdapter.spec.ts b/src/adapters/DebugProtocolAdapter.spec.ts index d3f3d8b7..71952cbc 100644 --- a/src/adapters/DebugProtocolAdapter.spec.ts +++ b/src/adapters/DebugProtocolAdapter.spec.ts @@ -70,7 +70,7 @@ describe('DebugProtocolAdapter', function() { files: [], outDir: outDir }); - adapter = new DebugProtocolAdapter(options, projectManager, breakpointManager, rendezvousTracker); + adapter = new DebugProtocolAdapter(options, projectManager, breakpointManager, rendezvousTracker, deviceInfo); if (!options.controlPort) { options.controlPort = await util.getPort(); diff --git a/src/adapters/DebugProtocolAdapter.ts b/src/adapters/DebugProtocolAdapter.ts index e46c0fdf..033c17f9 100644 --- a/src/adapters/DebugProtocolAdapter.ts +++ b/src/adapters/DebugProtocolAdapter.ts @@ -1,24 +1,25 @@ import * as EventEmitter from 'events'; import { Socket } from 'net'; +import { DiagnosticSeverity, util as bscUtil } from 'brighterscript'; import type { BSDebugDiagnostic } from '../CompileErrorProcessor'; import { CompileErrorProcessor } from '../CompileErrorProcessor'; import type { RendezvousHistory, RendezvousTracker } from '../RendezvousTracker'; import type { ChanperfData } from '../ChanperfTracker'; import { ChanperfTracker } from '../ChanperfTracker'; import type { SourceLocation } from '../managers/LocationManager'; -import { ErrorCode, PROTOCOL_ERROR_CODES } from '../debugProtocol/Constants'; +import { ErrorCode, PROTOCOL_ERROR_CODES, UpdateType } from '../debugProtocol/Constants'; import { defer, util } from '../util'; import { logger } from '../logging'; import * as semver from 'semver'; import type { AdapterOptions, HighLevelType, RokuAdapterEvaluateResponse } from '../interfaces'; import type { BreakpointManager } from '../managers/BreakpointManager'; import type { ProjectManager } from '../managers/ProjectManager'; -import { ActionQueue } from '../managers/ActionQueue'; import type { BreakpointsVerifiedEvent, ConstructorOptions, ProtocolVersionDetails } from '../debugProtocol/client/DebugProtocolClient'; import { DebugProtocolClient } from '../debugProtocol/client/DebugProtocolClient'; import type { Variable } from '../debugProtocol/events/responses/VariablesResponse'; import { VariableType } from '../debugProtocol/events/responses/VariablesResponse'; import type { TelnetAdapter } from './TelnetAdapter'; +import type { DeviceInfo } from '../DeviceInfo'; /** * A class that connects to a Roku device over telnet debugger port and provides a standardized way of interacting with it. @@ -28,7 +29,8 @@ export class DebugProtocolAdapter { private options: AdapterOptions & ConstructorOptions, private projectManager: ProjectManager, private breakpointManager: BreakpointManager, - private rendezvousTracker: RendezvousTracker + private rendezvousTracker: RendezvousTracker, + private deviceInfo: DeviceInfo ) { util.normalizeAdapterOptions(this.options); this.emitter = new EventEmitter(); @@ -119,11 +121,9 @@ export class DebugProtocolAdapter { public on(eventName: 'start', handler: () => void); public on(eventname: 'unhandled-console-output', handler: (output: string) => void); public on(eventName: string, handler: (payload: any) => void) { - this.emitter.on(eventName, handler); + this.emitter?.on(eventName, handler); return () => { - if (this.emitter !== undefined) { - this.emitter.removeListener(eventName, handler); - } + this.emitter?.removeListener(eventName, handler); }; } @@ -301,6 +301,18 @@ export class DebugProtocolAdapter { this.compileClient = undefined; } + this.socketDebugger.on('compile-error', (update) => { + let diagnostics: BSDebugDiagnostic[] = []; + diagnostics.push({ + path: update.data.filePath, + range: bscUtil.createRange(update.data.lineNumber - 1, 0, update.data.lineNumber - 1, 999), + message: update.data.errorMessage, + severity: DiagnosticSeverity.Error, + code: undefined + }); + this.emit('diagnostics', diagnostics); + }); + this.logger.log(`Connected to device`, { host: this.options.host, connected: this.connected }); this.emit('connected', this.connected); @@ -319,8 +331,19 @@ export class DebugProtocolAdapter { }, 200); } + /** + * Determines if the current version of the debug protocol supports emitting compile error updates. + */ + public get supportsCompileErrorReporting() { + return semver.satisfies(this.deviceInfo['brightscript-debugger-version'], '>=3.1.0'); + } + public async watchCompileOutput() { let deferred = defer(); + //If the debugProtocol supports compile error updates, don't scrape telnet logs for compile errors + if (this.supportsCompileErrorReporting) { + return deferred.resolve(); + } try { this.compileClient = new Socket(); this.compileErrorProcessor.on('diagnostics', (errors) => { @@ -334,7 +357,7 @@ export class DebugProtocolAdapter { }); this.logger.info('Connecting via telnet to gather compile info', { host: this.options.host, port: this.options.brightScriptConsolePort }); this.compileClient.connect(this.options.brightScriptConsolePort, this.options.host, () => { - this.logger.log(`Connected via telnet to gather compile info`, { host: this.options.host, port: this.options.brightScriptConsolePort }); + this.logger.log(`CONNECTED via telnet to gather compile info`, { host: this.options.host, port: this.options.brightScriptConsolePort }); }); this.logger.debug('Waiting for the compile client to settle'); @@ -707,13 +730,24 @@ export class DebugProtocolAdapter { } } + /** + * Indicates whether this class had `.destroy()` called at least once. Mostly used for checking externally to see if + * the whole debug session has been terminated or is in a bad state. + */ + public isDestroyed = false; /** * Disconnect from the telnet session and unset all objects */ public async destroy() { + this.isDestroyed = true; + // destroy the debug client if it's defined if (this.socketDebugger) { - await this.socketDebugger.destroy(); + try { + await this.socketDebugger.destroy(); + } catch (e) { + this.logger.error(e); + } } this.cache = undefined; diff --git a/src/adapters/TelnetAdapter.ts b/src/adapters/TelnetAdapter.ts index c9c03a65..47b04eed 100644 --- a/src/adapters/TelnetAdapter.ts +++ b/src/adapters/TelnetAdapter.ts @@ -1007,10 +1007,17 @@ export class TelnetAdapter { this.emitter?.removeAllListeners(); } + /** + * Indicates whether this class has had `.destroy()` called at least once. Mostly used for checking externally to see if + * the whole debug session has been terminated or is in a bad state. + */ + public isDestroyed = false; /** * Disconnect from the telnet session and unset all objects */ public destroy() { + this.isDestroyed = true; + if (this.requestPipeline) { this.requestPipeline.destroy(); } diff --git a/src/debugProtocol/client/DebugProtocolClient.ts b/src/debugProtocol/client/DebugProtocolClient.ts index 66f4e0ea..677139a4 100644 --- a/src/debugProtocol/client/DebugProtocolClient.ts +++ b/src/debugProtocol/client/DebugProtocolClient.ts @@ -173,6 +173,7 @@ export class DebugProtocolClient { }); } + public on(eventName: 'compile-error', handler: (event: CompileErrorUpdate) => void); public on(eventName: 'app-exit' | 'cannot-continue' | 'close' | 'start', handler: () => void); public on(eventName: 'breakpoints-verified', handler: (event: BreakpointsVerifiedEvent) => void); public on(eventName: 'response', handler: (response: ProtocolResponse) => void); @@ -194,6 +195,7 @@ export class DebugProtocolClient { }; } + private emit(eventName: 'compile-error', response: CompileErrorUpdate); private emit(eventName: 'response', response: ProtocolResponse); private emit(eventName: 'update', update: ProtocolUpdate); private emit(eventName: 'data', update: Buffer); @@ -208,23 +210,34 @@ export class DebugProtocolClient { }, 0); } + /** + * A function that can be used to cancel the repeating interval that's running to try and establish a connection to the control socket. + */ + private cancelControlConnectInterval: () => void; + + /** + * A collection of sockets created when trying to connect to the debug protocol's control socket. We keep these around for quicker tear-down + * whenever there is an early-terminated debug session + */ + private pendingControlConnectionSockets: Set; + private async establishControlConnection() { - const pendingSockets = new Set(); + this.pendingControlConnectionSockets = new Set(); const connection = await new Promise((resolve) => { - util.setInterval((cancelInterval) => { + this.cancelControlConnectInterval = util.setInterval((cancelInterval) => { const socket = new Net.Socket({ allowHalfOpen: false }); - pendingSockets.add(socket); + this.pendingControlConnectionSockets.add(socket); socket.on('error', (error) => { console.debug(Date.now(), 'Encountered an error connecting to the debug protocol socket. Ignoring and will try again soon', error); }); socket.connect({ port: this.options.controlPort, host: this.options.host }, () => { cancelInterval(); - this.logger.debug(`Connected to debug protocol control port. Socket ${[...pendingSockets].indexOf(socket)} of ${pendingSockets.size} was the winner`); + this.logger.debug(`Connected to debug protocol control port. Socket ${[...this.pendingControlConnectionSockets].indexOf(socket)} of ${this.pendingControlConnectionSockets.size} was the winner`); //clean up all remaining pending sockets - for (const pendingSocket of pendingSockets) { + for (const pendingSocket of this.pendingControlConnectionSockets) { pendingSocket.removeAllListeners(); //cleanup and destroy all other sockets if (pendingSocket !== socket) { @@ -232,7 +245,7 @@ export class DebugProtocolClient { pendingSocket?.destroy(); } } - pendingSockets.clear(); + this.pendingControlConnectionSockets.clear(); resolve(socket); }); }, this.options.controlConnectInterval ?? 250); @@ -316,11 +329,15 @@ export class DebugProtocolClient { * can be extracted and processed through the DebugProtocolClientReplaySession */ private writeToBufferLog(type: 'server-to-client' | 'client-to-server' | 'io', buffer: Buffer) { - this.logger.log('[[bufferLog]]:', JSON.stringify({ + let obj = { type: type, timestamp: new Date().toISOString(), buffer: buffer.toJSON() - })); + }; + if (type === 'io') { + (obj as any).text = buffer.toString(); + } + this.logger.log('[[bufferLog]]:', JSON.stringify(obj)); } public continue() { @@ -961,7 +978,11 @@ export class DebugProtocolClient { //we do nothing with breakpoint errors at this time. return BreakpointErrorUpdate.fromBuffer(buffer); case UpdateType.CompileError: - return CompileErrorUpdate.fromBuffer(buffer); + let compileErrorUpdate = CompileErrorUpdate.fromBuffer(buffer); + if (compileErrorUpdate?.data?.errorMessage !== '') { + this.emit('compile-error', compileErrorUpdate); + } + return compileErrorUpdate; case UpdateType.BreakpointVerified: let response = BreakpointVerifiedUpdate.fromBuffer(buffer); if (response?.data?.breakpoints?.length > 0) { @@ -1071,44 +1092,51 @@ export class DebugProtocolClient { }); // Send a connection request to the server. this.logger.log(`Connect to IO Port ${this.options.host}:${update.data.port}`); - this.ioSocket.connect({ - port: update.data.port, - host: this.options.host - }, () => { - // If there is no error, the server has accepted the request - this.logger.log('TCP connection established with the IO Port.'); - this.connectedToIoPort = true; - - let lastPartialLine = ''; - this.ioSocket.on('data', (buffer) => { - this.writeToBufferLog('io', buffer); - let responseText = buffer.toString(); - if (!responseText.endsWith('\n')) { - // buffer was split, save the partial line - lastPartialLine += responseText; - } else { - if (lastPartialLine) { - // there was leftover lines, join the partial lines back together - responseText = lastPartialLine + responseText; - lastPartialLine = ''; + + //sometimes the server shuts down before we had a chance to connect, so recover more gracefully + try { + this.ioSocket.connect({ + port: update.data.port, + host: this.options.host + }, () => { + // If there is no error, the server has accepted the request + this.logger.log('TCP connection established with the IO Port.'); + this.connectedToIoPort = true; + + let lastPartialLine = ''; + this.ioSocket.on('data', (buffer) => { + this.writeToBufferLog('io', buffer); + let responseText = buffer.toString(); + if (!responseText.endsWith('\n')) { + // buffer was split, save the partial line + lastPartialLine += responseText; + } else { + if (lastPartialLine) { + // there was leftover lines, join the partial lines back together + responseText = lastPartialLine + responseText; + lastPartialLine = ''; + } + // Emit the completed io string. + this.emit('io-output', responseText.trim()); } - // Emit the completed io string. - this.emit('io-output', responseText.trim()); - } - }); + }); - this.ioSocket.on('close', () => { - this.logger.log('IO socket closed'); - this.ioSocketClosed.tryResolve(); - }); + this.ioSocket.on('close', () => { + this.logger.log('IO socket closed'); + this.ioSocketClosed.tryResolve(); + }); - // Don't forget to catch error, for your own sake. - this.ioSocket.once('error', (err) => { - this.ioSocket.end(); - this.logger.error(err); + // Don't forget to catch error, for your own sake. + this.ioSocket.once('error', (err) => { + this.ioSocket.end(); + this.logger.error(err); + }); }); - }); - return true; + return true; + } catch (e) { + this.logger.error(`Failed to connect to IO socket at ${this.options.host}:${update.data.port}`, e); + void this.shutdown('app-exit'); + } } return false; } @@ -1123,6 +1151,17 @@ export class DebugProtocolClient { private async shutdown(eventName: 'app-exit' | 'close', immediate = false) { this.logger.log('Shutting down!'); + + this.cancelControlConnectInterval?.(); + for (const pendingSocket of this.pendingControlConnectionSockets) { + pendingSocket.removeAllListeners(); + //cleanup and destroy all other sockets + if (pendingSocket !== this.controlSocket) { + pendingSocket.end(); + pendingSocket?.destroy(); + } + } + let exitChannelTimeout = this.options?.exitChannelTimeout ?? 30_000; let shutdownTimeMax = this.options?.shutdownTimeout ?? 10_000; //if immediate is true, this is an instant shutdown force. don't wait for anything diff --git a/src/debugProtocol/events/updates/CompileErrorUpdate.spec.ts b/src/debugProtocol/events/updates/CompileErrorUpdate.spec.ts index d8e0eab9..67e646d3 100644 --- a/src/debugProtocol/events/updates/CompileErrorUpdate.spec.ts +++ b/src/debugProtocol/events/updates/CompileErrorUpdate.spec.ts @@ -20,13 +20,14 @@ describe('CompileErrorUpdate', () => { errorMessage: 'crashed', filePath: 'pkg:/source/main.brs', libraryName: 'complib1', - lineNumber: 3 + lineNumber: 3, + flags: undefined }); expect( CompileErrorUpdate.fromBuffer(command.toBuffer()).data ).to.eql({ - packetLength: 58, // 4 bytes + packetLength: 62, // 4 bytes requestId: 0, // 4 bytes errorCode: ErrorCode.OK, // 4 bytes updateType: UpdateType.CompileError, // 4 bytes @@ -34,7 +35,8 @@ describe('CompileErrorUpdate', () => { errorMessage: 'crashed', // 8 bytes filePath: 'pkg:/source/main.brs', // 21 bytes libraryName: 'complib1', // 9 bytes - lineNumber: 3 // 4 bytes + lineNumber: 3, // 4 bytes + flags: 0 // 4 bytes }); }); }); diff --git a/src/debugProtocol/events/updates/CompileErrorUpdate.ts b/src/debugProtocol/events/updates/CompileErrorUpdate.ts index 294824db..4d700f44 100644 --- a/src/debugProtocol/events/updates/CompileErrorUpdate.ts +++ b/src/debugProtocol/events/updates/CompileErrorUpdate.ts @@ -33,7 +33,7 @@ export class CompileErrorUpdate { const update = new CompileErrorUpdate(); protocolUtil.bufferLoaderHelper(update, buffer, 20, (smartBuffer) => { protocolUtil.loadCommonUpdateFields(update, smartBuffer, update.data.updateType); - + update.data.flags = smartBuffer.readUInt32LE(); // flags - always 0, reserved for future use update.data.errorMessage = protocolUtil.readStringNT(smartBuffer); // error_string update.data.filePath = protocolUtil.readStringNT(smartBuffer); // file_spec update.data.lineNumber = smartBuffer.readUInt32LE(); // line_number @@ -45,6 +45,7 @@ export class CompileErrorUpdate { public toBuffer() { let smartBuffer = new SmartBuffer(); + smartBuffer.writeUInt32LE(this.data.flags ?? 0); // flags smartBuffer.writeStringNT(this.data.errorMessage); // error_string smartBuffer.writeStringNT(this.data.filePath); // file_spec smartBuffer.writeUInt32LE(this.data.lineNumber); // line_number @@ -62,6 +63,7 @@ export class CompileErrorUpdate { public readOffset: number = undefined; public data = { + flags: undefined as number, /** * A text message describing the compiler error. * diff --git a/src/debugSession/BrightScriptDebugSession.spec.ts b/src/debugSession/BrightScriptDebugSession.spec.ts index 2734d849..c69f9382 100644 --- a/src/debugSession/BrightScriptDebugSession.spec.ts +++ b/src/debugSession/BrightScriptDebugSession.spec.ts @@ -13,7 +13,7 @@ import { defer } from '../util'; import { HighLevelType } from '../interfaces'; import type { LaunchConfiguration } from '../LaunchConfiguration'; import type { SinonStub } from 'sinon'; -import { util as bscUtil, standardizePath as s } from 'brighterscript'; +import { DiagnosticSeverity, util as bscUtil, standardizePath as s } from 'brighterscript'; import { DefaultFiles } from 'roku-deploy'; import type { AddProjectParams, ComponentLibraryConstructorParams } from '../managers/ProjectManager'; import { ComponentLibraryProject, Project } from '../managers/ProjectManager'; @@ -711,13 +711,16 @@ describe('BrightScriptDebugSession', () => { await session['handleDiagnostics']([{ message: 'Crash', path: 'SomeComponent.xml', - range: bscUtil.createRange(1, 2, 3, 4) + range: bscUtil.createRange(1, 2, 3, 4), + severity: DiagnosticSeverity.Warning }]); expect(stub.getCall(0).args[0]?.body).to.eql({ diagnostics: [{ message: 'Crash', path: s`${stagingDir}/.roku-deploy-staging/components/SomeComponent.xml`, - range: bscUtil.createRange(1, 2, 1, 4) + range: bscUtil.createRange(1, 2, 1, 4), + severity: DiagnosticSeverity.Warning, + source: 'roku-debug' }] }); }); diff --git a/src/debugSession/BrightScriptDebugSession.ts b/src/debugSession/BrightScriptDebugSession.ts index 6cd9823e..b1c6c28a 100644 --- a/src/debugSession/BrightScriptDebugSession.ts +++ b/src/debugSession/BrightScriptDebugSession.ts @@ -6,7 +6,6 @@ import type { RokuDeploy, RokuDeployOptions } from 'roku-deploy'; import { BreakpointEvent, DebugSession as BaseDebugSession, - ErrorDestination, Handles, InitializedEvent, InvalidatedEvent, @@ -49,10 +48,13 @@ import { LocationManager } from '../managers/LocationManager'; import type { AugmentedSourceBreakpoint } from '../managers/BreakpointManager'; import { BreakpointManager } from '../managers/BreakpointManager'; import type { LogMessage } from '../logging'; -import { logger, FileLoggingManager, debugServerLogOutputEventTransport } from '../logging'; +import { logger, FileLoggingManager, debugServerLogOutputEventTransport, LogLevelPriority } from '../logging'; import type { DeviceInfo } from '../DeviceInfo'; import * as xml2js from 'xml2js'; import { VariableType } from '../debugProtocol/events/responses/VariablesResponse'; +import { DiagnosticSeverity } from 'brighterscript'; + +const diagnosticSource = 'roku-debug'; export class BrightScriptDebugSession extends BaseDebugSession { public constructor() { @@ -134,6 +136,16 @@ export class BrightScriptDebugSession extends BaseDebugSession { public tempVarPrefix = '__rokudebug__'; + /** + * The first encountered compile error, will be used to send to the client as a runtime error (nicer UI presentation) + */ + private compileError: BSDebugDiagnostic; + + /** + * A magic number to represent a fake thread that will be used for showing compile errors in the UI as if they were runtime crashes + */ + private COMPILE_ERROR_THREAD_ID = 7_777; + private get enableDebugProtocol() { return this.launchConfiguration.enableDebugProtocol; } @@ -244,7 +256,11 @@ export class BrightScriptDebugSession extends BaseDebugSession { public deviceInfo: DeviceInfo; public async launchRequest(response: DebugProtocol.LaunchResponse, config: LaunchConfiguration) { + this.logger.log('[launchRequest] begin'); + //send the response right away so the UI immediately shows the debugger toolbar + this.sendResponse(response); + this.launchConfiguration = config; //set the logLevel provided by the launch config @@ -358,11 +374,6 @@ export class BrightScriptDebugSession extends BaseDebugSession { } }); - //ignore the compile error failure from within the publish - (this.launchConfiguration as any).failOnCompileError = false; - // Set the remote debug flag on the args to be passed to roku deploy so the socket debugger can be started if needed. - (this.launchConfiguration as any).remoteDebug = this.enableDebugProtocol; - await this.connectAndPublish(); this.sendEvent(new ChannelPublishedEvent( @@ -371,18 +382,18 @@ export class BrightScriptDebugSession extends BaseDebugSession { //tell the adapter adapter that the channel has been launched. await this.rokuAdapter.activate(); - + if (this.rokuAdapter.isDestroyed) { + throw new Error('Debug session encountered an error'); + } if (!error) { if (this.rokuAdapter.connected) { this.logger.info('Host connection was established before the main public process was completed'); this.logger.log(`deployed to Roku@${this.launchConfiguration.host}`); - this.sendResponse(response); } else { this.logger.info('Main public process was completed but we are still waiting for a connection to the host'); this.rokuAdapter.on('connected', (status) => { if (status) { this.logger.log(`deployed to Roku@${this.launchConfiguration.host}`); - this.sendResponse(response); } }); } @@ -393,14 +404,12 @@ export class BrightScriptDebugSession extends BaseDebugSession { //if the message is anything other than compile errors, we want to display the error if (!(e instanceof CompileError)) { util.log('Encountered an issue during the publish process'); - util.log((e as Error).message); - this.sendErrorResponse(response, -1, (e as Error).message); - } + util.log((e as Error)?.stack); + this.sendErrorResponse(response, -1, (e as Error)?.stack); - //send any compile errors to the client - await this.rokuAdapter?.sendErrors(); - this.logger.error('Error. Shutting down.', e); - return this.shutdown(); + //send any compile errors to the client + await this.rokuAdapter?.sendErrors(); + } } //at this point, the project has been deployed. If we need to use a deep link, launch it now. @@ -465,6 +474,7 @@ export class BrightScriptDebugSession extends BaseDebugSession { private async handleDiagnostics(diagnostics: BSDebugDiagnostic[]) { // Roku device and sourcemap work with 1-based line numbers, VSCode expects 0-based lines. for (let diagnostic of diagnostics) { + diagnostic.source = diagnosticSource; let sourceLocation = await this.projectManager.getSourceLocation(diagnostic.path, diagnostic.range.start.line + 1); if (sourceLocation) { diagnostic.path = sourceLocation.filePath; @@ -476,9 +486,17 @@ export class BrightScriptDebugSession extends BaseDebugSession { } } + //find the first compile error (i.e. first DiagnosticSeverity.Error) if there is one + this.compileError = diagnostics.find(x => x.severity === DiagnosticSeverity.Error); + if (this.compileError) { + this.sendEvent(new StoppedEvent( + StoppedEventReason.exception, + this.COMPILE_ERROR_THREAD_ID, + `CompileError: ${this.compileError.message}` + )); + } + this.sendEvent(new DiagnosticsEvent(diagnostics)); - //stop the roku adapter and exit the channel - return this.shutdown(); } private async connectAndPublish() { @@ -502,9 +520,18 @@ export class BrightScriptDebugSession extends BaseDebugSession { //publish the package to the target Roku const publishPromise = this.rokuDeploy.publish({ ...this.launchConfiguration, + //typing fix + logLevel: LogLevelPriority[this.logger.logLevel], + // enable the debug protocol if true + remoteDebug: this.enableDebugProtocol, + //necessary for capturing compile errors from the protocol (has no effect on telnet) + remoteDebugConnectEarly: false, + //we don't want to fail if there were compile errors...we'll let our compile error processor handle that failOnCompileError: true - } as any as RokuDeployOptions).then(() => { + }).then(() => { packageIsPublished = true; + }).catch((e) => { + this.logger.error(e); }); await publishPromise; @@ -745,17 +772,23 @@ export class BrightScriptDebugSession extends BaseDebugSession { let threads = []; - //only send the threads request if we are at the debugger prompt - if (this.rokuAdapter.isAtDebuggerPrompt) { - let rokuThreads = await this.rokuAdapter.getThreads(); + //This is a bit of a hack. If there's a compile error, send a thread to represent it so we can show the compile error like a runtime exception + if (this.compileError) { + threads.push(new Thread(this.COMPILE_ERROR_THREAD_ID, 'Compile Error')); + } else { + //only send the threads request if we are at the debugger prompt + if (this.rokuAdapter.isAtDebuggerPrompt) { + let rokuThreads = await this.rokuAdapter.getThreads(); - for (let thread of rokuThreads) { - threads.push( - new Thread(thread.threadId, `Thread ${thread.threadId}`) - ); + for (let thread of rokuThreads) { + threads.push( + new Thread(thread.threadId, `Thread ${thread.threadId}`) + ); + } + } else { + this.logger.log('Skipped getting threads because the RokuAdapter is not accepting input at this time.'); } - } else { - this.logger.log('Skipped getting threads because the RokuAdapter is not accepting input at this time.'); + } response.body = { @@ -770,48 +803,60 @@ export class BrightScriptDebugSession extends BaseDebugSession { this.logger.log('stackTraceRequest'); let frames = []; - if (this.rokuAdapter.isAtDebuggerPrompt) { - let stackTrace = await this.rokuAdapter.getStackTrace(args.threadId); - - for (let debugFrame of stackTrace) { - let sourceLocation = await this.projectManager.getSourceLocation(debugFrame.filePath, debugFrame.lineNumber); - - //the stacktrace returns function identifiers in all lower case. Try to get the actual case - //load the contents of the file and get the correct casing for the function identifier - try { - let functionName = this.fileManager.getCorrectFunctionNameCase(sourceLocation?.filePath, debugFrame.functionIdentifier); - if (functionName) { - - //search for original function name if this is an anonymous function. - //anonymous function names are prefixed with $ in the stack trace (i.e. $anon_1 or $functionname_40002) - if (functionName.startsWith('$')) { - functionName = this.fileManager.getFunctionNameAtPosition( - sourceLocation.filePath, - sourceLocation.lineNumber - 1, - functionName - ); + //this is a bit of a hack. If there's a compile error, send a full stack frame so we can show the compile error like a runtime crash + if (this.compileError) { + frames.push(new StackFrame( + 0, + 'Compile Error', + new Source(path.basename(this.compileError.path), this.compileError.path), + //diagnostics are 0 based, vscode expects 1 based + this.compileError.range.start.line + 1, + this.compileError.range.start.character + 1 + )); + } else { + if (this.rokuAdapter.isAtDebuggerPrompt) { + let stackTrace = await this.rokuAdapter.getStackTrace(args.threadId); + + for (let debugFrame of stackTrace) { + let sourceLocation = await this.projectManager.getSourceLocation(debugFrame.filePath, debugFrame.lineNumber); + + //the stacktrace returns function identifiers in all lower case. Try to get the actual case + //load the contents of the file and get the correct casing for the function identifier + try { + let functionName = this.fileManager.getCorrectFunctionNameCase(sourceLocation?.filePath, debugFrame.functionIdentifier); + if (functionName) { + + //search for original function name if this is an anonymous function. + //anonymous function names are prefixed with $ in the stack trace (i.e. $anon_1 or $functionname_40002) + if (functionName.startsWith('$')) { + functionName = this.fileManager.getFunctionNameAtPosition( + sourceLocation.filePath, + sourceLocation.lineNumber - 1, + functionName + ); + } + debugFrame.functionIdentifier = functionName; } - debugFrame.functionIdentifier = functionName; + } catch (error) { + this.logger.error('Error correcting function identifier case', { error, sourceLocation, debugFrame }); } - } catch (error) { - this.logger.error('Error correcting function identifier case', { error, sourceLocation, debugFrame }); - } - const filePath = sourceLocation?.filePath ?? debugFrame.filePath; - - const frame: DebugProtocol.StackFrame = new StackFrame( - debugFrame.frameId, - `${debugFrame.functionIdentifier}`, - new Source(path.basename(filePath), filePath), - sourceLocation?.lineNumber ?? debugFrame.lineNumber, - 1 - ); - if (!sourceLocation) { - frame.presentationHint = 'subtle'; + const filePath = sourceLocation?.filePath ?? debugFrame.filePath; + + const frame: DebugProtocol.StackFrame = new StackFrame( + debugFrame.frameId, + `${debugFrame.functionIdentifier}`, + new Source(path.basename(filePath), filePath), + sourceLocation?.lineNumber ?? debugFrame.lineNumber, + 1 + ); + if (!sourceLocation) { + frame.presentationHint = 'subtle'; + } + frames.push(frame); } - frames.push(frame); + } else { + this.logger.log('Skipped calculating stacktrace because the RokuAdapter is not accepting input at this time'); } - } else { - this.logger.log('Skipped calculating stacktrace because the RokuAdapter is not accepting input at this time'); } response.body = { stackFrames: frames, @@ -866,6 +911,13 @@ export class BrightScriptDebugSession extends BaseDebugSession { } protected async continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments) { + //if we have a compile error, we should shut down + if (this.compileError) { + this.sendResponse(response); + await this.shutdown(); + return; + } + this.logger.log('continueRequest'); await this.rokuAdapter.continue(); this.sendResponse(response); @@ -873,6 +925,14 @@ export class BrightScriptDebugSession extends BaseDebugSession { protected async pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments) { this.logger.log('pauseRequest'); + + //if we have a compile error, we should shut down + if (this.compileError) { + this.sendResponse(response); + await this.shutdown(); + return; + } + await this.rokuAdapter.pause(); this.sendResponse(response); } @@ -889,6 +949,14 @@ export class BrightScriptDebugSession extends BaseDebugSession { */ protected async nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments) { this.logger.log('[nextRequest] begin'); + + //if we have a compile error, we should shut down + if (this.compileError) { + this.sendResponse(response); + await this.shutdown(); + return; + } + try { await this.rokuAdapter.stepOver(args.threadId); this.logger.info('[nextRequest] end'); @@ -900,6 +968,14 @@ export class BrightScriptDebugSession extends BaseDebugSession { protected async stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments) { this.logger.log('[stepInRequest]'); + + //if we have a compile error, we should shut down + if (this.compileError) { + this.sendResponse(response); + await this.shutdown(); + return; + } + await this.rokuAdapter.stepInto(args.threadId); this.sendResponse(response); this.logger.info('[stepInRequest] end'); @@ -907,6 +983,14 @@ export class BrightScriptDebugSession extends BaseDebugSession { protected async stepOutRequest(response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments) { this.logger.log('[stepOutRequest] begin'); + + //if we have a compile error, we should shut down + if (this.compileError) { + this.sendResponse(response); + await this.shutdown(); + return; + } + await this.rokuAdapter.stepOut(args.threadId); this.sendResponse(response); this.logger.info('[stepOutRequest] end'); @@ -1124,20 +1208,17 @@ export class BrightScriptDebugSession extends BaseDebugSession { * @param args */ protected async disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments, request?: DebugProtocol.Request) { - if (this.rokuAdapter) { - await this.rokuAdapter.destroy(); - } //return to the home screen if (!this.enableDebugProtocol) { await this.rokuDeploy.pressHomeButton(this.launchConfiguration.host, this.launchConfiguration.remotePort); } - this.componentLibraryServer.stop(); this.sendResponse(response); + await this.shutdown(); } private createRokuAdapter(host: string, rendezvousTracker: RendezvousTracker) { if (this.enableDebugProtocol) { - this.rokuAdapter = new DebugProtocolAdapter(this.launchConfiguration, this.projectManager, this.breakpointManager, rendezvousTracker); + this.rokuAdapter = new DebugProtocolAdapter(this.launchConfiguration, this.projectManager, this.breakpointManager, rendezvousTracker, this.deviceInfo); } else { this.rokuAdapter = new TelnetAdapter(this.launchConfiguration, rendezvousTracker); } @@ -1381,6 +1462,8 @@ export class BrightScriptDebugSession extends BaseDebugSession { private async _shutdown(errorMessage?: string): Promise { try { + this.componentLibraryServer?.stop(); + this.rendezvousTracker?.destroy?.(); //if configured, delete the staging directory @@ -1403,19 +1486,17 @@ export class BrightScriptDebugSession extends BaseDebugSession { this.showPopupMessage(errorMessage, 'error'); } - if (this.launchConfiguration.stopDebuggerOnAppExit !== false) { - this.logger.log('Destroy rokuAdapter'); - await this.rokuAdapter?.destroy?.(); - //press the home button to return to the home screen - try { - this.logger.log('Press home button'); - await this.rokuDeploy.pressHomeButton(this.launchConfiguration.host, this.launchConfiguration.remotePort); - } catch (e) { - console.error(e); - this.logger.error(e); - } + this.logger.log('Destroy rokuAdapter'); + await this.rokuAdapter?.destroy?.(); + //press the home button to return to the home screen + try { + this.logger.log('Press home button'); + await this.rokuDeploy.pressHomeButton(this.launchConfiguration.host, this.launchConfiguration.remotePort); + } catch (e) { + this.logger.error(e); } + this.logger.log('Send terminated event'); this.sendEvent(new TerminatedEvent()); diff --git a/src/logging.ts b/src/logging.ts index 23e61c37..4bb0f426 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -30,6 +30,7 @@ const createLogger = logger.createLogger.bind(logger) as typeof Logger.prototype export { logger, createLogger }; export type { Logger, LogMessage, LogLevel } from '@rokucommunity/logger'; +export { LogLevelPriority } from '@rokucommunity/logger'; export class FileLoggingManager { diff --git a/src/managers/BreakpointManager.ts b/src/managers/BreakpointManager.ts index e0b195f3..be71a303 100644 --- a/src/managers/BreakpointManager.ts +++ b/src/managers/BreakpointManager.ts @@ -9,7 +9,6 @@ import { standardizePath as s } from 'roku-deploy'; import type { SourceMapManager } from './SourceMapManager'; import type { LocationManager } from './LocationManager'; import { util } from '../util'; -import { nextTick } from 'process'; import { EventEmitter } from 'eventemitter3'; export class BreakpointManager { @@ -217,6 +216,8 @@ export class BreakpointManager { }); } + private deviceIdByDestHash = new Map(); + /** * Find a breakpoint by its deviceId * @returns the breakpoint, or undefined if not found @@ -228,8 +229,6 @@ export class BreakpointManager { return this.getBreakpoint(bpRef?.srcHash); } - private deviceIdByDestHash = new Map(); - /** * Set the deviceId of a breakpoint */