iobroker.javascript 10.2.2 → 10.2.4
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/README.md +2 -1
- package/admin/assets/{AiChatPanel-C5vTuhiL.js → AiChatPanel-AZko1BsN.js} +2 -2
- package/admin/assets/Debugger-CVDh3kgz.js +15 -0
- package/admin/assets/RulesEditor-D3kTbD_T.css +1 -0
- package/admin/assets/RulesEditor-DsO4cKIk.js +1 -0
- package/admin/assets/{ScriptEditor-COwzdxKl.js → ScriptEditor-BCGahS47.js} +1 -1
- package/admin/assets/{ScriptEditorVanillaMonaco-C0biTTli.js → ScriptEditorVanillaMonaco-BDAw2Bw6.js} +2 -2
- package/admin/assets/aiPromptBuilder-aJfHa-eU.js +1 -0
- package/admin/assets/{blocks_action-CycTOoDY.js → blocks_action-DS28sNfo.js} +1 -1
- package/admin/assets/{blocks_convert-BUmTT450.js → blocks_convert-CLb0DLyc.js} +1 -1
- package/admin/assets/{blocks_logic-BP_rkFPu.js → blocks_logic-Dpp2fwlu.js} +1 -1
- package/admin/assets/{blocks_number-DZPdNWcv.js → blocks_number-N4IfHvYD.js} +1 -1
- package/admin/assets/{blocks_object-CUKKvXdl.js → blocks_object-uK7FHVhS.js} +1 -1
- package/admin/assets/{blocks_procedures-D3LiaeLD.js → blocks_procedures-C_KD0Bm6.js} +1 -1
- package/admin/assets/{blocks_sendto-BWmeOFOr.js → blocks_sendto-BxCMg9X4.js} +1 -1
- package/admin/assets/{blocks_switch-B17LJ99U.js → blocks_switch-C8Rls5pK.js} +1 -1
- package/admin/assets/{blocks_system-DdvuotD5.js → blocks_system-CEQnj5fY.js} +1 -1
- package/admin/assets/{blocks_text-D35PPiiy.js → blocks_text-BDEWxu0P.js} +1 -1
- package/admin/assets/{blocks_time-CuI_GrE0.js → blocks_time-aHQQK1Uz.js} +1 -1
- package/admin/assets/{blocks_timeout-tbB0KBqO.js → blocks_timeout-C2L93TLJ.js} +1 -1
- package/admin/assets/{blocks_trigger-Bw3qbQRd.js → blocks_trigger-XivsIDE5.js} +1 -1
- package/admin/assets/{helpers-D5PdK4BS.js → helpers-DiwtLpXM.js} +1 -1
- package/admin/assets/{index-PLY-IJik.js → index-B0FfAYMN.js} +7 -7
- package/admin/assets/{mf-entry-bootstrap-0-f35ca6a1.js → mf-entry-bootstrap-0-019228a5.js} +1 -1
- package/admin/mf-stats.json +1 -1
- package/admin/tab.html +2 -2
- package/build/lib/inspect.js +547 -632
- package/build/lib/inspect.js.map +1 -1
- package/build/main.js +8 -13
- package/build/main.js.map +1 -1
- package/io-package.json +27 -27
- package/package.json +1 -1
- package/admin/assets/Debugger-DnrhfguX.js +0 -15
- package/admin/assets/RulesEditor-BwQ6Bo1V.css +0 -1
- package/admin/assets/RulesEditor-u3ppR2Xl.js +0 -1
- package/admin/assets/aiPromptBuilder-4yeoONDF.js +0 -1
- package/build/lib/debugger.js +0 -1135
- package/build/lib/debugger.js.map +0 -1
package/build/lib/inspect.js
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.NodeInspector = void 0;
|
|
7
2
|
/*
|
|
8
3
|
* Copyright Node.js contributors. All rights reserved.
|
|
9
4
|
*
|
|
@@ -25,318 +20,202 @@ exports.NodeInspector = void 0;
|
|
|
25
20
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
26
21
|
* IN THE SOFTWARE.
|
|
27
22
|
*/
|
|
23
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
24
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
25
|
+
};
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
/*
|
|
28
|
+
* The inspector process. It is forked by the adapter (see `debugStart` in main.ts) and starts the process to debug -
|
|
29
|
+
* the javascript adapter itself with `--debugScript <scriptId>` or another adapter - with an enabled inspector.
|
|
30
|
+
* It connects to it via the DevTools protocol and translates the commands of the GUI into protocol calls and back.
|
|
31
|
+
*
|
|
32
|
+
* The communication with the host happens via IPC with JSON strings:
|
|
33
|
+
* - host -> inspector: `DebugCommand`
|
|
34
|
+
* - inspector -> host: `CommandToHost`
|
|
35
|
+
*/
|
|
28
36
|
const node_child_process_1 = require("node:child_process");
|
|
29
|
-
const node_events_1 = require("node:events");
|
|
30
|
-
const node_net_1 = require("node:net");
|
|
31
|
-
const node_util_1 = require("node:util");
|
|
32
|
-
const node_path_1 = require("node:path");
|
|
33
37
|
const node_fs_1 = require("node:fs");
|
|
38
|
+
const node_path_1 = require("node:path");
|
|
39
|
+
const node_url_1 = require("node:url");
|
|
40
|
+
const node_util_1 = require("node:util");
|
|
34
41
|
// @ts-expect-error no types available
|
|
35
42
|
const inspect_client_1 = __importDefault(require("node-inspect/lib/internal/inspect_client"));
|
|
36
|
-
const debugger_1 = __importDefault(require("./debugger"));
|
|
37
|
-
const breakOnStart = process.argv.includes('--breakOnStart');
|
|
38
43
|
const debuglog = (0, node_util_1.debuglog)('inspect');
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
/** Stay paused on the first line of the debugged process (used for the debugging of adapter instances) */
|
|
45
|
+
const breakOnStart = process.argv.includes('--breakOnStart');
|
|
46
|
+
/**
|
|
47
|
+
* By debugging of a script, stepping and "stop on exception" should not stop in the node internals,
|
|
48
|
+
* in 3rd party modules and in the adapter itself (e.g., in sandbox.js)
|
|
49
|
+
*/
|
|
50
|
+
const SCRIPT_BLACKBOX_PATTERNS = ['^node:', '/node_modules/', '[iI]o[bB]roker\\.javascript/build/'];
|
|
43
51
|
class StartupError extends Error {
|
|
44
52
|
constructor(message) {
|
|
45
53
|
super(message);
|
|
46
54
|
this.name = 'StartupError';
|
|
47
55
|
}
|
|
48
56
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
let inspector;
|
|
58
|
+
/** ID of the debugged script, like `script.js.myScript` */
|
|
59
|
+
let scriptToDebug = '';
|
|
60
|
+
/** Main file of the debugged adapter */
|
|
61
|
+
let instanceToDebug = '';
|
|
62
|
+
function sendToHost(data) {
|
|
63
|
+
if (data.cmd === 'error') {
|
|
64
|
+
console.error(`[DEBUGGER] ${data.error}`);
|
|
53
65
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
66
|
+
try {
|
|
67
|
+
process.send?.(JSON.stringify(data));
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
// The host is not reachable anymore
|
|
71
|
+
console.error(`[DEBUGGER] Cannot send to host: ${e}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function errorToString(e) {
|
|
75
|
+
if (e instanceof Error) {
|
|
76
|
+
return e.message;
|
|
77
|
+
}
|
|
78
|
+
return typeof e === 'string' ? e : JSON.stringify(e);
|
|
79
|
+
}
|
|
80
|
+
function reportError(text, e) {
|
|
81
|
+
sendToHost({ cmd: 'error', error: e === undefined ? text : `${text}: ${errorToString(e)}` });
|
|
82
|
+
}
|
|
83
|
+
function remoteObjectToString(obj) {
|
|
84
|
+
if (!obj) {
|
|
85
|
+
return '';
|
|
86
|
+
}
|
|
87
|
+
if (obj.value !== undefined) {
|
|
88
|
+
return typeof obj.value === 'string' ? obj.value : JSON.stringify(obj.value);
|
|
89
|
+
}
|
|
90
|
+
return obj.unserializableValue || obj.description || obj.type;
|
|
91
|
+
}
|
|
92
|
+
function consoleTypeToSeverity(type) {
|
|
93
|
+
switch (type) {
|
|
94
|
+
case 'warning':
|
|
95
|
+
return 'warn';
|
|
96
|
+
case 'error':
|
|
97
|
+
case 'assert':
|
|
98
|
+
return 'error';
|
|
99
|
+
case 'debug':
|
|
100
|
+
return 'debug';
|
|
101
|
+
default:
|
|
102
|
+
return 'info';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** Values like NaN, Infinity, -0 or BigInt cannot be transferred as JSON value */
|
|
106
|
+
function toCallArgument(value) {
|
|
107
|
+
if (typeof value === 'number' && (!Number.isFinite(value) || Object.is(value, -0))) {
|
|
108
|
+
return { unserializableValue: Object.is(value, -0) ? '-0' : String(value) };
|
|
109
|
+
}
|
|
110
|
+
if (typeof value === 'bigint') {
|
|
111
|
+
return { unserializableValue: `${value}n` };
|
|
112
|
+
}
|
|
113
|
+
return value === undefined ? {} : { value };
|
|
114
|
+
}
|
|
115
|
+
function isUrlOfFile(url, file) {
|
|
116
|
+
let path = url;
|
|
117
|
+
if (url.startsWith('file://')) {
|
|
118
|
+
try {
|
|
119
|
+
path = (0, node_url_1.fileURLToPath)(url);
|
|
85
120
|
}
|
|
86
|
-
|
|
87
|
-
|
|
121
|
+
catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
path = (0, node_path_1.normalize)(path);
|
|
126
|
+
file = (0, node_path_1.normalize)(file);
|
|
127
|
+
return process.platform === 'win32' ? path.toLowerCase() === file.toLowerCase() : path === file;
|
|
88
128
|
}
|
|
89
|
-
function
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
child.stderr.setEncoding('utf8');
|
|
97
|
-
child.stdout.on('data', childPrint);
|
|
98
|
-
child.stderr.on('data', text => childPrint(text, true));
|
|
99
|
-
let output = '';
|
|
100
|
-
function waitForListenHint(text) {
|
|
101
|
-
output += text;
|
|
102
|
-
const res = /Debugger listening on ws:\/\/\[?(.+?)]?:(\d+)\//.exec(output);
|
|
103
|
-
if (res) {
|
|
104
|
-
const host = res[1];
|
|
105
|
-
const port = Number.parseInt(res[2]);
|
|
106
|
-
child.stderr.removeListener('data', waitForListenHint);
|
|
107
|
-
resolve([child, port, host]);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
child.stderr.on('data', waitForListenHint);
|
|
111
|
-
});
|
|
129
|
+
function createDomain(domain, client) {
|
|
130
|
+
return new Proxy({}, {
|
|
131
|
+
get: (_target, method) =>
|
|
132
|
+
// the object must not look like a promise
|
|
133
|
+
typeof method === 'string' && method !== 'then'
|
|
134
|
+
? (params) => client.callMethod(`${domain}.${method}`, params)
|
|
135
|
+
: undefined,
|
|
112
136
|
});
|
|
113
137
|
}
|
|
114
|
-
function
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
//
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
138
|
+
function runScript(script, scriptArgs, childPrint) {
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
// Always break on start: the process must not run before the debugger is connected, else it could
|
|
141
|
+
// execute the `debugger;` statement of the script before. Port 0 means any free port.
|
|
142
|
+
const child = (0, node_child_process_1.spawn)(process.execPath, ['--inspect-brk=127.0.0.1:0', (0, node_path_1.normalize)(script), ...scriptArgs]);
|
|
143
|
+
child.stdout.setEncoding('utf8');
|
|
144
|
+
child.stderr.setEncoding('utf8');
|
|
145
|
+
child.stdout.on('data', (text) => childPrint(text));
|
|
146
|
+
child.stderr.on('data', (text) => childPrint(text, true));
|
|
147
|
+
let output = '';
|
|
148
|
+
const timeout = setTimeout(() => {
|
|
149
|
+
child.stderr.removeListener('data', waitForListenHint);
|
|
150
|
+
child.kill();
|
|
151
|
+
reject(new StartupError('Timeout by starting of the process to debug'));
|
|
152
|
+
}, 10_000);
|
|
153
|
+
function waitForListenHint(text) {
|
|
154
|
+
output += text;
|
|
155
|
+
const res = /Debugger listening on ws:\/\/\[?(.+?)]?:(\d+)\//.exec(output);
|
|
156
|
+
if (res) {
|
|
157
|
+
clearTimeout(timeout);
|
|
158
|
+
child.stderr.removeListener('data', waitForListenHint);
|
|
159
|
+
resolve([child, parseInt(res[2], 10), res[1]]);
|
|
129
160
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
161
|
+
}
|
|
162
|
+
child.stderr.on('data', waitForListenHint);
|
|
163
|
+
child.on('error', error => {
|
|
164
|
+
clearTimeout(timeout);
|
|
165
|
+
reject(new StartupError(`Cannot start the process to debug: ${error.message}`));
|
|
166
|
+
});
|
|
134
167
|
});
|
|
135
168
|
}
|
|
136
169
|
class NodeInspector {
|
|
137
|
-
options;
|
|
138
|
-
stdin;
|
|
139
|
-
stdout;
|
|
140
|
-
scripts;
|
|
141
|
-
delayedContext;
|
|
142
|
-
paused;
|
|
143
|
-
child;
|
|
144
|
-
_runScript;
|
|
145
|
-
client;
|
|
146
170
|
Debugger;
|
|
147
|
-
HeapProfiler;
|
|
148
|
-
Profiler;
|
|
149
171
|
Runtime;
|
|
150
|
-
|
|
172
|
+
client = new inspect_client_1.default();
|
|
173
|
+
file;
|
|
174
|
+
args;
|
|
175
|
+
child = null;
|
|
176
|
+
/** The protocol does not deliver the URLs in the call frames anymore, so remember them */
|
|
177
|
+
scriptUrls = {};
|
|
151
178
|
mainScriptId;
|
|
152
|
-
mainFile;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
this.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
this.
|
|
169
|
-
this.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (domain === 'Debugger' && name === 'scriptParsed') {
|
|
176
|
-
// console.log(`Parsed: ${params.url}`);
|
|
177
|
-
if ((scriptToDebug && params.url.includes(scriptToDebug)) ||
|
|
178
|
-
(instanceToDebug && params.url.includes(instanceToDebug))) {
|
|
179
|
-
console.log(`My scriptID: ${params.scriptId}`);
|
|
180
|
-
this.mainScriptId = params.scriptId;
|
|
181
|
-
this.mainFile = params.url.replace('file:///', '');
|
|
182
|
-
// load text of a script
|
|
183
|
-
this.scripts[this.mainScriptId] = this.Debugger.getScriptSource({
|
|
184
|
-
scriptId: this.mainScriptId,
|
|
185
|
-
}).then((script) => ({
|
|
186
|
-
script: script.scriptSource,
|
|
187
|
-
scriptId: this.mainScriptId,
|
|
188
|
-
}));
|
|
189
|
-
// sometimes the pause event comes before scriptParsed
|
|
190
|
-
if (this.delayedContext) {
|
|
191
|
-
console.log('Send to debugger: readyToDebug');
|
|
192
|
-
void this.scripts[this.mainScriptId]?.then(data => {
|
|
193
|
-
// console.log(`Send to debugger: readyToDebug ${JSON.stringify(data)}`);
|
|
194
|
-
sendToHost({
|
|
195
|
-
cmd: 'readyToDebug',
|
|
196
|
-
scriptId: this.mainScriptId,
|
|
197
|
-
script: data.script,
|
|
198
|
-
context: this.delayedContext,
|
|
199
|
-
url: this.mainFile,
|
|
200
|
-
});
|
|
201
|
-
this.delayedContext = null;
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
else if (domain === 'Debugger' && name === 'resumed') {
|
|
208
|
-
this.Debugger.emit(name, params);
|
|
209
|
-
sendToHost({ cmd: 'resumed', context: params });
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
else if (domain === 'Debugger' && name === 'paused') {
|
|
213
|
-
console.log(`PAUSED!! ${alreadyPausedOnFirstLine}`);
|
|
214
|
-
if (!alreadyPausedOnFirstLine && params.reason === 'exception') {
|
|
215
|
-
// ignore all exceptions by start
|
|
216
|
-
this.Debugger.resume();
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
//console.warn(`${fullName}: => \n${JSON.stringify(params, null, 2)}`);
|
|
220
|
-
this.Debugger.emit(name, params);
|
|
221
|
-
if (!alreadyPausedOnFirstLine) {
|
|
222
|
-
alreadyPausedOnFirstLine = true;
|
|
223
|
-
// sometimes the pause event comes before scriptParsed
|
|
224
|
-
if (this.mainScriptId && this.scripts[this.mainScriptId]) {
|
|
225
|
-
void this.scripts[this.mainScriptId]?.then(data => sendToHost({
|
|
226
|
-
cmd: 'readyToDebug',
|
|
227
|
-
scriptId: data.scriptId,
|
|
228
|
-
script: data.script,
|
|
229
|
-
context: params,
|
|
230
|
-
url: this.mainFile,
|
|
231
|
-
}));
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
// store context to send it later when script ID will be known
|
|
235
|
-
this.delayedContext = params;
|
|
236
|
-
console.log('PAUSED, but no scriptId');
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
// this.scripts[params.loca]
|
|
241
|
-
// .then(data =>
|
|
242
|
-
sendToHost({ cmd: 'paused', context: params });
|
|
243
|
-
}
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
if (domain === 'Runtime') {
|
|
247
|
-
//console.warn(`${fullName}: => \n${JSON.stringify(params, null, 2)}`);
|
|
248
|
-
}
|
|
249
|
-
if (domain === 'Runtime' && name === 'consoleAPICalled') {
|
|
250
|
-
const text = params.args[0].value;
|
|
251
|
-
if (instanceToDebug) {
|
|
252
|
-
sendToHost({
|
|
253
|
-
cmd: 'log',
|
|
254
|
-
severity: params.type === 'warning' ? 'warn' : 'error',
|
|
255
|
-
text,
|
|
256
|
-
ts: Date.now(),
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
else if (text.includes(`$$${scriptToDebug}$$`)) {
|
|
260
|
-
console.log(`${fullName} [${params.executionContextId}]: => ${text}`);
|
|
261
|
-
const [severity, _text] = text.split(`$$${scriptToDebug}$$`);
|
|
262
|
-
sendToHost({
|
|
263
|
-
cmd: 'log',
|
|
264
|
-
severity: severity,
|
|
265
|
-
text: _text,
|
|
266
|
-
ts: params.args[1] && params.args[1].value ? params.args[1].value : Date.now(),
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
else if (params.type === 'warning' || params.type === 'error') {
|
|
270
|
-
sendToHost({
|
|
271
|
-
cmd: 'log',
|
|
272
|
-
severity: params.type === 'warning' ? 'warn' : 'error',
|
|
273
|
-
text,
|
|
274
|
-
ts: Date.now(),
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
return;
|
|
179
|
+
mainFile = '';
|
|
180
|
+
mainScriptSource = null;
|
|
181
|
+
/** Sometimes the first pause comes before the main script is parsed */
|
|
182
|
+
delayedContext = null;
|
|
183
|
+
pausedOnFirstLine = false;
|
|
184
|
+
/** Execution context of the debugged script */
|
|
185
|
+
scriptContextId;
|
|
186
|
+
finished = false;
|
|
187
|
+
constructor(file, args) {
|
|
188
|
+
this.file = file;
|
|
189
|
+
this.args = args;
|
|
190
|
+
this.Debugger = createDomain('Debugger', this.client);
|
|
191
|
+
this.Runtime = createDomain('Runtime', this.client);
|
|
192
|
+
this.client.on('debugEvent', (fullName, params) => this.handleDebugEvent(fullName, params || {}));
|
|
193
|
+
}
|
|
194
|
+
async start() {
|
|
195
|
+
const [child, port, host] = await runScript(this.file, this.args, (text, isError) => this.childPrint(text, isError));
|
|
196
|
+
this.child = child;
|
|
197
|
+
child.on('exit', code => this.finish(`Process exited with code ${code}`));
|
|
198
|
+
for (let attempt = 1;; attempt++) {
|
|
199
|
+
try {
|
|
200
|
+
await this.client.connect(port, host);
|
|
201
|
+
break;
|
|
278
202
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
else if (name === 'executionContextDestroyed') {
|
|
284
|
-
console.warn(`${fullName}: =>\n${JSON.stringify(params, null, 2)}`);
|
|
285
|
-
sendToHost({ cmd: 'finished', context: params });
|
|
203
|
+
catch (e) {
|
|
204
|
+
debuglog(`Connection attempt #${attempt} failed: ${e}`);
|
|
205
|
+
if (attempt >= 10) {
|
|
206
|
+
throw new StartupError(`Cannot connect to the debugger on ${host}:${port}`);
|
|
286
207
|
}
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
else if (domain === 'Runtime' &&
|
|
290
|
-
name === 'executionContextDestroyed' &&
|
|
291
|
-
params.executionContextId === 1) {
|
|
292
|
-
sendToHost({ cmd: 'finished', context: params });
|
|
293
|
-
console.log('Exited!');
|
|
294
|
-
setTimeout(() => process.exit(125), 200);
|
|
208
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
295
209
|
}
|
|
296
|
-
else if (domain === 'Debugger' && name === 'scriptFailedToParse') {
|
|
297
|
-
// ignore
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
console.warn(`${fullName}: =>\n${JSON.stringify(params, null, 2)}`);
|
|
301
|
-
/*if (domain in this) {
|
|
302
|
-
this[domain].emit(name, params);
|
|
303
|
-
}*/
|
|
304
|
-
};
|
|
305
|
-
this.client.on('debugEvent', this.handleDebugEvent);
|
|
306
|
-
const startRepl = (0, debugger_1.default)(this);
|
|
307
|
-
// Handle all possible exits
|
|
308
|
-
process.on('exit', () => this.killChild());
|
|
309
|
-
process.once('SIGTERM', process.exit.bind(process, 0));
|
|
310
|
-
process.once('SIGHUP', process.exit.bind(process, 0));
|
|
311
|
-
this.run()
|
|
312
|
-
.then(() => startRepl())
|
|
313
|
-
.then(repl => {
|
|
314
|
-
this.repl = repl;
|
|
315
|
-
this.repl.on('exit', () => process.exit(0));
|
|
316
|
-
this.paused = false;
|
|
317
|
-
})
|
|
318
|
-
.then(null, error => process.nextTick(() => {
|
|
319
|
-
throw error;
|
|
320
|
-
}));
|
|
321
|
-
}
|
|
322
|
-
suspendReplWhile(fn) {
|
|
323
|
-
if (this.repl) {
|
|
324
|
-
this.repl.pause();
|
|
325
210
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
.
|
|
330
|
-
this.
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
335
|
-
this.stdin.resume();
|
|
336
|
-
})
|
|
337
|
-
.then(null, error => process.nextTick(() => {
|
|
338
|
-
throw error;
|
|
339
|
-
}));
|
|
211
|
+
await Promise.all([
|
|
212
|
+
this.Runtime.enable(),
|
|
213
|
+
this.Debugger.enable(),
|
|
214
|
+
this.Debugger.setPauseOnExceptions({ state: 'none' }),
|
|
215
|
+
this.Debugger.setAsyncCallStackDepth({ maxDepth: 1 }),
|
|
216
|
+
this.Debugger.setBlackboxPatterns({ patterns: scriptToDebug ? SCRIPT_BLACKBOX_PATTERNS : [] }),
|
|
217
|
+
this.Runtime.runIfWaitingForDebugger(),
|
|
218
|
+
]);
|
|
340
219
|
}
|
|
341
220
|
killChild() {
|
|
342
221
|
this.client.reset();
|
|
@@ -345,384 +224,420 @@ class NodeInspector {
|
|
|
345
224
|
this.child = null;
|
|
346
225
|
}
|
|
347
226
|
}
|
|
348
|
-
|
|
349
|
-
this.
|
|
350
|
-
|
|
351
|
-
this.child = child;
|
|
352
|
-
let connectionAttempts = 0;
|
|
353
|
-
const attemptConnect = () => {
|
|
354
|
-
++connectionAttempts;
|
|
355
|
-
debuglog('connection attempt #%d', connectionAttempts);
|
|
356
|
-
this.stdout.write('.');
|
|
357
|
-
return this.client.connect(port, host).then(() => {
|
|
358
|
-
debuglog('connection established');
|
|
359
|
-
this.stdout.write(' ok');
|
|
360
|
-
}, (error) => {
|
|
361
|
-
debuglog('connect failed', error);
|
|
362
|
-
// If it's failed to connect 10 times, then print a failed message
|
|
363
|
-
if (connectionAttempts >= 10) {
|
|
364
|
-
this.stdout.write(' failed to connect, please retry\n');
|
|
365
|
-
process.exit(1);
|
|
366
|
-
}
|
|
367
|
-
return new Promise(resolve => setTimeout(resolve, 500)).then(attemptConnect);
|
|
368
|
-
});
|
|
369
|
-
};
|
|
370
|
-
this.print(`connecting to ${host}:${port} ..`, true);
|
|
371
|
-
return attemptConnect();
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
clearLine() {
|
|
375
|
-
if (this.stdout.isTTY) {
|
|
376
|
-
this.stdout.cursorTo(0);
|
|
377
|
-
this.stdout.clearLine(1);
|
|
378
|
-
}
|
|
379
|
-
else {
|
|
380
|
-
this.stdout.write('\b');
|
|
227
|
+
finish(reason) {
|
|
228
|
+
if (this.finished) {
|
|
229
|
+
return;
|
|
381
230
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
this.
|
|
231
|
+
this.finished = true;
|
|
232
|
+
debuglog(reason);
|
|
233
|
+
sendToHost({ cmd: 'finished', text: reason });
|
|
234
|
+
this.killChild();
|
|
235
|
+
setTimeout(() => process.exit(0), 200);
|
|
386
236
|
}
|
|
387
237
|
childPrint(text, isError) {
|
|
388
|
-
isError
|
|
389
|
-
|
|
390
|
-
.toString()
|
|
238
|
+
if (isError) {
|
|
239
|
+
const lines = text
|
|
391
240
|
.split(/\r\n|\r|\n/g)
|
|
392
|
-
.filter(
|
|
393
|
-
.map(
|
|
394
|
-
|
|
395
|
-
if (!this.paused) {
|
|
396
|
-
this.repl?.displayPrompt(true);
|
|
241
|
+
.filter(line => !!line)
|
|
242
|
+
.map(line => `< ${line}`);
|
|
243
|
+
lines.length && process.stdout.write(`${lines.join('\n')}\n`);
|
|
397
244
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
245
|
+
// The debugged process ended, but it waits till the debugger disconnects
|
|
246
|
+
if (text.includes('Waiting for the debugger to disconnect')) {
|
|
247
|
+
this.finish('Process finished');
|
|
401
248
|
}
|
|
402
249
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
250
|
+
handleDebugEvent(fullName, params) {
|
|
251
|
+
switch (fullName) {
|
|
252
|
+
case 'Debugger.scriptParsed':
|
|
253
|
+
this.onScriptParsed(params);
|
|
254
|
+
break;
|
|
255
|
+
case 'Debugger.paused':
|
|
256
|
+
this.onPaused(params);
|
|
257
|
+
break;
|
|
258
|
+
case 'Debugger.resumed':
|
|
259
|
+
sendToHost({ cmd: 'resumed', context: params });
|
|
260
|
+
break;
|
|
261
|
+
case 'Runtime.consoleAPICalled':
|
|
262
|
+
this.onConsoleApiCalled(params);
|
|
263
|
+
break;
|
|
264
|
+
case 'Runtime.executionContextCreated':
|
|
265
|
+
// main.ts gives the context of a script the name of the script
|
|
266
|
+
if (scriptToDebug && params.context?.name === scriptToDebug) {
|
|
267
|
+
this.scriptContextId = params.context.id;
|
|
268
|
+
}
|
|
269
|
+
break;
|
|
270
|
+
case 'Runtime.executionContextDestroyed':
|
|
271
|
+
if (params.executionContextId === 1) {
|
|
272
|
+
// the main context of the debugged process is gone
|
|
273
|
+
this.finish('Process finished');
|
|
274
|
+
}
|
|
275
|
+
else if (this.scriptContextId !== undefined && params.executionContextId === this.scriptContextId) {
|
|
276
|
+
// the script was stopped, but the process is still running
|
|
277
|
+
sendToHost({ cmd: 'finished', context: params });
|
|
278
|
+
}
|
|
279
|
+
break;
|
|
280
|
+
default:
|
|
281
|
+
debuglog(`${fullName}: ${JSON.stringify(params)}`);
|
|
282
|
+
break;
|
|
436
283
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
throw e;
|
|
284
|
+
}
|
|
285
|
+
onScriptParsed(params) {
|
|
286
|
+
const { scriptId, url } = params;
|
|
287
|
+
if (!scriptId || !url) {
|
|
288
|
+
return;
|
|
443
289
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
host,
|
|
449
|
-
port,
|
|
450
|
-
isRemote,
|
|
451
|
-
script,
|
|
452
|
-
scriptArgs,
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
function startInspect(argv = process.argv.slice(2), stdin = process.stdin, stdout = process.stdout) {
|
|
456
|
-
/*
|
|
457
|
-
if (argv.length < 1) {
|
|
458
|
-
const invokedAs = runAsStandalone ? 'node-inspect' : `${process.argv0} ${process.argv[1]}`;
|
|
459
|
-
|
|
460
|
-
console.error(`Usage: ${invokedAs} script.js`);
|
|
461
|
-
console.error(` ${invokedAs} <host>:<port>`);
|
|
462
|
-
console.error(` ${invokedAs} -p <pid>`);
|
|
463
|
-
process.exit(1);
|
|
464
|
-
}
|
|
465
|
-
*/
|
|
466
|
-
const options = parseArgv(argv);
|
|
467
|
-
inspector = new NodeInspector(options, stdin, stdout);
|
|
468
|
-
stdin.resume();
|
|
469
|
-
function handleUnexpectedError(e) {
|
|
470
|
-
if (!(e instanceof StartupError)) {
|
|
471
|
-
console.error('There was an internal error in node-inspect. Please report this bug.');
|
|
472
|
-
console.error(e.message);
|
|
473
|
-
console.error(e.stack);
|
|
290
|
+
this.scriptUrls[scriptId] = url;
|
|
291
|
+
const isMain = scriptToDebug ? url === scriptToDebug : !!instanceToDebug && isUrlOfFile(url, instanceToDebug);
|
|
292
|
+
if (!isMain) {
|
|
293
|
+
return;
|
|
474
294
|
}
|
|
475
|
-
|
|
476
|
-
|
|
295
|
+
debuglog(`Script to debug parsed: ${scriptId}`);
|
|
296
|
+
this.mainScriptId = scriptId;
|
|
297
|
+
this.mainFile = url.replace('file:///', '');
|
|
298
|
+
this.mainScriptSource = this.Debugger.getScriptSource({ scriptId }).then((result) => result.scriptSource);
|
|
299
|
+
if (this.delayedContext) {
|
|
300
|
+
const context = this.delayedContext;
|
|
301
|
+
this.delayedContext = null;
|
|
302
|
+
void this.sendReadyToDebug(context);
|
|
477
303
|
}
|
|
478
|
-
inspector.child?.kill();
|
|
479
|
-
process.exit(1);
|
|
480
|
-
}
|
|
481
|
-
process.on('uncaughtException', handleUnexpectedError);
|
|
482
|
-
}
|
|
483
|
-
function extractErrorMessage(stack) {
|
|
484
|
-
if (!stack) {
|
|
485
|
-
return '<unknown>';
|
|
486
304
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
try {
|
|
500
|
-
message = JSON.parse(message);
|
|
305
|
+
onPaused(params) {
|
|
306
|
+
if (!this.pausedOnFirstLine) {
|
|
307
|
+
if (params.reason === 'Break on start' && !breakOnStart) {
|
|
308
|
+
// the process was started with --inspect-brk only to have time to set up the debugger
|
|
309
|
+
this.Debugger.resume().catch(e => reportError('Cannot resume', e));
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (params.reason === 'exception') {
|
|
313
|
+
// ignore all exceptions till the start of the script
|
|
314
|
+
this.Debugger.resume().catch(e => reportError('Cannot resume', e));
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
501
317
|
}
|
|
502
|
-
|
|
503
|
-
|
|
318
|
+
this.enrichCallFrames(params);
|
|
319
|
+
if (this.pausedOnFirstLine &&
|
|
320
|
+
scriptToDebug &&
|
|
321
|
+
params.reason === 'step' &&
|
|
322
|
+
!params.callFrames?.[0]?.url?.startsWith('script.js.')) {
|
|
323
|
+
// Stepping left the script, e.g., at the end of a callback. The blackbox patterns do not cover
|
|
324
|
+
// all internals of node.js, so continue the execution instead of stopping there
|
|
325
|
+
this.Debugger.resume().catch(e => reportError('Cannot resume', e));
|
|
326
|
+
return;
|
|
504
327
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
});
|
|
508
|
-
sendToHost({ cmd: 'ready' });
|
|
509
|
-
function processCommand(data) {
|
|
510
|
-
console.log(`[DEBUGGER]: processCommand: ${JSON.stringify(data)}`);
|
|
511
|
-
if (data.cmd === 'start') {
|
|
512
|
-
scriptToDebug = data.scriptName;
|
|
513
|
-
// we can request the script by name or iobroker instance to debug
|
|
514
|
-
if (scriptToDebug) {
|
|
515
|
-
startInspect([
|
|
516
|
-
`${__dirname}/../main.js`,
|
|
517
|
-
(data.instance || 0).toString(),
|
|
518
|
-
'--debug',
|
|
519
|
-
'--debugScript',
|
|
520
|
-
scriptToDebug,
|
|
521
|
-
]);
|
|
328
|
+
if (this.pausedOnFirstLine) {
|
|
329
|
+
sendToHost({ cmd: 'paused', context: params });
|
|
522
330
|
}
|
|
523
331
|
else {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
try {
|
|
528
|
-
file = require.resolve(`iobroker.${adapter}`);
|
|
332
|
+
this.pausedOnFirstLine = true;
|
|
333
|
+
if (this.mainScriptSource) {
|
|
334
|
+
void this.sendReadyToDebug(params);
|
|
529
335
|
}
|
|
530
|
-
|
|
531
|
-
//
|
|
532
|
-
|
|
533
|
-
if ((0, node_fs_1.existsSync)(dir)) {
|
|
534
|
-
const pack = require((0, node_path_1.join)(dir, 'package.json'));
|
|
535
|
-
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(dir, pack.main || `${adapter}.js`))) {
|
|
536
|
-
file = (0, node_path_1.join)(dir, pack.main || `${adapter}.js`);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
if (!file) {
|
|
540
|
-
sendToHost({ cmd: 'error', error: `Cannot locate iobroker.${adapter}`, errorContext: e });
|
|
541
|
-
setTimeout(() => {
|
|
542
|
-
sendToHost({ cmd: 'finished', context: `Cannot locate iobroker.${adapter}` });
|
|
543
|
-
setTimeout(() => process.exit(124), 500);
|
|
544
|
-
}, 200);
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
336
|
+
else {
|
|
337
|
+
// store the context to send it, when the main script is parsed
|
|
338
|
+
this.delayedContext = params;
|
|
547
339
|
}
|
|
548
|
-
file = file.replace(/\\/g, '/');
|
|
549
|
-
instanceToDebug = file;
|
|
550
|
-
console.log(`Start ${file} ${instance} --debug`);
|
|
551
|
-
startInspect([file, instance, '--debug']);
|
|
552
340
|
}
|
|
553
341
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
inspector.Debugger.getScriptSource({ scriptId: data.scriptId }).then((script) => sendToHost({ cmd: 'script', scriptId: data.scriptId, text: script.scriptSource }));
|
|
559
|
-
}
|
|
560
|
-
else if (data.cmd === 'cont') {
|
|
561
|
-
inspector.Debugger.resume().catch((e) => sendToHost({ cmd: 'error', error: e }));
|
|
562
|
-
}
|
|
563
|
-
else if (data.cmd === 'next') {
|
|
564
|
-
inspector.Debugger.stepOver().catch((e) => sendToHost({ cmd: 'error', error: e }));
|
|
565
|
-
}
|
|
566
|
-
else if (data.cmd === 'pause') {
|
|
567
|
-
inspector.Debugger.pause().catch((e) => sendToHost({ cmd: 'error', error: e }));
|
|
568
|
-
}
|
|
569
|
-
else if (data.cmd === 'step') {
|
|
570
|
-
inspector.Debugger.stepInto().catch((e) => sendToHost({ cmd: 'error', error: e }));
|
|
571
|
-
}
|
|
572
|
-
else if (data.cmd === 'out') {
|
|
573
|
-
inspector.Debugger.stepOut().catch((e) => sendToHost({ cmd: 'error', error: e }));
|
|
574
|
-
}
|
|
575
|
-
else if (data.cmd === 'sb') {
|
|
576
|
-
console.log(JSON.stringify(data));
|
|
577
|
-
void Promise.all(data.breakpoints?.map((bp) => inspector.Debugger.setBreakpoint({
|
|
578
|
-
location: {
|
|
579
|
-
scriptId: bp.scriptId,
|
|
580
|
-
lineNumber: bp.lineNumber,
|
|
581
|
-
columnNumber: bp.columnNumber,
|
|
582
|
-
},
|
|
583
|
-
})
|
|
584
|
-
.then((result) => ({
|
|
585
|
-
id: result.breakpointId,
|
|
586
|
-
location: result.actualLocation,
|
|
587
|
-
}))
|
|
588
|
-
.catch((e) => sendToHost({
|
|
589
|
-
cmd: 'error',
|
|
590
|
-
error: `Cannot set breakpoint: ${e}`,
|
|
591
|
-
errorContext: e,
|
|
592
|
-
bp,
|
|
593
|
-
}))) || []).then(breakpoints => sendToHost({ cmd: 'sb', breakpoints }));
|
|
594
|
-
}
|
|
595
|
-
else if (data.cmd === 'cb') {
|
|
596
|
-
void Promise.all(data.breakpoints?.map(breakpointId => inspector.Debugger.removeBreakpoint({ breakpointId })
|
|
597
|
-
.then(() => breakpointId)
|
|
598
|
-
.catch((e) => {
|
|
599
|
-
console.error(`[DEBUGGER]: Cannot clear breakpoint: ${e}`);
|
|
342
|
+
async sendReadyToDebug(context) {
|
|
343
|
+
try {
|
|
344
|
+
const script = await this.mainScriptSource;
|
|
345
|
+
this.enrichCallFrames(context);
|
|
600
346
|
sendToHost({
|
|
601
|
-
cmd: '
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
347
|
+
cmd: 'readyToDebug',
|
|
348
|
+
scriptId: this.mainScriptId,
|
|
349
|
+
script,
|
|
350
|
+
context,
|
|
351
|
+
url: this.mainFile,
|
|
605
352
|
});
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
|
|
353
|
+
}
|
|
354
|
+
catch (e) {
|
|
355
|
+
reportError('Cannot read the source of the script to debug', e);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
enrichCallFrames(context) {
|
|
359
|
+
context.callFrames?.forEach(frame => {
|
|
360
|
+
if (!frame.url) {
|
|
361
|
+
frame.url = this.scriptUrls[frame.location.scriptId] || '';
|
|
362
|
+
}
|
|
609
363
|
});
|
|
610
364
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
error: `Cannot watch expr: ${e}`,
|
|
615
|
-
errorContext: e,
|
|
616
|
-
expr,
|
|
617
|
-
}))) || []).then(() => console.log('Watch done'));
|
|
618
|
-
}
|
|
619
|
-
else if (data.cmd === 'unwatch') {
|
|
620
|
-
void Promise.all(data.expressions?.map(expr => inspector.Debugger.unwatch(expr).catch((e) => sendToHost({
|
|
621
|
-
cmd: 'error',
|
|
622
|
-
error: `Cannot unwatch expr: ${e}`,
|
|
623
|
-
errorContext: e,
|
|
624
|
-
expr,
|
|
625
|
-
}))) || []).then(() => console.log('Watch done'));
|
|
626
|
-
}
|
|
627
|
-
else if (data.cmd === 'scope') {
|
|
628
|
-
void Promise.all(data.scopes
|
|
629
|
-
?.filter(scope => scope?.object?.objectId)
|
|
630
|
-
.map(scope => inspector.Runtime.getProperties({
|
|
631
|
-
objectId: scope.object.objectId,
|
|
632
|
-
generatePreview: true,
|
|
633
|
-
})
|
|
634
|
-
.then((result) => {
|
|
635
|
-
return { type: scope.type, properties: result };
|
|
636
|
-
})
|
|
637
|
-
.catch((e) => sendToHost({
|
|
638
|
-
cmd: 'error',
|
|
639
|
-
error: `Cannot get scopes expr: ${e}`,
|
|
640
|
-
errorContext: e,
|
|
641
|
-
}))) || []).then(scopes => sendToHost({ cmd: 'scope', scopes }));
|
|
642
|
-
}
|
|
643
|
-
else if (data.cmd === 'setValue') {
|
|
644
|
-
inspector.Debugger.setVariableValue({
|
|
645
|
-
variableName: data.variableName,
|
|
646
|
-
scopeNumber: data.scopeNumber,
|
|
647
|
-
newValue: data.newValue,
|
|
648
|
-
callFrameId: data.callFrameId,
|
|
649
|
-
})
|
|
650
|
-
.catch((e) => {
|
|
651
|
-
console.error(`Cannot setValue "${data.variableName}": ${e}`);
|
|
365
|
+
onConsoleApiCalled(params) {
|
|
366
|
+
const args = params.args || [];
|
|
367
|
+
if (instanceToDebug) {
|
|
652
368
|
sendToHost({
|
|
653
|
-
cmd: '
|
|
654
|
-
|
|
655
|
-
|
|
369
|
+
cmd: 'log',
|
|
370
|
+
severity: consoleTypeToSeverity(params.type),
|
|
371
|
+
text: args.map(arg => remoteObjectToString(arg)).join(' '),
|
|
372
|
+
ts: Date.now(),
|
|
656
373
|
});
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
// The sandbox logs in debug mode as `console.log(`${severity}$$${scriptName}$$${message}`, Date.now())`
|
|
377
|
+
const text = remoteObjectToString(args[0]);
|
|
378
|
+
const marker = `$$${scriptToDebug}$$`;
|
|
379
|
+
const pos = text.indexOf(marker);
|
|
380
|
+
if (pos !== -1) {
|
|
660
381
|
sendToHost({
|
|
661
|
-
cmd: '
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
callFrameId: data.callFrameId,
|
|
382
|
+
cmd: 'log',
|
|
383
|
+
severity: text.substring(0, pos),
|
|
384
|
+
text: text.substring(pos + marker.length),
|
|
385
|
+
ts: typeof args[1]?.value === 'number' ? args[1].value : Date.now(),
|
|
666
386
|
});
|
|
667
|
-
}
|
|
387
|
+
}
|
|
388
|
+
else if (params.type === 'warning' || params.type === 'error') {
|
|
389
|
+
sendToHost({
|
|
390
|
+
cmd: 'log',
|
|
391
|
+
severity: consoleTypeToSeverity(params.type),
|
|
392
|
+
text: args.map(arg => remoteObjectToString(arg)).join(' '),
|
|
393
|
+
ts: Date.now(),
|
|
394
|
+
});
|
|
395
|
+
}
|
|
668
396
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
})
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
397
|
+
}
|
|
398
|
+
function resolveAdapterMainFile(adapter) {
|
|
399
|
+
try {
|
|
400
|
+
return require.resolve(`iobroker.${adapter}`);
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
// try to locate it in the same node_modules directory as this adapter
|
|
404
|
+
const dir = (0, node_path_1.normalize)((0, node_path_1.join)(__dirname, '..', '..', '..', `iobroker.${adapter}`));
|
|
405
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(dir, 'package.json'))) {
|
|
406
|
+
try {
|
|
407
|
+
const pack = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(dir, 'package.json'), 'utf8'));
|
|
408
|
+
const main = (0, node_path_1.join)(dir, pack.main || `${adapter}.js`);
|
|
409
|
+
if ((0, node_fs_1.existsSync)(main)) {
|
|
410
|
+
return main;
|
|
411
|
+
}
|
|
681
412
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
variableName: `Cannot setValue: ${e}`,
|
|
687
|
-
errorContext: e,
|
|
688
|
-
}))) || []).then(expressions => {
|
|
689
|
-
sendToHost({ cmd: 'expressions', expressions });
|
|
690
|
-
});
|
|
413
|
+
catch {
|
|
414
|
+
// ignore
|
|
415
|
+
}
|
|
416
|
+
}
|
|
691
417
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
errorContext: e,
|
|
699
|
-
}));
|
|
700
|
-
}
|
|
701
|
-
else if (data.cmd === 'getPossibleBreakpoints') {
|
|
702
|
-
inspector.Debugger.getPossibleBreakpoints({ start: data.start, end: data.end })
|
|
703
|
-
.then((breakpoints) => sendToHost({ cmd: 'getPossibleBreakpoints', breakpoints: breakpoints.locations }))
|
|
704
|
-
.catch((e) => sendToHost({
|
|
705
|
-
cmd: 'getPossibleBreakpoints',
|
|
706
|
-
variableName: `Cannot getPossibleBreakpoints: ${e}`,
|
|
707
|
-
errorContext: e,
|
|
708
|
-
}));
|
|
418
|
+
return undefined;
|
|
419
|
+
}
|
|
420
|
+
function startDebugging(data) {
|
|
421
|
+
if (inspector) {
|
|
422
|
+
reportError('The debugger is already started');
|
|
423
|
+
return;
|
|
709
424
|
}
|
|
710
|
-
|
|
711
|
-
|
|
425
|
+
if (data.scriptName) {
|
|
426
|
+
scriptToDebug = data.scriptName;
|
|
427
|
+
inspector = new NodeInspector((0, node_path_1.join)(__dirname, '..', 'main.js'), [
|
|
428
|
+
(data.instance || 0).toString(),
|
|
429
|
+
'--debug',
|
|
430
|
+
'--debugScript',
|
|
431
|
+
scriptToDebug,
|
|
432
|
+
]);
|
|
712
433
|
}
|
|
434
|
+
else {
|
|
435
|
+
const [adapter, instance] = (data.adapterInstance || '').split('.');
|
|
436
|
+
const file = resolveAdapterMainFile(adapter);
|
|
437
|
+
if (!file) {
|
|
438
|
+
reportError(`Cannot locate iobroker.${adapter}`);
|
|
439
|
+
setTimeout(() => {
|
|
440
|
+
sendToHost({ cmd: 'finished', context: `Cannot locate iobroker.${adapter}` });
|
|
441
|
+
setTimeout(() => process.exit(0), 500);
|
|
442
|
+
}, 200);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
instanceToDebug = file;
|
|
446
|
+
inspector = new NodeInspector(file, [instance || '0', '--debug']);
|
|
447
|
+
}
|
|
448
|
+
inspector.start().catch((e) => {
|
|
449
|
+
reportError(e instanceof StartupError
|
|
450
|
+
? e.message
|
|
451
|
+
: `Internal error in inspector: ${e instanceof Error ? e.stack : errorToString(e)}`);
|
|
452
|
+
inspector?.killChild();
|
|
453
|
+
setTimeout(() => process.exit(1), 200);
|
|
454
|
+
});
|
|
713
455
|
}
|
|
714
|
-
function
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
456
|
+
async function processCommand(data) {
|
|
457
|
+
debuglog(`processCommand: ${JSON.stringify(data)}`);
|
|
458
|
+
if (data.cmd === 'start') {
|
|
459
|
+
startDebugging(data);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
if (data.cmd === 'end') {
|
|
463
|
+
process.exit(0);
|
|
464
|
+
}
|
|
465
|
+
if (!inspector) {
|
|
466
|
+
reportError(`Cannot process "${data.cmd}": the debugger is not started`);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const { Debugger, Runtime } = inspector;
|
|
470
|
+
switch (data.cmd) {
|
|
471
|
+
case 'source':
|
|
472
|
+
try {
|
|
473
|
+
const { scriptSource } = (await Debugger.getScriptSource({
|
|
474
|
+
scriptId: data.scriptId,
|
|
475
|
+
}));
|
|
476
|
+
sendToHost({ cmd: 'script', scriptId: data.scriptId, text: scriptSource });
|
|
477
|
+
}
|
|
478
|
+
catch (e) {
|
|
479
|
+
reportError(`Cannot read the source of script ${data.scriptId}`, e);
|
|
480
|
+
}
|
|
481
|
+
break;
|
|
482
|
+
case 'cont':
|
|
483
|
+
case 'next':
|
|
484
|
+
case 'step':
|
|
485
|
+
case 'out':
|
|
486
|
+
case 'pause': {
|
|
487
|
+
const method = { cont: 'resume', next: 'stepOver', step: 'stepInto', out: 'stepOut', pause: 'pause' }[data.cmd];
|
|
488
|
+
try {
|
|
489
|
+
await Debugger[method]();
|
|
490
|
+
}
|
|
491
|
+
catch (e) {
|
|
492
|
+
reportError(`Cannot execute "${data.cmd}"`, e);
|
|
493
|
+
}
|
|
494
|
+
break;
|
|
719
495
|
}
|
|
720
|
-
|
|
721
|
-
|
|
496
|
+
case 'sb': {
|
|
497
|
+
const breakpoints = await Promise.all((data.breakpoints || []).map(async (bp) => {
|
|
498
|
+
try {
|
|
499
|
+
const result = (await Debugger.setBreakpoint({
|
|
500
|
+
location: {
|
|
501
|
+
scriptId: bp.scriptId,
|
|
502
|
+
lineNumber: bp.lineNumber,
|
|
503
|
+
columnNumber: bp.columnNumber,
|
|
504
|
+
},
|
|
505
|
+
}));
|
|
506
|
+
return { id: result.breakpointId, location: result.actualLocation };
|
|
507
|
+
}
|
|
508
|
+
catch (e) {
|
|
509
|
+
reportError(`Cannot set breakpoint on line ${bp.lineNumber + 1}`, e);
|
|
510
|
+
return null;
|
|
511
|
+
}
|
|
512
|
+
}));
|
|
513
|
+
sendToHost({ cmd: 'sb', breakpoints: breakpoints.filter(bp => bp) });
|
|
514
|
+
break;
|
|
722
515
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
516
|
+
case 'cb': {
|
|
517
|
+
const breakpointIds = await Promise.all((data.breakpoints || []).map(async (breakpointId) => {
|
|
518
|
+
try {
|
|
519
|
+
await Debugger.removeBreakpoint({ breakpointId });
|
|
520
|
+
}
|
|
521
|
+
catch (e) {
|
|
522
|
+
// The breakpoint does not exist (anymore), so it is removed anyway
|
|
523
|
+
debuglog(`Cannot clear breakpoint ${breakpointId}: ${e}`);
|
|
524
|
+
}
|
|
525
|
+
return breakpointId;
|
|
526
|
+
}));
|
|
527
|
+
sendToHost({ cmd: 'cb', breakpoints: breakpointIds });
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
case 'scope': {
|
|
531
|
+
const scopes = await Promise.all((data.scopes || [])
|
|
532
|
+
.filter(scope => scope?.object?.objectId)
|
|
533
|
+
.map(async (scope) => {
|
|
534
|
+
try {
|
|
535
|
+
const properties = (await Runtime.getProperties({
|
|
536
|
+
objectId: scope.object.objectId,
|
|
537
|
+
generatePreview: true,
|
|
538
|
+
}));
|
|
539
|
+
return { type: scope.type, name: scope.name, index: scope.index, properties };
|
|
540
|
+
}
|
|
541
|
+
catch (e) {
|
|
542
|
+
// e.g., the execution was resumed in the meantime
|
|
543
|
+
debuglog(`Cannot read scope: ${e}`);
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
}));
|
|
547
|
+
sendToHost({ cmd: 'scope', scopes: scopes.filter(scope => scope) });
|
|
548
|
+
break;
|
|
549
|
+
}
|
|
550
|
+
case 'setValue':
|
|
551
|
+
try {
|
|
552
|
+
await Debugger.setVariableValue({
|
|
553
|
+
variableName: data.variableName,
|
|
554
|
+
scopeNumber: data.scopeNumber,
|
|
555
|
+
newValue: toCallArgument(data.newValue?.value),
|
|
556
|
+
callFrameId: data.callFrameId,
|
|
557
|
+
});
|
|
558
|
+
sendToHost({
|
|
559
|
+
cmd: 'setValue',
|
|
560
|
+
variableName: data.variableName,
|
|
561
|
+
scopeNumber: data.scopeNumber,
|
|
562
|
+
newValue: data.newValue,
|
|
563
|
+
callFrameId: data.callFrameId,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
catch (e) {
|
|
567
|
+
reportError(`Cannot set value of "${data.variableName}"`, e);
|
|
568
|
+
}
|
|
569
|
+
break;
|
|
570
|
+
case 'expressions': {
|
|
571
|
+
const expressions = await Promise.all((data.expressions || []).map(async (item) => {
|
|
572
|
+
try {
|
|
573
|
+
const { result } = (await Debugger.evaluateOnCallFrame({
|
|
574
|
+
callFrameId: data.callFrameId,
|
|
575
|
+
expression: item.name,
|
|
576
|
+
objectGroup: 'node-inspect',
|
|
577
|
+
generatePreview: true,
|
|
578
|
+
}));
|
|
579
|
+
return { name: item.name, result };
|
|
580
|
+
}
|
|
581
|
+
catch (e) {
|
|
582
|
+
return {
|
|
583
|
+
name: item.name,
|
|
584
|
+
result: {
|
|
585
|
+
type: 'object',
|
|
586
|
+
subtype: 'error',
|
|
587
|
+
className: 'Error',
|
|
588
|
+
description: errorToString(e),
|
|
589
|
+
},
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
}));
|
|
593
|
+
sendToHost({ cmd: 'expressions', expressions });
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
case 'stopOnException':
|
|
597
|
+
try {
|
|
598
|
+
await Debugger.setPauseOnExceptions({ state: data.state ? 'all' : 'none' });
|
|
599
|
+
}
|
|
600
|
+
catch (e) {
|
|
601
|
+
reportError('Cannot change "stop on exception"', e);
|
|
602
|
+
}
|
|
603
|
+
break;
|
|
604
|
+
case 'getPossibleBreakpoints':
|
|
605
|
+
try {
|
|
606
|
+
const { locations } = (await Debugger.getPossibleBreakpoints({
|
|
607
|
+
start: data.start,
|
|
608
|
+
end: data.end,
|
|
609
|
+
}));
|
|
610
|
+
sendToHost({ cmd: 'getPossibleBreakpoints', breakpoints: locations });
|
|
611
|
+
}
|
|
612
|
+
catch (e) {
|
|
613
|
+
reportError('Cannot get possible breakpoints', e);
|
|
614
|
+
}
|
|
615
|
+
break;
|
|
616
|
+
default:
|
|
617
|
+
console.error(`[DEBUGGER] Unknown command: ${JSON.stringify(data)}`);
|
|
618
|
+
break;
|
|
726
619
|
}
|
|
727
620
|
}
|
|
621
|
+
process.on('message', (message) => {
|
|
622
|
+
let command;
|
|
623
|
+
try {
|
|
624
|
+
command = typeof message === 'string' ? JSON.parse(message) : message;
|
|
625
|
+
}
|
|
626
|
+
catch {
|
|
627
|
+
console.error(`[DEBUGGER] Cannot parse: ${JSON.stringify(message)}`);
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
processCommand(command).catch(e => reportError(`Cannot process "${command?.cmd}"`, e));
|
|
631
|
+
});
|
|
632
|
+
// Handle all possible exits and never leave the debugged process behind
|
|
633
|
+
process.on('exit', () => inspector?.killChild());
|
|
634
|
+
process.on('disconnect', () => process.exit(0));
|
|
635
|
+
process.once('SIGTERM', () => process.exit(0));
|
|
636
|
+
process.once('SIGHUP', () => process.exit(0));
|
|
637
|
+
process.on('uncaughtException', (e) => {
|
|
638
|
+
reportError(`Internal error in inspector: ${e.stack || e.message}`);
|
|
639
|
+
inspector?.killChild();
|
|
640
|
+
process.exit(1);
|
|
641
|
+
});
|
|
642
|
+
sendToHost({ cmd: 'ready' });
|
|
728
643
|
//# sourceMappingURL=inspect.js.map
|