Skip to content

feat(extension): make the local daemon port configurable in the popup - #175

Merged
iuyo5678 merged 7 commits into
mainfrom
feat/configurable-daemon-port
Sep 10, 2026
Merged

feat(extension): make the local daemon port configurable in the popup#175
iuyo5678 merged 7 commits into
mainfrom
feat/configurable-daemon-port

Conversation

@shnpd

@shnpd shnpd commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

摘要

Fixes #114

扩展 popup 支持配置本机 daemon 的连接端口,默认仍为 52800。用户通过“保存端口”或 Enter 提交,配置写入 chrome.storage.local,background 在安全清理现有会话后应用新端口,并在连接开关开启时重新连接。

实现

  • 保留现有存储方案和 WSTransport 实例,由 ConnectionController 统一协调端口切换、会话清理、握手取消和重新连接。
  • keepalive、唤醒及握手重试遵守同一切换状态;连续修改使用最新端口,切换期间关闭连接则保持断开。目标端口不可达时,修改配置和关闭连接不会等待旧连接成功。
  • 初始化与运行时更新使用一致的端口读取和校验规则;迟到的初始读取不会覆盖新配置,删除配置或非法存储值回落到默认端口。
  • popup 区分草稿、已保存值和保存状态,展示读写失败。打开、失焦和关闭 popup 不会自动写入端口。
  • 连接开关表示用户的启用偏好,断连期间仍可关闭;保留具体协议错误,普通不可达提示只在没有具体错误时显示。
  • 同步中文、英文、韩文文案与架构文档,说明保存端口会结束当前会话。CLI/daemon 协议保持兼容。

验证

  • pnpm lint:通过,包含插件类型检查及 232 项插件测试。
  • pnpm ext:test:1245 项测试通过,40 项浏览器专项测试默认跳过。
  • pnpm --filter @browser-skill/extension compile:通过。
  • pnpm ext:build:Chrome MV3 生产构建通过。
  • pnpm --filter @browser-skill/i18n test:52 项测试通过。

新增测试覆盖延迟读取、配置删除、保存失败、快速切换端口、清理期间关闭/重新开启连接、旧握手失效、清理失败后的重试,以及端口不可达时的底层退避。连接时序测试使用真实 WSTransport 配合可控 WebSocket 测试替身。

尚未进行真实浏览器扩展的手动验收;以上验证为自动化测试、类型检查和构建。

shnpd and others added 2 commits September 2, 2026 18:56
The extension was hardwired to 52800; persist a custom loopback port and reconnect when it changes so users can match a non-default daemon.

Co-authored-by: Cursor <cursoragent@cursor.com>
The field was too short to read; grow its height without widening the card.

