@aiot-nexus/plugin-sdk 2.0.2 → 2.1.1
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 +18 -8
- package/dev-host/host.html +13 -12
- package/dev-host/main.js +134 -9
- package/dev-host/plugin-host.js +1 -427
- package/dist/.generated/plugin-framework-version.d.ts +1 -1
- package/dist/.generated/plugin-framework-version.js +1 -1
- package/dist/.generated/plugin-manifest.schema.json +1 -1
- package/dist/internal/src-electron/main/core/runtime-result.js +42 -0
- package/dist/internal/src-electron/main/plugins/plugin-electron-facade-broker.js +890 -0
- package/dist/internal/src-electron/plugin-host-bootstrap.js +417 -0
- package/dist/internal/src-electron/shared/contracts.js +2 -0
- package/dist/internal/src-electron/shared/plugin-electron-facade-client.js +181 -0
- package/dist/internal/src-electron/shared/plugin-electron-facade-descriptors.js +70 -0
- package/dist/internal/src-electron/shared/plugin-electron-facade.js +243 -0
- package/dist/internal/src-electron/shared/plugin-rpc.js +245 -0
- package/dist/src/electron-facade.d.ts +4 -0
- package/dist/src/electron-facade.js +56 -0
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.js +2 -1
- package/dist/src/nexus-dev.js +5 -4
- package/dist/src/scaffold.js +2 -2
- package/dist/src/types.d.ts +86 -2
- package/dist/src/web-dev.js +2 -2
- package/package.json +7 -2
- package/templates/minimal/README.md.tmpl +84 -3
- package/templates/minimal/src/main.ts.tmpl +2 -1
- package/templates/sqlite/README.md.tmpl +30 -2
- package/templates/sqlite/src/better-sqlite3.d.ts +12 -1
- package/templates/sqlite/src/main.ts.tmpl +1034 -124
- package/templates/sqlite/src/renderer/app.ts.tmpl +691 -180
- package/templates/sqlite/src/renderer/index.html +40 -22
- package/templates/sqlite/src/renderer/plugin.css +302 -90
package/README.md
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# @aiot-nexus/plugin-sdk
|
|
2
2
|
|
|
3
|
-
> 版本适用性:当前包版本为 `2.
|
|
3
|
+
> 版本适用性:当前包版本为 `2.1.1`,面向 Framework API `2.1.0` 的 v2 worker/RPC 模型,并兼容仍声明 `2.0.x` 的插件。1.x SDK 和同步 Host API 已停止适用,但其历史语义保留在本文末尾和 [v1 到 v2 迁移说明](../docs/plugin-framework-v1-v2-migration.md);2.x 不提供 1.x 运行模型兼容层。
|
|
4
4
|
|
|
5
5
|
AIoT Nexus 独立插件开发包,包含公开 TypeScript 契约、PluginFrame 浏览器客户端和 `nexus-plugin` 命令行工具。
|
|
6
6
|
|
|
7
|
-
通信边界、`postMessage` 消息格式、`invokeMain` 请求/响应、分层超时和 worker 终止策略、长任务轮询示例见仓库文档:[插件 IPC 与事件通知示例](../docs/plugin-ipc-event-notification-example.md)
|
|
7
|
+
通信边界、`postMessage` 消息格式、`invokeMain` 请求/响应、分层超时和 worker 终止策略、长任务轮询示例见仓库文档:[插件 IPC 与事件通知示例](../docs/plugin-ipc-event-notification-example.md)。文件选择、Node 文件读写、插件窗口、IPC 和 Monaco worker 示例见 [Electron Facade 使用示例](../docs/plugin-electron-facade-examples.md)。Facade 的原生 Electron Event 首参是只读 `PluginElectronFacadeEventSnapshot`,不提供跨异步 RPC 无法成立的 `preventDefault()`。当前 SDK 提供 `onContextChanged()` 用于语言、主题和启动 context 更新,但不提供通用业务事件订阅 API。
|
|
8
8
|
|
|
9
9
|
## 环境
|
|
10
10
|
|
|
11
11
|
- Node.js `>=16.20.0 <17`
|
|
12
12
|
- npm `>=6.13.4`
|
|
13
|
-
- 插件框架 API `2.
|
|
13
|
+
- 插件框架 API `2.1.0`
|
|
14
14
|
- manifest schema `1`
|
|
15
15
|
|
|
16
16
|
## 命令
|
|
@@ -39,7 +39,7 @@ SDK 提供两层非阻塞版本检查:
|
|
|
39
39
|
- `nexus-plugin` 启动时先读取缓存。缓存新鲜期为 24 小时;缓存缺失或过期时在后台检查,不等待网络,不改变命令退出码。检测到普通更新时会显示:
|
|
40
40
|
|
|
41
41
|
```text
|
|
42
|
-
npm notice Update available for @aiot-nexus/plugin-sdk: 2.
|
|
42
|
+
npm notice Update available for @aiot-nexus/plugin-sdk: 2.1.1 -> 2.2.0
|
|
43
43
|
npm notice Run npm install --save-dev @aiot-nexus/plugin-sdk@latest
|
|
44
44
|
```
|
|
45
45
|
|
|
@@ -68,7 +68,17 @@ nexus-plugin create my-frame-plugin \
|
|
|
68
68
|
- 目标目录非空时命令以返回码 `2` 拒绝覆盖。生成或安装失败返回 `1`。
|
|
69
69
|
- 两套模板均读取宿主 `PluginRendererContext.language` 和 `themeMode`,通过 `onContextChanged` 在同一 iframe 内响应运行时语言与主题切换,并内置 `zh-CN` / `en-US` renderer 文案;manifest 使用相同文化资源本地化宿主入口。模板页面自身不提供语言或主题选择控件。
|
|
70
70
|
|
|
71
|
-
默认来源从 npm registry 安装。离线开发时先执行 `npm run pack:sdk`,再通过 `--sdk-source /path/to/aiot-nexus-plugin-sdk-2.
|
|
71
|
+
默认来源从 npm registry 安装。离线开发时先执行 `npm run pack:sdk`,再通过 `--sdk-source /path/to/aiot-nexus-plugin-sdk-2.1.1.tgz` 指定本地包;CLI 会把本地包一并放入生成工程。
|
|
72
|
+
|
|
73
|
+
### SQLite 数据库浏览器模板
|
|
74
|
+
|
|
75
|
+
`sqlite` 模板默认打开插件数据目录中的 `plugin.db`,并保留 `plugin_records` 示例表。工具栏中的“打开数据库”通过 worker 调用 `context.host.electron.dialog.showOpenDialog()`;普通插件无需 `electronHostControl`。iframe 只收到当前数据库信息和参数化业务 operation,不接收 Electron 对象,不提供路径输入、任意 SQL、DDL、`ATTACH` 或 SQL 控制台。外部数据库只在用户选择且通过 `sqlite_schema` 验证后以读写模式替换当前连接,取消或打开失败时继续使用原连接。
|
|
76
|
+
|
|
77
|
+
最近选择的外部路径会原子写入插件数据目录的 `sqlite-browser-state.json`。下次启动会先尝试恢复;文件缺失、损坏或不再是 SQLite 时清除记录、回退 `plugin.db`,并在页面显示恢复提示。外部数据库不会执行示例表建表或 journal pragma。
|
|
78
|
+
|
|
79
|
+
模板按 `table_list` / `table_xinfo` 动态显示普通用户表、列类型、SQLite affinity、主键、NULL、默认值和生成列。新增字段可选“使用默认值 / NULL / 输入值”,编辑字段可选“不修改 / NULL / 输入值”;INTEGER 使用精确十进制整数,REAL 必须有限,NUMERIC 可输入整数、浮点或文本,BLOB 输入偶数长度十六进制并按解码后的实际字节数计入 1 MiB/4 MiB 限制。普通 rowid 表优先用内建 rowid 定位;`WITHOUT ROWID` 表或 rowid 别名全部被遮蔽的表只回退到全部非空的单/复合主键。没有唯一 locator 的表仍可浏览和新增,超过 16 KiB 的主键 locator 也按只读行处理;更新和删除在事务内要求恰好命中一行。表格中的 TEXT 预览最多读取 16 KiB UTF-8 数据,BLOB 只读取总字节数和前 32 字节,避免把完整大字段复制进插件 worker 和 iframe。
|
|
80
|
+
|
|
81
|
+
该模板刻意不依赖 Quasar,以保持独立插件依赖最小化,因此数据库网格使用原生 `<table>`,这是宿主默认 `QTable` 规则之外的 SDK 模板例外。页面仍包含分页、横向表格滚动、中英文、亮暗主题、busy/empty/error 状态和窄窗口布局。
|
|
72
82
|
|
|
73
83
|
## 独立调试
|
|
74
84
|
|
|
@@ -81,8 +91,8 @@ npm run dev:debug
|
|
|
81
91
|
```
|
|
82
92
|
|
|
83
93
|
- `npm run dev:web` 先运行模板独立的 `build:web`,只编译 Renderer 到 `.nexus-plugin-dev/web-package/<plugin-id>/`,再启动仅绑定 `127.0.0.1:4173` 的本地 HTTP 宿主并打开系统浏览器;它不执行 native rebuild、不复制原生运行时、不覆盖正式 `dist`,支持浏览器 DevTools 与源码映射。
|
|
84
|
-
- 浏览器模式不加载 `mainEntry
|
|
85
|
-
- `npm run dev` 先构建和校验插件,再使用工程本地固定的 `electron@22.3.27` 启动 SDK 调试宿主;宿主渲染 UI,插件 `mainEntry` 在独立 `utilityProcess` worker
|
|
94
|
+
- 浏览器模式不加载 `mainEntry`,不创建插件 worker,也不提供 Electron Facade;`invokeMain` 会返回 `PluginCapabilityUnsupported`。设置仅在本次服务进程内保存,日志输出到 CLI。涉及插件 worker、SQLite、Node 或 Electron 时使用 `npm run dev`。
|
|
95
|
+
- `npm run dev` 先构建和校验插件,再使用工程本地固定的 `electron@22.3.27` 启动 SDK 调试宿主;宿主渲染 UI,插件 `mainEntry` 在独立 `utilityProcess` worker 中加载,并使用与正式 Nexus 相同的 Electron Facade client、wire codec、Broker、权限分层和资源清理逻辑。Renderer DevTools 默认自动打开。
|
|
86
96
|
- Electron 调试宿主的 `host.hostVersion` 默认是 SDK 构建时记录的 Nexus 应用版本;使用 `nexus-plugin dev . --host-version <X.Y.Z>` 可模拟其他应用版本,并按 manifest `minHostVersion` 拒绝不兼容组合。
|
|
87
97
|
- `npm run dev:debug` 额外把 `--inspect-brk=9229` 传给插件 worker,可在 `src/main.ts` 激活阶段命中断点;宿主 UI 仍保持可启动。
|
|
88
98
|
- 生成工程包含 `.vscode/launch.json`。在 VS Code 中选择 `Debug Nexus plugin` 可直接启动;若本机关闭了子进程自动附加,则先运行 `npm run dev:debug`,再选择 `Attach Nexus plugin worker (9229)`。renderer 断点在自动打开的 DevTools `Sources` 中设置。
|
|
@@ -103,7 +113,7 @@ nexus-plugin dev [plugin-project-directory]
|
|
|
103
113
|
[--no-devtools]
|
|
104
114
|
```
|
|
105
115
|
|
|
106
|
-
调试宿主工具栏中的语言和主题选择器用于模拟 Nexus 主框架 context
|
|
116
|
+
调试宿主工具栏中的语言和主题选择器用于模拟 Nexus 主框架 context,不属于插件页面。除该工具栏外,插件 iframe 无边距、无边框地填满窗口剩余区域;宿主不根据插件上报的内容尺寸限制 iframe 宽高,Electron 调试窗口也不设置最小宽高。切换 context 后调试宿主向同一 iframe 重发框架 `2.1.0` 的 `init` 消息;SDK 将后续 `init` 作为 context 更新并原地刷新语言和主题,同时仍接受可选的 `context-changed` 消息。调试宿主还复用正式的 `nexus-plugin://` 资源协议、iframe sandbox、CSP、消息结构、operation 校验、Electron Facade 和当前 Host API 权限语义,但不包含 Nexus 的完整页面路由、Pinia 状态和应用级功能。`mainEntry` 的激活、deactivate、Host API 请求和 renderer operation 调用都通过异步 RPC 连接 worker;交付前仍需把 `dist/<plugin-id>/` 放入正式 Nexus 完成集成验证。
|
|
107
117
|
|
|
108
118
|
浏览器 UI 命令支持 `--no-build`、`--no-open`、`--port <port>`,以及与 Electron 调试宿主相同的 `--language`、`--theme`、`--surface` 和 `--contribution-id` 参数。它使用普通 `http://127.0.0.1` iframe,不模拟 `nexus-plugin://` 协议或 Electron sandbox,因此只作为 UI 开发入口。Renderer-only 输出不会覆盖正式插件制品。
|
|
109
119
|
|
package/dev-host/host.html
CHANGED
|
@@ -34,9 +34,11 @@
|
|
|
34
34
|
html,
|
|
35
35
|
body {
|
|
36
36
|
width: 100%;
|
|
37
|
+
height: 100%;
|
|
37
38
|
min-width: 0;
|
|
38
|
-
min-height:
|
|
39
|
+
min-height: 0;
|
|
39
40
|
margin: 0;
|
|
41
|
+
overflow: hidden;
|
|
40
42
|
background: var(--page);
|
|
41
43
|
color: var(--text);
|
|
42
44
|
}
|
|
@@ -51,6 +53,7 @@
|
|
|
51
53
|
top: 0;
|
|
52
54
|
z-index: 1;
|
|
53
55
|
display: flex;
|
|
56
|
+
flex: 0 0 auto;
|
|
54
57
|
align-items: center;
|
|
55
58
|
min-height: 48px;
|
|
56
59
|
gap: 12px;
|
|
@@ -117,18 +120,21 @@
|
|
|
117
120
|
.frame-shell {
|
|
118
121
|
display: flex;
|
|
119
122
|
flex-direction: column;
|
|
120
|
-
flex: 1 1
|
|
123
|
+
flex: 1 1 0;
|
|
121
124
|
min-width: 0;
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
min-height: 0;
|
|
126
|
+
padding: 0;
|
|
127
|
+
overflow: hidden;
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
iframe {
|
|
127
131
|
display: block;
|
|
128
|
-
flex: 1 1
|
|
132
|
+
flex: 1 1 0;
|
|
129
133
|
width: 100%;
|
|
130
|
-
|
|
131
|
-
|
|
134
|
+
height: 100%;
|
|
135
|
+
min-width: 0;
|
|
136
|
+
min-height: 0;
|
|
137
|
+
border: 0;
|
|
132
138
|
background: var(--surface);
|
|
133
139
|
}
|
|
134
140
|
|
|
@@ -146,10 +152,6 @@
|
|
|
146
152
|
.status {
|
|
147
153
|
margin-left: auto;
|
|
148
154
|
}
|
|
149
|
-
|
|
150
|
-
.frame-shell {
|
|
151
|
-
padding: 8px;
|
|
152
|
-
}
|
|
153
155
|
}
|
|
154
156
|
</style>
|
|
155
157
|
</head>
|
|
@@ -255,7 +257,6 @@
|
|
|
255
257
|
return;
|
|
256
258
|
}
|
|
257
259
|
if (message.type === "nexus-plugin:resize" && typeof message.height === "number") {
|
|
258
|
-
iframe.style.height = Math.min(6000, Math.max(160, Math.ceil(message.height))) + "px";
|
|
259
260
|
resizeSeen = true;
|
|
260
261
|
scheduleSmokeReport();
|
|
261
262
|
return;
|
package/dev-host/main.js
CHANGED
|
@@ -5,6 +5,16 @@ const fs = require("fs");
|
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const { app, BrowserWindow, ipcMain, protocol, utilityProcess } = require("electron");
|
|
7
7
|
const sdk = require(path.join(__dirname, "..", "dist", "src", "index.js"));
|
|
8
|
+
const { PluginElectronFacadeBrokerService } = require(path.join(
|
|
9
|
+
__dirname,
|
|
10
|
+
"..",
|
|
11
|
+
"dist",
|
|
12
|
+
"internal",
|
|
13
|
+
"src-electron",
|
|
14
|
+
"main",
|
|
15
|
+
"plugins",
|
|
16
|
+
"plugin-electron-facade-broker.js"
|
|
17
|
+
));
|
|
8
18
|
|
|
9
19
|
const pluginRendererProtocol = "nexus-plugin";
|
|
10
20
|
const ipcChannels = {
|
|
@@ -12,11 +22,13 @@ const ipcChannels = {
|
|
|
12
22
|
invoke: "nexus-plugin-dev:invoke",
|
|
13
23
|
frameReady: "nexus-plugin-dev:frame-ready"
|
|
14
24
|
};
|
|
15
|
-
const pluginPermissions = ["businessLog", "settings", "filesystem", "udp", "serial", "externalProcess", "dialog"];
|
|
25
|
+
const pluginPermissions = ["businessLog", "settings", "filesystem", "udp", "serial", "externalProcess", "dialog", "electronHostControl"];
|
|
16
26
|
const implementedPermissions = new Set(["businessLog", "settings"]);
|
|
27
|
+
const hostControlPermissions = new Set(["electronHostControl"]);
|
|
17
28
|
const routeGuardPermissions = new Set(["filesystem", "udp", "serial", "dialog"]);
|
|
18
|
-
const workerProtocolVersion =
|
|
29
|
+
const workerProtocolVersion = 3;
|
|
19
30
|
const workerOperationTimeoutMs = 15000;
|
|
31
|
+
const electronFacadeBroker = new PluginElectronFacadeBrokerService();
|
|
20
32
|
|
|
21
33
|
protocol.registerSchemesAsPrivileged([
|
|
22
34
|
{
|
|
@@ -74,6 +86,7 @@ app.on("before-quit", function waitForPluginWorkerShutdown(event) {
|
|
|
74
86
|
process.stderr.write("[nexus-plugin-dev] plugin worker shutdown failed: " + errorMessage(error) + "\n");
|
|
75
87
|
})
|
|
76
88
|
.finally(function finishPluginWorkerShutdown() {
|
|
89
|
+
electronFacadeBroker.disposePlugin(configuration.manifest.pluginId);
|
|
77
90
|
workerShutdownComplete = true;
|
|
78
91
|
app.quit();
|
|
79
92
|
});
|
|
@@ -175,7 +188,7 @@ function resolvePluginRendererFile(url) {
|
|
|
175
188
|
path: realPath,
|
|
176
189
|
mimeType: mimeType(realPath),
|
|
177
190
|
headers: {
|
|
178
|
-
"Content-Security-Policy": "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'none'",
|
|
191
|
+
"Content-Security-Policy": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; worker-src 'self' blob:; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'none'",
|
|
179
192
|
"X-Content-Type-Options": "nosniff"
|
|
180
193
|
}
|
|
181
194
|
});
|
|
@@ -186,7 +199,19 @@ async function startPluginWorker() {
|
|
|
186
199
|
return;
|
|
187
200
|
}
|
|
188
201
|
pluginWorker = new PluginWorkerClient(configuration);
|
|
189
|
-
|
|
202
|
+
electronFacadeBroker.attachPlugin(configuration.manifest.pluginId, {
|
|
203
|
+
pluginDirectory: configuration.packageDirectory,
|
|
204
|
+
hostControl: configuration.manifest.permissions.includes("electronHostControl"),
|
|
205
|
+
invokeCallback: (callbackId, args) => pluginWorker.invokeElectronCallback(callbackId, args),
|
|
206
|
+
emitEvent: (callbackId, args) => pluginWorker.emitElectronEvent(callbackId, args)
|
|
207
|
+
});
|
|
208
|
+
let ready;
|
|
209
|
+
try {
|
|
210
|
+
ready = await pluginWorker.start();
|
|
211
|
+
} catch (error) {
|
|
212
|
+
electronFacadeBroker.disposePlugin(configuration.manifest.pluginId);
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
190
215
|
process.stdout.write(
|
|
191
216
|
"[nexus-plugin-dev] activated " + configuration.manifest.pluginId
|
|
192
217
|
+ " with " + String(ready.operations.length) + " renderer operation(s).\n"
|
|
@@ -293,6 +318,60 @@ class PluginWorkerClient {
|
|
|
293
318
|
});
|
|
294
319
|
}
|
|
295
320
|
|
|
321
|
+
invokeElectronCallback(callbackId, args) {
|
|
322
|
+
if (!this.child) {
|
|
323
|
+
return Promise.resolve(fail("PluginProcessNotReady", "Plugin worker is not available for Electron callback."));
|
|
324
|
+
}
|
|
325
|
+
const requestId = this.nextRequestId("electron-callback");
|
|
326
|
+
return new Promise((resolve) => {
|
|
327
|
+
const pending = {
|
|
328
|
+
kind: "electronCallback",
|
|
329
|
+
resolve,
|
|
330
|
+
timer: setTimeout(() => {
|
|
331
|
+
if (this.pending.get(requestId) !== pending) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
this.pending.delete(requestId);
|
|
335
|
+
pending.timer = undefined;
|
|
336
|
+
resolve(fail("PluginOperationTimeout", "Electron facade callback timed out.", {
|
|
337
|
+
pluginId: this.configuration.manifest.pluginId,
|
|
338
|
+
callbackId,
|
|
339
|
+
timeoutMs: workerOperationTimeoutMs
|
|
340
|
+
}));
|
|
341
|
+
}, workerOperationTimeoutMs)
|
|
342
|
+
};
|
|
343
|
+
this.pending.set(requestId, pending);
|
|
344
|
+
try {
|
|
345
|
+
this.post({
|
|
346
|
+
protocolVersion: workerProtocolVersion,
|
|
347
|
+
type: "electronCallback",
|
|
348
|
+
requestId,
|
|
349
|
+
payload: { callbackId, args }
|
|
350
|
+
});
|
|
351
|
+
} catch (error) {
|
|
352
|
+
this.pending.delete(requestId);
|
|
353
|
+
clearPendingTimer(pending);
|
|
354
|
+
resolve(fail("PluginProcessExited", "Electron facade callback could not be sent.", errorMessage(error)));
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
emitElectronEvent(callbackId, args) {
|
|
360
|
+
if (!this.child) {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
try {
|
|
364
|
+
this.post({
|
|
365
|
+
protocolVersion: workerProtocolVersion,
|
|
366
|
+
type: "electronEvent",
|
|
367
|
+
payload: { callbackId, args }
|
|
368
|
+
});
|
|
369
|
+
return true;
|
|
370
|
+
} catch {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
296
375
|
shutdown(reason) {
|
|
297
376
|
this.intentionalStop = true;
|
|
298
377
|
if (!this.child) {
|
|
@@ -360,7 +439,7 @@ class PluginWorkerClient {
|
|
|
360
439
|
this.handleProtocolViolation("Plugin worker Host API request id is invalid.");
|
|
361
440
|
return;
|
|
362
441
|
}
|
|
363
|
-
const result = await handlePluginHostRequest(this.configuration, message.payload);
|
|
442
|
+
const result = normalizeRpcResult(await handlePluginHostRequest(this.configuration, message.payload));
|
|
364
443
|
try {
|
|
365
444
|
this.post({ protocolVersion: workerProtocolVersion, type: "hostResult", requestId: message.requestId, result });
|
|
366
445
|
} catch {
|
|
@@ -377,6 +456,10 @@ class PluginWorkerClient {
|
|
|
377
456
|
if (!pending) {
|
|
378
457
|
return;
|
|
379
458
|
}
|
|
459
|
+
if (pending.kind === "electronCallback") {
|
|
460
|
+
this.handleProtocolViolation("Plugin worker used a normal result for an Electron callback.");
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
380
463
|
this.pending.delete(message.requestId);
|
|
381
464
|
clearPendingTimer(pending);
|
|
382
465
|
if (pending.kind === "shutdown") {
|
|
@@ -391,6 +474,24 @@ class PluginWorkerClient {
|
|
|
391
474
|
pending.resolve(message.result);
|
|
392
475
|
return;
|
|
393
476
|
}
|
|
477
|
+
if (message.type === "electronCallbackResult") {
|
|
478
|
+
if (!isWorkerRequestId(message.requestId) || !isNexusResult(message.result)) {
|
|
479
|
+
this.handleProtocolViolation("Plugin worker Electron callback result is invalid.");
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
const pending = this.pending.get(message.requestId);
|
|
483
|
+
if (!pending) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
if (pending.kind !== "electronCallback") {
|
|
487
|
+
this.handleProtocolViolation("Plugin worker used an Electron callback result for another request.");
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
this.pending.delete(message.requestId);
|
|
491
|
+
clearPendingTimer(pending);
|
|
492
|
+
pending.resolve(message.result);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
394
495
|
if (message.type === "fatal") {
|
|
395
496
|
const error = new Error(isRecord(message.payload) && typeof message.payload.message === "string" ? message.payload.message : "Plugin worker failed.");
|
|
396
497
|
if (this.startPromise) {
|
|
@@ -405,6 +506,7 @@ class PluginWorkerClient {
|
|
|
405
506
|
}
|
|
406
507
|
|
|
407
508
|
handleExit(code) {
|
|
509
|
+
electronFacadeBroker.disposePlugin(this.configuration.manifest.pluginId);
|
|
408
510
|
this.child = undefined;
|
|
409
511
|
this.ready = false;
|
|
410
512
|
this.initializationRequestId = undefined;
|
|
@@ -484,6 +586,9 @@ async function handlePluginHostRequest(workerConfiguration, request) {
|
|
|
484
586
|
}
|
|
485
587
|
const manifest = workerConfiguration.manifest;
|
|
486
588
|
const params = isRecord(request.params) ? request.params : undefined;
|
|
589
|
+
if (request.method === "electronFacade") {
|
|
590
|
+
return electronFacadeBroker.invoke(manifest.pluginId, params);
|
|
591
|
+
}
|
|
487
592
|
if (request.method === "getCapabilityStatus") {
|
|
488
593
|
return isPluginPermission(params && params.permission)
|
|
489
594
|
? ok(createPermissionSummary(params.permission))
|
|
@@ -727,8 +832,6 @@ function createWindow() {
|
|
|
727
832
|
mainWindow = new BrowserWindow({
|
|
728
833
|
width: 1200,
|
|
729
834
|
height: 800,
|
|
730
|
-
minWidth: 640,
|
|
731
|
-
minHeight: 480,
|
|
732
835
|
show: !configuration.smoke,
|
|
733
836
|
backgroundColor: configuration.themeMode === "dark" ? "#181a1d" : "#f4f5f7",
|
|
734
837
|
webPreferences: {
|
|
@@ -830,7 +933,7 @@ function appendLog(level, category, message, exception) {
|
|
|
830
933
|
level,
|
|
831
934
|
category,
|
|
832
935
|
message,
|
|
833
|
-
exception
|
|
936
|
+
...(exception ? { exception } : {})
|
|
834
937
|
};
|
|
835
938
|
const output = "[nexus-plugin-dev][" + String(level) + "][" + category + "] " + String(message)
|
|
836
939
|
+ (exception ? "\n" + exception : "") + "\n";
|
|
@@ -879,6 +982,23 @@ function isNexusResult(value) {
|
|
|
879
982
|
: isRecord(value.error) && typeof value.error.code === "string" && typeof value.error.message === "string";
|
|
880
983
|
}
|
|
881
984
|
|
|
985
|
+
function normalizeRpcResult(result) {
|
|
986
|
+
if (!isNexusResult(result)) {
|
|
987
|
+
return fail("UnexpectedFailure", "Plugin Host API returned an invalid result.");
|
|
988
|
+
}
|
|
989
|
+
if (result.ok) {
|
|
990
|
+
return { ok: true, value: sanitizeJsonValue(result.value) };
|
|
991
|
+
}
|
|
992
|
+
return {
|
|
993
|
+
ok: false,
|
|
994
|
+
error: {
|
|
995
|
+
code: result.error.code,
|
|
996
|
+
message: result.error.message,
|
|
997
|
+
...(result.error.details === undefined ? {} : { details: sanitizeJsonValue(result.error.details) })
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
|
|
882
1002
|
function isWorkerRequestId(value) {
|
|
883
1003
|
return typeof value === "string"
|
|
884
1004
|
&& value.length > 0
|
|
@@ -891,6 +1011,9 @@ function isPluginPermission(value) {
|
|
|
891
1011
|
}
|
|
892
1012
|
|
|
893
1013
|
function permissionSupportState(permission) {
|
|
1014
|
+
if (hostControlPermissions.has(permission)) {
|
|
1015
|
+
return "hostControl";
|
|
1016
|
+
}
|
|
894
1017
|
if (implementedPermissions.has(permission)) {
|
|
895
1018
|
return "implemented";
|
|
896
1019
|
}
|
|
@@ -904,6 +1027,8 @@ function createPermissionSummary(permission) {
|
|
|
904
1027
|
const supportState = permissionSupportState(permission);
|
|
905
1028
|
const statusMessage = supportState === "implemented"
|
|
906
1029
|
? "Available to trusted mainEntry Host API."
|
|
1030
|
+
: supportState === "hostControl"
|
|
1031
|
+
? "Grants direct control of Nexus Electron host objects; global changes can affect the application and may not be reversible."
|
|
907
1032
|
: supportState === "routeGuard"
|
|
908
1033
|
? "Declared for renderer route guards; not exposed to trusted mainEntry Host API."
|
|
909
1034
|
: "Reserved for a future Host API; trusted mainEntry calls return PluginCapabilityUnsupported.";
|
|
@@ -1026,7 +1151,7 @@ function ok(value) {
|
|
|
1026
1151
|
}
|
|
1027
1152
|
|
|
1028
1153
|
function fail(code, message, details) {
|
|
1029
|
-
return { ok: false, error: { code, message, details } };
|
|
1154
|
+
return { ok: false, error: { code, message, ...(details === undefined ? {} : { details }) } };
|
|
1030
1155
|
}
|
|
1031
1156
|
|
|
1032
1157
|
function failStartup(error) {
|