feat(desktop): fileTransfer bridge for plugin workbench webviews

This commit is contained in:
Jinpy authored and GitHub committed 2026-09-21 02:03:12 +08:00
1 parent 9b82ce86f9
commit bc490772eb
11 files changed
+1111 -2

No files matched your search

@@ -297,6 +297,7 @@ const assetUrl = await window.dbxPlugin.readAssetUrl("assets/empty-state.svg");
| `explainPlan({ connectionId, database?, schema?, sql, mode, timeoutMs? })` | 返回 `sql` 的估算执行计划;需要 `host.plans:read`。 |
| `capabilities` | 来自 init 消息的 `{ downloadFile, planApi }`。字段缺失或为 `false` 表示当前宿主不支持该组 Host API,应据此关闭功能而不是用真实请求试探。 |
| `onInit(fn)` / `onEvent(fn)` | 监听初始化、环境变化或后端事件;转发后端事件需要 `host.events`。 |
| `fileTransfer` | 本地文件流式传输与系统拖放(仅桌面端);见下文。 |
调用失败会以 Promise rejection 返回,处理时应展示可理解的错误并允许重试。通过 `request` 调用的宿主方法、参数和返回值以当前 Host API 版本为准,不要调用未在协议中声明的 DBX 内部函数。
@@ -346,6 +347,51 @@ if (window.dbxPlugin.capabilities.planApi) {
计划 API 属于 Host API 1.2。无法离开它工作的插件应在 manifest 里声明下限:`"engines": { "host_api": "^1.2" }`。manifest 里的版本范围是兼容下限,`capabilities.planApi` 是运行期探测,两者都保留。
### 文件传输与系统拖放
桌面端宿主通过 `window.dbxPlugin.fileTransfer` 把本地文件流式接入插件沙箱。句柄只在用户明确同意后打开:原生打开/保存对话框(`pick` / `beginSave`),或从系统拖放到本插件工作台区域内的文件(`onDrop`)。传输按分块进行,多 GB 文件也不会整体载入内存。
```js
const fileTransfer = window.dbxPlugin.fileTransfer;
if (!fileTransfer) {
// Web 宿主:回退到 <input type="file"> 和 sidecar 落盘。
}
// 上传:用户在原生对话框选择文件,插件把分块推给自己的 sidecar。
const { files } = await fileTransfer.pick({ multiple: true });
for (const file of files) {
let offset = 0;
for (;;) {
const chunk = await fileTransfer.read(file.handleId, offset, 256 * 1024);
await uploadChunk(file.name, offset, chunk.dataBase64); // 你自己的 RPC
if (chunk.eof) break;
offset += chunk.length;
}
await fileTransfer.cancel(file.handleId);
}
// 下载:打开保存目标,分块写入,最后 finish 落盘。
const target = await fileTransfer.beginSave({ name: "export.csv", size });
await fileTransfer.write(target.handleId, 0, bytes);
await fileTransfer.finish(target.handleId);
// 系统拖放(仅桌面端):被拖入的文件以已打开的句柄送达。
const offDrop = fileTransfer.onDrop((files) => uploadAll(files));
const offDrag = fileTransfer.onDragState((active) => showDropOverlay(active));
```
| API | 用途 |
| --- | --- |
| `pick({ multiple })` | 原生打开对话框;返回已打开的读句柄 `{ handleId, name, size, contentType }`。 |
| `read(handleId, offset, length?)` | 从读句柄流式读取一个分块 `{ dataBase64, length, eof }`。 |
| `beginSave({ name, contentType?, size? })` | 原生保存对话框 + 写句柄;返回 `{ handleId, chunkBytes }`,用户取消时为 `null`。 |
| `write(handleId, offset, data)` | 写入一个分块(二进制 transfer 或 base64);返回 `{ written, nextOffset }`。 |
| `finish(handleId)` / `cancel(handleId)` | 冲洗并关闭写句柄 / 丢弃任意句柄。 |
| `onDrop(fn)` | 拖入本工作台的文件以已打开的读句柄送达。 |
| `onDragState(fn)` | 是否有系统拖拽悬在本工作台上方;用它驱动拖放遮罩。 |
仅桌面端可用:Web 宿主下整个命名空间是 `undefined`,使用前请先探测。`onDrop` 只在拖放落点位于本插件工作台区域内时触发;落点在宿主其他区域时保持宿主自身的行为。
### 主题、布局和静态资源
DBX 将主题 tokens 注入文档根元素,并更新 `data-dbx-theme`。自定义组件库(包括 Svelte、shadcn-svelte)应使用这些变量,不要依赖父页面 CSS 自动穿透 iframe:
+46
View File
@@ -297,6 +297,7 @@ const assetUrl = await window.dbxPlugin.readAssetUrl("assets/empty-state.svg");
| `explainPlan({ connectionId, database?, schema?, sql, mode, timeoutMs? })` | Returns the estimated plan for `sql`; requires `host.plans:read`. |
| `capabilities` | `{ downloadFile, planApi }` from the init message. A missing or `false` entry means that Host API group is unavailable here, so gate the matching call on it instead of probing with a request. |
| `onInit(fn)` / `onEvent(fn)` | Observes initialization, environment changes, or backend events; forwarding backend events requires `host.events`. |
| `fileTransfer` | Streams local files and OS drops (desktop only); see below. |
Failed calls reject their Promise; show a useful error and offer retry. Host methods, parameters, and results are versioned API surface—do not call undocumented DBX internals.
@@ -346,6 +347,51 @@ Gate on capability rather than probing: read `dbxPlugin.capabilities.planApi` fr
The plan API is Host API 1.2. A plugin that cannot work without it declares the floor in its manifest with `"engines": { "host_api": "^1.2" }`. The manifest range is a compatibility floor and `capabilities.planApi` is the runtime check; keep both.
### File Transfer and OS Drops
Desktop hosts stream local files into plugin sandboxes through `window.dbxPlugin.fileTransfer`. Handles are opened only after explicit user consent: a native open/save dialog (`pick` / `beginSave`) or a file dropped from the OS onto this plugin's workbench area (`onDrop`). Transfers are chunked, so multi-gigabyte files never load into memory.
```js
const fileTransfer = window.dbxPlugin.fileTransfer;
if (!fileTransfer) {
// Web host: fall back to <input type="file"> and sidecar disk writes.
}
// Upload: the user picks files, the plugin streams chunks to its sidecar.
const { files } = await fileTransfer.pick({ multiple: true });
for (const file of files) {
let offset = 0;
for (;;) {
const chunk = await fileTransfer.read(file.handleId, offset, 256 * 1024);
await uploadChunk(file.name, offset, chunk.dataBase64); // your own RPC
if (chunk.eof) break;
offset += chunk.length;
}
await fileTransfer.cancel(file.handleId);
}
// Download: open a save target, stream chunks, then flush with finish.
const target = await fileTransfer.beginSave({ name: "export.csv", size });
await fileTransfer.write(target.handleId, 0, bytes);
await fileTransfer.finish(target.handleId);
// OS drag & drop (desktop only): dropped files arrive as opened handles.
const offDrop = fileTransfer.onDrop((files) => uploadAll(files));
const offDrag = fileTransfer.onDragState((active) => showDropOverlay(active));
```
| API | Purpose |
| --- | --- |
| `pick({ multiple })` | Native open dialog; resolves opened read handles `{ handleId, name, size, contentType }`. |
| `read(handleId, offset, length?)` | Streams a chunk `{ dataBase64, length, eof }` from a read handle. |
| `beginSave({ name, contentType?, size? })` | Native save dialog + write handle; resolves `{ handleId, chunkBytes }`, or `null` when the user cancels. |
| `write(handleId, offset, data)` | Writes one chunk (transferred binary or base64); resolves `{ written, nextOffset }`. |
| `finish(handleId)` / `cancel(handleId)` | Flush-closes a write handle / discards any handle. |
| `onDrop(fn)` | Files dropped onto this workbench arrive as already-opened read handles. |
| `onDragState(fn)` | Whether an OS drag is currently over this workbench; drive drop overlays with it. |
Desktop only: the whole namespace is `undefined` in the web host, so feature-detect before use. `onDrop` only fires for drops that land inside this plugin's workbench area; drops elsewhere keep the host's own behavior.
### Themes, Layout, and Static Assets
DBX injects theme tokens on the document root and updates `data-dbx-theme`. Custom libraries, including Svelte and shadcn-svelte, should consume these variables; parent-page CSS does not cross the iframe: