桌面扩展
这些操作通过当前启用实例的有效文档调用;Services拥有授权、资源归属和最终投递检查。能力均可拒绝,不因声明而授予。所有在途任务有界、可取消,取消不回滚已经提交的外部副作用。
授权目录写入
directory.write 单独授权写入,界面名“授权目录读写”;目录读取为基础接口,不受此开关控制。复用用户选择的 directory 绑定,不接受网页完整路径。directory.write {bindingId,relativePath,bodyBase64,overwrite?:boolean} 写入最多4 MiB普通文件,overwrite默认false;先写同目录临时文件、完成刷新,再原子重命名,失败保留旧文件。directory.mkdir {bindingId,relativePath} 只创建一级目录;directory.rename {bindingId,relativePath,newRelativePath} 在同一绑定内移动/改名,目标存在则拒绝;directory.remove {bindingId,relativePath} 永久删除一个普通文件或空目录,不递归、不进入回收站。返回 {},write额外返回 {writtenBytes}。根目录、重解析点、设备名、路径穿越、备用数据流及空路径均拒绝,整个执行期间保活祖先目录并核对绑定身份。
文件拖出
exports.startDrag {exportId} 复用已完成的导出资源,只有当前可见文档的原生鼠标主按钮拖动可启动,不能通过网页声明可信事件。只提供COPY;返回 {accepted:boolean},表示接收方接受拖放,不保证其最终持久保存。取消返回accepted=false。封存的虚拟文件描述符与内容流不暴露临时路径,沿用导出的格式、大小、有效期与配额;全宿主同时最多一个拖出快照,接收方仍持有流时也占用该额度。关闭、导航、暂停或撤权后的未交付会话停止。
组件快捷键
作者通过 shortcuts.setActions {actions:[{id,label}]} 注册当前主文档最多8个动作,不指定组合键。用户在显示页为具体实例分配组合键,冲突不替换已有可用设置。shortcuts.triggered {id} 只投对应实例的当前主文档;隐藏继续响应,停用、断屏和退出释放;暂停或锁屏丢弃,不排队补发。不提供键盘监听。动作替换、重载使旧注册失效,分配记录不进入分享布局。
系统音频回环
audio.capture.* 是无需声明或授权的基础接口,只采集默认输出设备系统混音,不采集麦克风。audio.capture.open {} 返回 {captureId,sampleRate,channels,format:"float32le"};audio.capture.read {captureId,maxFrames} 拉取最多4096帧,返回 {bodyBase64,frames,droppedFrames},无数据返回0帧,不等待填满;audio.capture.close {captureId} 关闭。每实例一个、全局四个会话,每会话最多保留一秒PCM,满时丢弃最旧帧并计数。隐藏可继续;暂停、锁屏、休眠、导航、停用或默认设备变化结束会话,不自动恢复采集。静音策略只控制组件播放,不保证系统混音中排除组件自身。
通知动作
notifications.dismiss {notificationId} 需要 notifications,返回 {},请求撤下当前实例、原文档通过 notifications.show 发出的即时通知,包含横幅及通知中心条目;不适用于持久提醒。功能标志为 notifications.dismiss。只接受宿主返回的有效 UUID,不接受安装、实例、系统标签或应用身份参数。未知、其他实例/文档、已消费、过期及失效的引用统一返回 RESOURCE_UNAVAILABLE;错误类型、非法 UUID 或未知字段返回 INVALID_ARGUMENT。引用沿用原通知十分钟期限及全局最多128条记录,不因撤下或重试延长。当前有效引用重复撤下成功;系统已经移除条目也视为成功,不返回是否显示、是否阅读或系统通知列表。
执行前及最终投递复核原通知与当前请求的授权、实例、文档和失效代次。开始撤下时先停止该通知尚未消费的动作;系统撤下失败返回 RESOURCE_UNAVAILABLE,动作仍失效,原文档可在原期限内重试。已投递的动作不撤销。重载、停用、撤权重授或重启不恢复引用。撤下不占用发送额度,也不返还发送额度,沿用桥接请求预算。成功仅表示系统撤下调用完成,不代表用户此前没有看到通知;取消无法回滚已发生的撤下。
const {notificationId} = await widgetHost.call('notifications.show', {
title: '处理完成', body: '结果已准备好'
});
// 作者在业务信息失效时主动撤下;宿主不判断业务是否完成。
await widgetHost.call('notifications.dismiss', {notificationId});notifications.show 增加可选 actions:[{id,label}],最多3个,返回 {notificationId} 表示已提交,横幅呈现由Windows通知设置决定。系统通知正文点击及动作按钮通过 notifications.activated {notificationId,actionId} 投递,正文actionId为null。原始文档、实例、授权或通知过期后不投递;不自动启用组件、不执行命令、不在宿主退出后启动组件或补发。动作ID不包含任意执行数据。提醒继续沿用自身持久和消费语义,不隐式增加跨重启动作收件箱。
宿主使用系统Toast与当前进程COM激活器,在运行视图启动前按数据目录登记通知身份和激活器;登记失败不阻止启动,实际调用可返回RESOURCE_UNAVAILABLE。正常退出隐藏自身通知并注销身份和COM登记。Windows所需的LocalServer32只指向立即退出的专用入口;即使崩溃残留登记被系统调用,也不读取用户数据、不打开工坊或启动组件。
附属窗口
panels.open {key,path,title,width,height} 返回 {panelId};相同key显示已有窗口,不额外创建。path仅为已安装包内HTML页面,每实例最多两个、全局最多16个。普通带标题栏窗口,共享实例授权、配置、存档及安装凭据,各自拥有独立文档会话与临时资源,不是持久组件实例。panels.activate {panelId} 按Windows前台规则尝试激活自己的窗口,返回 {activated:boolean},被系统拒绝不绕过前台保护;隐藏或暂停时不激活。panels.close {panelId} 关闭自己的窗口;panels.post {panelId,value} 和 panels.message {panelId,value} 在主文档与子页之间传递最多64 KiB的JSON,禁止跨实例与递归开窗。子页关闭投递 panels.closed {panelId}。握手增加 view:{kind:"main"|"panel",panelId?:string};面板ID只在panel视图提供。同实例的主/附属视图共享文件选择器占用,同一时间只允许一个选择器。
隐藏或暂停与主视图一起隐藏,恢复不抢焦点;主文档重载、停用、断屏和退出关闭。子页的窗口位置、尺寸不写主实例布局,不注册组件快捷键,不接管主文档的退出保存回调。远程页面、window.open及原生任意窗口控制不开放。
绑定应用启动
复用 launchTarget 配置字段和 target.open {bindingId},无需Capability,仍要求有效资源绑定。目标及快捷方式参数由可信UI绑定和确认,网页不能自行指定路径、命令行或工作目录,执行前重新检查文件身份。
组件凭据
credentials.set {key,value}、credentials.get {key}、credentials.remove {key}、credentials.clear {} 是无需Capability的组件自有加密存储,不在manifest中声明credentials能力。它只管理所属安装自行写入的凭据,同安装跨屏共享;不读取系统账号、其他安装或用户secret绑定。get返回 {found,value:string|null},其余返回 {}。每安装最多32个键,键1–64个ASCII字母数字/下划线/短横线,值非空、最多32768个UTF-8字节,总明文最多256 KiB。DPAPI当前用户加密后原子保存,不写日志、布局、存档或可回退元数据;卸载清理,普通停用/重载保留。执行、最终投递、实例停用、卸载及取消仍复核有效性。
该接口补充KV和浏览器存储的落盘保护,作者可选择只在内存保留令牌。登录流程、账号界面、令牌刷新和服务端撤销由组件实现。现有用户输入secret绑定仍只返回引用,与该存储分开,不可由此读取或替换。
作者示例
作者声明需要的Capability后,用户仍可分别拒绝。快捷键组合只由宿主管理界面分配。
widgetHost.on('shortcuts.triggered', async ({id}) => {
if (id === 'edit') {
const {panelId} = await widgetHost.call('panels.open', {
key: 'editor', path: 'editor.html', title: '编辑器', width: 640, height: 480
});
await widgetHost.call('panels.activate', {panelId});
}
});
await widgetHost.call('shortcuts.setActions', {
actions: [{id: 'edit', label: '打开编辑器'}]
});
widgetHost.on('notifications.activated', ({notificationId, actionId}) => {
if (actionId === 'done') finishTask(notificationId);
});
await widgetHost.call('notifications.show', {
title: '任务完成', body: '处理结果已准备好', actions: [{id: 'done', label: '完成'}]
});附属页面通过 widgetHost.context.view.panelId 获取自己的窗口ID,用 panels.post 与主文档通信;所有绘制、业务状态和编辑器逻辑仍由作者实现。真实鼠标拖动期间调用 exports.startDrag,不得用脚本制造按键或声明可信手势。