DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in
← Plugins
W

dsh-webhook-notify

Webhook Notify

DSH notification plugin: Push notifications from the host process to any Webhook when your action is required, a reply is complete, or an error occurs, with custom methods, headers, request body templates, and subscribed events, without browser cross-origin restrictions; it can also display browser

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add github:gitMFQ/dsh-webhook-notify#b5a24f6a84da2058c79abdf5d39f24286e5f28e2
READMECompatibilityVersions

Description

DSH notification plugin: Push notifications from the host process to any Webhook when your action is required, a reply is complete, or an error occurs, with custom methods, headers, request body templates, and subscribed events, without browser cross-origin restrictions; it can also display browser system notifications (see dsh-notify-me; click a notification to return to the corresponding conversation).

Compatibility and provenance

Webhook Notify is published as dsh-webhook-notify and currently resolves to version 0.4.0. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
web
Release source
github
Registry updated
10/28/2026

Versions

0.4.0stable
10/28/2026

Related plugins

Loading related plugins…

Latest
0.4.0
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
Unavailable
Files
Unavailable
Surface
web
License
MIT
Source
github
GitHub
★ 0
Weekly downloads
0
Last push
10/28/2026
View source ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue

Related plugins

More verified plugins in integrations-communication.

Remote Web Ui@linxin666/dsh-remote-web-uiScan-to-pair remote access for the dsh web GUI that shares one official interface: a QR beside the settings button pairs phones and PCs into the same Web GUI (a portrait-touch adaptation layer for phones, full desktop on PCs) through one-time tokens and rRemote Bridge@linxin666/dsh-remote-bridgeDSH remote bridge: the host half serves protocol v1 over one exact /ios/v1 WebSocket route — session roster, history baseline, prompting, live turn increments with tool activity, interrupt, and in-process agent approval — to a device paired by a one-shot Ds Botds-botDS Bot: a team of long-lived Bots for DeepSeek Harness, with Main Bots, Bot-to-Bot messages and group chatsFree Searchdsh-free-searchFree web search for DeepSeek Harness: 27 engines (Bing/DuckDuckGo/AnySearch/SearXNG/Exa/Tavily/Keenable/Firecrawl/Parallel/You.com keyless; Zhihu global/site via MCP and Perplexity/SerpBase/DeepSeek (official + native tools/search)/Baidu/Kimi/Aliyun/Douba

README

dsh-webhook-notify

DSH 通知插件:任务需要你操作 / 回复完成 / 本轮出错时,从 DSH 宿主进程把通知推送到你自己的 Webhook,同时(可选)在系统通知中心弹一条浏览器通知(参考 dsh-notify-me,点击通知回到对应会话),再进一步:把 DSH 变成可安装的 PWA(只补可安装性,不改宿主的窗口显示模式与状态栏),并支持 Web Push(页面全关也能收到提醒)。

为什么不是「填个地址就行」的前端插件

dsh-notify-me 这类浏览器端插件用 fetch 直接 POST,会撞上两道墙:

  1. 跨域(CORS):目标端点不放行 Access-Control-Allow-Origin 时,浏览器连请求体都读不到,只能降级成「已送出不可确认」;
  2. 自定义请求头:Authorization: Bearer … 会触发预检,端点不配合就发不出去——而这恰好是绝大多数 Bot / 推送网关的鉴权方式。

本插件把发送放在宿主(Node)进程里:任意请求头、任意目标、不受同源策略约束,页面关掉也照发。

触发事件

事件时机kind
需要你操作approval/asked(工具审批等)attention
回复完成turn/end 且 reason.kind === 'completed'done
本轮出错turn/end 且 reason.kind === 'error'error

浏览器通知(参考 dsh-notify-me)

除了推 Webhook,本插件还能在系统通知中心弹一条浏览器通知,页面开着也能立刻看到、点一下回到对应会话。

  • 持久通知走 Service Worker:页面被系统挂起后仍能弹出,点击通知带用户手势、能把窗口真正带到前台;没有 Service Worker(如 dsh-app:// 桌面端、非安全上下文)时自动退回页面内 new Notification()。
  • 触发规则与 Webhook 同源:同一批事件,Webhook 推到端点、浏览器通知弹到桌面,两边独立开关。
  • 点击回到会话:点击通知把 DSH 带到前台并切到该条提醒所属的会话(可在设置里关掉)。
  • 安静规则:默认「当前对话的等待不弹」(卡片就在眼前)、「回复完成只在页面隐藏时弹」、「子代理会话不弹」——都可在设置页调整。

设置页「浏览器通知」卡片里有权限状态、Service Worker 状态和「发送测试通知」按钮。

浏览器通知权限需要在页面上点一次(浏览器要求用户手势才能询问)。首次点设置页的「请求通知权限」即可。

PWA:可安装为应用

插件只补齐可安装性所需的东西(manifest 链接、图标、启动字段),不改写宿主自己的窗口显示模式与状态栏。

manifest 怎么处理

DSH 自带 manifest.webmanifest(品牌名、图标、display 都在里面)。插件不另起一份,而是:

  1. 宿主在下发 index.html 时做三件事(webServer.tapIndex 变换;老宿主没有这个 API 时由客户端兜底改写):
    • 在 <head> 最前面注入 /webhook-notify/pwa-capture.js——beforeinstallprompt 每次页面加载只派发一次,而客户端插件是异步加载的,跑晚了就永久错过(设置页只能显示「浏览器没有给安装入口」);
    • 把 <link rel="manifest"> 指向插件路由 /webhook-notify/manifest.webmanifest;
    • 插入一段 <style>:html,body{overscroll-behavior-y:contain},关掉下滑刷新(见下);
  2. 插件的路由回源抓取宿主原 manifest,在它基础上合并:
    • 补 192×192 / 512×512 位图图标(宿主只有 SVG 时过不了可安装性检查,见下);
    • 可选覆盖 name / short_name / background_color(都留空就沿用宿主);
    • id 稳定化、launch_handler: navigate-existing(单窗口复用);
    • 所有 URL(start_url / scope / icon.src …)按原 manifest 的地址解析,同源的写成 / 开头的根相对地址;
    • display / display_override / theme_color 原样保留:显示模式与状态栏属于宿主,插件一个字都不改(所以 DSH 默认的 display: "fullscreen" 全屏就是它自己的选择);
  3. 抓不到原 manifest 就 302 跳回原地址:宁可少几个字段,也不要让应用变得不可安装。

状态栏与底部导航栏:插件不碰

这两条栏的颜色归宿主 manifest(theme_color)与浏览器管:插件既不再往页面里插 <meta name="theme-color">,也不再改 viewport-fit 或注入底部安全区样式。之前那套「把宿主 fullscreen 改成 standalone + 给状态栏染色 + 让底部导航栏透明」的做法会连带改变宿主的全屏观感,现在已经全部拿掉。需要换配色或显示模式时,直接改宿主自己的 manifest 即可。

下滑刷新:为什么要用 CSS 关掉

装成应用之后,从页面顶部往下拽会拉起 Chrome 那个「转圈 → 整页重载」的手势。对原生感的界面这是纯粹的灾难:聊到一半的输入框、展开的面板、滚动位置全被一次误触抹掉。

Chrome 从 63 起把这件事交给 CSS 的 overscroll-behavior:在 html, body 上写 contain,越过顶部的那段 overscroll 就不再冒泡到浏览器窗口,整页重载也就不会发生。插件注入的是:

html, body { overscroll-behavior-y: contain; }

几个取舍:

  • 用 contain 而不是 none:none 连橡皮筋回弹一起去掉,手感反而更假;contain 只拦住向上的冒泡。
  • 只加在根滚动容器上,不动嵌套滚动区(消息列表、抽屉):它们的滚动链保持原生,root 那层已经兜住了越界。
  • 这是纯 CSS,没有 touchmove 监听,不抢滚动线程的合成器,也不影响惯性滚动。

为什么 manifest 里不能写死 http://host

这个坑很难自己看出来:宿主把 manifest 里的地址写成 http://<Host>/… 之后,经过 HTTPS 反向代理访问的用户拿到的就是混合内容——浏览器拦掉图标,start_url 又跨了协议,于是:

  • 地址栏没有安装图标;
  • 不派发 beforeinstallprompt,设置页只能说「浏览器没有给安装入口」;
  • 控制台往往一句错都不报。

而宿主那头完全正常(在 127.0.0.1 上直接打开当然没问题),所以很容易怀疑到别处。根相对地址把 origin 交给浏览器按当前访问地址补,https / 端口 / 反向代理怎么变都对;跨域资源(CDN 图标)保持绝对地址。

为什么不 preventDefault()

拦掉 beforeinstallprompt 的默认行为,等于把浏览器自己的安装入口(地址栏的安装图标)一起关掉。插件现在只记录不拦截:浏览器原生入口归浏览器,设置页的按钮只是多一个入口。

用 Chrome 的 Page.getAppManifest 验证过:解析无 error,displayOverrides 里第一个就是 kWindowControlsOverlay。

图标:为什么插件要自己生成 PNG

Chrome/Edge 的可安装性检查要求 manifest 里有 192×192 与 512×512 的位图图标。DSH 原 manifest 只有一个 favicon.svg(sizes: "any" → 实际尺寸 0×0),过不了「至少 192px 宽的图标」这一关——页面明明满足其它条件,浏览器也不给装。

所以插件在宿主侧现栅格化宿主 logo(见 lib/icon.js):解析 SVG 的 <path d> → 扫描线填充(非零环绕规则 + 4×4 超采样抗锯齿)→ 用 Node 自带 zlib 编码 PNG。产物:

路由尺寸用途外观
/webhook-notify/icon-192.png192×192purpose: any圆角 + 品牌蓝底 + 白色 logo
/webhook-notify/icon-512.png512×512purpose: any同上
/webhook-notify/icon-maskable-512.png512×512purpose: maskable满幅蓝底(系统自己裁形状),logo 收在安全区内

三张图长得一样:Windows 的任务栏、开始菜单、安装对话框、macOS 的 Dock 各自可能挑不同的那张,长得不一样就会显得很怪。

另外合并 manifest 时会把宿主里非位图的图标(favicon.svg,sizes: "any")从 icons 列表里换掉,不是留着当备选:浏览器挑图标会把 sizes: "any" 的标量图当万能解,装出来的应用就是一张透明底的黑色 logo——深色任务栏上几乎看不见。插件补的位图本来就是同一个 logo 栅格化出来的,换掉不丢品牌。

不随包发 PNG 资源:logo 形状来自宿主的品牌 SVG,宿主一改就不该留旧副本。栅格化要几百毫秒,所以只做一次并缓存在宿主内存里。

兜底图标不长期缓存:宿主刚启动时 webServer 可能还没开始监听,这时回源抓 favicon.svg 会 ECONNREFUSED;如果把这个失败结果钉死在缓存里,之后装出来的应用图标就一直是内置铃铛。所以兜底只管一分钟,之后自动重试真 logo。设置页的「应用图标」一行会直接写明当前用的是「宿主 logo」还是「内置铃铛」。

装完图标还是旧的? 那是操作系统的安装快照:Windows 把图标存进了应用快捷方式,宿主这边换图标它不会跟着变。到 edge://apps / chrome://apps 里卸载再装一次即可。

装不上?设置页会直接告诉你原因

「PWA 安装」分组里「安装为应用」旁边会显示当前状态:

提示含义
浏览器已给出安装入口可以装
当前不是安全上下文({origin})最常见的原因:PWA 安装与 Web Push 都要求 https:// 或 http://localhost;用 http:// 访问一个域名一定装不了
Service Worker 还没就绪刷新页面让 worker 装上
浏览器没有给安装入口多半是这台浏览器已经装过(装过之后地址栏就不会再有图标),也可能是企业策略;用下面的自检确认
页面上没有 manifest 链接宿主没下发改写后的 index.html,刷新一次即可
已经以应用方式在运行了装过了

光有文案还不够:卡片里还有 「安装自检」,在页面里逐项跑一遍可安装性条件——安全上下文、manifest 能不能取到且是合法 JSON、有 name/start_url/display、声明了 192×192 与 512×512 图标、manifest 里的地址与当前页面同源同协议(不是混合内容)、图标真能下载、Service Worker 已激活、浏览器有没有派发 beforeinstallprompt。哪一环断了一目了然,不用去猜。

离线兜底

Service Worker 监听导航请求(request.mode === 'navigate'),走「网络优先 + 缓存兜底」:网络正常时行为完全不变,断网时至少能打开上次缓存的应用外壳。其它请求(脚本、接口、WebSocket)一概不碰。可用 pwaOffline 关掉。

Web Push:页面全关也能收到

浏览器通知依赖页面活着;Web Push 则由推送服务直接投给 Service Worker,页面全关、浏览器在后台都照收。

链路

宿主事件 → VAPID 签名 + aes128gcm 加密 → POST 推送服务 → Service Worker 的 push 事件 → 系统通知
  • VAPID:插件自动生成 P-256 密钥对,公钥给浏览器订阅用,私钥留在宿主签名用。密钥存在 profile 配置里,必须长期稳定,换密钥等于所有订阅失效。
  • 出站代理(pushProxy):推送服务都在公网。宿主只能经代理出网时,直连只会得到一句 fetch failed——什么都不说。填 http://127.0.0.1:7890 这样的地址后,推送走 undici 的 ProxyAgent;留空=直连。只影响 Web Push,Webhook 投递仍然直连(这些地址通常在内网)。改完记得保存配置,代理 Agent 会跟着重建。
  • 载荷加密:按 RFC 8188 的 aes128gcm 内容编码(密钥派生链与 http_ece 参考实现逐字节一致;测试里直接用 RFC 8188 §3.1 的官方测试向量对齐)。
  • 点击行为:推送通知的 data 与页面内通知同形,点击照样切回对应会话。

用法

  1. 设置页 → 「Web Push」→ 打开「启用 Web Push」;
  2. 点「订阅推送」(需要先授予通知权限;浏览器要求用户手势);
  3. 订阅信息会存到宿主(profile 配置),宿主在事件发生时向所有订阅推送;
  4. 「发送测试推送」可验证整条链路。

推送端点默认要求 https://;本机回环地址(127.0.0.1 / localhost)允许 http://,方便接自建推送中继。

端点字段

字段说明
url必填,可含模板变量
methodPOST / PUT / PATCH / GET / HEAD / DELETE(其它值回落成 POST)
headers对象,或每行一条 Key: Value 的文本
bodyModejson(通用载荷)/ text(只发正文)/ none
body自定义请求体模板,留空用 bodyMode 生成
kinds订阅哪些事件;留空 = 全部
retries / timeoutMs重试次数 / 单端点超时。timeoutMs 为 0(默认)时跟随插件级 timeoutMs,只有显式填了值才覆盖它。4xx 不重试(408 / 425 / 429 除外);3xx 不自动跟随,按失败上报

模板变量

{{title}} {{body}} {{content}}(拼好的正文){{text}} {{session}} {{sessionId}} {{kind}} {{event}} {{time}} {{tool}} {{reason}} {{json}}(完整事件 JSON)

body 以 { 或 [ 开头时按 JSON 拼装,变量自动转义——正文里的换行和引号不会把 JSON 拆坏。

例子

OpeniLink Hub / 微信

url:     https://oih.osai.eu.org:8443/bot/v1/message/send
headers: Authorization: Bearer app_xxx
body:    {"content":"{{content}}"}

Discord

url:  https://discord.com/api/webhooks/xxx/yyy
body: {"username":"DSH","content":"{{content}}"}

Bark

url:  https://api.day.app/<key>/{{title}}/{{content}}
bodyMode: none

自定义接收端

url:  https://example.com/hook
body: {"event":"{{event}}","text":"{{content}}","session":"{{session}}","at":"{{time}}"}

设置页

DSH Web → 设置 → Webhook 通知。面板按 DSH 自身设置页的样式与结构重做:

  • 排版对齐宿主:行内「标签左 / 控件右」+ .5px 分隔线、14px 正文 / 12px 次要文字、36px 控件;颜色全部走 --dsw-* 设计令牌,浅色 / 深色主题自动跟随;
  • 开关、按钮、事件胶囊直接复用宿主暴露的原子组件 @deepseek-ai/dsh-client-ui-primitives(Switch / Button / Pill),拿不到时退回等价的自绘样式;
  • 分组:Webhook 投递(事件开关、默认超时、节流、正文模板、日志)→ 浏览器通知(开关、静音规则、权限与测试)→ PWA 安装(开关、应用名、manifest 与图标状态、自检)→ Web Push(开关、VAPID 联系地址、出站代理、订阅与测试)→ 推送端点 → 最近投递;
  • 保存 / 预览 / 添加端点固定在面板底部的操作条上,改动后随手可保存;
  • 端点卡片:备注名、启用开关、地址、方法、订阅事件、超时、重试、请求体模式与模板、请求头,以及「发送测试消息」。

配置字段

除端点字段外,插件级配置如下(都存在 profile 的 cordis.patch.yml):

字段默认说明
notifyAttention / notifyDone / notifyErrortrue三类事件各自的投递总开关
minIntervalMs0同一会话的最小推送间隔(毫秒,0=不限)
timeoutMs10000插件级默认请求超时;端点自身的 timeoutMs 为 0 时用它
textTemplate''正文模板(留空用内置排版)
logDeliveries / debugtrue / false投递日志 / 调试日志
pwaEnabledtrue接管 manifest 与可安装性(关闭后不再改写 index.html)
pwaOfflinetrueService Worker 缓存应用外壳(断网兜底)
pwaName / pwaShortName''覆盖应用名/短名(留空沿用宿主)
pwaBackgroundColor''覆盖启动背景色(留空沿用宿主)
pushEnabledfalse宿主向浏览器推送
pushSubjectmailto:dsh-webhook-notify@localhostVAPID 联系地址(须 mailto: 或 https:)
pushProxy''推送出站代理(如 http://127.0.0.1:7890);留空=直连
pushVapidPublicKey / pushVapidPrivateKey自动生成VAPID 密钥对,勿手改(换密钥=订阅全废)
pushSubscriptions[]浏览器订阅列表,自动维护

修复与行为变化(0.4.0)

这一版按代码审查结论做了一轮修复,并拿掉了对宿主窗口外观的改写:

  • 停用端点也能自测:此前对一个 enabled: false 的端点发「发送测试消息」,notify() 放行、投递层又按启用过滤,结果一律显示「找不到该端点」,一个请求都发不出去。现在自测路径绕过启用过滤;地址还没填的端点会得到真实的「地址无效」结果,真正不存在该 id 才报「找不到该端点」。广播路径仍然静默跳过空地址草稿行,不会往最近投递里塞红条。
  • 反向代理下设置页不再全 403:trusted() 只比对 Origin 与 Host,代理按默认改写 Host 后保存/测试/预览/订阅一律 403(manifest 却正常,极难排查)。现在 X-Forwarded-Host 存在时也计入允许集合。
  • 插件级 timeoutMs 真的生效:端点 timeoutMs 默认改成 0(跟随插件级超时),此前端点默认值总会覆盖插件级设置,导致 profile 里改 timeoutMs 毫无作用。
  • 「窗口拖拽条」开关修好:开关此前写到了 React 状态副本上,实际读取的是模块级状态,勾选后永远不生效;该功能随窗口覆盖改写一起移除(见下)。
  • 待处理交互不再重复提醒:去重键此前用 key.split(':') 反推会话 id,而宿主下发的 key 是 approval:<序号> 这类不含会话 id 的形式,导致同一次等待被反复弹。
  • 「点击通知回到对应对话」在持久通知路径下生效:该开关此前只作用于页面降级通知;现在偏好随通知 data 交给 Service Worker,worker 不再无条件抢焦点,页面侧也再判一次。
  • 结局分类不再用过期数据:「本轮出错」判定新增 60 秒有效期,并在页面回到前台时立即刷新,避免上一轮的 error 把后台完成的一轮误报成出错。
  • i18n 补齐:中文补上「推送代理」文案,英文补上图标来源文案,并清掉中文词典里重复定义(英文值)的三个死条目。
  • 死代码清理:未使用的 deliver / LIMITS 导入与无处调用的 resolveManifestSource() 删除。
  • 不再改写状态栏 / 底部导航栏 / 窗口显示模式:删掉 theme-color 注入、viewport-fit=cover 与底部安全区注入、manifest 的 display / display_override 强制值与 theme_color 覆盖、窗口覆盖(WCO)相关的 CSS 变量发布与拖拽条。宿主原本的 display: "fullscreen" 与全屏观感原样保留。

安全与一致性(0.3.0)

这一版修掉了几个会造成数据泄露或「看起来成功其实没成功」的问题,行为变化如下:

  • manifest 回源被收紧:/webhook-notify/manifest.webmanifest?src=… 只接受宿主自己的 /manifest.webmanifest,并拒绝 query / hash / 跨源地址。此前 ?src=/webhook-notify/state 会把宿主的内部状态接口当 manifest 抓取并原样返回,端点的 Authorization 头与私密 Webhook 地址会因此泄露。非法 src 现在直接 400,不会发起任何本机请求。
  • 图标回源固定走本机:Host / X-Forwarded-* 只用来决定 manifest 里对外的地址,不再参与「去哪里抓 favicon」。伪造这些头无法让宿主访问任意地址。
  • 设置页保存有字段白名单:pushVapidPrivateKey 与 pushSubscriptions 永远不下发、也无法被保存请求覆盖。此前一次保存可能把订阅列表清空。
  • 配置写回串行化:订阅上报、VAPID 密钥生成与设置页保存并发时不再互相覆盖,各自只写自己负责的字段。
  • 测试通知不再「空成功」:没有可用端点时返回 ok: false 与具体原因。
  • 3xx 不再自动跟随:端点返回重定向会明确报错(避免对端决定请求最终发往哪里)。429 / 408 会重试,其余 4xx 不重试。
  • 响应体有上限:最多读取 64 KiB,对方回一个超大响应不会吃掉宿主内存。

浏览器侧同时修掉了:同一条点击消息触发两次导航、showNotification() 失败被当成成功(并产生未处理拒绝)、订阅上报失败仍显示「已订阅」、以及插件重载后监听器与通知残留。

注意

  • HTTP 头只能是单字节字符:X-Tag: 中文 会被底层拒绝,插件会直接报「请求头含非 ASCII 字符」而不是静默失败。中文放请求体。
  • 端点配置存在 profile 的 cordis.patch.yml,重启保留;headers 里的 token 也一并存在那里,注意该文件的读取权限。VAPID 私钥同样存在这个文件里。
  • 同一会话的推送可用「最小间隔」节流,避免连续回合刷屏。
  • Webhook 正文上限 21000 字符:助手最后一条回复先截到 21000 字(超出以 … 结尾),拼上标题/时间等附加行后再按同一上限截一次,所以端点收到的 {{content}} 不会超过 21000 字。Web Push 通知正文仍截到 400 字——推送服务对单条载荷有 4 KiB 硬上限。
  • PWA 安装与 Web Push 都要求安全上下文:https:// 或 http://localhost / 127.0.0.1。用 http:// 访问一个域名(例如 http://dsh.example.com)时,浏览器不给注册 Service Worker,也就不给安装、不给推送——设置页会直接这么提示。反向代理要记得透传 X-Forwarded-Proto / X-Forwarded-Host,否则 manifest 里的绝对地址会指向内网。
  • 改了宿主 manifest 的 display / theme_color 之后:WebAPK 不会立刻更新,卸载重装最快(也可以在 Chrome 打开 chrome://webapks 点 Update)。插件本身不再改写这两个字段。
  • Web Push 依赖推送服务可达(Chrome 走 FCM / Edge 走 WNS)。网络不通时订阅会失败;本机自建中继可用 http://127.0.0.1 端点。
  • 设置页保存时不会覆盖 pushVapidPrivateKey 与 pushSubscriptions(客户端根本拿不到这两个字段)。

测试

cd /root/dsh-webhook-notify && npm test

npm test 等价于 node --test test/;当前共 116 项,全绿。

覆盖:端点投递与模板、浏览器通知运行时、RFC 8188 官方测试向量(内容编码逐字节对齐)、HKDF(RFC 5869 + Node 内置实现交叉验证)、VAPID 签名、manifest 合并与 URL 根相对化(含反向代理下的混合内容回归)、SVG 栅格化与 PNG 编码、Service Worker 源码真跑一遍(push / notificationclick / 订阅失效)、以及一条端到端推送(宿主 → 本地 HTTP 推送服务 → 用浏览器侧私钥解密校验)。

开发说明

node_modules 是指向 profile 依赖的软链,只为本地跑测试时解析 @deepseek-ai/schemastery 用;删除它插件在宿主里仍能正常工作(宿主自己解析依赖), 但 node --test 会因找不到 schemastery 而失败。

新增文件:

文件职责
lib/pwa.jsmanifest 链接改写、早期安装提示捕获脚本、禁止下滑刷新、manifest 合并(补图标/icon、稳定 id、启动字段;不动 display / display_override / theme_color)、URL 根相对化
lib/push.jsVAPID 签名、aes128gcm 加密、推送发送
lib/icon.jsSVG path 解析、扫描线栅格化、PNG 编码(PWA 图标)
lib/sw.jsService Worker 源码(通知点击中转 + push + 离线兜底;点击时按通知携带的 autoFocus 决定是否抢焦点)
lib/browser-notify.js浏览器通知运行时(页面半边的纯 ESM 孪生实现,页面侧那套由 lib/client.js 内联)

宿主半边的改动需要宿主重新加载插件才会生效(Profile 重组合即可;lib/index.js 的改动不会热替换到已运行的进程里)。