dsh-deepseek-site — 左侧栏「DeepSeek 官网」面板
在 Harness Web UI 的左侧图标栏加一个入口,点开后在应用窗口内部的中央面板里直接显示 https://chat.deepseek.com/,不跳系统浏览器。
为什么必须用 webview 而不是 iframe
chat.deepseek.com 的响应头带 content-security-policy: frame-ancestors 'none',任何 iframe 都会被渲染器直接拒绝。
桌面版 Harness(Electron)把 window.open/新窗口一律交给 shell.openExternal,所以「点一下就跳浏览器」是外壳的既定行为。
唯一的合法通道是桌面版自带的受限浏览器容器:
- preload 在
dsh-app://app/文档里暴露window.dshDesktop.browser; bridge.acquire(workspaceKey)向主进程申请一个 guest lease,返回{ lease, partition };- 渲染
<webview name=lease partition=partition src="about:blank#lease">,外壳的will-attach-webview校验通过后才真正挂载; - 该 guest 是独立的 WebContents(不是 frame),因此
frame-ancestors不适用; - 面板卸载时
element.remove()+bridge.release(lease)。
本插件走的就是这条路径,与 Harness 自带侧边浏览器使用同一套 API。
行为
- 左侧图标栏第 4 个位置(
order: 3)出现「官网」图标(外链箭头)。 - 点击后中央面板加载官网;面板自带工具栏:重新加载、在系统浏览器打开(兜底)。
- 同一个
WORKSPACE_KEY复用同一个 guest 分区,面板来回切换不会丢登录态。 - 若运行环境没有
window.dshDesktop.browser(例如在普通浏览器里打开 Web UI),面板会显示说明卡片并给出兜底按钮,不再静默跳转。
登录记忆(重启后免登录)
外壳给每个 workspace 分配的分区是进程内生命周期的(dsh-sidebar-browser-<uuid>,没有 persist: 前缀),所以 guest 自己的 localStorage 在应用重启后就空了。
插件用「快照 + 回放」绕开这个限制:
- guest 在站点文档上每次
did-finish-load、站内路由变化、以及每 15 秒(且仅在窗口可见时)执行CAPTURE_SCRIPT,只读出userToken/__appKit_userInfo两个键; - 有 token 就写进应用文档自己的 localStorage(
deepseek-site:sign-in:v1)。应用文档是dsh-app://app/(registerSchemesAsPrivileged里standard: true),默认 session 持久化,所以这份快照能跨重启保留; - 下次打开面板,
did-finish-load时先executeJavaScript(restoreScript(...))把快照写回 guest 的 localStorage,写过就reload()一次,站点启动时直接看到有效 token; - guest 里已有有效 token(同一次运行内切面板)则不覆盖,避免把新登录态写回旧值。
工具栏出现「已记住登录」胶囊表示快照已保存;旁边的状态文字说明最近一次打开走了哪条路:
- 「已回填登录」:快照写进内嵌页并重新加载了一次;
- 「登录有效」:内嵌页本来就有 token,没有覆盖;
- 「无登录快照」:本机还没记录过登录,或回填被拒。
必须避开的坑:about:blank 占位文档
<webview> 以 src="about:blank#<lease>" 挂载,dom-ready / did-finish-load 会先为这个占位文档触发一次。占位文档是 opaque origin,localStorage 不可用;若在第一次 did-finish-load 就回填,回填会落在占位文档上并静默失败,真正的站点文档再也不会回填——表现就是"重启后仍要登录"。因此回填只在导航事件记录的 URL 属于 chat.deepseek.com 时才执行,并用 did-start-navigation / did-navigate 追踪主框架 URL。
必须避开的坑:登出标记不是登录
站点在未登录时会往 userToken 里写一个登出标记 {"value":null,"__version":"0"}。它是非空字符串,所以"键存在且不为空"不等于"已登录"。v1.3.0 只看字符串非空,导致两个后果:回填把登出标记当成登录态,工具栏还显示"登录有效";而每次登出后的捕获又会用这个标记覆盖掉真正的快照。v1.4.0 起统一用 tokenValue() 解析 {"value":…},只有 value 非空才算登录态,捕获与回填都不会再用空值覆盖已有快照。
忘记本机登录
工具栏上的「忘记本机登录」(只有存在快照时才出现)会同时删掉应用文档里的快照和 guest 里的两份凭据,然后重载内嵌页,站点回到登录页。 如果只删应用文档,下一次捕获会把仍然登录的 guest 里的 token 又写回去,所以两边都要清。
诊断记录
面板每次在站点文档上完成加载时,会在应用文档的 localStorage 写一条 deepseek-site:diag:v1(环形缓冲,最近 20 条,且只在结论/次数/token 长度变化时才追加)。每条只有四个字段:at(时间)、note(本次回填结论)、attempts(回填尝试次数)、tokenLen(快照里有效 token 的字符数)。
这条记录不含 token、响应体、cookie 名和完整 URL——早期版本曾带这些字段,审核时按隐私风险删掉了。
隐私与凭据
- 快照是明文 JSON,存在本机应用数据目录(
C:\Users\15139\AppData\Roaming\@deepseek-ai\dsh-desktop\Local Storage\leveldb)。它与 Harness 自身的登录态同等敏感:能读这个目录的人就能拿到你的 DeepSeek 账号凭据。 - 快照只保留两个键:
userToken(凭据本体)和__appKit_userInfo(站点恢复会话身份用的用户资料)。__appKit_*特性位、__ds_remote_feature_did之类的键刻意不复制——站点自己会重建,复制只会扩大暴露面。 - 快照在应用文档的 localStorage 里,与 Harness Web UI 同源,因此同源的插件代码理论上也能读到。不要装来路不明的插件。
- 不想要了:点「忘记本机登录」,或从插件管理里移除插件后手动清理应用数据目录。
WORKSPACE_KEY(client.js:19)刻意是固定值dsh-deepseek-site,不按工作区拆分:外壳按这个键缓存一个 guest 分区,固定值让所有工作区共用同一个内嵌浏览器容器,登录态与站点偏好不会随工作区切换而重来。代价是工作区之间不再隔离浏览容器——对本插件(只开一个固定站点)来说是划算的。
测试
cd plugins/dsh-deepseek-site
node --check client.js && node --check index.js
node test-signin.cjs
test-signin.cjs 从 client.js 里切片取出真实的声明(不是副本),在伪造的 localStorage 上验证:捕获白名单、tokenValue、minimizeSnapshot(含超长值)、回填四态(空 guest / 已登录 / 登出标记 / 被删 token)、CLEAR_SCRIPT 只清两个键、应用侧读写/忘记往返、诊断环形缓冲与字段白名单、pickUrl / isSiteUrl、shouldCapture 的可见性与去重门控。
发布到公开插件市场
包名已经是公开可用的 dsh-deepseek-site(npm 上未被占用),private 已移除,license/repository/homepage/engines.dsh 已补齐。
路线 A:npm 源(推荐,装起来最省事)
cd plugins/dsh-deepseek-site
npm login # 需要你本人登录,token 不在本机
npm pack --dry-run # 先确认打进去的文件
npm publish --access public
路线 B:Git 源(不想发 npm 时用)
仓库打好 dsh-plugin Topic,索引里用 {type:'git', url, commit, path},宿主会按 40 位 commit 克隆安装。
生成并校验索引
cd plugins/dsh-deepseek-site/publish
npm install # 只为 semver,一次性
node build-index.mjs # 默认 npm 源
node build-index.mjs --git https://github.com/WJMACTD/dsh-deepseek-site --commit <sha> --path .
生成后会立刻用从 DSH 安装包里抽出来的真实校验器(publish/market-index-validator.mjs,即 @deepseek-ai/dsh-plugin-market-index 的 parseMarketIndex)跑一遍,打印 validated: N plugin(s) 和最终的安装 spec。校验不过就直接报错,不会等到宿主 Worker 里才失败。
publishTimestamp是构建时的当前时间。先npm publish,再生成索引,时间才和 registry 一致。
让用户能订阅
索引需要放在一个固定 HTTPS 直连地址上,且宿主拒绝重定向:
- 把生成的
index.json推到WJMACTD/dsh-market仓库根目录,订阅地址是https://raw.githubusercontent.com/WJMACTD/dsh-market/main/index.json; - 不要用
github.com/.../raw/...(那是 302 重定向,宿主会拒); - 用户侧:插件 → 发现 → 添加索引源,粘贴上面的地址保存(索引源只能由用户自己添加,插件无法代劳)。
进社区目录(可选,但曝光量大)
bruc3van/awesome-dsh-plugin 收录 21073 个已核实仓库,机制很省事:
- 把插件仓库(或包含它的仓库)设为公开,并打上 GitHub Topic
dsh-plugin; - 每日自动刷新时进入待审核队列,人工核实后进全量目录——不需要提 PR;
- 想上首页「作者自荐」区:按
CONTRIBUTING.md#作者自荐--self-promotion,在SHOWCASE.md末尾追加中英各一行(首页只展示最近 10 条)。
被收录不代表安全审查或兼容背书,但确实是社区最主要的发现入口。
文件
| 文件 | 作用 |
|---|---|
package.json |
bundle 清单:dsh.bundle.patch、exports、dsh.client |
cordis.patch.yml |
把 deepseek-site 这一行插入 profile |
index.js |
宿主侧 apply() {}(空实现,仅为满足 bundle 入口) |
client.js |
客户端模块:注册 sidebar.panellist 图标 + main 面板,内嵌 webview 逻辑 |
locale/zh.json、locale/en.json |
插件市场展示用的 meta.title / meta.description(界面文案内联在 client.js,平台只从 locale JSON 读 meta) |
icon.svg |
128×128 原创图标 |
安装 / 更新
# 安装(已安装时先移除再装,或直接安装同级版本)
# 在 Harness 里执行:
plugin_manager(action="install_bundle", target="C:\\Users\\15139\\Desktop\\AI-SESSION\\plugins\\dsh-deepseek-site")
装好后按 Ctrl+R 重新加载窗口即可生效;若图标未出现,重启 Harness 桌面版。
No comments yet. Be the first to write one.