@aiot-nexus/plugin-sdk 3.1.1 → 3.3.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 +34 -22
- package/dev-host/main.js +20 -5
- package/dist/.generated/plugin-framework-version.d.ts +2 -1
- package/dist/.generated/plugin-framework-version.js +3 -2
- package/dist/.generated/plugin-manifest.schema.json +1 -1
- package/dist/.generated/theme-contract.d.ts +43 -0
- package/dist/.generated/theme-contract.js +75 -0
- package/dist/browser/frame-client.d.ts +1 -0
- package/dist/browser/frame-client.mjs +197 -0
- package/dist/dev-host-metadata.json +1 -1
- package/dist/src/browser/frame-client.d.ts +1 -0
- package/dist/src/browser/frame-client.js +197 -0
- package/dist/src/cli/run.js +2 -2
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +3 -1
- package/dist/src/nexus-dev-deployment.js +2 -2
- package/dist/src/scaffold.js +1 -1
- package/dist/src/sdk-info.d.ts +1 -1
- package/dist/src/sdk-info.js +1 -1
- package/dist/src/types.d.ts +37 -0
- package/dist/src/web-dev.js +2 -0
- package/package.json +1 -1
- package/templates/minimal/README.md.tmpl +13 -9
- package/templates/minimal/src/renderer/app.ts.tmpl +4 -1
- package/templates/minimal/src/renderer/plugin.css +78 -52
- package/templates/sqlite/README.md.tmpl +13 -9
- package/templates/sqlite/src/renderer/app.ts.tmpl +4 -1
- package/templates/sqlite/src/renderer/plugin.css +135 -57
package/README.md
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
# @aiot-nexus/plugin-sdk
|
|
2
2
|
|
|
3
|
-
> 当前 SDK 包版本为 `3.
|
|
3
|
+
> 当前 SDK 包版本为 `3.3.0`,Framework API 为 `3.2.0`,manifest schema 保持 `1`。Framework 3 将可信插件的 `mainEntry` 直接加载到 AIoT Toolkits Electron 主进程;它不是安全沙箱。PluginFrame iframe 仍保持 sandbox、无 Node/Electron 访问。宿主运行模型见 [Framework v3 架构](../docs/plugin-framework-v3-architecture.md)。
|
|
4
4
|
|
|
5
5
|
本包提供公开 TypeScript 契约、PluginFrame 浏览器客户端和 `nexus-plugin` CLI。v2 项目升级前请先阅读 [v2 到 v3 迁移说明](../docs/plugin-framework-v2-v3-migration.md)。完整开发流程见 [独立插件开发指南](../docs/standalone-plugin-development-guide.md)。
|
|
6
6
|
|
|
7
|
+
宿主产品名为 AIoT Toolkits,SDK 包名和 `dev:nexus` 命令保持不变。
|
|
8
|
+
本仓库 SDK 的 `--host` 目录参数只查找 `AIoT Toolkits.exe`,文件不存在时直接报错。
|
|
9
|
+
尚未更新的已发布 SDK 应传完整路径,例如 `--host "C:\Tools\AIoT Toolkits.exe"`;
|
|
10
|
+
目录自动识别的新行为需使用包含本次修改的 SDK 构建。
|
|
11
|
+
|
|
7
12
|
## 兼容基线
|
|
8
13
|
|
|
9
14
|
| 项目 | 固定值 |
|
|
10
15
|
| --- | --- |
|
|
11
|
-
| SDK | `3.
|
|
12
|
-
| Framework API | `3.
|
|
16
|
+
| SDK | `3.3.0` |
|
|
17
|
+
| Framework API | `3.2.0` |
|
|
13
18
|
| Manifest schema | `1` |
|
|
14
19
|
| Node 安装和构建 | `>=16.20.0 <17` |
|
|
15
20
|
| Electron | `22.3.27` |
|
|
@@ -27,7 +32,7 @@ nexus-plugin create <plugin-id> --template minimal|sqlite --output <parent-direc
|
|
|
27
32
|
nexus-plugin upgrade [plugin-project-directory] [--check] [--sdk-source <version-or-local-tgz>]
|
|
28
33
|
nexus-plugin dev [plugin-project-directory]
|
|
29
34
|
nexus-plugin dev:web [plugin-project-directory]
|
|
30
|
-
nexus-plugin dev:nexus [plugin-project-directory] --host <
|
|
35
|
+
nexus-plugin dev:nexus [plugin-project-directory] --host <host-exe-or-directory>
|
|
31
36
|
```
|
|
32
37
|
|
|
33
38
|
仓库内 SDK 命令:
|
|
@@ -48,15 +53,15 @@ npm run publish:sdk
|
|
|
48
53
|
registry 升级与只读预检:
|
|
49
54
|
|
|
50
55
|
```bash
|
|
51
|
-
npx --package=@aiot-nexus/plugin-sdk@3.
|
|
52
|
-
npx --package=@aiot-nexus/plugin-sdk@3.
|
|
56
|
+
npx --package=@aiot-nexus/plugin-sdk@3.3.0 nexus-plugin upgrade . --check
|
|
57
|
+
npx --package=@aiot-nexus/plugin-sdk@3.3.0 nexus-plugin upgrade .
|
|
53
58
|
```
|
|
54
59
|
|
|
55
60
|
发布前或本地 tgz:
|
|
56
61
|
|
|
57
62
|
```bash
|
|
58
|
-
npx --package=./aiot-nexus-plugin-sdk-3.
|
|
59
|
-
--sdk-source ./aiot-nexus-plugin-sdk-3.
|
|
63
|
+
npx --package=./aiot-nexus-plugin-sdk-3.3.0.tgz nexus-plugin upgrade . \
|
|
64
|
+
--sdk-source ./aiot-nexus-plugin-sdk-3.3.0.tgz
|
|
60
65
|
```
|
|
61
66
|
|
|
62
67
|
CI 可以追加 `--json` 获得 `up-to-date`、`upgrade-available`、`upgraded`、`rolled-back` 或 `blocked`。`--allow-major` 只解除 major 保护,不会改写业务源码;跨 major 必须先阅读迁移指南。`--skip-project-checks` 只跳过项目 typecheck/build,安装后的 SDK、package 和 lockfile 一致性仍会验证。
|
|
@@ -67,7 +72,7 @@ CI 可以追加 `--json` 获得 `up-to-date`、`upgrade-available`、`upgraded`
|
|
|
67
72
|
|
|
68
73
|
安装或项目验证失败时恢复备份,并在原 lockfile 存在时运行 `npm ci`。没有原 lockfile 时会运行 `npm install` 并恢复原 SDK,但无法证明完整传递依赖树与升级前一致,因此返回 `blocked` 并保留恢复数据。标准脚手架 build 的 `dist/<pluginId>` 在回滚范围内;自定义 typecheck/build 若修改其他路径,这些副作用不属于升级事务,项目必须自行管理。
|
|
69
74
|
|
|
70
|
-
SDK `3.0.
|
|
75
|
+
SDK `3.0.x` / `3.1.x` 插件可以由 3.2.x 宿主继续加载。升级到 SDK `3.2.0` 只提供主题能力和新的契约;插件必须引用 `--nexus-color-*` 或读取 `context.theme` 才会使用宿主主题色。没有引用新变量的插件保持原有颜色;需要自定义主题时传入 `autoApplyHostTheme: false`。原有 `hostApiVersion: 3.0.0` 可以保持不变,只有实际使用 `showHostStatusBar` 时才需要声明 `hostApiVersion: 3.1.0` 或更高。SDK 3.1.1 修复 registry 升级命令丢失 npm 包名的问题,SDK 3.3.0 将 `dev:nexus --host` 目录查找目标改为 `AIoT Toolkits.exe`,均作为历史兼容说明保留。
|
|
71
76
|
|
|
72
77
|
## 创建插件
|
|
73
78
|
|
|
@@ -90,14 +95,14 @@ nexus-plugin create my-frame-plugin \
|
|
|
90
95
|
|
|
91
96
|
```bash
|
|
92
97
|
npm run pack:sdk
|
|
93
|
-
nexus-plugin create my-frame-plugin --sdk-source ./package/aiot-nexus-plugin-sdk-3.
|
|
98
|
+
nexus-plugin create my-frame-plugin --sdk-source ./package/aiot-nexus-plugin-sdk-3.3.0.tgz --install
|
|
94
99
|
```
|
|
95
100
|
|
|
96
|
-
生成插件后,可以在 `src/manifest.json` 的业务入口中静态控制
|
|
101
|
+
生成插件后,可以在 `src/manifest.json` 的业务入口中静态控制 AIoT Toolkits 宿主状态栏。相关 manifest 片段如下,其余必填字段沿用脚手架生成值:
|
|
97
102
|
|
|
98
103
|
```json
|
|
99
104
|
{
|
|
100
|
-
"hostApiVersion": "3.
|
|
105
|
+
"hostApiVersion": "3.2.0",
|
|
101
106
|
"businessEntries": [
|
|
102
107
|
{
|
|
103
108
|
"businessId": "my-frame-plugin",
|
|
@@ -115,7 +120,7 @@ nexus-plugin create my-frame-plugin --sdk-source ./package/aiot-nexus-plugin-sdk
|
|
|
115
120
|
|
|
116
121
|
仅显式设置为 `false` 时隐藏。字段省略或设置为 `true` 时显示;离开该业务入口后,宿主状态栏自动恢复。该字段要求 `hostApiVersion >= 3.1.0`。
|
|
117
122
|
|
|
118
|
-
## Framework 3.
|
|
123
|
+
## Framework 3.2 mainEntry
|
|
119
124
|
|
|
120
125
|
`mainEntry` 是可信 Electron 主进程代码,可直接导入 Electron、Node 内置模块和插件本地依赖:
|
|
121
126
|
|
|
@@ -183,9 +188,9 @@ function failure(code: string, message: string) {
|
|
|
183
188
|
- `context.host.*` 直接返回 `NexusResult<T>`,不需要 `await`。
|
|
184
189
|
- 注册的 operation handler 可以返回 Promise,可等待异步文件对话框、网络或其他用户交互。
|
|
185
190
|
- operation 默认没有业务超时。浏览器客户端 `requestTimeoutMs` 默认是 `0`;20 秒仅用于 iframe 初始化握手。
|
|
186
|
-
- 同步 Electron API 也能调用,但 `showOpenDialogSync()`、同步文件 I/O、长 CPU 任务会阻塞
|
|
191
|
+
- 同步 Electron API 也能调用,但 `showOpenDialogSync()`、同步文件 I/O、长 CPU 任务会阻塞 AIoT Toolkits 主进程、所有窗口和插件,应优先使用异步版本。
|
|
187
192
|
- Framework 不跟踪插件直接创建的 Electron/Node 资源。插件必须在 `deactivate` 中自行清理。
|
|
188
|
-
- Node 模块图和原生模块不能安全热卸载。已加载 `mainEntry`、依赖、原生模块或相关 manifest 变化必须重启
|
|
193
|
+
- Node 模块图和原生模块不能安全热卸载。已加载 `mainEntry`、依赖、原生模块或相关 manifest 变化必须重启 AIoT Toolkits。
|
|
189
194
|
|
|
190
195
|
Framework 3 不再提供 `context.host.electron` Facade,也没有 `electronHostControl` 权限。manifest 权限只描述 Host API 能力,不限制可信 `mainEntry` 直接调用 Electron/Node。SDK 保留 `createPluginElectronIpcChannel()` 和 `createPluginElectronProtocolScheme()` 作为命名约定帮助函数;宿主不会自动隔离或清理对应注册。
|
|
191
196
|
|
|
@@ -205,6 +210,12 @@ script-src 'self'; style-src 'self' 'unsafe-inline'; worker-src 'self' blob:
|
|
|
205
210
|
|
|
206
211
|
`createPluginFrameClient()` 的首次初始化默认等待 20 秒。业务请求默认不设超时;只有插件显式传入正数 `requestTimeoutMs` 时客户端才会主动超时。进度、取消和恢复仍建议使用 `taskId + getStatus + cancel`,但这是任务 UX 和资源管理策略,不是 worker 存活要求。通信契约见 [插件 IPC 与事件通知示例](../docs/plugin-ipc-event-notification-example.md)。
|
|
207
212
|
|
|
213
|
+
### 宿主主题色
|
|
214
|
+
|
|
215
|
+
Framework 3.2 在 `PluginRendererContext.theme` 中提供 `light` 和 `dark` 两套语义 palette。SDK frame client 默认把当前 palette 的固定白名单字段写入 `--nexus-color-*`,并设置 `data-nexus-theme="light|dark"`;`themeMode: "system"` 会跟随系统主题变化。SDK 不会修改插件已有的 `--bg`、`--accent`、`data-theme` 或 `color-scheme`。
|
|
216
|
+
|
|
217
|
+
SDK 升级本身只提供能力。插件需要在 CSS 中引用 `var(--nexus-color-primary, #8fc31f)` 等变量,或从 `context.theme?.dark.primary` 读取值,才会使用宿主主题色。旧宿主或缺少 `theme` 的上下文仍可正常初始化。自定义主题插件可以显式使用 `createPluginFrameClient({ autoApplyHostTheme: false })`,此时不注入变量、不设置宿主主题标记,但仍可手动读取 `context.theme`。
|
|
218
|
+
|
|
208
219
|
## SQLite 模板
|
|
209
220
|
|
|
210
221
|
`sqlite` 模板默认打开 `pluginDataDirectory/plugin.db` 并初始化 `plugin_records`。工具栏“打开数据库”由 `mainEntry` 直接调用 `dialog.showOpenDialog()`;iframe 只接收数据库信息和参数化 operation。
|
|
@@ -228,7 +239,7 @@ script-src 'self'; style-src 'self' 'unsafe-inline'; worker-src 'self' blob:
|
|
|
228
239
|
| `npm run dev:web` | 不执行 | 不可用 | 只调试 Renderer;`invokeMain` 返回 `PluginCapabilityUnsupported`。 |
|
|
229
240
|
| `npm run dev` | Electron 调试宿主主进程直接加载 | 可用 | 调试真实生命周期、对话框、SQLite 和插件窗口。 |
|
|
230
241
|
| `npm run dev:debug` | 同 `dev` | 可用 | Electron 主进程以 `--inspect-brk=9229` 启动。 |
|
|
231
|
-
| `npm run dev:nexus -- --host ...` | 正式
|
|
242
|
+
| `npm run dev:nexus -- --host ...` | 正式 AIoT Toolkits 主进程直接加载 | 可用 | 最终集成和监听更新。 |
|
|
232
243
|
|
|
233
244
|
Electron 调试壳仅保留 context 工具栏;插件 iframe 无边距、无边框,填满剩余窗口,不设置最小宽高,也不根据内容上报限制尺寸。Renderer DevTools 默认打开。
|
|
234
245
|
|
|
@@ -238,16 +249,16 @@ VS Code 模板提供 `Debug Nexus plugin` 和 `Attach Nexus plugin main process
|
|
|
238
249
|
|
|
239
250
|
| 修改范围 | 处理方式 |
|
|
240
251
|
| --- | --- |
|
|
241
|
-
| `src/renderer/**` | 只构建 Renderer 并重载 iframe,
|
|
252
|
+
| `src/renderer/**` | 只构建 Renderer 并重载 iframe,AIoT Toolkits PID 不变。 |
|
|
242
253
|
| 无 `mainEntry` 插件的安全 manifest 展示/贡献字段 | 原子替换 manifest 并重扫,PID 不变。 |
|
|
243
|
-
| 有 `mainEntry` 插件的 manifest 变化 | 完整构建并重启
|
|
244
|
-
| `src/main.*`、依赖、原生模块、构建脚本 | 完整构建并重启
|
|
254
|
+
| 有 `mainEntry` 插件的 manifest 变化 | 完整构建并重启 AIoT Toolkits。 |
|
|
255
|
+
| `src/main.*`、依赖、原生模块、构建脚本 | 完整构建并重启 AIoT Toolkits。 |
|
|
245
256
|
|
|
246
|
-
构建失败保留旧部署;新宿主启动或就绪失败会恢复旧包和旧宿主。`--inspect` / `--inspect-brk` 调试的是
|
|
257
|
+
构建失败保留旧部署;新宿主启动或就绪失败会恢复旧包和旧宿主。`--inspect` / `--inspect-brk` 调试的是 AIoT Toolkits Electron 主进程,因此会同时暂停或暴露宿主代码。按 `Ctrl+C` 时 CLI 关闭自己启动的宿主。
|
|
247
258
|
|
|
248
259
|
## 发布前检查
|
|
249
260
|
|
|
250
|
-
1. `hostApiVersion`
|
|
261
|
+
1. `hostApiVersion` 使用当前 `3.2.0`(或按实际能力保持旧版本),manifest schema 仍为 `1`。
|
|
251
262
|
2. 业务入口的 `showHostStatusBar` 可静态声明是否保留宿主状态栏;省略时默认保留,显式使用时要求 `hostApiVersion >= 3.1.0`。
|
|
252
263
|
3. `activate/deactivate` 同步,operation handler 才使用异步。
|
|
253
264
|
4. Renderer 只使用窄口径 operation,所有输入在 mainEntry 再校验。
|
|
@@ -255,4 +266,5 @@ VS Code 模板提供 `Debug Nexus plugin` 和 `Attach Nexus plugin main process
|
|
|
255
266
|
6. IPC、protocol、快捷键、菜单和文件路径使用插件命名空间并检测冲突。
|
|
256
267
|
7. `deactivate` 关闭窗口、Session 监听、IPC、protocol、快捷键、定时器、数据库和子进程。
|
|
257
268
|
8. 没有同步长任务、无限载荷、任意命令/SQL/路径透传或日志敏感信息。
|
|
258
|
-
9.
|
|
269
|
+
9. 主题集成明确引用 `--nexus-color-*` / `context.theme`,或显式关闭 `autoApplyHostTheme`。
|
|
270
|
+
10. 运行 `npm test`、插件构建、正式 AIoT Toolkits 集成和 Windows 7 真机验收。
|
package/dev-host/main.js
CHANGED
|
@@ -191,13 +191,13 @@ function activatePluginMainEntry() {
|
|
|
191
191
|
throw new Error("Plugin activate export must be a function.");
|
|
192
192
|
}
|
|
193
193
|
if (isAsyncFunction(activate)) {
|
|
194
|
-
throw new Error("Plugin activate must complete synchronously in Framework API 3.
|
|
194
|
+
throw new Error("Plugin activate must complete synchronously in Framework API 3.2.0.");
|
|
195
195
|
}
|
|
196
196
|
activationStarted = true;
|
|
197
197
|
const result = activate(createActivationContext());
|
|
198
198
|
if (isPromiseLike(result)) {
|
|
199
199
|
observeRejectedLifecyclePromise(result, "activate");
|
|
200
|
-
throw new Error("Plugin activate must complete synchronously in Framework API 3.
|
|
200
|
+
throw new Error("Plugin activate must complete synchronously in Framework API 3.2.0.");
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
203
|
pluginActivated = true;
|
|
@@ -265,13 +265,13 @@ function deactivatePluginMainEntry(reason) {
|
|
|
265
265
|
return;
|
|
266
266
|
}
|
|
267
267
|
if (typeof deactivate !== "function" || isAsyncFunction(deactivate)) {
|
|
268
|
-
process.stderr.write("[nexus-plugin-dev] plugin deactivate must complete synchronously in Framework API 3.
|
|
268
|
+
process.stderr.write("[nexus-plugin-dev] plugin deactivate must complete synchronously in Framework API 3.2.0.\n");
|
|
269
269
|
return;
|
|
270
270
|
}
|
|
271
271
|
const result = deactivate(reason);
|
|
272
272
|
if (isPromiseLike(result)) {
|
|
273
273
|
observeRejectedLifecyclePromise(result, "deactivate");
|
|
274
|
-
process.stderr.write("[nexus-plugin-dev] plugin deactivate must complete synchronously in Framework API 3.
|
|
274
|
+
process.stderr.write("[nexus-plugin-dev] plugin deactivate must complete synchronously in Framework API 3.2.0.\n");
|
|
275
275
|
}
|
|
276
276
|
} catch (error) {
|
|
277
277
|
process.stderr.write("[nexus-plugin-dev] plugin deactivation failed: " + errorMessage(error) + "\n");
|
|
@@ -448,6 +448,11 @@ async function verifyRendererContextSmoke() {
|
|
|
448
448
|
|| configuration.manifest.minHostVersion === undefined) {
|
|
449
449
|
throw new Error("Development Host API version contract is inconsistent.");
|
|
450
450
|
}
|
|
451
|
+
const frameSmokeToken = "nexus-plugin-frame-" + String(Date.now()) + "-" + String(Math.random());
|
|
452
|
+
await frame.executeJavaScript(
|
|
453
|
+
"window.__nexusPluginDevHostFrameToken = " + JSON.stringify(frameSmokeToken) + ";",
|
|
454
|
+
true
|
|
455
|
+
);
|
|
451
456
|
const initialState = await readRendererContextState(frame);
|
|
452
457
|
const targetLanguage = configuration.manifest.supportedCultures.find(function findAlternative(culture) {
|
|
453
458
|
return culture !== configuration.language;
|
|
@@ -468,7 +473,11 @@ async function verifyRendererContextSmoke() {
|
|
|
468
473
|
lastState = await readRendererContextState(frame);
|
|
469
474
|
const languageChanged = lastState.language === targetLanguage
|
|
470
475
|
&& (targetLanguage === configuration.language || lastState.localizedText !== initialState.localizedText);
|
|
471
|
-
if (languageChanged
|
|
476
|
+
if (languageChanged
|
|
477
|
+
&& lastState.theme === targetTheme
|
|
478
|
+
&& lastState.nexusTheme === targetTheme
|
|
479
|
+
&& lastState.primary === sdk.NEXUS_PLUGIN_RENDERER_THEME[targetTheme].primary
|
|
480
|
+
&& lastState.frameToken === frameSmokeToken) {
|
|
472
481
|
return;
|
|
473
482
|
}
|
|
474
483
|
await new Promise(function wait(resolve) {
|
|
@@ -477,6 +486,8 @@ async function verifyRendererContextSmoke() {
|
|
|
477
486
|
}
|
|
478
487
|
throw new Error(
|
|
479
488
|
"Expected language=" + targetLanguage + " and theme=" + targetTheme
|
|
489
|
+
+ " and primary=" + sdk.NEXUS_PLUGIN_RENDERER_THEME[targetTheme].primary
|
|
490
|
+
+ " and unchanged frame token"
|
|
480
491
|
+ ", received " + JSON.stringify(lastState) + "."
|
|
481
492
|
);
|
|
482
493
|
}
|
|
@@ -498,6 +509,9 @@ async function readRendererContextState(frame) {
|
|
|
498
509
|
return frame.executeJavaScript(`({
|
|
499
510
|
language: document.documentElement.lang,
|
|
500
511
|
theme: document.documentElement.dataset.theme || "",
|
|
512
|
+
nexusTheme: document.documentElement.dataset.nexusTheme || "",
|
|
513
|
+
primary: document.documentElement.style.getPropertyValue("--nexus-color-primary").trim(),
|
|
514
|
+
frameToken: window.__nexusPluginDevHostFrameToken || "",
|
|
501
515
|
localizedText: document.querySelector("[data-i18n]")
|
|
502
516
|
? document.querySelector("[data-i18n]").textContent
|
|
503
517
|
: ""
|
|
@@ -525,6 +539,7 @@ function createRendererConfiguration() {
|
|
|
525
539
|
contributionId: configuration.contributionId,
|
|
526
540
|
language: configuration.language,
|
|
527
541
|
themeMode: configuration.themeMode,
|
|
542
|
+
theme: sdk.NEXUS_PLUGIN_RENDERER_THEME,
|
|
528
543
|
hostApiVersion: sdk.PLUGIN_FRAMEWORK_VERSION
|
|
529
544
|
}
|
|
530
545
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export declare const PLUGIN_FRAMEWORK_VERSION = "3.
|
|
1
|
+
export declare const PLUGIN_FRAMEWORK_VERSION = "3.2.0";
|
|
2
2
|
export declare const PLUGIN_MANIFEST_SCHEMA_VERSION = 1;
|
|
3
3
|
export declare const PLUGIN_COMPATIBLE_VERSION_PATTERN = "^(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))?(\\.(0|[1-9][0-9]*))?$";
|
|
4
|
+
export declare const PLUGIN_VERSION_PATTERN = "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(?:-((?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\\+([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?$";
|
|
4
5
|
export interface ParsedPluginVersion {
|
|
5
6
|
major: number;
|
|
6
7
|
minor: number;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isPluginFrameworkVersionCompatible = exports.comparePluginVersions = exports.parsePluginCompatibleVersion = exports.PLUGIN_COMPATIBLE_VERSION_PATTERN = exports.PLUGIN_MANIFEST_SCHEMA_VERSION = exports.PLUGIN_FRAMEWORK_VERSION = void 0;
|
|
4
|
-
exports.PLUGIN_FRAMEWORK_VERSION = "3.
|
|
3
|
+
exports.isPluginFrameworkVersionCompatible = exports.comparePluginVersions = exports.parsePluginCompatibleVersion = exports.PLUGIN_VERSION_PATTERN = exports.PLUGIN_COMPATIBLE_VERSION_PATTERN = exports.PLUGIN_MANIFEST_SCHEMA_VERSION = exports.PLUGIN_FRAMEWORK_VERSION = void 0;
|
|
4
|
+
exports.PLUGIN_FRAMEWORK_VERSION = "3.2.0";
|
|
5
5
|
exports.PLUGIN_MANIFEST_SCHEMA_VERSION = 1;
|
|
6
6
|
exports.PLUGIN_COMPATIBLE_VERSION_PATTERN = "^(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))?(\\.(0|[1-9][0-9]*))?$";
|
|
7
|
+
exports.PLUGIN_VERSION_PATTERN = "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(?:-((?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\\+([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?$";
|
|
7
8
|
function parsePluginCompatibleVersion(value) {
|
|
8
9
|
const match = /^(0|[1-9][0-9]*)(?:\.(0|[1-9][0-9]*))?(?:\.(0|[1-9][0-9]*))?$/.exec(value.trim());
|
|
9
10
|
if (!match) {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"pluginId": { "type": "string", "pattern": "^[a-z0-9][a-z0-9._-]{1,63}$" },
|
|
21
21
|
"displayNameKey": { "type": "string", "minLength": 1 },
|
|
22
22
|
"descriptionKey": { "type": "string" },
|
|
23
|
-
"pluginVersion": { "type": "string", "
|
|
23
|
+
"pluginVersion": { "type": "string", "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(?:-((?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\\+([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?$" },
|
|
24
24
|
"hostApiVersion": { "type": "string", "pattern": "^(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))?(\\.(0|[1-9][0-9]*))?$" },
|
|
25
25
|
"minHostVersion": { "type": "string", "pattern": "^(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))?(\\.(0|[1-9][0-9]*))?$" },
|
|
26
26
|
"supportedCultures": {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export declare const NEXUS_THEME_BACKGROUND_COLORS: {
|
|
2
|
+
readonly light: "#eef1f4";
|
|
3
|
+
readonly dark: "#101418";
|
|
4
|
+
};
|
|
5
|
+
export declare const NEXUS_BRAND_ACCENT_KEY = "green";
|
|
6
|
+
export interface PluginThemeTokens {
|
|
7
|
+
background: string;
|
|
8
|
+
surface: string;
|
|
9
|
+
surfaceAlt: string;
|
|
10
|
+
surfaceRaised: string;
|
|
11
|
+
surfaceHover: string;
|
|
12
|
+
surfaceInset: string;
|
|
13
|
+
border: string;
|
|
14
|
+
borderSoft: string;
|
|
15
|
+
borderStrong: string;
|
|
16
|
+
text: string;
|
|
17
|
+
textMuted: string;
|
|
18
|
+
textSoft: string;
|
|
19
|
+
textFaint: string;
|
|
20
|
+
primary: string;
|
|
21
|
+
accent: string;
|
|
22
|
+
onPrimary: string;
|
|
23
|
+
primarySoft: string;
|
|
24
|
+
info: string;
|
|
25
|
+
infoSoft: string;
|
|
26
|
+
positive: string;
|
|
27
|
+
positiveSoft: string;
|
|
28
|
+
warning: string;
|
|
29
|
+
warningSoft: string;
|
|
30
|
+
negative: string;
|
|
31
|
+
negativeSoft: string;
|
|
32
|
+
mutedSoft: string;
|
|
33
|
+
onInfo: string;
|
|
34
|
+
onPositive: string;
|
|
35
|
+
onWarning: string;
|
|
36
|
+
onNegative: string;
|
|
37
|
+
}
|
|
38
|
+
export interface PluginRendererTheme {
|
|
39
|
+
light: PluginThemeTokens;
|
|
40
|
+
dark: PluginThemeTokens;
|
|
41
|
+
}
|
|
42
|
+
/** Public semantic colors exposed to isolated PluginFrame renderers. */
|
|
43
|
+
export declare const NEXUS_PLUGIN_RENDERER_THEME: PluginRendererTheme;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NEXUS_PLUGIN_RENDERER_THEME = exports.NEXUS_BRAND_ACCENT_KEY = exports.NEXUS_THEME_BACKGROUND_COLORS = void 0;
|
|
4
|
+
exports.NEXUS_THEME_BACKGROUND_COLORS = {
|
|
5
|
+
light: "#eef1f4",
|
|
6
|
+
dark: "#101418"
|
|
7
|
+
};
|
|
8
|
+
exports.NEXUS_BRAND_ACCENT_KEY = "green";
|
|
9
|
+
/** Public semantic colors exposed to isolated PluginFrame renderers. */
|
|
10
|
+
exports.NEXUS_PLUGIN_RENDERER_THEME = {
|
|
11
|
+
light: {
|
|
12
|
+
background: "#eef1f4",
|
|
13
|
+
surface: "#ffffff",
|
|
14
|
+
surfaceAlt: "#f7f9fa",
|
|
15
|
+
surfaceRaised: "#f7f9fa",
|
|
16
|
+
surfaceHover: "#eef3f6",
|
|
17
|
+
surfaceInset: "#e4e9ed",
|
|
18
|
+
border: "#d7dde3",
|
|
19
|
+
borderSoft: "#e2e7eb",
|
|
20
|
+
borderStrong: "#c2cbd3",
|
|
21
|
+
text: "#212a32",
|
|
22
|
+
textMuted: "#5a6772",
|
|
23
|
+
textSoft: "#8b97a1",
|
|
24
|
+
textFaint: "#8b97a1",
|
|
25
|
+
primary: "#8fc31f",
|
|
26
|
+
accent: "#6f9a15",
|
|
27
|
+
onPrimary: "#ffffff",
|
|
28
|
+
primarySoft: "rgba(143, 195, 31, 0.12)",
|
|
29
|
+
info: "#2277c4",
|
|
30
|
+
infoSoft: "rgba(34, 119, 196, 0.12)",
|
|
31
|
+
positive: "#1f9a4e",
|
|
32
|
+
positiveSoft: "rgba(31, 154, 78, 0.12)",
|
|
33
|
+
warning: "#e0a53a",
|
|
34
|
+
warningSoft: "rgba(224, 165, 58, 0.14)",
|
|
35
|
+
negative: "#c2402f",
|
|
36
|
+
negativeSoft: "rgba(194, 64, 47, 0.1)",
|
|
37
|
+
mutedSoft: "rgba(90, 103, 114, 0.1)",
|
|
38
|
+
onInfo: "#ffffff",
|
|
39
|
+
onPositive: "#ffffff",
|
|
40
|
+
onWarning: "#ffffff",
|
|
41
|
+
onNegative: "#ffffff"
|
|
42
|
+
},
|
|
43
|
+
dark: {
|
|
44
|
+
background: "#101418",
|
|
45
|
+
surface: "#1b222a",
|
|
46
|
+
surfaceAlt: "#171c22",
|
|
47
|
+
surfaceRaised: "#171c22",
|
|
48
|
+
surfaceHover: "#222a34",
|
|
49
|
+
surfaceInset: "#0c0f13",
|
|
50
|
+
border: "#2a333e",
|
|
51
|
+
borderSoft: "#303a45",
|
|
52
|
+
borderStrong: "#38434f",
|
|
53
|
+
text: "#e2e8ee",
|
|
54
|
+
textMuted: "#97a3af",
|
|
55
|
+
textSoft: "#64707c",
|
|
56
|
+
textFaint: "#64707c",
|
|
57
|
+
primary: "#8fc31f",
|
|
58
|
+
accent: "#a5d43e",
|
|
59
|
+
onPrimary: "#ffffff",
|
|
60
|
+
primarySoft: "rgba(143, 195, 31, 0.14)",
|
|
61
|
+
info: "#4da3e8",
|
|
62
|
+
infoSoft: "rgba(77, 163, 232, 0.14)",
|
|
63
|
+
positive: "#46b96e",
|
|
64
|
+
positiveSoft: "rgba(70, 185, 110, 0.14)",
|
|
65
|
+
warning: "#e0a53a",
|
|
66
|
+
warningSoft: "rgba(224, 165, 58, 0.14)",
|
|
67
|
+
negative: "#e2604f",
|
|
68
|
+
negativeSoft: "rgba(226, 96, 79, 0.14)",
|
|
69
|
+
mutedSoft: "rgba(151, 163, 175, 0.12)",
|
|
70
|
+
onInfo: "#101418",
|
|
71
|
+
onPositive: "#101418",
|
|
72
|
+
onWarning: "#101418",
|
|
73
|
+
onNegative: "#101418"
|
|
74
|
+
}
|
|
75
|
+
};
|
|
@@ -10,6 +10,7 @@ export interface PluginFrameClientOptions {
|
|
|
10
10
|
initializationTimeoutMs?: number;
|
|
11
11
|
requestTimeoutMs?: number;
|
|
12
12
|
autoResize?: boolean;
|
|
13
|
+
autoApplyHostTheme?: boolean;
|
|
13
14
|
resizeRoot?: HTMLElement;
|
|
14
15
|
}
|
|
15
16
|
export type PluginFrameContextListener = (context: PluginRendererContext) => void;
|
|
@@ -5,6 +5,40 @@ const requestMessageType = "nexus-plugin:request";
|
|
|
5
5
|
const responseMessageType = "nexus-plugin:response";
|
|
6
6
|
const resizeMessageType = "nexus-plugin:resize";
|
|
7
7
|
const defaultInitializationTimeoutMs = 20000;
|
|
8
|
+
const systemThemeMediaQuery = "(prefers-color-scheme: dark)";
|
|
9
|
+
const themeTokenCssVariables = [
|
|
10
|
+
["background", "--nexus-color-background"],
|
|
11
|
+
["surface", "--nexus-color-surface"],
|
|
12
|
+
["surfaceAlt", "--nexus-color-surface-alt"],
|
|
13
|
+
["surfaceRaised", "--nexus-color-surface-raised"],
|
|
14
|
+
["surfaceHover", "--nexus-color-surface-hover"],
|
|
15
|
+
["surfaceInset", "--nexus-color-surface-inset"],
|
|
16
|
+
["border", "--nexus-color-border"],
|
|
17
|
+
["borderSoft", "--nexus-color-border-soft"],
|
|
18
|
+
["borderStrong", "--nexus-color-border-strong"],
|
|
19
|
+
["text", "--nexus-color-text"],
|
|
20
|
+
["textMuted", "--nexus-color-text-muted"],
|
|
21
|
+
["textSoft", "--nexus-color-text-soft"],
|
|
22
|
+
["textFaint", "--nexus-color-text-faint"],
|
|
23
|
+
["primary", "--nexus-color-primary"],
|
|
24
|
+
["accent", "--nexus-color-accent"],
|
|
25
|
+
["onPrimary", "--nexus-color-on-primary"],
|
|
26
|
+
["primarySoft", "--nexus-color-primary-soft"],
|
|
27
|
+
["info", "--nexus-color-info"],
|
|
28
|
+
["infoSoft", "--nexus-color-info-soft"],
|
|
29
|
+
["positive", "--nexus-color-positive"],
|
|
30
|
+
["positiveSoft", "--nexus-color-positive-soft"],
|
|
31
|
+
["warning", "--nexus-color-warning"],
|
|
32
|
+
["warningSoft", "--nexus-color-warning-soft"],
|
|
33
|
+
["negative", "--nexus-color-negative"],
|
|
34
|
+
["negativeSoft", "--nexus-color-negative-soft"],
|
|
35
|
+
["mutedSoft", "--nexus-color-muted-soft"],
|
|
36
|
+
["onInfo", "--nexus-color-on-info"],
|
|
37
|
+
["onPositive", "--nexus-color-on-positive"],
|
|
38
|
+
["onWarning", "--nexus-color-on-warning"],
|
|
39
|
+
["onNegative", "--nexus-color-on-negative"]
|
|
40
|
+
];
|
|
41
|
+
const hostThemeOwners = new WeakMap();
|
|
8
42
|
export class PluginFrameClientError extends Error {
|
|
9
43
|
constructor(code, message) {
|
|
10
44
|
super(message);
|
|
@@ -21,6 +55,7 @@ export function createPluginFrameClient(options = {}) {
|
|
|
21
55
|
const targetWindow = options.targetWindow || frameWindow.parent;
|
|
22
56
|
const initializationTimeoutMs = options.initializationTimeoutMs === undefined ? defaultInitializationTimeoutMs : options.initializationTimeoutMs;
|
|
23
57
|
const requestTimeoutMs = options.requestTimeoutMs === undefined ? 0 : options.requestTimeoutMs;
|
|
58
|
+
const autoApplyHostTheme = options.autoApplyHostTheme !== false;
|
|
24
59
|
if (!Number.isFinite(initializationTimeoutMs) || initializationTimeoutMs <= 0) {
|
|
25
60
|
throw new PluginFrameClientError("ProtocolError", "initializationTimeoutMs must be a positive finite number.");
|
|
26
61
|
}
|
|
@@ -35,6 +70,16 @@ export function createPluginFrameClient(options = {}) {
|
|
|
35
70
|
let contextTimeoutId;
|
|
36
71
|
let resizeTimer;
|
|
37
72
|
let resizeObserver;
|
|
73
|
+
const hostThemeOwner = Symbol("nexus-plugin-host-theme");
|
|
74
|
+
let hostThemeRoot;
|
|
75
|
+
let hostThemeMarker;
|
|
76
|
+
let hostThemePreviousMarker;
|
|
77
|
+
let hostThemeHadPreviousMarker = false;
|
|
78
|
+
const hostThemeAppliedValues = new Map();
|
|
79
|
+
const hostThemePreviousValues = new Map();
|
|
80
|
+
const systemThemeMedia = autoApplyHostTheme && typeof frameWindow.matchMedia === "function"
|
|
81
|
+
? frameWindow.matchMedia(systemThemeMediaQuery)
|
|
82
|
+
: undefined;
|
|
38
83
|
const pendingRequests = new Map();
|
|
39
84
|
const contextListeners = new Set();
|
|
40
85
|
let resolveContext;
|
|
@@ -52,6 +97,14 @@ export function createPluginFrameClient(options = {}) {
|
|
|
52
97
|
}
|
|
53
98
|
}, initializationTimeoutMs);
|
|
54
99
|
frameWindow.addEventListener("message", handleMessage);
|
|
100
|
+
if (systemThemeMedia) {
|
|
101
|
+
if (typeof systemThemeMedia.addEventListener === "function") {
|
|
102
|
+
systemThemeMedia.addEventListener("change", handleSystemThemeChange);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
systemThemeMedia.addListener(handleSystemThemeChange);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
55
108
|
if (options.autoResize !== false) {
|
|
56
109
|
frameWindow.addEventListener("resize", scheduleResize);
|
|
57
110
|
const ResizeObserverConstructor = frameWindow.ResizeObserver;
|
|
@@ -124,6 +177,7 @@ export function createPluginFrameClient(options = {}) {
|
|
|
124
177
|
const hadContext = contextValue !== undefined;
|
|
125
178
|
hostOrigin = event.origin;
|
|
126
179
|
contextValue = event.data.context;
|
|
180
|
+
applyCurrentHostTheme();
|
|
127
181
|
if (!contextSettled) {
|
|
128
182
|
contextSettled = true;
|
|
129
183
|
if (contextTimeoutId !== undefined) {
|
|
@@ -166,6 +220,99 @@ export function createPluginFrameClient(options = {}) {
|
|
|
166
220
|
}
|
|
167
221
|
}
|
|
168
222
|
}
|
|
223
|
+
function handleSystemThemeChange() {
|
|
224
|
+
if (contextValue?.themeMode === "system") {
|
|
225
|
+
applyCurrentHostTheme();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function applyCurrentHostTheme() {
|
|
229
|
+
if (!autoApplyHostTheme || !contextValue) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const root = frameWindow.document.documentElement;
|
|
233
|
+
if (!root?.dataset || !root.style) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
initializeHostThemeOwnership(root);
|
|
237
|
+
const resolvedMode = contextValue.themeMode === "system"
|
|
238
|
+
? (systemThemeMedia?.matches ? "dark" : "light")
|
|
239
|
+
: contextValue.themeMode;
|
|
240
|
+
root.dataset.nexusTheme = resolvedMode;
|
|
241
|
+
hostThemeMarker = resolvedMode;
|
|
242
|
+
const tokens = contextValue.theme?.[resolvedMode];
|
|
243
|
+
for (const [tokenName, cssVariable] of themeTokenCssVariables) {
|
|
244
|
+
if (tokens) {
|
|
245
|
+
const value = tokens[tokenName];
|
|
246
|
+
root.style.setProperty(cssVariable, value);
|
|
247
|
+
hostThemeAppliedValues.set(cssVariable, value);
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
restoreHostThemeVariable(root, cssVariable);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function initializeHostThemeOwnership(root) {
|
|
255
|
+
if (hostThemeRoot === root) {
|
|
256
|
+
hostThemeOwners.set(root, hostThemeOwner);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (hostThemeRoot) {
|
|
260
|
+
cleanupHostTheme();
|
|
261
|
+
}
|
|
262
|
+
hostThemeRoot = root;
|
|
263
|
+
hostThemeOwners.set(root, hostThemeOwner);
|
|
264
|
+
hostThemePreviousMarker = root.dataset.nexusTheme;
|
|
265
|
+
hostThemeHadPreviousMarker = Object.prototype.hasOwnProperty.call(root.dataset, "nexusTheme");
|
|
266
|
+
hostThemePreviousValues.clear();
|
|
267
|
+
for (const [, cssVariable] of themeTokenCssVariables) {
|
|
268
|
+
hostThemePreviousValues.set(cssVariable, root.style.getPropertyValue(cssVariable));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function restoreHostThemeVariable(root, cssVariable) {
|
|
272
|
+
const appliedValue = hostThemeAppliedValues.get(cssVariable);
|
|
273
|
+
if (appliedValue === undefined || root.style.getPropertyValue(cssVariable) !== appliedValue) {
|
|
274
|
+
hostThemeAppliedValues.delete(cssVariable);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const previousValue = hostThemePreviousValues.get(cssVariable) || "";
|
|
278
|
+
if (previousValue) {
|
|
279
|
+
root.style.setProperty(cssVariable, previousValue);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
root.style.removeProperty(cssVariable);
|
|
283
|
+
}
|
|
284
|
+
hostThemeAppliedValues.delete(cssVariable);
|
|
285
|
+
}
|
|
286
|
+
function cleanupHostTheme() {
|
|
287
|
+
const root = hostThemeRoot;
|
|
288
|
+
if (!root) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (hostThemeOwners.get(root) !== hostThemeOwner) {
|
|
292
|
+
hostThemeRoot = undefined;
|
|
293
|
+
hostThemeAppliedValues.clear();
|
|
294
|
+
hostThemePreviousValues.clear();
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
for (const [, cssVariable] of themeTokenCssVariables) {
|
|
298
|
+
restoreHostThemeVariable(root, cssVariable);
|
|
299
|
+
}
|
|
300
|
+
if (hostThemeMarker !== undefined && root.dataset.nexusTheme === hostThemeMarker) {
|
|
301
|
+
if (hostThemeHadPreviousMarker) {
|
|
302
|
+
root.dataset.nexusTheme = hostThemePreviousMarker || "";
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
delete root.dataset.nexusTheme;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
hostThemeOwners.delete(root);
|
|
309
|
+
hostThemeRoot = undefined;
|
|
310
|
+
hostThemeMarker = undefined;
|
|
311
|
+
hostThemePreviousMarker = undefined;
|
|
312
|
+
hostThemeHadPreviousMarker = false;
|
|
313
|
+
hostThemeAppliedValues.clear();
|
|
314
|
+
hostThemePreviousValues.clear();
|
|
315
|
+
}
|
|
169
316
|
function scheduleResize() {
|
|
170
317
|
if (disposed || !contextValue || options.autoResize === false) {
|
|
171
318
|
return;
|
|
@@ -200,6 +347,19 @@ export function createPluginFrameClient(options = {}) {
|
|
|
200
347
|
disposed = true;
|
|
201
348
|
frameWindow.removeEventListener("message", handleMessage);
|
|
202
349
|
frameWindow.removeEventListener("resize", scheduleResize);
|
|
350
|
+
if (systemThemeMedia) {
|
|
351
|
+
if (typeof systemThemeMedia.removeEventListener === "function") {
|
|
352
|
+
systemThemeMedia.removeEventListener("change", handleSystemThemeChange);
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
systemThemeMedia.removeListener(handleSystemThemeChange);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
cleanupHostTheme();
|
|
359
|
+
if (contextTimeoutId !== undefined) {
|
|
360
|
+
frameWindow.clearTimeout(contextTimeoutId);
|
|
361
|
+
contextTimeoutId = undefined;
|
|
362
|
+
}
|
|
203
363
|
resizeObserver?.disconnect();
|
|
204
364
|
if (resizeTimer !== undefined) {
|
|
205
365
|
frameWindow.clearTimeout(resizeTimer);
|
|
@@ -236,8 +396,45 @@ function isPluginRendererContext(value) {
|
|
|
236
396
|
&& typeof value.contributionId === "string"
|
|
237
397
|
&& typeof value.language === "string"
|
|
238
398
|
&& (value.themeMode === "light" || value.themeMode === "dark" || value.themeMode === "system")
|
|
399
|
+
&& (value.theme === undefined || isPluginRendererTheme(value.theme))
|
|
239
400
|
&& typeof value.hostApiVersion === "string";
|
|
240
401
|
}
|
|
402
|
+
function isPluginRendererTheme(value) {
|
|
403
|
+
if (!hasExactKeys(value, ["light", "dark"])) {
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
return isPluginThemeTokens(value.light) && isPluginThemeTokens(value.dark);
|
|
407
|
+
}
|
|
408
|
+
function isPluginThemeTokens(value) {
|
|
409
|
+
const tokenNames = themeTokenCssVariables.map(([tokenName]) => tokenName);
|
|
410
|
+
return hasExactKeys(value, tokenNames)
|
|
411
|
+
&& tokenNames.every((tokenName) => isSafeCssColorValue(value[tokenName]));
|
|
412
|
+
}
|
|
413
|
+
function hasExactKeys(value, expectedKeys) {
|
|
414
|
+
if (!isRecord(value)) {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
const keys = Object.keys(value);
|
|
418
|
+
return keys.length === expectedKeys.length && keys.every((key) => expectedKeys.includes(key));
|
|
419
|
+
}
|
|
420
|
+
function isSafeCssColorValue(value) {
|
|
421
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 64 || /[;{}]/.test(value)) {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
if (/^#[0-9a-f]{6}(?:[0-9a-f]{2})?$/i.test(value)) {
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
const match = /^(rgb|rgba)\(([^)]+)\)$/.exec(value);
|
|
428
|
+
if (!match) {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
const parts = match[2].split(",").map((part) => part.trim());
|
|
432
|
+
const expectedLength = match[1] === "rgba" ? 4 : 3;
|
|
433
|
+
if (parts.length !== expectedLength || !parts.slice(0, 3).every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255)) {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
return expectedLength === 3 || /^(?:0|1|0?\.\d+)$/.test(parts[3]) && Number(parts[3]) <= 1;
|
|
437
|
+
}
|
|
241
438
|
function isNexusResult(value) {
|
|
242
439
|
if (!isRecord(value) || typeof value.ok !== "boolean") {
|
|
243
440
|
return false;
|