@aiot-nexus/plugin-sdk 2.0.1 → 2.0.2
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 +5 -3
- package/dev-host/main.js +30 -1
- package/dev-host/plugin-host.js +14 -2
- package/dist/browser/frame-client.mjs +1 -1
- package/dist/src/browser/frame-client.js +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/package.json +1 -1
- package/templates/minimal/README.md.tmpl +2 -0
- package/templates/sqlite/README.md.tmpl +2 -0
package/README.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# @aiot-nexus/plugin-sdk
|
|
2
2
|
|
|
3
|
-
> 版本适用性:当前包版本为 `2.0.
|
|
3
|
+
> 版本适用性:当前包版本为 `2.0.2`,只面向 Framework API `2.0.0` 的 v2 worker/RPC 模型。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)。当前 SDK 提供 `onContextChanged()` 用于语言、主题和启动 context 更新,但不提供通用业务事件订阅 API。
|
|
8
|
+
|
|
7
9
|
## 环境
|
|
8
10
|
|
|
9
11
|
- Node.js `>=16.20.0 <17`
|
|
@@ -37,7 +39,7 @@ SDK 提供两层非阻塞版本检查:
|
|
|
37
39
|
- `nexus-plugin` 启动时先读取缓存。缓存新鲜期为 24 小时;缓存缺失或过期时在后台检查,不等待网络,不改变命令退出码。检测到普通更新时会显示:
|
|
38
40
|
|
|
39
41
|
```text
|
|
40
|
-
npm notice Update available for @aiot-nexus/plugin-sdk: 2.0.
|
|
42
|
+
npm notice Update available for @aiot-nexus/plugin-sdk: 2.0.2 -> 2.1.0
|
|
41
43
|
npm notice Run npm install --save-dev @aiot-nexus/plugin-sdk@latest
|
|
42
44
|
```
|
|
43
45
|
|
|
@@ -66,7 +68,7 @@ nexus-plugin create my-frame-plugin \
|
|
|
66
68
|
- 目标目录非空时命令以返回码 `2` 拒绝覆盖。生成或安装失败返回 `1`。
|
|
67
69
|
- 两套模板均读取宿主 `PluginRendererContext.language` 和 `themeMode`,通过 `onContextChanged` 在同一 iframe 内响应运行时语言与主题切换,并内置 `zh-CN` / `en-US` renderer 文案;manifest 使用相同文化资源本地化宿主入口。模板页面自身不提供语言或主题选择控件。
|
|
68
70
|
|
|
69
|
-
默认来源从 npm registry 安装。离线开发时先执行 `npm run pack:sdk`,再通过 `--sdk-source /path/to/aiot-nexus-plugin-sdk-2.0.
|
|
71
|
+
默认来源从 npm registry 安装。离线开发时先执行 `npm run pack:sdk`,再通过 `--sdk-source /path/to/aiot-nexus-plugin-sdk-2.0.2.tgz` 指定本地包;CLI 会把本地包一并放入生成工程。
|
|
70
72
|
|
|
71
73
|
## 独立调试
|
|
72
74
|
|
package/dev-host/main.js
CHANGED
|
@@ -16,6 +16,7 @@ const pluginPermissions = ["businessLog", "settings", "filesystem", "udp", "seri
|
|
|
16
16
|
const implementedPermissions = new Set(["businessLog", "settings"]);
|
|
17
17
|
const routeGuardPermissions = new Set(["filesystem", "udp", "serial", "dialog"]);
|
|
18
18
|
const workerProtocolVersion = 2;
|
|
19
|
+
const workerOperationTimeoutMs = 15000;
|
|
19
20
|
|
|
20
21
|
protocol.registerSchemesAsPrivileged([
|
|
21
22
|
{
|
|
@@ -263,11 +264,30 @@ class PluginWorkerClient {
|
|
|
263
264
|
}
|
|
264
265
|
const requestId = this.nextRequestId("invoke");
|
|
265
266
|
return new Promise((resolve) => {
|
|
266
|
-
|
|
267
|
+
const pending = {
|
|
268
|
+
kind: "invoke",
|
|
269
|
+
resolve,
|
|
270
|
+
timer: setTimeout(() => {
|
|
271
|
+
if (this.pending.get(requestId) !== pending) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
this.pending.delete(requestId);
|
|
275
|
+
pending.timer = undefined;
|
|
276
|
+
this.ready = false;
|
|
277
|
+
resolve(fail("PluginOperationTimeout", "Plugin worker operation timed out.", {
|
|
278
|
+
pluginId: this.configuration.manifest.pluginId,
|
|
279
|
+
operation: payload.operation,
|
|
280
|
+
timeoutMs: workerOperationTimeoutMs
|
|
281
|
+
}));
|
|
282
|
+
this.kill();
|
|
283
|
+
}, workerOperationTimeoutMs)
|
|
284
|
+
};
|
|
285
|
+
this.pending.set(requestId, pending);
|
|
267
286
|
try {
|
|
268
287
|
this.post({ protocolVersion: workerProtocolVersion, type: "invoke", requestId, payload });
|
|
269
288
|
} catch (error) {
|
|
270
289
|
this.pending.delete(requestId);
|
|
290
|
+
clearPendingTimer(pending);
|
|
271
291
|
resolve(fail("PluginWorkerExited", "Plugin worker request could not be sent.", errorMessage(error)));
|
|
272
292
|
}
|
|
273
293
|
});
|
|
@@ -358,6 +378,7 @@ class PluginWorkerClient {
|
|
|
358
378
|
return;
|
|
359
379
|
}
|
|
360
380
|
this.pending.delete(message.requestId);
|
|
381
|
+
clearPendingTimer(pending);
|
|
361
382
|
if (pending.kind === "shutdown") {
|
|
362
383
|
clearTimeout(this.shutdownTimer);
|
|
363
384
|
this.shutdownTimer = undefined;
|
|
@@ -398,6 +419,7 @@ class PluginWorkerClient {
|
|
|
398
419
|
this.failStart(new Error(result.error.message));
|
|
399
420
|
}
|
|
400
421
|
for (const pending of this.pending.values()) {
|
|
422
|
+
clearPendingTimer(pending);
|
|
401
423
|
pending.resolve(result);
|
|
402
424
|
}
|
|
403
425
|
this.pending.clear();
|
|
@@ -445,6 +467,13 @@ class PluginWorkerClient {
|
|
|
445
467
|
}
|
|
446
468
|
}
|
|
447
469
|
|
|
470
|
+
function clearPendingTimer(pending) {
|
|
471
|
+
if (pending && pending.timer) {
|
|
472
|
+
clearTimeout(pending.timer);
|
|
473
|
+
pending.timer = undefined;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
448
477
|
function readWorkerMessage(message) {
|
|
449
478
|
return isRecord(message) && Object.prototype.hasOwnProperty.call(message, "data") ? message.data : message;
|
|
450
479
|
}
|
package/dev-host/plugin-host.js
CHANGED
|
@@ -16,6 +16,7 @@ if (!port) {
|
|
|
16
16
|
|
|
17
17
|
const operations = new Map();
|
|
18
18
|
const pendingHostRequests = new Map();
|
|
19
|
+
const inFlightOperations = new Set();
|
|
19
20
|
let initialized = false;
|
|
20
21
|
let ready = false;
|
|
21
22
|
let shuttingDown = false;
|
|
@@ -56,7 +57,7 @@ async function handleMessage(message) {
|
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
58
59
|
if (message.type === "invoke") {
|
|
59
|
-
await
|
|
60
|
+
await invokeTracked(message);
|
|
60
61
|
return;
|
|
61
62
|
}
|
|
62
63
|
if (message.type === "shutdown") {
|
|
@@ -162,12 +163,23 @@ async function invoke(message) {
|
|
|
162
163
|
}
|
|
163
164
|
}
|
|
164
165
|
|
|
166
|
+
async function invokeTracked(message) {
|
|
167
|
+
const operation = invoke(message);
|
|
168
|
+
inFlightOperations.add(operation);
|
|
169
|
+
try {
|
|
170
|
+
await operation;
|
|
171
|
+
} finally {
|
|
172
|
+
inFlightOperations.delete(operation);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
165
176
|
async function shutdown(message) {
|
|
166
177
|
if (shuttingDown || !isWorkerRequestId(message.requestId)) {
|
|
167
178
|
return;
|
|
168
179
|
}
|
|
169
180
|
shuttingDown = true;
|
|
170
181
|
try {
|
|
182
|
+
await Promise.allSettled(Array.from(inFlightOperations));
|
|
171
183
|
const deactivate = pluginModule && (typeof pluginModule.deactivate === "function"
|
|
172
184
|
? pluginModule.deactivate
|
|
173
185
|
: pluginModule.default && typeof pluginModule.default.deactivate === "function"
|
|
@@ -262,7 +274,7 @@ function createHostApi() {
|
|
|
262
274
|
}
|
|
263
275
|
|
|
264
276
|
function requestHost(method, params) {
|
|
265
|
-
if (exiting
|
|
277
|
+
if (exiting) {
|
|
266
278
|
return Promise.resolve(fail("PluginWorkerExited", "Plugin worker is stopping."));
|
|
267
279
|
}
|
|
268
280
|
const requestId = pluginId + "-host-" + String(++requestSequence);
|
|
@@ -4,7 +4,7 @@ const readyMessageType = "nexus-plugin:ready";
|
|
|
4
4
|
const requestMessageType = "nexus-plugin:request";
|
|
5
5
|
const responseMessageType = "nexus-plugin:response";
|
|
6
6
|
const resizeMessageType = "nexus-plugin:resize";
|
|
7
|
-
const defaultRequestTimeoutMs =
|
|
7
|
+
const defaultRequestTimeoutMs = 20000;
|
|
8
8
|
export class PluginFrameClientError extends Error {
|
|
9
9
|
constructor(code, message) {
|
|
10
10
|
super(message);
|
|
@@ -7,7 +7,7 @@ const readyMessageType = "nexus-plugin:ready";
|
|
|
7
7
|
const requestMessageType = "nexus-plugin:request";
|
|
8
8
|
const responseMessageType = "nexus-plugin:response";
|
|
9
9
|
const resizeMessageType = "nexus-plugin:resize";
|
|
10
|
-
const defaultRequestTimeoutMs =
|
|
10
|
+
const defaultRequestTimeoutMs = 20000;
|
|
11
11
|
class PluginFrameClientError extends Error {
|
|
12
12
|
constructor(code, message) {
|
|
13
13
|
super(message);
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import pluginManifestSchema from "../.generated/plugin-manifest.schema.json";
|
|
2
|
-
export declare const PLUGIN_SDK_VERSION = "2.0.
|
|
2
|
+
export declare const PLUGIN_SDK_VERSION = "2.0.2";
|
|
3
3
|
export { pluginManifestSchema };
|
|
4
4
|
export * from "../.generated/plugin-framework-version";
|
|
5
5
|
export * from "./types";
|
package/dist/src/index.js
CHANGED
|
@@ -20,7 +20,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
20
20
|
exports.pluginManifestSchema = exports.PLUGIN_SDK_VERSION = void 0;
|
|
21
21
|
const plugin_manifest_schema_json_1 = __importDefault(require("../.generated/plugin-manifest.schema.json"));
|
|
22
22
|
exports.pluginManifestSchema = plugin_manifest_schema_json_1.default;
|
|
23
|
-
exports.PLUGIN_SDK_VERSION = "2.0.
|
|
23
|
+
exports.PLUGIN_SDK_VERSION = "2.0.2";
|
|
24
24
|
__exportStar(require("../.generated/plugin-framework-version"), exports);
|
|
25
25
|
__exportStar(require("./types"), exports);
|
|
26
26
|
__exportStar(require("./browser/frame-client"), exports);
|
package/package.json
CHANGED
|
@@ -21,6 +21,8 @@ The renderer calls the plugin-worker `demo.echo` operation through `@aiot-nexus/
|
|
|
21
21
|
|
|
22
22
|
The renderer follows `context.language` and `context.themeMode` from Nexus, subscribes to in-place context updates without reloading the iframe, and includes `en-US` and `zh-CN` message catalogs. The plugin page has no language or theme selector; the controls shown by `npm run dev` belong to the debug host and simulate Nexus context changes. Add or edit renderer messages in `src/renderer/app.ts`; edit host-shell titles and descriptions in `src/manifest.json` resources.
|
|
23
23
|
|
|
24
|
+
The current SDK has no generic business-event subscription API. For a long-running operation, register a fast `job.start` operation that returns a `taskId`, then expose `job.getStatus` and optional `job.cancel` for renderer polling. See the Nexus repository's plugin IPC and event notification example for the full contract and code.
|
|
25
|
+
|
|
24
26
|
Use `npm run dev:web` for renderer-only UI work in the system browser. It runs `build:web`, writes an isolated package under `.nexus-plugin-dev/web-package/`, builds with source maps, serves only on `127.0.0.1:4173`, and opens the browser automatically. It does not execute `mainEntry` or overwrite the deployable `dist` package. Language and theme controls still simulate Nexus context, while `invokeMain` is intentionally unavailable. Pass `-- --no-open` or `-- --port <port>` when needed; use `npm run dev` for Host API integration.
|
|
25
27
|
|
|
26
28
|
Use `npm run dev:nexus -- --host C:\path\to\nexus.exe` for live development against a compiled, packaged Nexus. The CLI atomically deploys this plugin to `<exeDir>/Plugins/__PLUGIN_ID__/`. Renderer changes rebuild and reload the iframe without changing the Nexus PID; safe manifest changes rescan contributions in the same process; `mainEntry`, dependency, native-module, or worker-sensitive manifest changes rebuild the full package, stop the old plugin worker, replace the package, and start a new worker while keeping the Nexus PID. Failed builds keep the previous deployment; a failed worker reload rolls the package back and restores the previous worker. Add `--no-devtools`, `--inspect[=<port>]`, `--inspect-brk[=<port>]`, `--surface`, or `--contribution-id` after the second `--` as needed.
|
|
@@ -21,6 +21,8 @@ The deployable plugin package is `dist/__PLUGIN_ID__/`. For a manual installatio
|
|
|
21
21
|
|
|
22
22
|
The renderer follows `context.language` and `context.themeMode` from Nexus, subscribes to in-place context updates without reloading the iframe, and includes `en-US` and `zh-CN` message catalogs. The plugin page has no language or theme selector; the controls shown by `npm run dev` belong to the debug host and simulate Nexus context changes. Static labels, CRUD status messages, action buttons, validation text, and date formatting switch together. Edit renderer messages in `src/renderer/app.ts`; edit host-shell titles and descriptions in `src/manifest.json` resources.
|
|
23
23
|
|
|
24
|
+
The current SDK has no generic business-event subscription API. For a long-running operation, register a fast `job.start` operation that returns a `taskId`, then expose `job.getStatus` and optional `job.cancel` for renderer polling. See the Nexus repository's plugin IPC and event notification example for the full contract and code.
|
|
25
|
+
|
|
24
26
|
Use `npm run dev:web` for renderer-only layout, styling, localization, and responsive checks in the system browser. It runs `build:web`, writes an isolated package under `.nexus-plugin-dev/web-package/`, and does not rebuild or load `better-sqlite3`, execute `mainEntry`, copy native runtime files, or overwrite the deployable `dist` package. It builds with source maps, serves only on `127.0.0.1:4173`, and opens the browser automatically. Database operations report unsupported; use `npm run dev` for SQLite and Host API integration. Pass `-- --no-open` or `-- --port <port>` when needed.
|
|
25
27
|
|
|
26
28
|
Use `npm run dev:nexus -- --host C:\path\to\nexus.exe` to run the plugin inside a compiled, packaged Nexus. The initial deployment and every full package replacement are atomic. Renderer-only and safe manifest updates keep the Nexus PID; `mainEntry`, dependency, native-module, or worker-sensitive manifest changes run the complete build (including the Electron 22.3.27 native rebuild), stop the old plugin worker, replace the package, and start a new worker in the same Nexus process. The database remains under the host-provided plugin data directory across worker replacements. Failed builds keep the previous deployment, and a failed worker reload rolls the package back and restores the previous worker. Add `--no-devtools`, `--inspect[=<port>]`, `--inspect-brk[=<port>]`, `--surface`, or `--contribution-id` after the second `--` as needed.
|