@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/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 handlers = new Map();
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
- activatePluginMain();
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 activatePluginMain() {
183
+ async function startPluginWorker() {
160
184
  if (!configuration.manifest.mainEntry) {
161
185
  return;
162
186
  }
163
- const mainEntryPath = path.resolve(configuration.packageDirectory, configuration.manifest.mainEntry);
164
- const pluginModule = require(mainEntryPath);
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(handlers.size) + " renderer operation(s).\n"
191
+ + " with " + String(ready.operations.length) + " renderer operation(s).\n"
181
192
  );
182
193
  }
183
194
 
184
- function createHostApi() {
185
- const manifest = configuration.manifest;
186
- return {
187
- hostVersion: configuration.hostVersion,
188
- hostApiVersion: sdk.PLUGIN_FRAMEWORK_VERSION,
189
- pluginId: manifest.pluginId,
190
- pluginDataDirectory: configuration.pluginDataDirectory,
191
- hasPermission: function hasPermission(permission) {
192
- return manifest.permissions.includes(permission);
193
- },
194
- registerRendererMethod: function registerRendererMethod(name, handler) {
195
- if (!isSafeOperationName(name)) {
196
- return fail("InvalidRequest", "Plugin renderer operation name is invalid.", { operation: name });
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
- if (typeof handler !== "function") {
199
- return fail("InvalidRequest", "Plugin renderer operation handler must be a function.", { operation: name });
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
- if (handlers.has(name)) {
202
- return fail("InvalidRequest", "Plugin renderer operation is already registered.", { operation: name });
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
- handlers.set(name, handler);
205
- return ok(true);
206
- },
207
- getCapabilityStatus: function getCapabilityStatus(permission) {
208
- return isPluginPermission(permission)
209
- ? ok(createPermissionSummary(permission))
210
- : fail("InvalidRequest", "Plugin capability name is invalid.", { permission });
211
- },
212
- unsupportedCapability: function unsupportedCapability(permission, operation) {
213
- return unsupportedCapability(manifest, permission, operation);
214
- },
215
- log: function log(level, message, exception) {
216
- if (!manifest.permissions.includes("businessLog")) {
217
- return fail("PluginPermissionDenied", "Plugin does not declare businessLog permission.");
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
- return ok(appendLog(level, "plugin:" + manifest.pluginId, message, exception));
220
- },
221
- getSettings: function getSettings() {
222
- return manifest.permissions.includes("settings")
223
- ? ok(loadSettings())
224
- : fail("PluginPermissionDenied", "Plugin does not declare settings permission.");
225
- },
226
- updateSettings: function updateSettings(values) {
227
- if (!manifest.permissions.includes("settings")) {
228
- return fail("PluginPermissionDenied", "Plugin does not declare settings permission.");
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
- if (!isRecord(values)) {
231
- return fail("InvalidRequest", "Plugin settings values must be an object.");
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 ok(saveSettings(values));
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
- return new Proxy(target, {
255
- get: function get(apiTarget, property) {
256
- if (property in apiTarget) {
257
- return Reflect.get(apiTarget, property);
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
- if (typeof property === "symbol" || property === "then") {
260
- return undefined;
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
- return function unsupportedOperation() {
263
- return unsupportedCapability(manifest, permission, property);
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.exit(0);
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
- app.exit(1);
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
- const hostApi = createHostApi();
326
- if (hostApi.hostVersion !== configuration.hostVersion
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
- const handler = handlers.get(params.operation);
464
- if (!handler) {
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
- try {
468
- const value = handler({
469
- pluginId: configuration.manifest.pluginId,
470
- surface: configuration.surface,
471
- contributionId: configuration.contributionId,
472
- operation: params.operation,
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
- app.exit(1);
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
- app.exit(1);
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
- app.exit(1);
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
- app.exit(1);
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) {