mirror of
https://github.com/SteamFramelyHomebrew/framely.git
synced 2026-10-29 04:02:47 +01:00
Make English the default repository documentation
Keep Simplified Chinese translations in README.zh-CN.md and docs/zh-CN, update language navigation, and align generated plugin and release-package documentation.
This commit is contained in:
1 parent
cdbe974e13
commit
14a15f8a2c
60 files changed
+1848
-1848
No files matched your search
+35
-81
@@ -1,64 +1,49 @@
|
||||
# 插件开发参考
|
||||
# Plugin development reference
|
||||
|
||||
[English](en/plugin-development.md)
|
||||
[简体中文](zh-CN/plugin-development.md)
|
||||
|
||||
第一次开发请先阅读[完整开发流程](developer-guide/README.md)和[发布指南](developer-guide/publishing.md)。
|
||||
Start with the [workflow](developer-guide/README.md), [SDK](developer-guide/sdk.md), [Manifest](developer-guide/manifest.md) and [publishing](developer-guide/publishing.md).
|
||||
|
||||
Dock 右侧快捷入口为与主窗口同朝向的弧面临时浮层,常用/已安装/设置三个底部 tab;插件管理从设置打开独立的原生 Dock 大窗口。安装表单与插件源设置只在管理窗口中出现。
|
||||
The Dock quick menu has Favorites/Installed/Settings tabs; management opens a separate Dock window. Installation and sources are handled there. See `examples/showcase` and `sdk/src/index.tsx`. Release `tools/source` includes UI/SDK/examples/template/tools. Bundle pages into browser JS and call `registerPlugin({QuickPage, WindowPage})`. The host installs the bridge first. Ordinary bundled page iframes use `sandbox="allow-scripts"` with opaque origins and cannot call administrator HTTP APIs.
|
||||
|
||||
示例源码在 `examples/showcase`;React SDK 在 `sdk/src/index.tsx`。发行包 `tools/source` 提供完整 UI/SDK 示例构建工作区。插件页面需要自行打包为浏览器 JS,调用 `registerPlugin({QuickPage, WindowPage})` 挂载 React,宿主先加载桥接 bootstrap。普通 bundle 插件 iframe 为 `sandbox="allow-scripts"` 的独立不透明来源;通过父窗口转发被允许的 SDK 请求,不能直接调用管理员 HTTP API。
|
||||
|
||||
## Manifest
|
||||
## Manifest and identities
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"apiVersion": 1,
|
||||
"id": "example.plugin",
|
||||
"name": "Example",
|
||||
"author": "Developer",
|
||||
"version": "1.0.0",
|
||||
"schemaVersion": 1, "apiVersion": 1,
|
||||
"id": "example.plugin", "name": "Example", "author": "Developer", "version": "1.0.0",
|
||||
"description": "Example plugin",
|
||||
"backend": {"entry": "backend.py", "runAs": "steamos", "autostart": false},
|
||||
"ui": {
|
||||
"quickPage": "page.js",
|
||||
"windows": {"main": {"entry": "page.js", "title": "Example", "dockIcon": true}}
|
||||
},
|
||||
"ui": {"quickPage": "page.js", "windows": {"main": {"entry": "page.js", "title": "Example", "dockIcon": true}}},
|
||||
"files": {}
|
||||
}
|
||||
```
|
||||
|
||||
`backend` 可省略。`runAs` 只支持 `steamos` 和 `root`,省略时为 `steamos`;`autostart` 默认 false。不再使用 `permissions` 字段,窗口、通知和联网无需权限声明。后端使用设备网络,普通插件页面允许 HTTP/HTTPS 请求及 WebSocket。
|
||||
Backend is optional. Runtime users are `steamos` (default) or `root`; autostart defaults false. Retired `permissions` is unsupported; windows, notifications and networking need no declarations. Backend networking uses the device; ordinary pages permit HTTP/HTTPS and WebSockets.
|
||||
|
||||
ID 只允许小写字母、数字、点、短横线、下划线;载荷路径限 ASCII 字母/数字、`/._-+`,禁止绝对路径、空段、`.`、`..`。入口必须在 files 中;pack 会自动计算 files,不必手写哈希。后端入口须可直接执行,脚本需要正确 shebang(示例 `/usr/bin/python3`)。Python/系统依赖不会由 Framely 自动下载。
|
||||
IDs permit lowercase letters, numbers, dots, hyphens, underscores. Payload paths permit ASCII letters/numbers and `/._-+`, never absolute paths, empty/dot/dot-dot segments. Packaging supplies files hashes. Entries must be hashed; executable scripts need shebangs, e.g. `/usr/bin/python3`. Framely does not install system/Python dependencies.
|
||||
|
||||
版本不可覆盖,相同版本再次发布需要使用新版本号。运行身份变化会选择新的数据目录 `/var/lib/framely/data/<id>/<identity>`;旧身份数据保留,安装使用旧身份的版本可恢复访问,不自动跨身份迁移文件。
|
||||
Published versions are immutable. User changes select `/var/lib/framely/data/<id>/<identity>`; old data remains without automatic migration, and reinstalling the old identity can access it again.
|
||||
|
||||
## React API
|
||||
## React bridge
|
||||
|
||||
```tsx
|
||||
import {framely, registerPlugin, Button, Section} from '@framely/sdk';
|
||||
|
||||
function QuickPage() {
|
||||
return <Section title="Example">
|
||||
<Button onClick={() => framely.windows.open('main')}>打开窗口</Button>
|
||||
<Button onClick={() => framely.call('save', {text: 'hello'})}>保存</Button>
|
||||
<Button onClick={() => framely.notifications.send({
|
||||
id: 'download', title: '完成', body: '下载已完成',
|
||||
durationMs: 8000, actions: [{id: 'open', label: '打开', icon: '↗'}]
|
||||
})}>发送通知</Button>
|
||||
<Button onClick={() => framely.windows.open('main')}>Open</Button>
|
||||
<Button onClick={() => framely.call('save', {text: 'hello'})}>Save</Button>
|
||||
<Button onClick={() => framely.notifications.send({id: 'download', title: 'Done', body: 'Downloaded', durationMs: 8000, actions: [{id:'open',label:'Open',icon:'↗'}]})}>Notify</Button>
|
||||
</Section>;
|
||||
}
|
||||
registerPlugin({QuickPage});
|
||||
```
|
||||
|
||||
窗口 key 必须在 manifest 中声明;`framely.windows.close(key)` 关闭。关闭页面不会停止后端。通知 ID 用于更新/撤回;`framely.notifications.remove(id)` 撤回。图片为 PNG/JPEG data URL(最多 1 MB)或 HTTPS URL。时长 1–60 秒,每插件每 10 秒最多 10 次发送。
|
||||
Keys must match Manifest. Closing windows does not stop backend. IDs update/withdraw notifications. Images accept PNG/JPEG data URL ≤1 MiB or HTTPS. At most three actions, 1–60 seconds and ten sends per ten seconds/plugin. `onEvent` returns unsubscribe and receives `{type,data}`. Each window has its own event cursor. Input focuses the native keyboard rather than web prompts.
|
||||
|
||||
`framely.onEvent(callback)` 返回取消订阅函数;接收 `{type, data}`。多个窗口分别按游标接收同一后台事件。文本输入框获得焦点时请求 SteamVR 虚拟键盘,完成后通过原生 setter 触发 React input/change;不使用网页 prompt。
|
||||
## Backend protocol
|
||||
|
||||
## 后端协议
|
||||
|
||||
每行一个 JSON,stdin 请求、stdout 响应。stdout 只写协议,日志写 stderr(单文件最多约 2 MiB,保留一份轮转备份)。每条消息最多 64 KiB,输入写入超时 5 秒或响应超时 15 秒会停止后端,下次打开可重试。
|
||||
Newline-delimited JSON on stdin/stdout; logs on stderr, rotating around 2 MiB. Messages ≤64 KiB; 5-second input or 15-second response timeouts stop the backend and allow later retry.
|
||||
|
||||
```json
|
||||
{"id":1,"method":"save","params":{"text":"hello"}}
|
||||
@@ -67,9 +52,9 @@ registerPlugin({QuickPage});
|
||||
{"event":"progress","data":{"percent":50}}
|
||||
```
|
||||
|
||||
系统传入 `FRAMELY_PLUGIN_ID`、`FRAMELY_DATA_DIR` 和对应身份 HOME。通知按钮向后端调用 `notification.action`,参数 `{id, action}`。后端主动发送通知使用 `{"event":"notification","data":{...通知...}}`,受同一 manifest 与频率限制。
|
||||
Environment includes plugin ID, data directory and the user's HOME. Notification actions invoke `notification.action` with `{id,action}` and also reach pages; UI-only plugins can listen. Backend `notification` events send notifications under the same limits.
|
||||
|
||||
## 打包与源发布
|
||||
## Packaging and catalogs
|
||||
|
||||
```bash
|
||||
framely pack --manifest manifest.json --payload payload --output example.plugin-1.0.0.framely
|
||||
@@ -77,51 +62,28 @@ framely verify example.plugin-1.0.0.framely
|
||||
framely catalog --name 'My plugins' --base-url https://example.org/plugins --packages ./packages --output ./packages/catalog.json
|
||||
```
|
||||
|
||||
包中包含 manifest.json 和文件载荷,files 记录每个载荷的 SHA256;不生成公钥或签名。只支持 manifest.json 与清单中声明的载荷文件,拒绝未声明的附加文件。
|
||||
Archives contain Manifest and hashed payload only, without keys/signatures. Host the main catalog and relative history JSON. Authors can host packages on GitHub Releases. Current first initialization includes community stable/testing sources. Origins remain explicit; same-ID plugins in other sources do not silently replace existing origins. HTTP needs explicit development permission; up to five validated redirects, without HTTPS downgrade.
|
||||
|
||||
源需静态托管 catalog.json 及相对路径下的 plugins/<ID>/versions.json;插件包可放在作者自己的 GitHub Release。在插件源管理中添加目录完整 URL;当前代码首次初始化加入社区 stable/testing 来源,实际内容以服务器为准。目录条目有版本、下载 URL、SHA256。每个源独立显示,插件记录所选来源;另一个源同 ID 不会静默替换。HTTP 必须用户明确允许;目录和包支持最多五次重定向,HTTPS 不允许降级为 HTTP。
|
||||
## Hover feedback
|
||||
|
||||
### 悬停反馈
|
||||
Interactive controls automatically receive one hover pulse per entry, excluding disabled/inert controls and movement between their children. Supported elements include buttons, links, inputs, interactive ARIA roles/focusable controls or pointer cursors. Custom controls may mark `data-framely-interactive`. Native feedback uses the current hit controller, 12 ms/120 Hz/amplitude 0.15 with at least 80 ms spacing. Pages cannot target other windows/controllers.
|
||||
|
||||
Framely 主页面和插件 iframe 自动为可交互控件提供轻微的手柄振动,插件无需自行触发。进入按钮、链接、输入控件、可操作 ARIA role、可聚焦控件或 `cursor:pointer` 区域时触发一次;同一控件的子元素之间移动不会重复触发,禁用或 inert 控件不触发。自定义 React 控件应采用正确的 ARIA role,也可以加 `data-framely-interactive` 标记。
|
||||
## Metadata and publication
|
||||
|
||||
振动由 OpenVR 的当前激光控制器执行(12 ms、120 Hz、幅度 0.15),宿主只接受当前可见且被激光命中的视图,限制相邻脉冲间隔为 80 ms。插件发出的反馈消息由 iframe 宿主绑定到实际视图,不能指定另一个窗口或手柄。
|
||||
|
||||
## 商店资料与图标
|
||||
|
||||
Manifest 可声明 `icon`(载荷内 PNG,最多 1 MiB、1024×1024)、`details`、`tags`、`screenshots`(最多 8 个载荷内 PNG/JPEG 路径)、`changelog`。这些文件会和页面一样加入文件哈希清单。图标用于插件列表和插件独立窗口的原生 Dock 缩略图,省略时使用默认拼图图标。
|
||||
|
||||
清单还支持三个可选网页地址,未填写的入口不显示:`authorUrl`(作者主页)、`documentationUrl`(插件文档)、`homepage`(插件主页)。地址必须是 HTTP 或 HTTPS,可包含文档锚点,不接受带用户名密码的链接。这些字段会保留在安装后的清单,并由 `framely catalog` 导出到商店目录;作者主页链接直接显示在作者名称上,未提供时作者名称为普通文本;商店列表、详情、管理弹窗和安装确认使用相同行为。文档和插件主页保留独立入口,点击后由系统默认浏览器打开。
|
||||
|
||||
```json
|
||||
{
|
||||
"authorUrl": "https://github.com/author",
|
||||
"documentationUrl": "https://github.com/author/plugin#readme",
|
||||
"homepage": "https://github.com/author/plugin"
|
||||
}
|
||||
```
|
||||
|
||||
插件仓库可声明发布信息:
|
||||
Manifest supports payload PNG icon ≤1 MiB/1024×1024, details/tags/changelog and up to eight PNG/JPEG screenshots. Icons appear in lists/Dock; missing icons use the default. Optional `authorUrl`, `documentationUrl`, `homepage` are credential-free HTTP/HTTPS links, preserved in state/catalog. Author URLs link author labels; device default browsers open external pages.
|
||||
|
||||
```json
|
||||
{
|
||||
"downloadUrl": "https://github.com/author/plugin/releases/download/v1.0.0/plugin.framely",
|
||||
"publish": {
|
||||
"icon": "https://github.com/author/plugin/releases/download/v1.0.0/icon.png",
|
||||
"screenshots": ["https://github.com/author/plugin/releases/download/v1.0.0/screen.png"]
|
||||
}
|
||||
"publish": {"icon": "https://github.com/author/plugin/releases/download/v1.0.0/icon.png", "screenshots": ["https://github.com/author/plugin/releases/download/v1.0.0/screen.png"]}
|
||||
}
|
||||
```
|
||||
|
||||
上述为显式地址示例,`downloadUrl` 并非源码必填字段:GitHub 社区登记可按仓库、`v<version>` 和 `<id>-<version>.framely` 自动生成。包内仍需最终地址,当前 pack 不会自动读取 Git remote;按[发布指南](developer-guide/publishing.md)生成临时清单即可。社区图标通常只需 `icon`,数据库从固定源码提交生成并校验 Raw 地址;无需额外 `publish.icon`。本地 catalog CLI 使用 `publish` 外部图片,省略时不会导出包内图标/截图,商店采用默认图标。包内资源仍用于已安装插件。
|
||||
The fragment above shows explicit URLs; GitHub registration source may omit `downloadUrl`, deriving repository, `v<version>` and `<id>-<version>.framely`. The packaged Manifest still requires the final URL; current pack does not derive it. See [publishing](developer-guide/publishing.md) for temporary-Manifest commands. Community icons normally use top-level `icon`, with derived and verified pinned Raw URLs, without `publish.icon`. The local catalog CLI uses external `publish` images and does not export packaged image paths; absent external icons use the store default. Community registration pins submodule commits, obtains expected SHA256 from Release digests or source `downloadSha256`, then validates downloaded package hashes, declarations and payloads. Generated channels provide catalog/history JSON without mirroring packages/images. It does not execute author code or establish reproducible source/binary equivalence.
|
||||
|
||||
插件数据库只登记 submodule 固定提交,读取该提交的清单,补全默认下载地址,先取得 Release digest 或源码 `downloadSha256` 中的预期整包哈希,再下载并比对 SHA256、清单声明和载荷。发布分支提供各渠道目录及历史版本 JSON,不保存插件包或图片,不需要 entries 登记。数据库 CI 自动展示版本和运行用户变化,发布时禁止同 ID、版本更换 SHA256;不执行作者代码,不要求逐插件人工源码审核,也不验证源码与二进制可复现关系。
|
||||
Catalog CLI only emits JSON, preferring `downloadUrl` over base URL/filename and using external publish images. It does not copy resources. Stores filter keywords/tags/origins/states, retain session caches on failures and install reviewed bytes without downloading again after confirmation.
|
||||
|
||||
`framely catalog` 也只生成 JSON:下载 URL 优先使用 `downloadUrl`,没有该字段时采用 `--base-url` 加包文件名。图标与截图来自 `publish` 的外部 URL,不复制资源。商店支持关键词、标签、来源、已安装和可更新筛选;源暂时不可用时显示会话缓存。安装使用已经校验的同一份包,不在确认后重新下载。
|
||||
|
||||
后端默认按失败自动恢复,包括已经启动的按需插件;可用 `backend.restart: "never"` 关闭。默认第三次连续失败后停用,运行稳定一分钟后重置计数,详见[生命周期](plugin-lifecycle.md)。
|
||||
|
||||
## 脚手架与预览
|
||||
## Scaffold and preview
|
||||
|
||||
```bash
|
||||
node tools/plugin-dev.mjs init ./my-plugin example.my-plugin
|
||||
@@ -131,22 +93,14 @@ npm run dev
|
||||
npm run build
|
||||
```
|
||||
|
||||
发行包中的工具位置为 `tools/source/tools/plugin-dev.mjs`。预览监听本机 127.0.0.1:5173,修改页面后自动重载;它不会启动后端或获得 Frame 系统权限。窗口和通知在浏览器里模拟,身份、键盘、振动及后台调用仍需安装到设备测试。
|
||||
Release tool location: `tools/source/tools/plugin-dev.mjs`. Preview binds `127.0.0.1:5173` and reloads changes, simulating windows/notifications only. Backend calls, identities, keyboards and haptics require Frame. SDK includes Section/Button/Toggle/Slider/TextField/Select/Tabs/Notice/useBackend/usePluginEvent; window maps can register different components. See [SDK](developer-guide/sdk.md).
|
||||
|
||||
SDK 提供 `Section`、`Button`、`Toggle`、`Slider`、`TextField`、`Select`、`Tabs`、`Notice`、`useBackend`、`usePluginEvent`。Select 使用页面内弹层,兼容离屏 CEF。`registerPlugin` 还可传入 `windows: {main: Component, other: Component}` 为多个声明窗口选择不同 React 页面。
|
||||
## Windows, tags and history
|
||||
|
||||
通知按钮向后端发送 `notification.action`,同时广播给当前打开的插件页面,事件为 `{type:'notification.action',data:{id,action}}`;无后端插件可以通过 `framely.onEvent` 接收。
|
||||
Default windows are 1600×900 and 3 meters, with width 640–2560, height 360–1440, physical width 0.4–4.0. Reopen to apply changes. A `localWeb` window asks backend `window.get` for `http://localhost:<port>/framely-window/<key>`; this is a loading mode, not a permission.
|
||||
|
||||
插件安装、更新、停止和异常恢复的约定见[生命周期开发说明](plugin-lifecycle.md)。
|
||||
Use tags instead of legacy category, accepted only for compatibility. Database `tags.json` aggregates, deduplicates and sorts current channel tags; each channel is separate, clients merge sources locally. Tags change automatically with releases.
|
||||
|
||||
窗口可选声明 `width`、`height`(逻辑像素)和 `widthMeters`(VR 物理宽度,米)。默认 1600×900(16:9)、VR 宽度 3 米;像素范围为宽 640–2560、高 360–1440,物理宽度 0.4–4.0 米。示例:`{"entry":"page.js","title":"温控","dockIcon":true,"width":1600,"height":900,"widthMeters":3.0}`。尺寸在重新打开窗口时生效。
|
||||
Packages/dependencies/uploads stage in `/tmp/framely-package-*`, removed on completion/cancel/failure. Idle uploads/reviews expire after 15 minutes, checked every 30 seconds.
|
||||
|
||||
依赖、可选依赖、冲突和独占资源见[插件关系](plugin-relationships.md),多个插件源聚合见[源订阅](source-subscriptions.md)。
|
||||
|
||||
插件只使用 `tags` 描述用途,不再使用分类;为兼容已发布的包,旧 `category` 字段读取时忽略,新的清单及目录不再导出它。插件数据库每次生成目录时同时生成 `tags.json`,格式为 `{ "schemaVersion": 1, "tags": ["工具", "温控"] }`,聚合当前目录全部插件的标签、精确去重并排序。stable/testing 分别生成自己的标签文件,不生成跨渠道并集;程序加载多个源后在本地聚合去重。标签随插件更新自动增删,无须手工维护。
|
||||
|
||||
上传的插件包、商店下载及其依赖包暂存于 `/tmp/framely-package-*`,确认安装后才写入插件目录。取消、安装完成或失败后删除暂存包;未完成上传和待确认安装包闲置 15 分钟后过期,每 30 秒清理一次。
|
||||
|
||||
本机网页窗口在 `ui.windows.<key>` 设置 `localWeb: true`,宿主通过后端 `window.get` 获取窗口 URL;这是窗口加载方式,不是权限声明。URL 使用 `http://localhost:<端口>/framely-window/<key>`。普通窗口无需设置此项。
|
||||
|
||||
商店主目录 `catalog.json` 中每个插件 ID 仅出现一次,代表最新版。历史条目存放在相对目录 `plugins/<插件ID>/versions.json`,格式为 `{ "schemaVersion": 1, "id": "作者.插件", "versions": [...] }`;版本条目沿用目录条目的字段,列表不包含主目录的最新版。商店进入详情时按需读取历史,依赖解析仅在最新版不满足范围时读取历史。附加目录字段可扩展,读取端忽略未知字段,但校验已知字段;改变必需语义应升级 `schemaVersion`。插件安装包清单仍严格校验。
|
||||
Main `catalog.json` has one latest entry per ID. `plugins/<ID>/versions.json` contains `{schemaVersion:1,id,versions:[...]}` without that main entry. Details load history on demand, as does resolution when latest fails a range. Unknown catalog fields are ignored while known fields are checked; changed required semantics need a schema bump. Package Manifests remain strict. See [relationships](plugin-relationships.md), [subscriptions](source-subscriptions.md) and [lifecycle](plugin-lifecycle.md).
|
||||
Reference in new issue
Block a user