@aiot-nexus/plugin-sdk 1.1.0 → 2.0.0
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 +29 -9
- package/dev-host/main.js +370 -138
- package/dev-host/plugin-host.js +418 -0
- package/dist/.generated/plugin-framework-version.d.ts +1 -1
- package/dist/.generated/plugin-framework-version.js +1 -1
- package/dist/src/cli/run.js +2 -2
- package/dist/src/cli/update-notice.d.ts +20 -0
- package/dist/src/cli/update-notice.js +33 -0
- package/dist/src/cli.js +33 -2
- package/dist/src/dev.js +3 -3
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/nexus-dev-control.d.ts +1 -2
- package/dist/src/nexus-dev-control.js +6 -10
- package/dist/src/nexus-dev.d.ts +5 -2
- package/dist/src/nexus-dev.js +64 -93
- package/dist/src/types.d.ts +12 -9
- package/package.json +5 -3
- package/scripts/sdk-update-check.js +388 -0
- package/templates/minimal/.vscode/launch.json +1 -1
- package/templates/minimal/README.md.tmpl +10 -4
- package/templates/minimal/src/main.ts.tmpl +6 -3
- package/templates/sqlite/.vscode/launch.json +1 -1
- package/templates/sqlite/README.md.tmpl +9 -3
- package/templates/sqlite/src/better-sqlite3.d.ts +1 -0
- package/templates/sqlite/src/main.ts.tmpl +17 -9
package/dev-host/main.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
const fs = require("fs");
|
|
5
5
|
const path = require("path");
|
|
6
|
-
const { app, BrowserWindow, ipcMain, protocol } = require("electron");
|
|
6
|
+
const { app, BrowserWindow, ipcMain, protocol, utilityProcess } = require("electron");
|
|
7
7
|
const sdk = require(path.join(__dirname, "..", "dist", "src", "index.js"));
|
|
8
8
|
|
|
9
9
|
const pluginRendererProtocol = "nexus-plugin";
|
|
@@ -15,7 +15,7 @@ const ipcChannels = {
|
|
|
15
15
|
const pluginPermissions = ["businessLog", "settings", "filesystem", "udp", "serial", "externalProcess", "dialog"];
|
|
16
16
|
const implementedPermissions = new Set(["businessLog", "settings"]);
|
|
17
17
|
const routeGuardPermissions = new Set(["filesystem", "udp", "serial", "dialog"]);
|
|
18
|
-
const
|
|
18
|
+
const workerProtocolVersion = 2;
|
|
19
19
|
|
|
20
20
|
protocol.registerSchemesAsPrivileged([
|
|
21
21
|
{
|
|
@@ -44,10 +44,13 @@ app.setName("Nexus Plugin Dev: " + configuration.manifest.pluginId);
|
|
|
44
44
|
let mainWindow;
|
|
45
45
|
let smokeTimeout;
|
|
46
46
|
let smokeVerificationRunning = false;
|
|
47
|
+
let pluginWorker;
|
|
48
|
+
let workerShutdownStarted = false;
|
|
49
|
+
let workerShutdownComplete = false;
|
|
47
50
|
|
|
48
|
-
app.whenReady().then(function startDevHost() {
|
|
51
|
+
app.whenReady().then(async function startDevHost() {
|
|
49
52
|
registerPluginRendererProtocol();
|
|
50
|
-
|
|
53
|
+
await startPluginWorker();
|
|
51
54
|
registerIpcHandlers();
|
|
52
55
|
createWindow();
|
|
53
56
|
}).catch(failStartup);
|
|
@@ -56,6 +59,25 @@ app.on("window-all-closed", function quitAfterWindowClose() {
|
|
|
56
59
|
app.quit();
|
|
57
60
|
});
|
|
58
61
|
|
|
62
|
+
app.on("before-quit", function waitForPluginWorkerShutdown(event) {
|
|
63
|
+
if (!pluginWorker || workerShutdownComplete) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
if (workerShutdownStarted) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
workerShutdownStarted = true;
|
|
71
|
+
void pluginWorker.shutdown("quit")
|
|
72
|
+
.catch(function reportWorkerShutdownFailure(error) {
|
|
73
|
+
process.stderr.write("[nexus-plugin-dev] plugin worker shutdown failed: " + errorMessage(error) + "\n");
|
|
74
|
+
})
|
|
75
|
+
.finally(function finishPluginWorkerShutdown() {
|
|
76
|
+
workerShutdownComplete = true;
|
|
77
|
+
app.quit();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
59
81
|
function loadConfiguration(args) {
|
|
60
82
|
const packageDirectory = requiredPathArgument(args, "--plugin-package");
|
|
61
83
|
const projectDirectory = requiredPathArgument(args, "--project-directory");
|
|
@@ -71,6 +93,7 @@ function loadConfiguration(args) {
|
|
|
71
93
|
const language = requiredArgument(args, "--language");
|
|
72
94
|
const themeMode = requiredArgument(args, "--theme");
|
|
73
95
|
const hostVersion = requiredArgument(args, "--host-version");
|
|
96
|
+
const workerInspector = parseWorkerInspector(args);
|
|
74
97
|
if (!isStrictVersion(hostVersion)) {
|
|
75
98
|
throw new Error("--host-version must use X.Y.Z format.");
|
|
76
99
|
}
|
|
@@ -90,6 +113,7 @@ function loadConfiguration(args) {
|
|
|
90
113
|
language,
|
|
91
114
|
themeMode,
|
|
92
115
|
hostVersion,
|
|
116
|
+
workerInspector,
|
|
93
117
|
openDevTools: !args.includes("--no-devtools"),
|
|
94
118
|
smoke: args.includes("--smoke"),
|
|
95
119
|
devDataDirectory,
|
|
@@ -156,114 +180,313 @@ function resolvePluginRendererFile(url) {
|
|
|
156
180
|
});
|
|
157
181
|
}
|
|
158
182
|
|
|
159
|
-
function
|
|
183
|
+
async function startPluginWorker() {
|
|
160
184
|
if (!configuration.manifest.mainEntry) {
|
|
161
185
|
return;
|
|
162
186
|
}
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
const activate = typeof pluginModule.activate === "function"
|
|
166
|
-
? pluginModule.activate
|
|
167
|
-
: pluginModule.default && typeof pluginModule.default.activate === "function"
|
|
168
|
-
? pluginModule.default.activate
|
|
169
|
-
: undefined;
|
|
170
|
-
if (activate) {
|
|
171
|
-
activate({
|
|
172
|
-
pluginId: configuration.manifest.pluginId,
|
|
173
|
-
pluginDirectory: configuration.packageDirectory,
|
|
174
|
-
manifest: configuration.manifest,
|
|
175
|
-
host: createHostApi()
|
|
176
|
-
});
|
|
177
|
-
}
|
|
187
|
+
pluginWorker = new PluginWorkerClient(configuration);
|
|
188
|
+
const ready = await pluginWorker.start();
|
|
178
189
|
process.stdout.write(
|
|
179
190
|
"[nexus-plugin-dev] activated " + configuration.manifest.pluginId
|
|
180
|
-
+ " with " + String(
|
|
191
|
+
+ " with " + String(ready.operations.length) + " renderer operation(s).\n"
|
|
181
192
|
);
|
|
182
193
|
}
|
|
183
194
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
195
|
+
class PluginWorkerClient {
|
|
196
|
+
constructor(workerConfiguration) {
|
|
197
|
+
this.configuration = workerConfiguration;
|
|
198
|
+
this.child = undefined;
|
|
199
|
+
this.pending = new Map();
|
|
200
|
+
this.startPromise = undefined;
|
|
201
|
+
this.startResolve = undefined;
|
|
202
|
+
this.startReject = undefined;
|
|
203
|
+
this.startTimer = undefined;
|
|
204
|
+
this.shutdownPromise = undefined;
|
|
205
|
+
this.shutdownResolve = undefined;
|
|
206
|
+
this.shutdownTimer = undefined;
|
|
207
|
+
this.initializationRequestId = undefined;
|
|
208
|
+
this.requestSequence = 0;
|
|
209
|
+
this.intentionalStop = false;
|
|
210
|
+
this.ready = false;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
start() {
|
|
214
|
+
if (this.startPromise) {
|
|
215
|
+
return this.startPromise;
|
|
216
|
+
}
|
|
217
|
+
const startPromise = new Promise((resolve, reject) => {
|
|
218
|
+
this.startResolve = resolve;
|
|
219
|
+
this.startReject = reject;
|
|
220
|
+
});
|
|
221
|
+
this.startPromise = startPromise;
|
|
222
|
+
this.intentionalStop = false;
|
|
223
|
+
this.ready = false;
|
|
224
|
+
try {
|
|
225
|
+
this.child = utilityProcess.fork(path.join(__dirname, "plugin-host.js"), [], {
|
|
226
|
+
cwd: this.configuration.packageDirectory,
|
|
227
|
+
serviceName: "nexus-plugin-dev-" + this.configuration.manifest.pluginId,
|
|
228
|
+
stdio: this.configuration.workerInspector ? "inherit" : "ignore",
|
|
229
|
+
execArgv: this.configuration.workerInspector ? [this.configuration.workerInspector.execArg] : []
|
|
230
|
+
});
|
|
231
|
+
this.child.on("message", (message) => { void this.handleMessage(readWorkerMessage(message)); });
|
|
232
|
+
this.child.on("exit", (code) => this.handleExit(code));
|
|
233
|
+
if (!this.configuration.workerInspector || !this.configuration.workerInspector.breakOnStart) {
|
|
234
|
+
this.startTimer = setTimeout(() => {
|
|
235
|
+
this.failStart(new Error("Plugin worker activation timed out."));
|
|
236
|
+
this.kill();
|
|
237
|
+
}, 15_000);
|
|
197
238
|
}
|
|
198
|
-
|
|
199
|
-
|
|
239
|
+
const initializationRequestId = this.nextRequestId("initialize");
|
|
240
|
+
this.initializationRequestId = initializationRequestId;
|
|
241
|
+
this.post({
|
|
242
|
+
protocolVersion: workerProtocolVersion,
|
|
243
|
+
type: "initialize",
|
|
244
|
+
requestId: initializationRequestId,
|
|
245
|
+
payload: {
|
|
246
|
+
pluginId: this.configuration.manifest.pluginId,
|
|
247
|
+
pluginDirectory: this.configuration.packageDirectory,
|
|
248
|
+
pluginDataDirectory: this.configuration.pluginDataDirectory,
|
|
249
|
+
manifest: sanitizeJsonValue(this.configuration.manifest),
|
|
250
|
+
hostVersion: this.configuration.hostVersion,
|
|
251
|
+
hostApiVersion: sdk.PLUGIN_FRAMEWORK_VERSION
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
} catch (error) {
|
|
255
|
+
this.failStart(error);
|
|
256
|
+
}
|
|
257
|
+
return startPromise;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
invoke(payload) {
|
|
261
|
+
if (!this.child || !this.ready) {
|
|
262
|
+
return Promise.resolve(fail("PluginWorkerNotReady", "Plugin worker is not ready."));
|
|
263
|
+
}
|
|
264
|
+
const requestId = this.nextRequestId("invoke");
|
|
265
|
+
return new Promise((resolve) => {
|
|
266
|
+
this.pending.set(requestId, { kind: "invoke", resolve });
|
|
267
|
+
try {
|
|
268
|
+
this.post({ protocolVersion: workerProtocolVersion, type: "invoke", requestId, payload });
|
|
269
|
+
} catch (error) {
|
|
270
|
+
this.pending.delete(requestId);
|
|
271
|
+
resolve(fail("PluginWorkerExited", "Plugin worker request could not be sent.", errorMessage(error)));
|
|
200
272
|
}
|
|
201
|
-
|
|
202
|
-
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
shutdown(reason) {
|
|
277
|
+
this.intentionalStop = true;
|
|
278
|
+
if (!this.child) {
|
|
279
|
+
return Promise.resolve(ok(true));
|
|
280
|
+
}
|
|
281
|
+
if (this.shutdownPromise) {
|
|
282
|
+
return this.shutdownPromise;
|
|
283
|
+
}
|
|
284
|
+
const requestId = this.nextRequestId("shutdown");
|
|
285
|
+
this.shutdownPromise = new Promise((resolve) => {
|
|
286
|
+
this.shutdownResolve = resolve;
|
|
287
|
+
this.pending.set(requestId, { kind: "shutdown", resolve });
|
|
288
|
+
this.shutdownTimer = setTimeout(() => {
|
|
289
|
+
this.pending.delete(requestId);
|
|
290
|
+
this.shutdownResolve = undefined;
|
|
291
|
+
this.shutdownPromise = undefined;
|
|
292
|
+
const result = fail("PluginWorkerShutdownTimeout", "Plugin worker shutdown timed out.");
|
|
293
|
+
resolve(result);
|
|
294
|
+
this.kill();
|
|
295
|
+
}, 5000);
|
|
296
|
+
try {
|
|
297
|
+
this.post({ protocolVersion: workerProtocolVersion, type: "shutdown", requestId, payload: { reason: reason || "quit" } });
|
|
298
|
+
} catch (error) {
|
|
299
|
+
clearTimeout(this.shutdownTimer);
|
|
300
|
+
this.shutdownTimer = undefined;
|
|
301
|
+
this.pending.delete(requestId);
|
|
302
|
+
this.shutdownPromise = undefined;
|
|
303
|
+
resolve(fail("PluginWorkerExited", "Plugin worker shutdown could not be sent.", errorMessage(error)));
|
|
304
|
+
this.kill();
|
|
203
305
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
306
|
+
});
|
|
307
|
+
return this.shutdownPromise;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async handleMessage(message) {
|
|
311
|
+
if (!isRecord(message) || message.protocolVersion !== workerProtocolVersion) {
|
|
312
|
+
this.handleProtocolViolation("Plugin worker sent an invalid RPC message.");
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (message.type === "ready") {
|
|
316
|
+
if (!this.startPromise
|
|
317
|
+
|| this.ready
|
|
318
|
+
|| message.requestId !== this.initializationRequestId
|
|
319
|
+
|| !isRecord(message.payload)
|
|
320
|
+
|| !Array.isArray(message.payload.operations)
|
|
321
|
+
|| !message.payload.operations.every(isSafeOperationName)
|
|
322
|
+
|| new Set(message.payload.operations).size !== message.payload.operations.length
|
|
323
|
+
|| typeof message.payload.activatedAtUtc !== "string"
|
|
324
|
+
|| !message.payload.activatedAtUtc) {
|
|
325
|
+
this.handleProtocolViolation("Plugin worker ready response did not match initialization.");
|
|
326
|
+
return;
|
|
218
327
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
328
|
+
clearTimeout(this.startTimer);
|
|
329
|
+
this.startTimer = undefined;
|
|
330
|
+
this.initializationRequestId = undefined;
|
|
331
|
+
this.ready = true;
|
|
332
|
+
this.startResolve?.(message.payload);
|
|
333
|
+
this.startResolve = undefined;
|
|
334
|
+
this.startReject = undefined;
|
|
335
|
+
this.startPromise = undefined;
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (message.type === "hostRequest") {
|
|
339
|
+
if (!isWorkerRequestId(message.requestId)) {
|
|
340
|
+
this.handleProtocolViolation("Plugin worker Host API request id is invalid.");
|
|
341
|
+
return;
|
|
229
342
|
}
|
|
230
|
-
|
|
231
|
-
|
|
343
|
+
const result = await handlePluginHostRequest(this.configuration, message.payload);
|
|
344
|
+
try {
|
|
345
|
+
this.post({ protocolVersion: workerProtocolVersion, type: "hostResult", requestId: message.requestId, result });
|
|
346
|
+
} catch {
|
|
347
|
+
// The worker may have exited while a Host API request was running.
|
|
232
348
|
}
|
|
233
|
-
return
|
|
234
|
-
},
|
|
235
|
-
filesystem: createUnsupportedCapabilityApi(manifest, "filesystem"),
|
|
236
|
-
udp: createUnsupportedCapabilityApi(manifest, "udp"),
|
|
237
|
-
serial: createUnsupportedCapabilityApi(manifest, "serial"),
|
|
238
|
-
dialog: createUnsupportedCapabilityApi(manifest, "dialog"),
|
|
239
|
-
externalProcess: createUnsupportedCapabilityApi(manifest, "externalProcess")
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function createUnsupportedCapabilityApi(manifest, permission) {
|
|
244
|
-
const target = {
|
|
245
|
-
permission,
|
|
246
|
-
supportState: permissionSupportState(permission),
|
|
247
|
-
describe: function describe() {
|
|
248
|
-
return ok(createPermissionSummary(permission));
|
|
249
|
-
},
|
|
250
|
-
unsupported: function unsupported(operation) {
|
|
251
|
-
return unsupportedCapability(manifest, permission, operation);
|
|
349
|
+
return;
|
|
252
350
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
351
|
+
if (message.type === "result") {
|
|
352
|
+
if (!isWorkerRequestId(message.requestId) || !isNexusResult(message.result)) {
|
|
353
|
+
this.handleProtocolViolation("Plugin worker result is invalid.");
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const pending = this.pending.get(message.requestId);
|
|
357
|
+
if (!pending) {
|
|
358
|
+
return;
|
|
258
359
|
}
|
|
259
|
-
|
|
260
|
-
|
|
360
|
+
this.pending.delete(message.requestId);
|
|
361
|
+
if (pending.kind === "shutdown") {
|
|
362
|
+
clearTimeout(this.shutdownTimer);
|
|
363
|
+
this.shutdownTimer = undefined;
|
|
364
|
+
this.shutdownResolve = undefined;
|
|
365
|
+
this.shutdownPromise = undefined;
|
|
366
|
+
pending.resolve(message.result);
|
|
367
|
+
this.kill();
|
|
368
|
+
return;
|
|
261
369
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
};
|
|
370
|
+
pending.resolve(message.result);
|
|
371
|
+
return;
|
|
265
372
|
}
|
|
266
|
-
|
|
373
|
+
if (message.type === "fatal") {
|
|
374
|
+
const error = new Error(isRecord(message.payload) && typeof message.payload.message === "string" ? message.payload.message : "Plugin worker failed.");
|
|
375
|
+
if (this.startPromise) {
|
|
376
|
+
this.failStart(error);
|
|
377
|
+
} else {
|
|
378
|
+
process.stderr.write("[nexus-plugin-dev] plugin worker failed: " + error.message + "\n");
|
|
379
|
+
}
|
|
380
|
+
this.kill();
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
this.handleProtocolViolation("Plugin worker sent a message in the wrong direction.");
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
handleExit(code) {
|
|
387
|
+
this.child = undefined;
|
|
388
|
+
this.ready = false;
|
|
389
|
+
this.initializationRequestId = undefined;
|
|
390
|
+
clearTimeout(this.startTimer);
|
|
391
|
+
this.startTimer = undefined;
|
|
392
|
+
clearTimeout(this.shutdownTimer);
|
|
393
|
+
this.shutdownTimer = undefined;
|
|
394
|
+
this.shutdownPromise = undefined;
|
|
395
|
+
this.shutdownResolve = undefined;
|
|
396
|
+
const result = fail("PluginWorkerExited", "Plugin worker process exited.", { code });
|
|
397
|
+
if (this.startPromise) {
|
|
398
|
+
this.failStart(new Error(result.error.message));
|
|
399
|
+
}
|
|
400
|
+
for (const pending of this.pending.values()) {
|
|
401
|
+
pending.resolve(result);
|
|
402
|
+
}
|
|
403
|
+
this.pending.clear();
|
|
404
|
+
if (!this.intentionalStop) {
|
|
405
|
+
process.stderr.write("[nexus-plugin-dev] plugin worker exited with code " + String(code) + ".\n");
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
failStart(error) {
|
|
410
|
+
clearTimeout(this.startTimer);
|
|
411
|
+
this.startTimer = undefined;
|
|
412
|
+
this.initializationRequestId = undefined;
|
|
413
|
+
this.startReject?.(error instanceof Error ? error : new Error(String(error)));
|
|
414
|
+
this.startResolve = undefined;
|
|
415
|
+
this.startReject = undefined;
|
|
416
|
+
this.startPromise = undefined;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
handleProtocolViolation(message) {
|
|
420
|
+
const error = new Error(message);
|
|
421
|
+
if (this.startPromise) {
|
|
422
|
+
this.failStart(error);
|
|
423
|
+
} else {
|
|
424
|
+
process.stderr.write("[nexus-plugin-dev] " + message + "\n");
|
|
425
|
+
}
|
|
426
|
+
this.kill();
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
post(message) {
|
|
430
|
+
if (!this.child) {
|
|
431
|
+
throw new Error("Plugin worker process has exited.");
|
|
432
|
+
}
|
|
433
|
+
this.child.postMessage(message);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
kill() {
|
|
437
|
+
if (this.child) {
|
|
438
|
+
this.child.kill();
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
nextRequestId(kind) {
|
|
443
|
+
this.requestSequence += 1;
|
|
444
|
+
return this.configuration.manifest.pluginId + "-" + kind + "-" + String(this.requestSequence);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function readWorkerMessage(message) {
|
|
449
|
+
return isRecord(message) && Object.prototype.hasOwnProperty.call(message, "data") ? message.data : message;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async function handlePluginHostRequest(workerConfiguration, request) {
|
|
453
|
+
if (!isRecord(request) || typeof request.method !== "string") {
|
|
454
|
+
return fail("InvalidRequest", "Plugin Host API request is invalid.");
|
|
455
|
+
}
|
|
456
|
+
const manifest = workerConfiguration.manifest;
|
|
457
|
+
const params = isRecord(request.params) ? request.params : undefined;
|
|
458
|
+
if (request.method === "getCapabilityStatus") {
|
|
459
|
+
return isPluginPermission(params && params.permission)
|
|
460
|
+
? ok(createPermissionSummary(params.permission))
|
|
461
|
+
: fail("InvalidRequest", "Plugin capability name is invalid.");
|
|
462
|
+
}
|
|
463
|
+
if (request.method === "unsupportedCapability") {
|
|
464
|
+
return unsupportedCapability(manifest, params && params.permission, params && params.operation);
|
|
465
|
+
}
|
|
466
|
+
if (request.method === "log") {
|
|
467
|
+
if (!manifest.permissions.includes("businessLog")) {
|
|
468
|
+
return fail("PluginPermissionDenied", "Plugin does not declare businessLog permission.");
|
|
469
|
+
}
|
|
470
|
+
if (!params || (params.level !== "info" && params.level !== "warn" && params.level !== "error") || typeof params.message !== "string" || !params.message.trim()) {
|
|
471
|
+
return fail("InvalidRequest", "Plugin log request is invalid.");
|
|
472
|
+
}
|
|
473
|
+
return ok(appendLog(params.level, "plugin:" + manifest.pluginId, params.message.trim(), typeof params.exception === "string" ? params.exception : undefined));
|
|
474
|
+
}
|
|
475
|
+
if (request.method === "getSettings") {
|
|
476
|
+
return manifest.permissions.includes("settings")
|
|
477
|
+
? ok(loadSettings())
|
|
478
|
+
: fail("PluginPermissionDenied", "Plugin does not declare settings permission.");
|
|
479
|
+
}
|
|
480
|
+
if (request.method === "updateSettings") {
|
|
481
|
+
if (!manifest.permissions.includes("settings")) {
|
|
482
|
+
return fail("PluginPermissionDenied", "Plugin does not declare settings permission.");
|
|
483
|
+
}
|
|
484
|
+
if (!params || !isRecord(params.values)) {
|
|
485
|
+
return fail("InvalidRequest", "Plugin settings values must be an object.");
|
|
486
|
+
}
|
|
487
|
+
return ok(saveSettings(params.values));
|
|
488
|
+
}
|
|
489
|
+
return fail("InvalidRequest", "Plugin Host API method is invalid.", { method: request.method });
|
|
267
490
|
}
|
|
268
491
|
|
|
269
492
|
function unsupportedCapability(manifest, permission, operation) {
|
|
@@ -303,12 +526,13 @@ function registerIpcHandlers() {
|
|
|
303
526
|
clearTimeout(smokeTimeout);
|
|
304
527
|
process.stdout.write("[nexus-plugin-dev] renderer context synchronization smoke passed.\n");
|
|
305
528
|
setTimeout(function exitAfterSmokeSettles() {
|
|
306
|
-
app.
|
|
529
|
+
app.quit();
|
|
307
530
|
}, 100);
|
|
308
531
|
}).catch(function reportSmokeFailure(error) {
|
|
309
532
|
clearTimeout(smokeTimeout);
|
|
310
533
|
process.stderr.write("[nexus-plugin-dev] renderer context synchronization smoke failed: " + errorMessage(error) + "\n");
|
|
311
|
-
|
|
534
|
+
process.exitCode = 1;
|
|
535
|
+
app.quit();
|
|
312
536
|
});
|
|
313
537
|
}
|
|
314
538
|
});
|
|
@@ -322,9 +546,8 @@ async function verifyRendererContextSmoke() {
|
|
|
322
546
|
if (!frame) {
|
|
323
547
|
throw new Error("Plugin renderer frame was not found.");
|
|
324
548
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|| hostApi.hostApiVersion !== sdk.PLUGIN_FRAMEWORK_VERSION
|
|
549
|
+
if (configuration.manifest.hostApiVersion !== sdk.PLUGIN_FRAMEWORK_VERSION
|
|
550
|
+
|| (configuration.manifest.mainEntry && !pluginWorker)
|
|
328
551
|
|| configuration.manifest.minHostVersion === undefined) {
|
|
329
552
|
throw new Error("Development Host API version contract is inconsistent.");
|
|
330
553
|
}
|
|
@@ -410,7 +633,7 @@ function createRendererConfiguration() {
|
|
|
410
633
|
};
|
|
411
634
|
}
|
|
412
635
|
|
|
413
|
-
function invokeRendererRequest(request) {
|
|
636
|
+
async function invokeRendererRequest(request) {
|
|
414
637
|
if (!isRecord(request) || typeof request.method !== "string") {
|
|
415
638
|
return fail("InvalidRequest", "Plugin renderer request is invalid.");
|
|
416
639
|
}
|
|
@@ -450,7 +673,7 @@ function invokeRendererRequest(request) {
|
|
|
450
673
|
return fail("InvalidRequest", "Plugin renderer request method is invalid.", { method: request.method });
|
|
451
674
|
}
|
|
452
675
|
|
|
453
|
-
function invokeMain(params) {
|
|
676
|
+
async function invokeMain(params) {
|
|
454
677
|
if (!params || typeof params.operation !== "string") {
|
|
455
678
|
return fail("InvalidRequest", "Plugin renderer invokeMain request requires an operation name.");
|
|
456
679
|
}
|
|
@@ -460,40 +683,15 @@ function invokeMain(params) {
|
|
|
460
683
|
if (!isSafeOperationName(params.operation)) {
|
|
461
684
|
return fail("InvalidRequest", "Plugin renderer operation name is invalid.", { operation: params.operation });
|
|
462
685
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
return fail("NotFound", "Plugin renderer operation was not registered.", { operation: params.operation });
|
|
686
|
+
if (!pluginWorker) {
|
|
687
|
+
return fail("PluginCapabilityUnsupported", "Plugin mainEntry is not available in this debug target.", { operation: params.operation });
|
|
466
688
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
input: isRecord(params.input) ? params.input : undefined
|
|
474
|
-
});
|
|
475
|
-
return normalizeRendererOperationResult(value);
|
|
476
|
-
} catch (error) {
|
|
477
|
-
process.stderr.write("[nexus-plugin-dev] operation failed: " + params.operation + ": " + errorMessage(error) + "\n");
|
|
478
|
-
return fail("UnexpectedFailure", "Plugin renderer operation failed.", {
|
|
479
|
-
operation: params.operation,
|
|
480
|
-
message: errorMessage(error)
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
function normalizeRendererOperationResult(value) {
|
|
486
|
-
if (isNexusResult(value)) {
|
|
487
|
-
if (value.ok) {
|
|
488
|
-
return ok(sanitizeJsonValue(value.value));
|
|
489
|
-
}
|
|
490
|
-
return fail(
|
|
491
|
-
value.error.code,
|
|
492
|
-
value.error.message,
|
|
493
|
-
value.error.details === undefined ? undefined : sanitizeJsonValue(value.error.details)
|
|
494
|
-
);
|
|
495
|
-
}
|
|
496
|
-
return ok(sanitizeJsonValue(value));
|
|
689
|
+
return pluginWorker.invoke({
|
|
690
|
+
surface: configuration.surface,
|
|
691
|
+
contributionId: configuration.contributionId,
|
|
692
|
+
operation: params.operation,
|
|
693
|
+
input: isRecord(params.input) ? sanitizeJsonValue(params.input) : undefined
|
|
694
|
+
});
|
|
497
695
|
}
|
|
498
696
|
|
|
499
697
|
function createWindow() {
|
|
@@ -520,13 +718,13 @@ function createWindow() {
|
|
|
520
718
|
"[nexus-plugin-dev] load failed: " + String(errorCode) + " " + errorDescription + " " + validatedUrl + "\n"
|
|
521
719
|
);
|
|
522
720
|
if (configuration.smoke && isMainFrame) {
|
|
523
|
-
|
|
721
|
+
failAndQuit(1);
|
|
524
722
|
}
|
|
525
723
|
});
|
|
526
724
|
mainWindow.webContents.on("render-process-gone", function reportRendererExit(_event, details) {
|
|
527
725
|
process.stderr.write("[nexus-plugin-dev] renderer process exited: " + details.reason + "\n");
|
|
528
726
|
if (configuration.smoke) {
|
|
529
|
-
|
|
727
|
+
failAndQuit(1);
|
|
530
728
|
}
|
|
531
729
|
});
|
|
532
730
|
mainWindow.on("closed", function clearWindow() {
|
|
@@ -541,7 +739,7 @@ function createWindow() {
|
|
|
541
739
|
if (configuration.smoke) {
|
|
542
740
|
smokeTimeout = setTimeout(function failSmokeTimeout() {
|
|
543
741
|
process.stderr.write("[nexus-plugin-dev] renderer context synchronization smoke timed out.\n");
|
|
544
|
-
|
|
742
|
+
failAndQuit(1);
|
|
545
743
|
}, 15000);
|
|
546
744
|
}
|
|
547
745
|
}
|
|
@@ -652,6 +850,13 @@ function isNexusResult(value) {
|
|
|
652
850
|
: isRecord(value.error) && typeof value.error.code === "string" && typeof value.error.message === "string";
|
|
653
851
|
}
|
|
654
852
|
|
|
853
|
+
function isWorkerRequestId(value) {
|
|
854
|
+
return typeof value === "string"
|
|
855
|
+
&& value.length > 0
|
|
856
|
+
&& value.length <= 128
|
|
857
|
+
&& /^[A-Za-z0-9._:-]+$/.test(value);
|
|
858
|
+
}
|
|
859
|
+
|
|
655
860
|
function isPluginPermission(value) {
|
|
656
861
|
return typeof value === "string" && pluginPermissions.includes(value);
|
|
657
862
|
}
|
|
@@ -750,6 +955,28 @@ function isStrictVersion(value) {
|
|
|
750
955
|
}));
|
|
751
956
|
}
|
|
752
957
|
|
|
958
|
+
function parseWorkerInspector(args) {
|
|
959
|
+
const prefix = "--worker-inspector=";
|
|
960
|
+
const matches = args.filter(function isWorkerInspectorArgument(value) {
|
|
961
|
+
return value.startsWith(prefix);
|
|
962
|
+
});
|
|
963
|
+
if (matches.length > 1) {
|
|
964
|
+
throw new Error("--worker-inspector must not be repeated.");
|
|
965
|
+
}
|
|
966
|
+
if (matches.length === 0) {
|
|
967
|
+
return undefined;
|
|
968
|
+
}
|
|
969
|
+
const match = /^(inspect|inspect-brk):([0-9]+)$/.exec(matches[0].slice(prefix.length));
|
|
970
|
+
const port = match ? Number(match[2]) : 0;
|
|
971
|
+
if (!match || !Number.isInteger(port) || port < 1 || port > 65535) {
|
|
972
|
+
throw new Error("--worker-inspector must use inspect:<port> or inspect-brk:<port>.");
|
|
973
|
+
}
|
|
974
|
+
return {
|
|
975
|
+
execArg: "--" + match[1] + "=" + String(port),
|
|
976
|
+
breakOnStart: match[1] === "inspect-brk"
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
|
|
753
980
|
function requiredArgument(args, name) {
|
|
754
981
|
const prefix = name + "=";
|
|
755
982
|
const argument = args.find(function findArgument(value) {
|
|
@@ -776,7 +1003,12 @@ function fail(code, message, details) {
|
|
|
776
1003
|
function failStartup(error) {
|
|
777
1004
|
clearTimeout(smokeTimeout);
|
|
778
1005
|
process.stderr.write("[nexus-plugin-dev] startup failed: " + errorMessage(error) + "\n");
|
|
779
|
-
|
|
1006
|
+
failAndQuit(1);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
function failAndQuit(code) {
|
|
1010
|
+
process.exitCode = code;
|
|
1011
|
+
app.quit();
|
|
780
1012
|
}
|
|
781
1013
|
|
|
782
1014
|
function errorMessage(error) {
|