roku-debug 0.20.6 → 0.20.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/CompileErrorProcessor.d.ts +7 -2
- package/dist/CompileErrorProcessor.js +64 -69
- package/dist/CompileErrorProcessor.js.map +1 -1
- package/dist/DeviceInfo.d.ts +1 -0
- package/dist/adapters/DebugProtocolAdapter.d.ts +64 -17
- package/dist/adapters/DebugProtocolAdapter.js +264 -157
- package/dist/adapters/DebugProtocolAdapter.js.map +1 -1
- package/dist/adapters/TelnetAdapter.d.ts +8 -3
- package/dist/adapters/TelnetAdapter.js +14 -6
- package/dist/adapters/TelnetAdapter.js.map +1 -1
- package/dist/debugProtocol/Constants.d.ts +171 -81
- package/dist/debugProtocol/Constants.js +206 -126
- package/dist/debugProtocol/Constants.js.map +1 -1
- package/dist/debugProtocol/DebugProtocolClientReplaySession.d.ts +46 -0
- package/dist/debugProtocol/DebugProtocolClientReplaySession.js +278 -0
- package/dist/debugProtocol/DebugProtocolClientReplaySession.js.map +1 -0
- package/dist/debugProtocol/PluginInterface.d.ts +38 -0
- package/dist/debugProtocol/PluginInterface.js +86 -0
- package/dist/debugProtocol/PluginInterface.js.map +1 -0
- package/dist/debugProtocol/ProtocolUtil.d.ts +56 -0
- package/dist/debugProtocol/ProtocolUtil.js +164 -0
- package/dist/debugProtocol/ProtocolUtil.js.map +1 -0
- package/dist/debugProtocol/client/DebugProtocolClient.d.ts +310 -0
- package/dist/debugProtocol/client/DebugProtocolClient.js +1079 -0
- package/dist/debugProtocol/client/DebugProtocolClient.js.map +1 -0
- package/dist/debugProtocol/client/DebugProtocolClientPlugin.d.ts +44 -0
- package/dist/debugProtocol/client/DebugProtocolClientPlugin.js +3 -0
- package/dist/debugProtocol/client/DebugProtocolClientPlugin.js.map +1 -0
- package/dist/debugProtocol/events/ProtocolEvent.d.ts +66 -0
- package/dist/debugProtocol/events/ProtocolEvent.js +3 -0
- package/dist/debugProtocol/events/ProtocolEvent.js.map +1 -0
- package/dist/debugProtocol/events/requests/AddBreakpointsRequest.d.ts +30 -0
- package/dist/debugProtocol/events/requests/AddBreakpointsRequest.js +63 -0
- package/dist/debugProtocol/events/requests/AddBreakpointsRequest.js.map +1 -0
- package/dist/debugProtocol/events/requests/AddConditionalBreakpointsRequest.d.ts +48 -0
- package/dist/debugProtocol/events/requests/AddConditionalBreakpointsRequest.js +69 -0
- package/dist/debugProtocol/events/requests/AddConditionalBreakpointsRequest.js.map +1 -0
- package/dist/debugProtocol/events/requests/ContinueRequest.d.ts +20 -0
- package/dist/debugProtocol/events/requests/ContinueRequest.js +40 -0
- package/dist/debugProtocol/events/requests/ContinueRequest.js.map +1 -0
- package/dist/debugProtocol/events/requests/ExecuteRequest.d.ts +26 -0
- package/dist/debugProtocol/events/requests/ExecuteRequest.js +49 -0
- package/dist/debugProtocol/events/requests/ExecuteRequest.js.map +1 -0
- package/dist/debugProtocol/events/requests/ExitChannelRequest.d.ts +20 -0
- package/dist/debugProtocol/events/requests/ExitChannelRequest.js +40 -0
- package/dist/debugProtocol/events/requests/ExitChannelRequest.js.map +1 -0
- package/dist/debugProtocol/events/requests/HandshakeRequest.d.ts +26 -0
- package/dist/debugProtocol/events/requests/HandshakeRequest.js +47 -0
- package/dist/debugProtocol/events/requests/HandshakeRequest.js.map +1 -0
- package/dist/debugProtocol/events/requests/ListBreakpointsRequest.d.ts +20 -0
- package/dist/debugProtocol/events/requests/ListBreakpointsRequest.js +40 -0
- package/dist/debugProtocol/events/requests/ListBreakpointsRequest.js.map +1 -0
- package/dist/debugProtocol/events/requests/RemoveBreakpointsRequest.d.ts +29 -0
- package/dist/debugProtocol/events/requests/RemoveBreakpointsRequest.js +60 -0
- package/dist/debugProtocol/events/requests/RemoveBreakpointsRequest.js.map +1 -0
- package/dist/debugProtocol/events/requests/StackTraceRequest.d.ts +22 -0
- package/dist/debugProtocol/events/requests/StackTraceRequest.js +43 -0
- package/dist/debugProtocol/events/requests/StackTraceRequest.js.map +1 -0
- package/dist/debugProtocol/events/requests/StepRequest.d.ts +25 -0
- package/dist/debugProtocol/events/requests/StepRequest.js +46 -0
- package/dist/debugProtocol/events/requests/StepRequest.js.map +1 -0
- package/dist/debugProtocol/events/requests/StopRequest.d.ts +20 -0
- package/dist/debugProtocol/events/requests/StopRequest.js +39 -0
- package/dist/debugProtocol/events/requests/StopRequest.js.map +1 -0
- package/dist/debugProtocol/events/requests/ThreadsRequest.d.ts +20 -0
- package/dist/debugProtocol/events/requests/ThreadsRequest.js +40 -0
- package/dist/debugProtocol/events/requests/ThreadsRequest.js.map +1 -0
- package/dist/debugProtocol/events/requests/VariablesRequest.d.ts +59 -0
- package/dist/debugProtocol/events/requests/VariablesRequest.js +123 -0
- package/dist/debugProtocol/events/requests/VariablesRequest.js.map +1 -0
- package/dist/debugProtocol/events/responses/AddBreakpointsResponse.js.map +1 -0
- package/dist/debugProtocol/events/responses/AddConditionalBreakpointsResponse.d.ts +3 -0
- package/dist/debugProtocol/events/responses/AddConditionalBreakpointsResponse.js +9 -0
- package/dist/debugProtocol/events/responses/AddConditionalBreakpointsResponse.js.map +1 -0
- package/dist/debugProtocol/events/responses/ExecuteV3Response.d.ts +42 -0
- package/dist/debugProtocol/events/responses/ExecuteV3Response.js +94 -0
- package/dist/debugProtocol/events/responses/ExecuteV3Response.js.map +1 -0
- package/dist/debugProtocol/events/responses/GenericResponse.d.ts +17 -0
- package/dist/debugProtocol/events/responses/GenericResponse.js +40 -0
- package/dist/debugProtocol/events/responses/GenericResponse.js.map +1 -0
- package/dist/debugProtocol/events/responses/GenericV3Response.d.ts +15 -0
- package/dist/debugProtocol/events/responses/GenericV3Response.js +38 -0
- package/dist/debugProtocol/events/responses/GenericV3Response.js.map +1 -0
- package/dist/debugProtocol/events/responses/HandshakeResponse.d.ts +28 -0
- package/dist/debugProtocol/events/responses/HandshakeResponse.js +69 -0
- package/dist/debugProtocol/events/responses/HandshakeResponse.js.map +1 -0
- package/dist/debugProtocol/events/responses/HandshakeV3Response.d.ts +42 -0
- package/dist/debugProtocol/events/responses/HandshakeV3Response.js +98 -0
- package/dist/debugProtocol/events/responses/HandshakeV3Response.js.map +1 -0
- package/dist/debugProtocol/events/responses/ListBreakpointsResponse.d.ts +35 -0
- package/dist/debugProtocol/events/responses/ListBreakpointsResponse.js +68 -0
- package/dist/debugProtocol/events/responses/ListBreakpointsResponse.js.map +1 -0
- package/dist/debugProtocol/events/responses/RemoveBreakpointsResponse.js.map +1 -0
- package/dist/debugProtocol/events/responses/StackTraceResponse.d.ts +24 -0
- package/dist/debugProtocol/events/responses/StackTraceResponse.js +72 -0
- package/dist/debugProtocol/events/responses/StackTraceResponse.js.map +1 -0
- package/dist/debugProtocol/events/responses/StackTraceV3Response.d.ts +37 -0
- package/dist/debugProtocol/events/responses/StackTraceV3Response.js +67 -0
- package/dist/debugProtocol/events/responses/StackTraceV3Response.js.map +1 -0
- package/dist/debugProtocol/events/responses/ThreadsResponse.d.ts +54 -0
- package/dist/debugProtocol/events/responses/ThreadsResponse.js +80 -0
- package/dist/debugProtocol/events/responses/ThreadsResponse.js.map +1 -0
- package/dist/debugProtocol/events/responses/VariablesResponse.d.ts +117 -0
- package/dist/debugProtocol/events/responses/VariablesResponse.js +335 -0
- package/dist/debugProtocol/events/responses/VariablesResponse.js.map +1 -0
- package/dist/debugProtocol/events/updates/AllThreadsStoppedUpdate.d.ts +35 -0
- package/dist/debugProtocol/events/updates/AllThreadsStoppedUpdate.js +58 -0
- package/dist/debugProtocol/events/updates/AllThreadsStoppedUpdate.js.map +1 -0
- package/dist/debugProtocol/events/updates/BreakpointErrorUpdate.d.ts +42 -0
- package/dist/debugProtocol/events/updates/BreakpointErrorUpdate.js +97 -0
- package/dist/debugProtocol/events/updates/BreakpointErrorUpdate.js.map +1 -0
- package/dist/debugProtocol/events/updates/BreakpointVerifiedUpdate.d.ts +41 -0
- package/dist/debugProtocol/events/updates/BreakpointVerifiedUpdate.js +74 -0
- package/dist/debugProtocol/events/updates/BreakpointVerifiedUpdate.js.map +1 -0
- package/dist/debugProtocol/events/updates/CompileErrorUpdate.d.ts +60 -0
- package/dist/debugProtocol/events/updates/CompileErrorUpdate.js +89 -0
- package/dist/debugProtocol/events/updates/CompileErrorUpdate.js.map +1 -0
- package/dist/debugProtocol/events/updates/IOPortOpenedUpdate.d.ts +23 -0
- package/dist/debugProtocol/events/updates/IOPortOpenedUpdate.js +49 -0
- package/dist/debugProtocol/events/updates/IOPortOpenedUpdate.js.map +1 -0
- package/dist/debugProtocol/events/updates/ThreadAttachedUpdate.d.ts +26 -0
- package/dist/debugProtocol/events/updates/ThreadAttachedUpdate.js +50 -0
- package/dist/debugProtocol/events/updates/ThreadAttachedUpdate.js.map +1 -0
- package/dist/debugProtocol/server/DebugProtocolServer.d.ts +110 -0
- package/dist/debugProtocol/server/DebugProtocolServer.js +306 -0
- package/dist/debugProtocol/server/DebugProtocolServer.js.map +1 -0
- package/dist/debugProtocol/server/DebugProtocolServerPlugin.d.ts +41 -0
- package/dist/debugProtocol/server/DebugProtocolServerPlugin.js +3 -0
- package/dist/debugProtocol/server/DebugProtocolServerPlugin.js.map +1 -0
- package/dist/debugSession/BrightScriptDebugSession.d.ts +9 -1
- package/dist/debugSession/BrightScriptDebugSession.js +178 -89
- package/dist/debugSession/BrightScriptDebugSession.js.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/logging.d.ts +1 -0
- package/dist/logging.js +4 -2
- package/dist/logging.js.map +1 -1
- package/dist/managers/ActionQueue.d.ts +7 -1
- package/dist/managers/ActionQueue.js +27 -5
- package/dist/managers/ActionQueue.js.map +1 -1
- package/dist/managers/BreakpointManager.d.ts +82 -27
- package/dist/managers/BreakpointManager.js +168 -63
- package/dist/managers/BreakpointManager.js.map +1 -1
- package/dist/managers/ProjectManager.d.ts +3 -0
- package/dist/managers/ProjectManager.js +42 -36
- package/dist/managers/ProjectManager.js.map +1 -1
- package/dist/util.d.ts +8 -6
- package/dist/util.js +36 -19
- package/dist/util.js.map +1 -1
- package/package.json +14 -5
- package/roku-debug-0.20.7.tgz +0 -0
- package/dist/debugProtocol/Debugger.d.ts +0 -161
- package/dist/debugProtocol/Debugger.js +0 -635
- package/dist/debugProtocol/Debugger.js.map +0 -1
- package/dist/debugProtocol/responses/AddBreakpointsResponse.js.map +0 -1
- package/dist/debugProtocol/responses/BreakpointErrorUpdateResponse.d.ts +0 -32
- package/dist/debugProtocol/responses/BreakpointErrorUpdateResponse.js +0 -67
- package/dist/debugProtocol/responses/BreakpointErrorUpdateResponse.js.map +0 -1
- package/dist/debugProtocol/responses/BreakpointVerifiedUpdateResponse.d.ts +0 -32
- package/dist/debugProtocol/responses/BreakpointVerifiedUpdateResponse.js +0 -56
- package/dist/debugProtocol/responses/BreakpointVerifiedUpdateResponse.js.map +0 -1
- package/dist/debugProtocol/responses/ConnectIOPortResponse.d.ts +0 -10
- package/dist/debugProtocol/responses/ConnectIOPortResponse.js +0 -39
- package/dist/debugProtocol/responses/ConnectIOPortResponse.js.map +0 -1
- package/dist/debugProtocol/responses/ExecuteResponseV3.d.ts +0 -23
- package/dist/debugProtocol/responses/ExecuteResponseV3.js +0 -54
- package/dist/debugProtocol/responses/ExecuteResponseV3.js.map +0 -1
- package/dist/debugProtocol/responses/HandshakeResponse.d.ts +0 -12
- package/dist/debugProtocol/responses/HandshakeResponse.js +0 -39
- package/dist/debugProtocol/responses/HandshakeResponse.js.map +0 -1
- package/dist/debugProtocol/responses/HandshakeResponseV3.d.ts +0 -14
- package/dist/debugProtocol/responses/HandshakeResponseV3.js +0 -47
- package/dist/debugProtocol/responses/HandshakeResponseV3.js.map +0 -1
- package/dist/debugProtocol/responses/ListBreakpointsResponse.d.ts +0 -25
- package/dist/debugProtocol/responses/ListBreakpointsResponse.js +0 -65
- package/dist/debugProtocol/responses/ListBreakpointsResponse.js.map +0 -1
- package/dist/debugProtocol/responses/ProtocolEvent.d.ts +0 -11
- package/dist/debugProtocol/responses/ProtocolEvent.js +0 -38
- package/dist/debugProtocol/responses/ProtocolEvent.js.map +0 -1
- package/dist/debugProtocol/responses/ProtocolEventV3.d.ts +0 -11
- package/dist/debugProtocol/responses/ProtocolEventV3.js +0 -42
- package/dist/debugProtocol/responses/ProtocolEventV3.js.map +0 -1
- package/dist/debugProtocol/responses/RemoveBreakpointsResponse.js.map +0 -1
- package/dist/debugProtocol/responses/StackTraceResponse.d.ts +0 -18
- package/dist/debugProtocol/responses/StackTraceResponse.js +0 -58
- package/dist/debugProtocol/responses/StackTraceResponse.js.map +0 -1
- package/dist/debugProtocol/responses/StackTraceResponseV3.d.ts +0 -18
- package/dist/debugProtocol/responses/StackTraceResponseV3.js +0 -58
- package/dist/debugProtocol/responses/StackTraceResponseV3.js.map +0 -1
- package/dist/debugProtocol/responses/ThreadsResponse.d.ts +0 -22
- package/dist/debugProtocol/responses/ThreadsResponse.js +0 -63
- package/dist/debugProtocol/responses/ThreadsResponse.js.map +0 -1
- package/dist/debugProtocol/responses/UndefinedResponse.d.ts +0 -10
- package/dist/debugProtocol/responses/UndefinedResponse.js +0 -39
- package/dist/debugProtocol/responses/UndefinedResponse.js.map +0 -1
- package/dist/debugProtocol/responses/UpdateThreadsResponse.d.ts +0 -25
- package/dist/debugProtocol/responses/UpdateThreadsResponse.js +0 -91
- package/dist/debugProtocol/responses/UpdateThreadsResponse.js.map +0 -1
- package/dist/debugProtocol/responses/VariableResponse.d.ts +0 -27
- package/dist/debugProtocol/responses/VariableResponse.js +0 -141
- package/dist/debugProtocol/responses/VariableResponse.js.map +0 -1
- package/dist/debugProtocol/responses/index.d.ts +0 -11
- package/dist/debugProtocol/responses/index.js +0 -24
- package/dist/debugProtocol/responses/index.js.map +0 -1
- package/roku-debug-0.20.6.tgz +0 -0
- /package/dist/debugProtocol/{responses → events/responses}/AddBreakpointsResponse.d.ts +0 -0
- /package/dist/debugProtocol/{responses → events/responses}/AddBreakpointsResponse.js +0 -0
- /package/dist/debugProtocol/{responses → events/responses}/RemoveBreakpointsResponse.d.ts +0 -0
- /package/dist/debugProtocol/{responses → events/responses}/RemoveBreakpointsResponse.js +0 -0
|
@@ -0,0 +1,1079 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isProtocolUpdate = exports.isProtocolResponse = exports.isProtocolRequest = exports.DebugProtocolClient = void 0;
|
|
4
|
+
const Net = require("net");
|
|
5
|
+
const debounce = require("debounce");
|
|
6
|
+
const EventEmitter = require("eventemitter3");
|
|
7
|
+
const semver = require("semver");
|
|
8
|
+
const Constants_1 = require("../Constants");
|
|
9
|
+
const logging_1 = require("../../logging");
|
|
10
|
+
const ExecuteV3Response_1 = require("../events/responses/ExecuteV3Response");
|
|
11
|
+
const ListBreakpointsResponse_1 = require("../events/responses/ListBreakpointsResponse");
|
|
12
|
+
const AddBreakpointsResponse_1 = require("../events/responses/AddBreakpointsResponse");
|
|
13
|
+
const RemoveBreakpointsResponse_1 = require("../events/responses/RemoveBreakpointsResponse");
|
|
14
|
+
const util_1 = require("../../util");
|
|
15
|
+
const BreakpointErrorUpdate_1 = require("../events/updates/BreakpointErrorUpdate");
|
|
16
|
+
const ContinueRequest_1 = require("../events/requests/ContinueRequest");
|
|
17
|
+
const StopRequest_1 = require("../events/requests/StopRequest");
|
|
18
|
+
const ExitChannelRequest_1 = require("../events/requests/ExitChannelRequest");
|
|
19
|
+
const StepRequest_1 = require("../events/requests/StepRequest");
|
|
20
|
+
const RemoveBreakpointsRequest_1 = require("../events/requests/RemoveBreakpointsRequest");
|
|
21
|
+
const ListBreakpointsRequest_1 = require("../events/requests/ListBreakpointsRequest");
|
|
22
|
+
const VariablesRequest_1 = require("../events/requests/VariablesRequest");
|
|
23
|
+
const StackTraceRequest_1 = require("../events/requests/StackTraceRequest");
|
|
24
|
+
const ThreadsRequest_1 = require("../events/requests/ThreadsRequest");
|
|
25
|
+
const ExecuteRequest_1 = require("../events/requests/ExecuteRequest");
|
|
26
|
+
const AddBreakpointsRequest_1 = require("../events/requests/AddBreakpointsRequest");
|
|
27
|
+
const AddConditionalBreakpointsRequest_1 = require("../events/requests/AddConditionalBreakpointsRequest");
|
|
28
|
+
const HandshakeResponse_1 = require("../events/responses/HandshakeResponse");
|
|
29
|
+
const HandshakeV3Response_1 = require("../events/responses/HandshakeV3Response");
|
|
30
|
+
const HandshakeRequest_1 = require("../events/requests/HandshakeRequest");
|
|
31
|
+
const GenericV3Response_1 = require("../events/responses/GenericV3Response");
|
|
32
|
+
const AllThreadsStoppedUpdate_1 = require("../events/updates/AllThreadsStoppedUpdate");
|
|
33
|
+
const CompileErrorUpdate_1 = require("../events/updates/CompileErrorUpdate");
|
|
34
|
+
const GenericResponse_1 = require("../events/responses/GenericResponse");
|
|
35
|
+
const ThreadsResponse_1 = require("../events/responses/ThreadsResponse");
|
|
36
|
+
const VariablesResponse_1 = require("../events/responses/VariablesResponse");
|
|
37
|
+
const IOPortOpenedUpdate_1 = require("../events/updates/IOPortOpenedUpdate");
|
|
38
|
+
const ThreadAttachedUpdate_1 = require("../events/updates/ThreadAttachedUpdate");
|
|
39
|
+
const StackTraceV3Response_1 = require("../events/responses/StackTraceV3Response");
|
|
40
|
+
const ActionQueue_1 = require("../../managers/ActionQueue");
|
|
41
|
+
const PluginInterface_1 = require("../PluginInterface");
|
|
42
|
+
const BreakpointVerifiedUpdate_1 = require("../events/updates/BreakpointVerifiedUpdate");
|
|
43
|
+
class DebugProtocolClient {
|
|
44
|
+
constructor(options) {
|
|
45
|
+
this.logger = logging_1.logger.createLogger(`[client]`);
|
|
46
|
+
// The highest tested version of the protocol we support.
|
|
47
|
+
this.supportedVersionRange = '<=3.0.0';
|
|
48
|
+
this.isHandshakeComplete = false;
|
|
49
|
+
this.connectedToIoPort = false;
|
|
50
|
+
/**
|
|
51
|
+
* Debug protocol version 3.0.0 introduced a packet_length to all responses. Prior to that, most responses had no packet length at all.
|
|
52
|
+
* This field indicates whether we should be looking for packet_length or not in the responses we get from the device
|
|
53
|
+
*/
|
|
54
|
+
this.watchPacketLength = false;
|
|
55
|
+
/**
|
|
56
|
+
* A collection of plugins that can interact with the client at lifecycle points
|
|
57
|
+
*/
|
|
58
|
+
this.plugins = new PluginInterface_1.default();
|
|
59
|
+
this.emitter = new EventEmitter();
|
|
60
|
+
/**
|
|
61
|
+
* Promise that is resolved when the control socket is closed
|
|
62
|
+
*/
|
|
63
|
+
this.controlSocketClosed = (0, util_1.defer)();
|
|
64
|
+
/**
|
|
65
|
+
* Resolves when the ioSocket has closed
|
|
66
|
+
*/
|
|
67
|
+
this.ioSocketClosed = (0, util_1.defer)();
|
|
68
|
+
/**
|
|
69
|
+
* The buffer where all unhandled data will be stored until successfully consumed
|
|
70
|
+
*/
|
|
71
|
+
this.buffer = Buffer.alloc(0);
|
|
72
|
+
/**
|
|
73
|
+
* Is the debugger currently stopped at a line of code in the program
|
|
74
|
+
*/
|
|
75
|
+
this.isStopped = false;
|
|
76
|
+
this.requestIdSequence = 1;
|
|
77
|
+
this.activeRequests = new Map();
|
|
78
|
+
/**
|
|
79
|
+
* A queue for processing the incoming buffer, every transmission at a time
|
|
80
|
+
*/
|
|
81
|
+
this.bufferQueue = new ActionQueue_1.ActionQueue();
|
|
82
|
+
/**
|
|
83
|
+
* A counter to help give a unique id to each update (mostly just for logging purposes)
|
|
84
|
+
*/
|
|
85
|
+
this.updateSequence = 1;
|
|
86
|
+
this.handleUpdateQueue = new ActionQueue_1.ActionQueue();
|
|
87
|
+
this.isDestroyingControlSocket = false;
|
|
88
|
+
this.isDestroyingIOSocket = false;
|
|
89
|
+
this.options = {
|
|
90
|
+
controlPort: 8081,
|
|
91
|
+
host: undefined,
|
|
92
|
+
//override the defaults with the options from parameters
|
|
93
|
+
...options !== null && options !== void 0 ? options : {}
|
|
94
|
+
};
|
|
95
|
+
//add the internal plugin last, so it's the final plugin to handle the events
|
|
96
|
+
this.addCorePlugin();
|
|
97
|
+
}
|
|
98
|
+
addCorePlugin() {
|
|
99
|
+
this.plugins.add({
|
|
100
|
+
onUpdate: (event) => {
|
|
101
|
+
return this.handleUpdate(event.update);
|
|
102
|
+
}
|
|
103
|
+
}, 999);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Prior to protocol v3.1.0, the Roku device would regularly set the wrong thread as "active",
|
|
107
|
+
* so this flag lets us know if we should use our better-than-nothing workaround
|
|
108
|
+
*/
|
|
109
|
+
get enableThreadHoppingWorkaround() {
|
|
110
|
+
return semver.satisfies(this.protocolVersion, '<3.1.0');
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Starting in protocol v3.1.0, component libary breakpoints must be added in the format `lib:/<library_name>/<filepath>`, but prior they didn't require this.
|
|
114
|
+
* So this flag tells us which format to support
|
|
115
|
+
*/
|
|
116
|
+
get enableComponentLibrarySpecificBreakpoints() {
|
|
117
|
+
return semver.satisfies(this.protocolVersion, '>=3.1.0');
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Starting in protocol v3.1.0, breakpoints can support conditional expressions. This flag indicates whether the current sessuion supports that functionality.
|
|
121
|
+
*/
|
|
122
|
+
get supportsConditionalBreakpoints() {
|
|
123
|
+
return semver.satisfies(this.protocolVersion, '>=3.1.0');
|
|
124
|
+
}
|
|
125
|
+
get supportsBreakpointRegistrationWhileRunning() {
|
|
126
|
+
return semver.satisfies(this.protocolVersion, '>=3.2.0');
|
|
127
|
+
}
|
|
128
|
+
get supportsBreakpointVerification() {
|
|
129
|
+
return semver.satisfies(this.protocolVersion, '>=3.2.0');
|
|
130
|
+
}
|
|
131
|
+
once(eventName) {
|
|
132
|
+
return new Promise((resolve) => {
|
|
133
|
+
const disconnect = this.on(eventName, (...args) => {
|
|
134
|
+
disconnect();
|
|
135
|
+
resolve(...args);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
// public on(eventname: 'rendezvous', handler: (output: RendezvousHistory) => void);
|
|
140
|
+
// public on(eventName: 'runtime-error', handler: (error: BrightScriptRuntimeError) => void);
|
|
141
|
+
on(eventName, handler) {
|
|
142
|
+
this.emitter.on(eventName, handler);
|
|
143
|
+
return () => {
|
|
144
|
+
this.emitter.removeListener(eventName, handler);
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
emit(eventName, data) {
|
|
148
|
+
//emit these events on next tick, otherwise they will be processed immediately which could cause issues
|
|
149
|
+
setTimeout(() => {
|
|
150
|
+
//in rare cases, this event is fired after the debugger has closed, so make sure the event emitter still exists
|
|
151
|
+
this.emitter.emit(eventName, data);
|
|
152
|
+
}, 0);
|
|
153
|
+
}
|
|
154
|
+
async establishControlConnection() {
|
|
155
|
+
this.pendingControlConnectionSockets = new Set();
|
|
156
|
+
const connection = await new Promise((resolve) => {
|
|
157
|
+
var _a;
|
|
158
|
+
this.cancelControlConnectInterval = util_1.util.setInterval((cancelInterval) => {
|
|
159
|
+
const socket = new Net.Socket({
|
|
160
|
+
allowHalfOpen: false
|
|
161
|
+
});
|
|
162
|
+
this.pendingControlConnectionSockets.add(socket);
|
|
163
|
+
socket.on('error', (error) => {
|
|
164
|
+
console.debug(Date.now(), 'Encountered an error connecting to the debug protocol socket. Ignoring and will try again soon', error);
|
|
165
|
+
});
|
|
166
|
+
socket.connect({ port: this.options.controlPort, host: this.options.host }, () => {
|
|
167
|
+
cancelInterval();
|
|
168
|
+
this.logger.debug(`Connected to debug protocol control port. Socket ${[...this.pendingControlConnectionSockets].indexOf(socket)} of ${this.pendingControlConnectionSockets.size} was the winner`);
|
|
169
|
+
//clean up all remaining pending sockets
|
|
170
|
+
for (const pendingSocket of this.pendingControlConnectionSockets) {
|
|
171
|
+
pendingSocket.removeAllListeners();
|
|
172
|
+
//cleanup and destroy all other sockets
|
|
173
|
+
if (pendingSocket !== socket) {
|
|
174
|
+
pendingSocket.end();
|
|
175
|
+
pendingSocket === null || pendingSocket === void 0 ? void 0 : pendingSocket.destroy();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
this.pendingControlConnectionSockets.clear();
|
|
179
|
+
resolve(socket);
|
|
180
|
+
});
|
|
181
|
+
}, (_a = this.options.controlConnectInterval) !== null && _a !== void 0 ? _a : 250);
|
|
182
|
+
});
|
|
183
|
+
await this.plugins.emit('onServerConnected', {
|
|
184
|
+
client: this,
|
|
185
|
+
server: connection
|
|
186
|
+
});
|
|
187
|
+
return connection;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Connect to the debug server.
|
|
191
|
+
* @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
|
|
192
|
+
*/
|
|
193
|
+
async connect(sendHandshake = true) {
|
|
194
|
+
this.logger.log('connect', this.options);
|
|
195
|
+
// If there is no error, the server has accepted the request and created a new dedicated control socket
|
|
196
|
+
this.controlSocket = await this.establishControlConnection();
|
|
197
|
+
this.controlSocket.on('data', (data) => {
|
|
198
|
+
this.writeToBufferLog('server-to-client', data);
|
|
199
|
+
this.emit('data', data);
|
|
200
|
+
//queue up processing the new data, chunk by chunk
|
|
201
|
+
void this.bufferQueue.run(async () => {
|
|
202
|
+
this.buffer = Buffer.concat([this.buffer, data]);
|
|
203
|
+
while (this.buffer.length > 0 && await this.process()) {
|
|
204
|
+
//the loop condition is the actual work
|
|
205
|
+
}
|
|
206
|
+
return true;
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
this.controlSocket.on('close', () => {
|
|
210
|
+
this.logger.log('Control socket closed');
|
|
211
|
+
this.controlSocketClosed.tryResolve();
|
|
212
|
+
void this.shutdown('app-exit');
|
|
213
|
+
});
|
|
214
|
+
// Don't forget to catch error, for your own sake.
|
|
215
|
+
this.controlSocket.once('error', (error) => {
|
|
216
|
+
var _a, _b;
|
|
217
|
+
//the Roku closed the connection for some unknown reason...
|
|
218
|
+
this.logger.error(`error on control port`, error);
|
|
219
|
+
(_b = (_a = this.controlSocket) === null || _a === void 0 ? void 0 : _a.destroy) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
220
|
+
void this.shutdown('close');
|
|
221
|
+
});
|
|
222
|
+
if (sendHandshake) {
|
|
223
|
+
await this.sendHandshake();
|
|
224
|
+
}
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Send the initial handshake request, and wait for the handshake response
|
|
229
|
+
*/
|
|
230
|
+
async sendHandshake() {
|
|
231
|
+
return this.processHandshakeRequest(HandshakeRequest_1.HandshakeRequest.fromJson({
|
|
232
|
+
magic: DebugProtocolClient.DEBUGGER_MAGIC
|
|
233
|
+
}));
|
|
234
|
+
}
|
|
235
|
+
async processHandshakeRequest(request) {
|
|
236
|
+
//send the magic, which triggers the debug session
|
|
237
|
+
this.logger.log('Sending magic to server');
|
|
238
|
+
//send the handshake request, and wait for the handshake response from the device
|
|
239
|
+
return this.sendRequest(request);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Write a specific buffer log entry to the logger, which, when file logging is enabled
|
|
243
|
+
* can be extracted and processed through the DebugProtocolClientReplaySession
|
|
244
|
+
*/
|
|
245
|
+
writeToBufferLog(type, buffer) {
|
|
246
|
+
let obj = {
|
|
247
|
+
type: type,
|
|
248
|
+
timestamp: new Date().toISOString(),
|
|
249
|
+
buffer: buffer.toJSON()
|
|
250
|
+
};
|
|
251
|
+
if (type === 'io') {
|
|
252
|
+
obj.text = buffer.toString();
|
|
253
|
+
}
|
|
254
|
+
this.logger.log('[[bufferLog]]:', JSON.stringify(obj));
|
|
255
|
+
}
|
|
256
|
+
continue() {
|
|
257
|
+
return this.processContinueRequest(ContinueRequest_1.ContinueRequest.fromJson({
|
|
258
|
+
requestId: this.requestIdSequence++
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
async processContinueRequest(request) {
|
|
262
|
+
if (this.isStopped) {
|
|
263
|
+
this.isStopped = false;
|
|
264
|
+
return this.sendRequest(request);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
pause() {
|
|
268
|
+
return this.processStopRequest(StopRequest_1.StopRequest.fromJson({
|
|
269
|
+
requestId: this.requestIdSequence++
|
|
270
|
+
}));
|
|
271
|
+
}
|
|
272
|
+
async processStopRequest(request) {
|
|
273
|
+
if (this.isStopped === false) {
|
|
274
|
+
return this.sendRequest(request);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Send the "exit channel" command, which will tell the debug session to immediately quit
|
|
279
|
+
*/
|
|
280
|
+
async exitChannel() {
|
|
281
|
+
return this.sendRequest(ExitChannelRequest_1.ExitChannelRequest.fromJson({
|
|
282
|
+
requestId: this.requestIdSequence++
|
|
283
|
+
}));
|
|
284
|
+
}
|
|
285
|
+
async stepIn(threadIndex = this.primaryThread) {
|
|
286
|
+
return this.step(Constants_1.StepType.Line, threadIndex);
|
|
287
|
+
}
|
|
288
|
+
async stepOver(threadIndex = this.primaryThread) {
|
|
289
|
+
return this.step(Constants_1.StepType.Over, threadIndex);
|
|
290
|
+
}
|
|
291
|
+
async stepOut(threadIndex = this.primaryThread) {
|
|
292
|
+
return this.step(Constants_1.StepType.Out, threadIndex);
|
|
293
|
+
}
|
|
294
|
+
async step(stepType, threadIndex) {
|
|
295
|
+
return this.processStepRequest(StepRequest_1.StepRequest.fromJson({
|
|
296
|
+
requestId: this.requestIdSequence++,
|
|
297
|
+
stepType: stepType,
|
|
298
|
+
threadIndex: threadIndex
|
|
299
|
+
}));
|
|
300
|
+
}
|
|
301
|
+
async processStepRequest(request) {
|
|
302
|
+
if (this.isStopped) {
|
|
303
|
+
this.isStopped = false;
|
|
304
|
+
let stepResult = await this.sendRequest(request);
|
|
305
|
+
if (stepResult.data.errorCode === Constants_1.ErrorCode.OK) {
|
|
306
|
+
this.isStopped = true;
|
|
307
|
+
//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?
|
|
308
|
+
this.emit('suspend', stepResult);
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
// there is a CANT_CONTINUE error code but we can likely treat all errors like a CANT_CONTINUE
|
|
312
|
+
this.emit('cannot-continue');
|
|
313
|
+
}
|
|
314
|
+
return stepResult;
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
this.logger.log('[processStepRequest] skipped because debugger is not paused');
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
async threads() {
|
|
321
|
+
return this.processThreadsRequest(ThreadsRequest_1.ThreadsRequest.fromJson({
|
|
322
|
+
requestId: this.requestIdSequence++
|
|
323
|
+
}));
|
|
324
|
+
}
|
|
325
|
+
async processThreadsRequest(request) {
|
|
326
|
+
if (this.isStopped) {
|
|
327
|
+
let result = await this.sendRequest(request);
|
|
328
|
+
if (result.data.errorCode === Constants_1.ErrorCode.OK) {
|
|
329
|
+
//older versions of the debug protocol had issues with maintaining the active thread, so our workaround is to keep track of it elsewhere
|
|
330
|
+
if (this.enableThreadHoppingWorkaround) {
|
|
331
|
+
//ignore the `isPrimary` flag on threads
|
|
332
|
+
this.logger.debug(`Ignoring the 'isPrimary' flag from threads because protocol version 3.0.0 and lower has a bug`);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
//trust the debug protocol's `isPrimary` flag on threads
|
|
336
|
+
for (let i = 0; i < result.data.threads.length; i++) {
|
|
337
|
+
let thread = result.data.threads[i];
|
|
338
|
+
if (thread.isPrimary) {
|
|
339
|
+
this.primaryThread = i;
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return result;
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
this.logger.log('[processThreadsRequest] skipped because not stopped');
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Get the stackTrace from the device IF currently stopped
|
|
353
|
+
*/
|
|
354
|
+
async getStackTrace(threadIndex = this.primaryThread) {
|
|
355
|
+
return this.processStackTraceRequest(StackTraceRequest_1.StackTraceRequest.fromJson({
|
|
356
|
+
requestId: this.requestIdSequence++,
|
|
357
|
+
threadIndex: threadIndex
|
|
358
|
+
}));
|
|
359
|
+
}
|
|
360
|
+
async processStackTraceRequest(request) {
|
|
361
|
+
var _a, _b;
|
|
362
|
+
if (!this.isStopped) {
|
|
363
|
+
this.logger.log('[getStackTrace] skipped because debugger is not paused');
|
|
364
|
+
}
|
|
365
|
+
else if (((_a = request === null || request === void 0 ? void 0 : request.data) === null || _a === void 0 ? void 0 : _a.threadIndex) > -1) {
|
|
366
|
+
return this.sendRequest(request);
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
this.logger.log(`[getStackTrace] skipped because ${(_b = request === null || request === void 0 ? void 0 : request.data) === null || _b === void 0 ? void 0 : _b.threadIndex} is not valid threadIndex`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* @param variablePathEntries One or more path entries to the variable to be inspected. E.g., m.top.myObj["someKey"] can be accessed with ["m","top","myobj","\"someKey\""].
|
|
374
|
+
*
|
|
375
|
+
* If no path is specified, the variables accessible from the specified stack frame are returned.
|
|
376
|
+
*
|
|
377
|
+
* Starting in protocol v3.1.0, The keys for indexed gets (i.e. obj["key"]) should be wrapped in quotes so they can be handled in a case-sensitive fashion (if applicable on device).
|
|
378
|
+
* All non-quoted keys (i.e. strings without leading and trailing quotes inside them) will be treated as case-insensitive).
|
|
379
|
+
* @param getChildKeys If set, VARIABLES response include the child keys for container types like lists and associative arrays
|
|
380
|
+
* @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
|
|
381
|
+
* @param threadIndex the index (or perhaps ID?) of the thread to get variables for
|
|
382
|
+
*/
|
|
383
|
+
async getVariables(variablePathEntries = [], stackFrameIndex = this.stackFrameIndex, threadIndex = this.primaryThread) {
|
|
384
|
+
const response = await this.processVariablesRequest(VariablesRequest_1.VariablesRequest.fromJson({
|
|
385
|
+
requestId: this.requestIdSequence++,
|
|
386
|
+
threadIndex: threadIndex,
|
|
387
|
+
stackFrameIndex: stackFrameIndex,
|
|
388
|
+
getChildKeys: true,
|
|
389
|
+
variablePathEntries: variablePathEntries.map(x => ({
|
|
390
|
+
//remove leading and trailing quotes
|
|
391
|
+
name: x.replace(/^"/, '').replace(/"$/, ''),
|
|
392
|
+
forceCaseInsensitive: !x.startsWith('"') && !x.endsWith('"')
|
|
393
|
+
})),
|
|
394
|
+
//starting in protocol v3.1.0, it supports marking certain path items as case-insensitive (i.e. parts of DottedGet expressions)
|
|
395
|
+
enableForceCaseInsensitivity: semver.satisfies(this.protocolVersion, '>=3.1.0') && variablePathEntries.length > 0
|
|
396
|
+
}));
|
|
397
|
+
//if there was an issue, build a "fake" variables response for several known situationsm or throw nicer errors
|
|
398
|
+
if (util_1.util.hasNonNullishProperty(response === null || response === void 0 ? void 0 : response.data.errorData)) {
|
|
399
|
+
let variable = {
|
|
400
|
+
value: null,
|
|
401
|
+
isContainer: false,
|
|
402
|
+
isConst: false,
|
|
403
|
+
refCount: 0,
|
|
404
|
+
childCount: 0
|
|
405
|
+
};
|
|
406
|
+
const simulatedResponse = VariablesResponse_1.VariablesResponse.fromJson({
|
|
407
|
+
...response.data,
|
|
408
|
+
variables: [variable]
|
|
409
|
+
});
|
|
410
|
+
let parentVarType;
|
|
411
|
+
let parentVarTypeText;
|
|
412
|
+
const loadParentVarInfo = async (index) => {
|
|
413
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
414
|
+
//fetch the variable one level back from the bad one to get its type
|
|
415
|
+
const parentVar = await this.getVariables(variablePathEntries.slice(0, index), stackFrameIndex, threadIndex);
|
|
416
|
+
parentVarType = (_c = (_b = (_a = parentVar === null || parentVar === void 0 ? void 0 : parentVar.data) === null || _a === void 0 ? void 0 : _a.variables) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.type;
|
|
417
|
+
parentVarTypeText = parentVarType;
|
|
418
|
+
//convert `roSGNode; Node` to `roSGNode (Node)`
|
|
419
|
+
if (parentVarType === VariablesResponse_1.VariableType.SubtypedObject) {
|
|
420
|
+
const chunks = (_g = (_f = (_e = (_d = parentVar === null || parentVar === void 0 ? void 0 : parentVar.data) === null || _d === void 0 ? void 0 : _d.variables) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.value) === null || _g === void 0 ? void 0 : _g.toString().split(';').map(x => x.trim());
|
|
421
|
+
parentVarTypeText = `${chunks[0]} (${chunks[1]})`;
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
if (!util_1.util.isNullish(response.data.errorData.missingKeyIndex)) {
|
|
425
|
+
const { missingKeyIndex } = response.data.errorData;
|
|
426
|
+
//leftmost var is uninitialized, and we tried to read it
|
|
427
|
+
//ex: variablePathEntries = [`notThere`]
|
|
428
|
+
if (variablePathEntries.length === 1 && missingKeyIndex === 0) {
|
|
429
|
+
variable.name = variablePathEntries[0];
|
|
430
|
+
variable.type = VariablesResponse_1.VariableType.Uninitialized;
|
|
431
|
+
return simulatedResponse;
|
|
432
|
+
}
|
|
433
|
+
//leftmost var was uninitialized, and tried to read a prop on it
|
|
434
|
+
//ex: variablePathEntries = ["notThere", "definitelyNotThere"]
|
|
435
|
+
if (missingKeyIndex === 0 && variablePathEntries.length > 1) {
|
|
436
|
+
throw new Error(`Cannot read '${variablePathEntries[missingKeyIndex + 1]}' on type 'Uninitialized'`);
|
|
437
|
+
}
|
|
438
|
+
if (variablePathEntries.length > 1 && missingKeyIndex > 0) {
|
|
439
|
+
await loadParentVarInfo(missingKeyIndex);
|
|
440
|
+
// prop at the end of Node or AA doesn't exist. Treat like `invalid`.
|
|
441
|
+
// ex: variablePathEntries = ['there', 'notThere']
|
|
442
|
+
if (missingKeyIndex === variablePathEntries.length - 1 &&
|
|
443
|
+
[VariablesResponse_1.VariableType.AssociativeArray, VariablesResponse_1.VariableType.SubtypedObject].includes(parentVarType)) {
|
|
444
|
+
variable.name = variablePathEntries[variablePathEntries.length - 1];
|
|
445
|
+
variable.type = VariablesResponse_1.VariableType.Invalid;
|
|
446
|
+
variable.value = 'Invalid (not defined)';
|
|
447
|
+
return simulatedResponse;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
//prop in the middle is missing, tried reading a prop on it
|
|
451
|
+
// ex: variablePathEntries = ["there", "notThere", "definitelyNotThere"]
|
|
452
|
+
throw new Error(`Cannot read '${variablePathEntries[missingKeyIndex]}'${parentVarType ? ` on type '${parentVarTypeText}'` : ''}`);
|
|
453
|
+
}
|
|
454
|
+
//this flow is when the item at the index exists, but is set to literally `invalid` or is an unknown value
|
|
455
|
+
if (!util_1.util.isNullish(response.data.errorData.invalidPathIndex)) {
|
|
456
|
+
const { invalidPathIndex } = response.data.errorData;
|
|
457
|
+
//leftmost var is literal `invalid`, tried to read it
|
|
458
|
+
if (variablePathEntries.length === 1 && invalidPathIndex === 0) {
|
|
459
|
+
variable.name = variablePathEntries[variablePathEntries.length - 1];
|
|
460
|
+
variable.type = VariablesResponse_1.VariableType.Invalid;
|
|
461
|
+
return simulatedResponse;
|
|
462
|
+
}
|
|
463
|
+
if (variablePathEntries.length > 1 &&
|
|
464
|
+
invalidPathIndex > 0 &&
|
|
465
|
+
//only do this logic if the invalid item is not the last item
|
|
466
|
+
invalidPathIndex < variablePathEntries.length - 1) {
|
|
467
|
+
await loadParentVarInfo(invalidPathIndex + 1);
|
|
468
|
+
//leftmost var is set to literal `invalid`, tried to read prop
|
|
469
|
+
if (invalidPathIndex === 0 && variablePathEntries.length > 1) {
|
|
470
|
+
throw new Error(`Cannot read '${variablePathEntries[invalidPathIndex + 1]}' on type '${parentVarTypeText}'`);
|
|
471
|
+
}
|
|
472
|
+
// prop at the end doesn't exist. Treat like `invalid`.
|
|
473
|
+
// ex: variablePathEntries = ['there', 'notThere']
|
|
474
|
+
if (invalidPathIndex === variablePathEntries.length - 1 &&
|
|
475
|
+
[VariablesResponse_1.VariableType.AssociativeArray, VariablesResponse_1.VariableType.SubtypedObject].includes(parentVarType)) {
|
|
476
|
+
variable.name = variablePathEntries[variablePathEntries.length - 1];
|
|
477
|
+
variable.type = VariablesResponse_1.VariableType.Invalid;
|
|
478
|
+
variable.value = 'Invalid (not defined)';
|
|
479
|
+
return simulatedResponse;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
console.log('Bronley');
|
|
483
|
+
//prop in the middle is missing, tried reading a prop on it
|
|
484
|
+
// ex: variablePathEntries = ["there", "thereButSetToInvalid", "definitelyNotThere"]
|
|
485
|
+
throw new Error(`Cannot read '${variablePathEntries[invalidPathIndex + 1]}'${parentVarType ? ` on type '${parentVarTypeText}'` : ''}`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return response;
|
|
489
|
+
}
|
|
490
|
+
async processVariablesRequest(request) {
|
|
491
|
+
if (this.isStopped && request.data.threadIndex > -1) {
|
|
492
|
+
return this.sendRequest(request);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
async executeCommand(sourceCode, stackFrameIndex = this.stackFrameIndex, threadIndex = this.primaryThread) {
|
|
496
|
+
return this.processExecuteRequest(ExecuteRequest_1.ExecuteRequest.fromJson({
|
|
497
|
+
requestId: this.requestIdSequence++,
|
|
498
|
+
threadIndex: threadIndex,
|
|
499
|
+
stackFrameIndex: stackFrameIndex,
|
|
500
|
+
sourceCode: sourceCode
|
|
501
|
+
}));
|
|
502
|
+
}
|
|
503
|
+
async processExecuteRequest(request) {
|
|
504
|
+
if (this.isStopped && request.data.threadIndex > -1) {
|
|
505
|
+
return this.sendRequest(request);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
async addBreakpoints(breakpoints) {
|
|
509
|
+
const { enableComponentLibrarySpecificBreakpoints } = this;
|
|
510
|
+
if ((breakpoints === null || breakpoints === void 0 ? void 0 : breakpoints.length) > 0) {
|
|
511
|
+
const json = {
|
|
512
|
+
requestId: this.requestIdSequence++,
|
|
513
|
+
breakpoints: breakpoints.map(x => {
|
|
514
|
+
let breakpoint = {
|
|
515
|
+
...x,
|
|
516
|
+
ignoreCount: x.hitCount
|
|
517
|
+
};
|
|
518
|
+
if (enableComponentLibrarySpecificBreakpoints && breakpoint.componentLibraryName) {
|
|
519
|
+
breakpoint.filePath = breakpoint.filePath.replace(/^pkg:\//i, `lib:/${breakpoint.componentLibraryName}/`);
|
|
520
|
+
}
|
|
521
|
+
return breakpoint;
|
|
522
|
+
})
|
|
523
|
+
};
|
|
524
|
+
const useConditionalBreakpoints = (
|
|
525
|
+
//does this protocol version support conditional breakpoints?
|
|
526
|
+
this.supportsConditionalBreakpoints &&
|
|
527
|
+
//is there at least one conditional breakpoint present?
|
|
528
|
+
!!breakpoints.find(x => { var _a; return !!((_a = x === null || x === void 0 ? void 0 : x.conditionalExpression) === null || _a === void 0 ? void 0 : _a.trim()); }));
|
|
529
|
+
let response;
|
|
530
|
+
if (useConditionalBreakpoints) {
|
|
531
|
+
response = await this.sendRequest(AddConditionalBreakpointsRequest_1.AddConditionalBreakpointsRequest.fromJson(json));
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
response = await this.sendRequest(AddBreakpointsRequest_1.AddBreakpointsRequest.fromJson(json));
|
|
535
|
+
}
|
|
536
|
+
//if the device does not support breakpoint verification, then auto-mark all of these as verified
|
|
537
|
+
if (!this.supportsBreakpointVerification) {
|
|
538
|
+
this.emit('breakpoints-verified', {
|
|
539
|
+
breakpoints: response.data.breakpoints
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
return response;
|
|
543
|
+
}
|
|
544
|
+
return AddBreakpointsResponse_1.AddBreakpointsResponse.fromBuffer(null);
|
|
545
|
+
}
|
|
546
|
+
async listBreakpoints() {
|
|
547
|
+
return this.processRequest(ListBreakpointsRequest_1.ListBreakpointsRequest.fromJson({
|
|
548
|
+
requestId: this.requestIdSequence++
|
|
549
|
+
}));
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Remove breakpoints having the specified IDs
|
|
553
|
+
*/
|
|
554
|
+
async removeBreakpoints(breakpointIds) {
|
|
555
|
+
return this.processRemoveBreakpointsRequest(RemoveBreakpointsRequest_1.RemoveBreakpointsRequest.fromJson({
|
|
556
|
+
requestId: this.requestIdSequence++,
|
|
557
|
+
breakpointIds: breakpointIds
|
|
558
|
+
}));
|
|
559
|
+
}
|
|
560
|
+
async processRemoveBreakpointsRequest(request) {
|
|
561
|
+
var _a, _b, _c;
|
|
562
|
+
//throw out null breakpoints
|
|
563
|
+
request.data.breakpointIds = (_b = (_a = request.data.breakpointIds) === null || _a === void 0 ? void 0 : _a.filter(x => typeof x === 'number')) !== null && _b !== void 0 ? _b : [];
|
|
564
|
+
if (((_c = request.data.breakpointIds) === null || _c === void 0 ? void 0 : _c.length) > 0) {
|
|
565
|
+
return this.sendRequest(request);
|
|
566
|
+
}
|
|
567
|
+
return RemoveBreakpointsResponse_1.RemoveBreakpointsResponse.fromJson(null);
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Given a request, process it in the proper fashion. This is mostly used for external mocking/testing of
|
|
571
|
+
* this client, but it should force the client to flow in the same fashion as a live debug session
|
|
572
|
+
*/
|
|
573
|
+
async processRequest(request) {
|
|
574
|
+
switch (request === null || request === void 0 ? void 0 : request.constructor.name) {
|
|
575
|
+
case ContinueRequest_1.ContinueRequest.name:
|
|
576
|
+
return this.processContinueRequest(request);
|
|
577
|
+
case ExecuteRequest_1.ExecuteRequest.name:
|
|
578
|
+
return this.processExecuteRequest(request);
|
|
579
|
+
case HandshakeRequest_1.HandshakeRequest.name:
|
|
580
|
+
return this.processHandshakeRequest(request);
|
|
581
|
+
case RemoveBreakpointsRequest_1.RemoveBreakpointsRequest.name:
|
|
582
|
+
return this.processRemoveBreakpointsRequest(request);
|
|
583
|
+
case StackTraceRequest_1.StackTraceRequest.name:
|
|
584
|
+
return this.processStackTraceRequest(request);
|
|
585
|
+
case StepRequest_1.StepRequest.name:
|
|
586
|
+
return this.processStepRequest(request);
|
|
587
|
+
case StopRequest_1.StopRequest.name:
|
|
588
|
+
return this.processStopRequest(request);
|
|
589
|
+
case ThreadsRequest_1.ThreadsRequest.name:
|
|
590
|
+
return this.processThreadsRequest(request);
|
|
591
|
+
case VariablesRequest_1.VariablesRequest.name:
|
|
592
|
+
return this.processVariablesRequest(request);
|
|
593
|
+
//for all other request types, there's no custom business logic, so just pipe them through manually
|
|
594
|
+
case AddBreakpointsRequest_1.AddBreakpointsRequest.name:
|
|
595
|
+
case AddConditionalBreakpointsRequest_1.AddConditionalBreakpointsRequest.name:
|
|
596
|
+
case ExitChannelRequest_1.ExitChannelRequest.name:
|
|
597
|
+
case ListBreakpointsRequest_1.ListBreakpointsRequest.name:
|
|
598
|
+
return this.sendRequest(request);
|
|
599
|
+
default:
|
|
600
|
+
this.logger.log('Unknown request type. Sending anyway...', request);
|
|
601
|
+
//unknown request type. try sending it as-is
|
|
602
|
+
return this.sendRequest(request);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Send a request to the roku device, and get a promise that resolves once we have received the response
|
|
607
|
+
*/
|
|
608
|
+
async sendRequest(request) {
|
|
609
|
+
request = (await this.plugins.emit('beforeSendRequest', {
|
|
610
|
+
client: this,
|
|
611
|
+
request: request
|
|
612
|
+
})).request;
|
|
613
|
+
this.activeRequests.set(request.data.requestId, request);
|
|
614
|
+
return new Promise((resolve, reject) => {
|
|
615
|
+
let unsubscribe = this.on('response', (response) => {
|
|
616
|
+
if (response.data.requestId === request.data.requestId) {
|
|
617
|
+
unsubscribe();
|
|
618
|
+
this.activeRequests.delete(request.data.requestId);
|
|
619
|
+
resolve(response);
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
this.logEvent(request);
|
|
623
|
+
if (this.controlSocket) {
|
|
624
|
+
const buffer = request.toBuffer();
|
|
625
|
+
this.writeToBufferLog('client-to-server', buffer);
|
|
626
|
+
this.controlSocket.write(buffer);
|
|
627
|
+
void this.plugins.emit('afterSendRequest', {
|
|
628
|
+
client: this,
|
|
629
|
+
request: request
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
reject(new Error(`Control socket was closed - Command: ${Constants_1.Command[request.data.command]}`));
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Sometimes a request arrives that we don't understand. If that's the case, this function can be used
|
|
639
|
+
* to discard that entire response by discarding `packet_length` number of bytes
|
|
640
|
+
*/
|
|
641
|
+
discardNextResponseOrUpdate() {
|
|
642
|
+
const response = GenericV3Response_1.GenericV3Response.fromBuffer(this.buffer);
|
|
643
|
+
if (response.success && response.data.packetLength > 0) {
|
|
644
|
+
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));
|
|
645
|
+
//we have a valid event. Clear the buffer of this data
|
|
646
|
+
this.buffer = this.buffer.slice(response.data.packetLength);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
logEvent(event) {
|
|
650
|
+
var _a;
|
|
651
|
+
const [, eventName, eventType] = (_a = /(.+?)((?:v\d+_?\d*)?(?:request|response|update))/ig.exec(event === null || event === void 0 ? void 0 : event.constructor.name)) !== null && _a !== void 0 ? _a : [];
|
|
652
|
+
if (isProtocolRequest(event)) {
|
|
653
|
+
this.logger.log(`${eventName} ${event.data.requestId} (${eventType})`, event, `(${event === null || event === void 0 ? void 0 : event.constructor.name})`);
|
|
654
|
+
}
|
|
655
|
+
else if (isProtocolUpdate(event)) {
|
|
656
|
+
this.logger.log(`${eventName} ${this.updateSequence++} (${eventType})`, event, `(${event === null || event === void 0 ? void 0 : event.constructor.name})`);
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
if (event.data.errorCode === Constants_1.ErrorCode.OK) {
|
|
660
|
+
this.logger.log(`${eventName} ${event.data.requestId} (${eventType})`, event, `(${event === null || event === void 0 ? void 0 : event.constructor.name})`);
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
this.logger.log(`[error] ${eventName} ${event.data.requestId} (${eventType})`, event, `(${event === null || event === void 0 ? void 0 : event.constructor.name})`);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
async process() {
|
|
668
|
+
var _a;
|
|
669
|
+
try {
|
|
670
|
+
this.logger.info('[process()]: buffer=', this.buffer.toJSON());
|
|
671
|
+
let { responseOrUpdate } = await this.plugins.emit('provideResponseOrUpdate', {
|
|
672
|
+
client: this,
|
|
673
|
+
activeRequests: this.activeRequests,
|
|
674
|
+
buffer: this.buffer
|
|
675
|
+
});
|
|
676
|
+
if (!responseOrUpdate) {
|
|
677
|
+
responseOrUpdate = await this.getResponseOrUpdate(this.buffer);
|
|
678
|
+
}
|
|
679
|
+
//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)
|
|
680
|
+
if (!responseOrUpdate) {
|
|
681
|
+
this.logger.info('Unable to convert buffer into anything meaningful', this.buffer);
|
|
682
|
+
//if we have packet length, and we have at least that many bytes, throw out this message so we can hopefully recover
|
|
683
|
+
this.discardNextResponseOrUpdate();
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
686
|
+
if (!responseOrUpdate.success || responseOrUpdate.data.packetLength > this.buffer.length) {
|
|
687
|
+
this.logger.log(`event parse failed. ${(_a = responseOrUpdate === null || responseOrUpdate === void 0 ? void 0 : responseOrUpdate.data) === null || _a === void 0 ? void 0 : _a.packetLength} bytes required, ${this.buffer.length} bytes available`);
|
|
688
|
+
return false;
|
|
689
|
+
}
|
|
690
|
+
//we have a valid event. Remove this data from the buffer
|
|
691
|
+
this.buffer = this.buffer.slice(responseOrUpdate.readOffset);
|
|
692
|
+
if (responseOrUpdate.data.errorCode !== Constants_1.ErrorCode.OK) {
|
|
693
|
+
this.logEvent(responseOrUpdate);
|
|
694
|
+
}
|
|
695
|
+
//we got a result
|
|
696
|
+
if (responseOrUpdate) {
|
|
697
|
+
//emit the corresponding event
|
|
698
|
+
if (isProtocolUpdate(responseOrUpdate)) {
|
|
699
|
+
this.logEvent(responseOrUpdate);
|
|
700
|
+
this.emit('update', responseOrUpdate);
|
|
701
|
+
await this.plugins.emit('onUpdate', {
|
|
702
|
+
client: this,
|
|
703
|
+
update: responseOrUpdate
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
else {
|
|
707
|
+
this.logEvent(responseOrUpdate);
|
|
708
|
+
this.emit('response', responseOrUpdate);
|
|
709
|
+
await this.plugins.emit('onResponse', {
|
|
710
|
+
client: this,
|
|
711
|
+
response: responseOrUpdate
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
return true;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
catch (e) {
|
|
718
|
+
this.logger.error(`process() failed:`, e);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Given a buffer, try to parse into a specific ProtocolResponse or ProtocolUpdate
|
|
723
|
+
*/
|
|
724
|
+
async getResponseOrUpdate(buffer) {
|
|
725
|
+
//if we haven't seen a handshake yet, try to convert the buffer into a handshake
|
|
726
|
+
if (!this.isHandshakeComplete) {
|
|
727
|
+
let handshake;
|
|
728
|
+
//try building the v3 handshake response first
|
|
729
|
+
handshake = HandshakeV3Response_1.HandshakeV3Response.fromBuffer(buffer);
|
|
730
|
+
//we didn't get a v3 handshake. try building an older handshake response
|
|
731
|
+
if (!handshake.success) {
|
|
732
|
+
handshake = HandshakeResponse_1.HandshakeResponse.fromBuffer(buffer);
|
|
733
|
+
}
|
|
734
|
+
if (handshake.success) {
|
|
735
|
+
await this.verifyHandshake(handshake);
|
|
736
|
+
return handshake;
|
|
737
|
+
}
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
let genericResponse = this.watchPacketLength ? GenericV3Response_1.GenericV3Response.fromBuffer(buffer) : GenericResponse_1.GenericResponse.fromBuffer(buffer);
|
|
741
|
+
//if the response has a non-OK error code, we won't receive the expected response type,
|
|
742
|
+
//so return the generic response
|
|
743
|
+
if (genericResponse.success && genericResponse.data.errorCode !== Constants_1.ErrorCode.OK) {
|
|
744
|
+
return genericResponse;
|
|
745
|
+
}
|
|
746
|
+
// a nonzero requestId means this is a response to a request that we sent
|
|
747
|
+
if (genericResponse.data.requestId !== 0) {
|
|
748
|
+
//requestId 0 means this is an update
|
|
749
|
+
const request = this.activeRequests.get(genericResponse.data.requestId);
|
|
750
|
+
if (request) {
|
|
751
|
+
return DebugProtocolClient.getResponse(this.buffer, request.data.command);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
return this.getUpdate(this.buffer);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
static getResponse(buffer, command) {
|
|
759
|
+
switch (command) {
|
|
760
|
+
case Constants_1.Command.Stop:
|
|
761
|
+
case Constants_1.Command.Continue:
|
|
762
|
+
case Constants_1.Command.Step:
|
|
763
|
+
case Constants_1.Command.ExitChannel:
|
|
764
|
+
return GenericV3Response_1.GenericV3Response.fromBuffer(buffer);
|
|
765
|
+
case Constants_1.Command.Execute:
|
|
766
|
+
return ExecuteV3Response_1.ExecuteV3Response.fromBuffer(buffer);
|
|
767
|
+
case Constants_1.Command.AddBreakpoints:
|
|
768
|
+
case Constants_1.Command.AddConditionalBreakpoints:
|
|
769
|
+
return AddBreakpointsResponse_1.AddBreakpointsResponse.fromBuffer(buffer);
|
|
770
|
+
case Constants_1.Command.ListBreakpoints:
|
|
771
|
+
return ListBreakpointsResponse_1.ListBreakpointsResponse.fromBuffer(buffer);
|
|
772
|
+
case Constants_1.Command.RemoveBreakpoints:
|
|
773
|
+
return RemoveBreakpointsResponse_1.RemoveBreakpointsResponse.fromBuffer(buffer);
|
|
774
|
+
case Constants_1.Command.Variables:
|
|
775
|
+
return VariablesResponse_1.VariablesResponse.fromBuffer(buffer);
|
|
776
|
+
case Constants_1.Command.StackTrace:
|
|
777
|
+
return StackTraceV3Response_1.StackTraceV3Response.fromBuffer(buffer);
|
|
778
|
+
case Constants_1.Command.Threads:
|
|
779
|
+
return ThreadsResponse_1.ThreadsResponse.fromBuffer(buffer);
|
|
780
|
+
default:
|
|
781
|
+
return undefined;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
getUpdate(buffer) {
|
|
785
|
+
var _a, _b, _c, _d;
|
|
786
|
+
//read the update_type from the buffer (save some buffer parsing time by narrowing to the exact update type)
|
|
787
|
+
const updateTypeCode = buffer.readUInt32LE(
|
|
788
|
+
// if the protocol supports packet length, then update_type is bytes 12-16. Otherwise, it's bytes 8-12
|
|
789
|
+
this.watchPacketLength ? 12 : 8);
|
|
790
|
+
const updateType = Constants_1.UpdateTypeCode[updateTypeCode];
|
|
791
|
+
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.log('getUpdate(): update Type:', updateType);
|
|
792
|
+
switch (updateType) {
|
|
793
|
+
case Constants_1.UpdateType.IOPortOpened:
|
|
794
|
+
//TODO handle this
|
|
795
|
+
return IOPortOpenedUpdate_1.IOPortOpenedUpdate.fromBuffer(buffer);
|
|
796
|
+
case Constants_1.UpdateType.AllThreadsStopped:
|
|
797
|
+
return AllThreadsStoppedUpdate_1.AllThreadsStoppedUpdate.fromBuffer(buffer);
|
|
798
|
+
case Constants_1.UpdateType.ThreadAttached:
|
|
799
|
+
return ThreadAttachedUpdate_1.ThreadAttachedUpdate.fromBuffer(buffer);
|
|
800
|
+
case Constants_1.UpdateType.BreakpointError:
|
|
801
|
+
//we do nothing with breakpoint errors at this time.
|
|
802
|
+
return BreakpointErrorUpdate_1.BreakpointErrorUpdate.fromBuffer(buffer);
|
|
803
|
+
case Constants_1.UpdateType.CompileError:
|
|
804
|
+
let compileErrorUpdate = CompileErrorUpdate_1.CompileErrorUpdate.fromBuffer(buffer);
|
|
805
|
+
if (((_b = compileErrorUpdate === null || compileErrorUpdate === void 0 ? void 0 : compileErrorUpdate.data) === null || _b === void 0 ? void 0 : _b.errorMessage) !== '') {
|
|
806
|
+
this.emit('compile-error', compileErrorUpdate);
|
|
807
|
+
}
|
|
808
|
+
return compileErrorUpdate;
|
|
809
|
+
case Constants_1.UpdateType.BreakpointVerified:
|
|
810
|
+
let response = BreakpointVerifiedUpdate_1.BreakpointVerifiedUpdate.fromBuffer(buffer);
|
|
811
|
+
if (((_d = (_c = response === null || response === void 0 ? void 0 : response.data) === null || _c === void 0 ? void 0 : _c.breakpoints) === null || _d === void 0 ? void 0 : _d.length) > 0) {
|
|
812
|
+
this.emit('breakpoints-verified', response.data);
|
|
813
|
+
}
|
|
814
|
+
return response;
|
|
815
|
+
default:
|
|
816
|
+
return undefined;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Handle/process any received updates from the debug protocol
|
|
821
|
+
*/
|
|
822
|
+
async handleUpdate(update) {
|
|
823
|
+
return this.handleUpdateQueue.run(async () => {
|
|
824
|
+
update = (await this.plugins.emit('beforeHandleUpdate', {
|
|
825
|
+
client: this,
|
|
826
|
+
update: update
|
|
827
|
+
})).update;
|
|
828
|
+
if (update instanceof AllThreadsStoppedUpdate_1.AllThreadsStoppedUpdate || update instanceof ThreadAttachedUpdate_1.ThreadAttachedUpdate) {
|
|
829
|
+
this.isStopped = true;
|
|
830
|
+
let eventName;
|
|
831
|
+
if (update.data.stopReason === Constants_1.StopReason.RuntimeError) {
|
|
832
|
+
eventName = 'runtime-error';
|
|
833
|
+
}
|
|
834
|
+
else {
|
|
835
|
+
eventName = 'suspend';
|
|
836
|
+
}
|
|
837
|
+
const isValidStopReason = [Constants_1.StopReason.RuntimeError, Constants_1.StopReason.Break, Constants_1.StopReason.StopStatement].includes(update.data.stopReason);
|
|
838
|
+
if (update instanceof AllThreadsStoppedUpdate_1.AllThreadsStoppedUpdate && isValidStopReason) {
|
|
839
|
+
this.primaryThread = update.data.threadIndex;
|
|
840
|
+
this.stackFrameIndex = 0;
|
|
841
|
+
this.emit(eventName, update);
|
|
842
|
+
}
|
|
843
|
+
else if (update instanceof ThreadAttachedUpdate_1.ThreadAttachedUpdate && isValidStopReason) {
|
|
844
|
+
this.primaryThread = update.data.threadIndex;
|
|
845
|
+
this.emit(eventName, update);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
else if ((0, IOPortOpenedUpdate_1.isIOPortOpenedUpdate)(update)) {
|
|
849
|
+
this.connectToIoPort(update);
|
|
850
|
+
}
|
|
851
|
+
return true;
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Verify all the handshake data
|
|
856
|
+
*/
|
|
857
|
+
async verifyHandshake(response) {
|
|
858
|
+
if (DebugProtocolClient.DEBUGGER_MAGIC === response.data.magic) {
|
|
859
|
+
this.logger.log('Magic is valid.');
|
|
860
|
+
this.protocolVersion = response.data.protocolVersion;
|
|
861
|
+
this.logger.log('Protocol Version:', this.protocolVersion);
|
|
862
|
+
this.watchPacketLength = semver.satisfies(this.protocolVersion, '>=3.0.0');
|
|
863
|
+
this.isHandshakeComplete = true;
|
|
864
|
+
let handshakeVerified = true;
|
|
865
|
+
if (semver.satisfies(this.protocolVersion, this.supportedVersionRange)) {
|
|
866
|
+
this.logger.log('supported');
|
|
867
|
+
this.emit('protocol-version', {
|
|
868
|
+
message: `Protocol Version ${this.protocolVersion} is supported!`,
|
|
869
|
+
errorCode: Constants_1.PROTOCOL_ERROR_CODES.SUPPORTED
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
else if (semver.gtr(this.protocolVersion, this.supportedVersionRange)) {
|
|
873
|
+
this.logger.log('roku-debug has not been tested against protocol version', this.protocolVersion);
|
|
874
|
+
this.emit('protocol-version', {
|
|
875
|
+
message: `Protocol Version ${this.protocolVersion} has not been tested and my not work as intended.\nPlease open any issues you have with this version to https://github.com/rokucommunity/roku-debug/issues`,
|
|
876
|
+
errorCode: Constants_1.PROTOCOL_ERROR_CODES.NOT_TESTED
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
else {
|
|
880
|
+
this.logger.log('not supported');
|
|
881
|
+
this.emit('protocol-version', {
|
|
882
|
+
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`,
|
|
883
|
+
errorCode: Constants_1.PROTOCOL_ERROR_CODES.NOT_SUPPORTED
|
|
884
|
+
});
|
|
885
|
+
await this.shutdown('close');
|
|
886
|
+
handshakeVerified = false;
|
|
887
|
+
}
|
|
888
|
+
this.emit('handshake-verified', handshakeVerified);
|
|
889
|
+
return handshakeVerified;
|
|
890
|
+
}
|
|
891
|
+
else {
|
|
892
|
+
this.logger.log('Closing connection due to bad debugger magic', response.data.magic);
|
|
893
|
+
this.emit('handshake-verified', false);
|
|
894
|
+
await this.shutdown('close');
|
|
895
|
+
return false;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* When the debugger emits the IOPortOpenedUpdate, we need to immediately connect to the IO port to start receiving that data
|
|
900
|
+
*/
|
|
901
|
+
connectToIoPort(update) {
|
|
902
|
+
if (update.success) {
|
|
903
|
+
// Create a new TCP client.
|
|
904
|
+
this.ioSocket = new Net.Socket({
|
|
905
|
+
allowHalfOpen: false
|
|
906
|
+
});
|
|
907
|
+
// Send a connection request to the server.
|
|
908
|
+
this.logger.log(`Connect to IO Port ${this.options.host}:${update.data.port}`);
|
|
909
|
+
//sometimes the server shuts down before we had a chance to connect, so recover more gracefully
|
|
910
|
+
try {
|
|
911
|
+
this.ioSocket.connect({
|
|
912
|
+
port: update.data.port,
|
|
913
|
+
host: this.options.host
|
|
914
|
+
}, () => {
|
|
915
|
+
// If there is no error, the server has accepted the request
|
|
916
|
+
this.logger.log('TCP connection established with the IO Port.');
|
|
917
|
+
this.connectedToIoPort = true;
|
|
918
|
+
let lastPartialLine = '';
|
|
919
|
+
this.ioSocket.on('data', (buffer) => {
|
|
920
|
+
this.writeToBufferLog('io', buffer);
|
|
921
|
+
let responseText = buffer.toString();
|
|
922
|
+
if (!responseText.endsWith('\n')) {
|
|
923
|
+
// buffer was split, save the partial line
|
|
924
|
+
lastPartialLine += responseText;
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
if (lastPartialLine) {
|
|
928
|
+
// there was leftover lines, join the partial lines back together
|
|
929
|
+
responseText = lastPartialLine + responseText;
|
|
930
|
+
lastPartialLine = '';
|
|
931
|
+
}
|
|
932
|
+
// Emit the completed io string.
|
|
933
|
+
this.emit('io-output', responseText.trim());
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
this.ioSocket.on('close', () => {
|
|
937
|
+
this.logger.log('IO socket closed');
|
|
938
|
+
this.ioSocketClosed.tryResolve();
|
|
939
|
+
});
|
|
940
|
+
// Don't forget to catch error, for your own sake.
|
|
941
|
+
this.ioSocket.once('error', (err) => {
|
|
942
|
+
this.ioSocket.end();
|
|
943
|
+
this.logger.error(err);
|
|
944
|
+
});
|
|
945
|
+
});
|
|
946
|
+
return true;
|
|
947
|
+
}
|
|
948
|
+
catch (e) {
|
|
949
|
+
this.logger.error(`Failed to connect to IO socket at ${this.options.host}:${update.data.port}`, e);
|
|
950
|
+
void this.shutdown('app-exit');
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
return false;
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Destroy this instance, shutting down any sockets or other long-running items and cleaning up.
|
|
957
|
+
* @param immediate if true, all sockets are immediately closed and do not gracefully shut down
|
|
958
|
+
*/
|
|
959
|
+
async destroy(immediate = false) {
|
|
960
|
+
await this.shutdown('close', immediate);
|
|
961
|
+
}
|
|
962
|
+
async shutdown(eventName, immediate = false) {
|
|
963
|
+
var _a, _b, _c, _d, _e, _f;
|
|
964
|
+
this.logger.log('Shutting down!');
|
|
965
|
+
(_a = this.cancelControlConnectInterval) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
966
|
+
for (const pendingSocket of this.pendingControlConnectionSockets) {
|
|
967
|
+
pendingSocket.removeAllListeners();
|
|
968
|
+
//cleanup and destroy all other sockets
|
|
969
|
+
if (pendingSocket !== this.controlSocket) {
|
|
970
|
+
pendingSocket.end();
|
|
971
|
+
pendingSocket === null || pendingSocket === void 0 ? void 0 : pendingSocket.destroy();
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
let exitChannelTimeout = (_c = (_b = this.options) === null || _b === void 0 ? void 0 : _b.exitChannelTimeout) !== null && _c !== void 0 ? _c : 30000;
|
|
975
|
+
let shutdownTimeMax = (_e = (_d = this.options) === null || _d === void 0 ? void 0 : _d.shutdownTimeout) !== null && _e !== void 0 ? _e : 10000;
|
|
976
|
+
//if immediate is true, this is an instant shutdown force. don't wait for anything
|
|
977
|
+
if (immediate) {
|
|
978
|
+
exitChannelTimeout = 0;
|
|
979
|
+
shutdownTimeMax = 0;
|
|
980
|
+
}
|
|
981
|
+
//tell the device to exit the channel
|
|
982
|
+
try {
|
|
983
|
+
//ask the device to terminate the debug session. We have to wait for this to come back.
|
|
984
|
+
//The device might be running unstoppable code, so this might take a while. Wait for the device to send back
|
|
985
|
+
//the response before we continue with the teardown process
|
|
986
|
+
await Promise.race([
|
|
987
|
+
immediate
|
|
988
|
+
? Promise.resolve(null)
|
|
989
|
+
: this.exitChannel().finally(() => this.logger.log('exit channel completed')),
|
|
990
|
+
//if the exit channel request took this long to finish, something's terribly wrong
|
|
991
|
+
util_1.util.sleep(exitChannelTimeout)
|
|
992
|
+
]);
|
|
993
|
+
}
|
|
994
|
+
finally { }
|
|
995
|
+
await Promise.all([
|
|
996
|
+
this.destroyControlSocket(shutdownTimeMax),
|
|
997
|
+
this.destroyIOSocket(shutdownTimeMax, immediate)
|
|
998
|
+
]);
|
|
999
|
+
this.emit(eventName);
|
|
1000
|
+
(_f = this.emitter) === null || _f === void 0 ? void 0 : _f.removeAllListeners();
|
|
1001
|
+
this.buffer = Buffer.alloc(0);
|
|
1002
|
+
this.bufferQueue.destroy();
|
|
1003
|
+
}
|
|
1004
|
+
async destroyControlSocket(timeout) {
|
|
1005
|
+
if (this.controlSocket && !this.isDestroyingControlSocket) {
|
|
1006
|
+
this.isDestroyingControlSocket = true;
|
|
1007
|
+
//wait for the controlSocket to be closed
|
|
1008
|
+
await Promise.race([
|
|
1009
|
+
this.controlSocketClosed.promise,
|
|
1010
|
+
util_1.util.sleep(timeout)
|
|
1011
|
+
]);
|
|
1012
|
+
this.logger.log('[destroy] controlSocket is: ', this.controlSocketClosed.isResolved ? 'closed' : 'not closed');
|
|
1013
|
+
//destroy the controlSocket
|
|
1014
|
+
this.controlSocket.removeAllListeners();
|
|
1015
|
+
this.controlSocket.destroy();
|
|
1016
|
+
this.controlSocket = undefined;
|
|
1017
|
+
this.isDestroyingControlSocket = false;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* @param immediate if true, force close immediately instead of waiting for it to settle
|
|
1022
|
+
*/
|
|
1023
|
+
async destroyIOSocket(timeout, immediate = false) {
|
|
1024
|
+
var _a, _b, _c, _d;
|
|
1025
|
+
if (this.ioSocket && !this.isDestroyingIOSocket) {
|
|
1026
|
+
this.isDestroyingIOSocket = true;
|
|
1027
|
+
//wait for the ioSocket to be closed
|
|
1028
|
+
await Promise.race([
|
|
1029
|
+
this.ioSocketClosed.promise.then(() => this.logger.log('IO socket closed')),
|
|
1030
|
+
util_1.util.sleep(timeout)
|
|
1031
|
+
]);
|
|
1032
|
+
//if the io socket is not closed, wait for it to at least settle
|
|
1033
|
+
if (!this.ioSocketClosed.isCompleted && !immediate) {
|
|
1034
|
+
await new Promise((resolve) => {
|
|
1035
|
+
var _a;
|
|
1036
|
+
const callback = debounce(() => {
|
|
1037
|
+
resolve();
|
|
1038
|
+
}, 250);
|
|
1039
|
+
//trigger the current callback once.
|
|
1040
|
+
callback();
|
|
1041
|
+
(_a = this.ioSocket) === null || _a === void 0 ? void 0 : _a.on('drain', callback);
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
this.logger.log('[destroy] ioSocket is: ', this.ioSocketClosed.isResolved ? 'closed' : 'not closed');
|
|
1045
|
+
//destroy the ioSocket
|
|
1046
|
+
(_b = (_a = this.ioSocket) === null || _a === void 0 ? void 0 : _a.removeAllListeners) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
1047
|
+
(_d = (_c = this.ioSocket) === null || _c === void 0 ? void 0 : _c.destroy) === null || _d === void 0 ? void 0 : _d.call(_c);
|
|
1048
|
+
this.ioSocket = undefined;
|
|
1049
|
+
this.isDestroyingIOSocket = false;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
exports.DebugProtocolClient = DebugProtocolClient;
|
|
1054
|
+
DebugProtocolClient.DEBUGGER_MAGIC = 'bsdebug'; // 64-bit = [b'bsdebug\0' little-endian]
|
|
1055
|
+
/**
|
|
1056
|
+
* Is the event a ProtocolRequest
|
|
1057
|
+
*/
|
|
1058
|
+
function isProtocolRequest(event) {
|
|
1059
|
+
var _a, _b;
|
|
1060
|
+
return ((_a = event === null || event === void 0 ? void 0 : event.constructor) === null || _a === void 0 ? void 0 : _a.name.endsWith('Request')) && ((_b = event === null || event === void 0 ? void 0 : event.data) === null || _b === void 0 ? void 0 : _b.requestId) > 0;
|
|
1061
|
+
}
|
|
1062
|
+
exports.isProtocolRequest = isProtocolRequest;
|
|
1063
|
+
/**
|
|
1064
|
+
* Is the event a ProtocolResponse
|
|
1065
|
+
*/
|
|
1066
|
+
function isProtocolResponse(event) {
|
|
1067
|
+
var _a, _b;
|
|
1068
|
+
return ((_a = event === null || event === void 0 ? void 0 : event.constructor) === null || _a === void 0 ? void 0 : _a.name.endsWith('Response')) && ((_b = event === null || event === void 0 ? void 0 : event.data) === null || _b === void 0 ? void 0 : _b.requestId) !== 0;
|
|
1069
|
+
}
|
|
1070
|
+
exports.isProtocolResponse = isProtocolResponse;
|
|
1071
|
+
/**
|
|
1072
|
+
* Is the event a ProtocolUpdate update
|
|
1073
|
+
*/
|
|
1074
|
+
function isProtocolUpdate(event) {
|
|
1075
|
+
var _a, _b;
|
|
1076
|
+
return ((_a = event === null || event === void 0 ? void 0 : event.constructor) === null || _a === void 0 ? void 0 : _a.name.endsWith('Update')) && ((_b = event === null || event === void 0 ? void 0 : event.data) === null || _b === void 0 ? void 0 : _b.requestId) === 0;
|
|
1077
|
+
}
|
|
1078
|
+
exports.isProtocolUpdate = isProtocolUpdate;
|
|
1079
|
+
//# sourceMappingURL=DebugProtocolClient.js.map
|