Co-authored-by: Cursor <cursoragent@cursor.com>
@shnpd
shnpd requested review from Ljy-0827 and iuyo5678 and a lite review from Copilot September 2, 2026 11:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Port parsing and URL construction need to be made stricter/more robust (e.g., avoid parseInt-accepting malformed input and avoid manual URL concatenation that can break IPv6 formatting).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR implements runtime configuration of the extension’s local daemon WebSocket port via the popup UI, persisting the setting in chrome.storage.local and updating the background connection behavior accordingly (addressing issue #114).

Changes:

  • Added a popup “Connection port” input with validation + persistence (bsk_daemon_port), plus friendlier disconnected-state messaging.
  • Updated background to read the stored port at startup and react to storage changes by updating the WebSocket URL and reconnecting.
  • Extended transport to allow runtime URL changes and added/updated docs + i18n + tests.
File summaries
File Description
packages/i18n/src/locales/zh-CN/extension.json Adds zh-CN strings for daemon port UI and unreachable messaging.
packages/i18n/src/locales/en-US/extension.json Adds en-US strings for daemon port UI and unreachable messaging.
docs/architecture.md Documents that the default daemon WS port is configurable via the popup.
apps/extension/src/transport/ws-transport.ts Allows updating transport URL via setUrl() without auto-reconnect.
apps/extension/src/transport/daemon-endpoint.ts New helpers for default port, URL resolution, and input/storage normalization.
apps/extension/src/transport/tests/ws-transport.test.ts Adds coverage for setUrl() behavior.
apps/extension/src/transport/tests/daemon-endpoint.test.ts Adds coverage for daemon endpoint URL/port parsing helpers.
apps/extension/src/lib/popup-bridge.ts Removes unused set_port message variant; port now stored via storage.
apps/extension/src/lib/instance-id.ts Adds get/set helpers and storage key for bsk_daemon_port.
apps/extension/src/lib/tests/instance-id.test.ts Adds tests for daemon port persistence/normalization.
apps/extension/src/entrypoints/popup/use-daemon-port.ts New hook to load/commit daemon port preference via chrome.storage.local.
apps/extension/src/entrypoints/popup/use-connection-state.ts Updates comment to reflect current popup bridge semantics.
apps/extension/src/entrypoints/popup/App.tsx Renders port input, adjusts disconnected messaging, and hides transport errors when disconnected.
apps/extension/src/entrypoints/popup/App.test.tsx Adds tests for disconnected UX and daemon port input behavior.
apps/extension/src/entrypoints/background.ts Reads stored port on startup and reconnects on storage updates.
apps/extension/PRIVACY.zh-CN.md Updates privacy text to note configurable loopback port.
apps/extension/PRIVACY.md Updates privacy text to note configurable loopback port.
Review details
  • Files reviewed: 17/17 changed files
  • Comments generated: 5
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread apps/extension/src/transport/daemon-endpoint.ts
Comment thread apps/extension/src/entrypoints/background.ts Outdated
Comment thread apps/extension/src/transport/daemon-endpoint.ts
Comment thread apps/extension/src/transport/daemon-endpoint.ts
Comment thread apps/extension/src/entrypoints/popup/use-daemon-port.ts Outdated
shnpd and others added 2 commits September 3, 2026 19:57
如果有字符就报错,不直接转为数字

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
修复注释不准确

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@iuyo5678

iuyo5678 commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

这个 PR 解决了 #114 中扩展端口无法与 CLI 对齐的问题。建议保留目前的主要实现方向:chrome.storage.local 持久化、storage.onChanged 驱动配置生效,以及 WSTransport.setUrl() 只更新 URL、不自动重连。

合并前建议调整一下连接生命周期和 popup 的状态处理。这样既能保留当前功能,也能减少对现有连接行为的影响。下面是基于当前提交的具体改进建议。

1. 恢复连接开关与错误展示的原有语义

连接开关只表示“是否允许连接”

App.tsx 目前使用:

checked={snapshot.connectionEnabled && !isDisconnected}

connectionEnabled === true、但 daemon 不可达时,开关会显示关闭。此时点击开关发送的仍是 true,Controller 因为状态没有变化直接返回,用户无法通过开关停止自动重连。

建议恢复为:

checked={snapshot.connectionEnabled}

开关表示用户是否启用连接,状态文字表示当前是否已连接。这两个状态可以独立展示,断连期间也应允许用户关闭连接。

保留具体错误,友好提示作为补充

App.tsx 新增的 !isDisconnected 会隐藏所有断连状态下的错误。但协议主版本不兼容、版本过旧也会进入 disconnected,这些情况需要提示升级,不能都显示成“检查 daemon 是否启动、端口是否一致”。

建议本 PR 恢复原有 lastError 展示。新增的连接提示可以只在没有具体错误时显示;更完整的错误分类和文案优化可以单独处理。

收益: 这两处主要通过撤回额外行为修改完成,不需要引入新状态模型,同时保留用户关闭连接和排查协议问题的能力。

2. 让 Controller 统一协调端口切换

background.ts 当前直接执行 setUrl → disconnect → connect。但 disconnect() 又会触发 Controller 的恢复流程,后者也负责清理会话和重连。

这里等待 transport.disconnect() 并不等于等待会话清理完成。两个流程并行后,新连接可能已经建立,旧会话仍在归还标签页或解绑 CDP。

建议增加一个由 ConnectionController 管理的配置切换入口,例如 reconfigureTransport。具体接口名可以按现有风格确定,职责建议如下:

模块 职责
popup 编辑、校验和保存端口偏好
background 接收 storage 变化,归一化配置并交给 Controller
ConnectionController 协调切换、清理、握手取消和重新连接
WSTransport 更新 URL,处理 socket 及其底层重试

继续复用同一个 WSTransport 实例即可,避免重建实例后重新绑定 dispatcher、heartbeat 和其他监听器。URL 的生成仍放在端口模块;Controller 可以通过配置回调调用现有 setUrl(),不必给通用 Transport 接口强行加入 WebSocket 专属配置。

切换过程

记录最新目标配置,进入受控切换状态
→ 暂停其他连接入口,取消旧握手和重试
→ 受控断开旧连接
→ 等待旧会话清理完成
→ 应用最新目标 URL
→ 根据最新 connectionEnabled 决定是否发起连接

实现时建议保证以下行为:

  • 受控切换只有一个执行流程。 主动断开产生的事件不能再启动另一条意外断连恢复流程;如果已有清理正在执行,就复用该清理过程。
  • 重连入口统一经过 Controller。 keepalive、唤醒事件和握手重试都应遵守初始化、切换及清理状态。可以给 keepalive 注入 Controller 的连接请求回调,避免它直接绕过协调过程调用 transport.connect()
  • 连续修改采用最新配置。 清理期间从 A 改成 B、再改成 C,结束后应用 C;同一个已生效或待应用端口不重复触发切换。
  • 用户关闭连接立即影响后续决策。 清理期间关闭开关,结束后保持断开。端口切换本身不要通过临时修改用户的 connectionEnabled 偏好来实现。
  • 旧连接的异步结果失效。 复用现有 generation / abort 思路,防止旧握手或旧连接尝试的完成、失败回调覆盖新状态。
  • 清理未成功就不越过清理阶段连接新端点。 保留失败信息和可重试状态,后续连接请求仍通过同一个协调入口处理。

这里有一个需要特别注意的实现细节:不要让串行协调流程一直等待 connect() 成功。 当前 transport 在目标不可达时可能持续等待重试;如果把整个 await connect() 放入队列,后续换端口和关闭连接会被它堵住。应当协调有限的清理和配置操作,连接尝试则允许被后续配置或关闭操作取消。

同理,如果复用现有恢复逻辑,应区分“清理完成的 Promise”和“包含连接等待的整个恢复 Promise”,避免间接引入相同的阻塞。

收益: 端口切换和现有断连恢复共享协调规则,旧会话清理与新连接之间有明确顺序;同时覆盖快速切换端口、切换时关闭连接和后台唤醒等时序,不需要在各处分别补重连逻辑。

3. 启动读取与运行时更新走同一套配置规则

建议启动时读取端口与连接开关,完成必要监听器的安装后,再允许发起首次连接。初始化期间收到的 storage 变化先记录,后续应用最新值,避免迟到的初始读取覆盖更新。

这里的“初始化完成”指配置和监听器准备完成,不能以首次连接成功为条件#114 的场景本身就是默认端口不可达,用户必须能够在未连接时修改端口。

端口校验也建议统一:

  • 输入时完整校验数字及 1–65535 范围,保留当前“用户主动清空则恢复默认值”的约定。
  • 从 storage 读取和监听变化时使用同一归一化规则;删除配置或遇到非法值时,行为与冷启动一致。
  • 保存前比较归一化后的端口,未变化则不重复写入或重连。

收益: 首次启动、扩展被唤醒和运行时修改不会形成三套不同的配置行为。

4. popup 明确区分草稿、加载和保存状态

use-daemon-port.ts 初始草稿为 "",但 "" 又代表提交默认端口。如果初始读取尚未完成就触发提交,就有覆盖原配置的风险;迟到的读取结果也可能覆盖用户已经输入的内容。

建议 hook 明确维护以下状态,其中 dirty 可以由草稿和已保存值推导,不必全部做成独立 state:

状态 含义
savedPort 最近确认的持久化配置
draft 用户正在编辑的文字
loaded 初始读取是否成功完成
dirty 是否存在尚未提交的编辑
saving 是否正在保存

处理规则建议如下:

  • 未加载完成时禁用编辑和提交;读取失败显示错误,不把空草稿当作默认值写回。
  • 没有修改时不写入 storage。
  • 收到外部 storage 更新时同步 savedPort,只有没有本地未提交编辑时才同步 draft
  • 保存成功后才清除未保存状态;保存失败保留草稿并展示失败。
  • 保存期间短暂禁用编辑和重复提交,可减少旧写入完成覆盖新草稿的时序。

交互上更推荐 “保存按钮 + Enter 提交”。端口修改会触发连接中断,明确提交能让生效时机更清楚,也能让保存失败在 popup 仍打开时被看到。

如果希望保留 blur 保存,也可以复用同一个提交入口。但建议不把“关闭 popup 时保存”作为正确性保证:pagehide 本身没有必达保证,关闭时的回调至多作为尽力提交。相关生命周期说明

另外,“配置保存成功”和“连接成功”应继续分别展示:前者由 storage 写入结果确定,后者由现有 Controller 快照确定。

收益: 避免默认值误写和异步读取覆盖输入;用户能够分辨保存失败与 daemon 不可达,hook 的提交条件也更容易测试。

5. 建议补充的验证

现有测试覆盖了端口解析、保存和单独的 setUrl()。建议重点补齐组件之间的时序测试,用可控制完成时机的 storage、清理和连接 mock 验证以下行为:

场景 预期结果
初始读取尚未完成,关闭或尝试提交 popup 不写入默认端口
初始读取期间收到较新的配置 最终应用新配置
保存相同端口 不断开、不重连
连接开关关闭时修改端口 保存配置,保持断开
会话清理未完成时触发 keepalive、唤醒或握手重试 不提前连接
清理期间连续修改多个端口 最终应用最新配置
切换过程中关闭连接开关 清理结束后仍保持断开
旧端口不可达、连接 Promise 未完成,再换端口或关闭 新操作不被阻塞
旧握手或旧连接尝试较晚完成 不覆盖新状态
清理失败或 storage 写入失败 保留具体错误,不误报成功
断连时操作开关、协议不兼容时打开 popup 能关闭自动连接,能看到协议诊断

修改范围可以集中在 popup/hook、端口配置模块、Controller,以及 background/keepalive 的连接入口接线;CLI 和 daemon 协议不需要随之调整。已有会话清理逻辑可以复用,重点是保证它完成之前不会建立新连接。

Coordinate endpoint changes, session cleanup and reconnect requests in the connection controller without waiting for unreachable sockets. Preserve connection preferences and protocol diagnostics, and explicitly save popup port drafts with loading and write-error handling.

Cover storage ordering, rapid configuration changes, disconnect cleanup, handshake cancellation and transport backoff with regression tests.
Preserve current session cleanup dependencies and resolve popup imports. Add matching Korean port messages and keep the popup disconnected while connection cleanup is in progress.
Explain that the daemon must already listen on the selected port and that the extension setting does not change the local CLI configuration. Show the saved daemon WebSocket address beside the save button.
@iuyo5678
iuyo5678 merged commit eb167fc into main Sep 10, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Allow configuring daemon port in extension UI | 浏览器扩展支持配置 daemon 端口

4 participants