[v1.4] VSCodeConnect 代码重构#1170
Conversation
c83a2d9 to
a6c016e
Compare
VSCodeConnect 代码重构
d62d2ad to
efc7ed2
Compare
|
能放1.4就1.4了,现在两个版本内容挺多了,时间也挺久了 |
AI 再重构以下是对这两个版本
总结评价v1(重构版)整体明显优于 v0,尤其在以下几个关键点:
|
There was a problem hiding this comment.
Pull request overview
本次 PR 聚焦于重构 Offscreen 环境下的 VSCodeConnect 连接管理(VS Code 扩展 scriptcat-vscode ↔ ScriptCat),目标是提升 WebSocket 连接生命周期管理、重连稳定性与可维护性,服务于开发模式下的热重载/即时安装流程。
Changes:
- 将 VSCodeConnect 由“分散的 WebSocket + interval 重试”重构为集中式状态机:epoch、超时、重连退避、统一清理等。
- 抽出并复用
VSCodeConnectParam类型,更新 service worker / offscreen 客户端调用签名。 - 增强日志与消息解析健壮性(try-catch、unknown action 日志等)。
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/app/service/service_worker/client.ts | SystemClient.connectVSCode 改为使用 VSCodeConnectParam 类型,简化/固定调用签名。 |
| src/app/service/offscreen/vscode-connect.ts | VSCodeConnect 核心重构:引入 epoch、connect timeout、重连退避、统一 dispose 与更健壮的消息处理。 |
| src/app/service/offscreen/client.ts | VscodeConnectClient.connect 改为使用 VSCodeConnectParam 类型,与新的连接管理器对齐。 |
| private connect(sessionEpoch: number): void { | ||
| const url = this.currentParams?.url; | ||
| if (!url) return; | ||
|
|
||
| try { | ||
| this.logger.debug(`Attempting connection (Epoch: ${sessionEpoch})`, { url }); | ||
| this.isReconnecting = false; // 开始新连接时重置锁 | ||
| this.ws = new WebSocket(url); | ||
|
|
||
| // 设置连接超时看门狗 | ||
| this.connectTimeoutTimer = setTimeout(() => { | ||
| if (sessionEpoch === this.epoch) { | ||
| this.logger.warn("Connection timeout"); | ||
| this.ws?.close(); | ||
| } | ||
| }, CONFIG.CONNECT_TIMEOUT); | ||
|
|
There was a problem hiding this comment.
connect() 每次都会新建 WebSocket 并覆盖 this.ws,但没有在重连场景下先清理旧的 connectTimeoutTimer/旧 ws。若 scheduleReconnect 在同一 epoch 内多次触发(例如 onerror 后又触发 timeout/watchdog),旧的 timeout 回调仍会运行并对“新的”this.ws 执行 close,造成新连接被旧定时器误杀。建议在 connect() 开始处先 clear 现有 connectTimeoutTimer,并对当前 this.ws 做一次统一的 close+解绑(或抽成 cleanupSocket() 在每次重连前调用)。
| this.logger.error("WebSocket error", { | ||
| event: ev instanceof Event ? ev.type : undefined, | ||
| error: ev instanceof Error ? ev.message : String(ev), | ||
| }); | ||
| // 发生错误时立即尝试介入重连,无需等待 onclose 事件。 | ||
| // 内部锁会拦截后续 handleClose 发起的重复请求。 |
There was a problem hiding this comment.
handleError() 里直接调用 scheduleReconnect(),但没有清理当前连接尝试对应的 connectTimeoutTimer。在“error 但未 close”的情况下,timeout 仍可能在稍后触发并关闭(或干扰)后续重连创建的新 ws。建议在进入重连前同步 clear connectTimeoutTimer,并视情况主动 close 当前 ws(再由 onclose 触发后续流程),以避免定时器/连接状态残留。
| this.logger.error("WebSocket error", { | |
| event: ev instanceof Event ? ev.type : undefined, | |
| error: ev instanceof Error ? ev.message : String(ev), | |
| }); | |
| // 发生错误时立即尝试介入重连,无需等待 onclose 事件。 | |
| // 内部锁会拦截后续 handleClose 发起的重复请求。 | |
| this.logger.error("WebSocket error", { | |
| event: ev instanceof Event ? ev.type : undefined, | |
| error: ev instanceof Error ? ev.message : String(ev), | |
| }); | |
| // 出现 error 时,当前连接对应的超时计时器已经失去意义,需要立即清理, | |
| // 否则旧定时器可能在后续重连成功后才触发,误操作新的连接。 | |
| if (this.connectTimeoutTimer) { | |
| clearTimeout(this.connectTimeoutTimer); | |
| this.connectTimeoutTimer = null; | |
| } | |
| // 优先尝试关闭当前 ws,由 onclose → handleClose 统一收敛清理与重连逻辑。 | |
| if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) { | |
| try { | |
| this.ws.close(); | |
| // 后续重连由 handleClose 触发,无需在此重复 scheduleReconnect。 | |
| return; | |
| } catch (e) { | |
| this.logger.warn("Failed to close WebSocket after error", Logger.E(e)); | |
| } | |
| } | |
| // 若当前不存在可关闭的 ws(或关闭失败),直接介入重连作为保底。 |
| this.startSession(params); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * 启动一个新的连接会话 | ||
| * 每次调用都会递增 epoch,自动使旧的连接和定时器失效 | ||
| */ | ||
| private startSession(params: VSCodeConnectParam): void { | ||
| this.dispose(); // 彻底清理旧资源 | ||
| this.currentParams = params; | ||
|
|
||
| // 开启新一轮连接 | ||
| this.epoch++; | ||
| this.connect(this.epoch); |
There was a problem hiding this comment.
目前 messageGroup.on("connect", ...) 的处理函数没有返回 Promise(直接 startSession 后返回 void)。在 message server 中这会立刻回传 {code:0,data:undefined},导致 SystemClient.connectVSCode() / Tools 页面会“立即显示连接成功”,即使 WebSocket 还没 open、甚至随后失败也不会 reject。建议让 connect handler 返回一个会话级 Promise:在 handleOpen resolve,在 handleError/timeout reject(并在 epoch 变更时取消/忽略旧 Promise)。
| this.startSession(params); | |
| }); | |
| } | |
| /** | |
| * 启动一个新的连接会话 | |
| * 每次调用都会递增 epoch,自动使旧的连接和定时器失效 | |
| */ | |
| private startSession(params: VSCodeConnectParam): void { | |
| this.dispose(); // 彻底清理旧资源 | |
| this.currentParams = params; | |
| // 开启新一轮连接 | |
| this.epoch++; | |
| this.connect(this.epoch); | |
| // 返回会话级 Promise,让调用方能够感知连接是否成功建立 | |
| return this.startSession(params); | |
| }); | |
| } | |
| /** | |
| * 启动一个新的连接会话 | |
| * 每次调用都会递增 epoch,自动使旧的连接和定时器失效 | |
| * | |
| * 返回值为当前会话级的 Promise: | |
| * - 预计在 WebSocket open/hello 完成时 resolve | |
| * - 在连接出错或超时时 reject | |
| */ | |
| private startSession(params: VSCodeConnectParam): Promise<void> { | |
| this.dispose(); // 彻底清理旧资源 | |
| this.currentParams = params; | |
| // 开启新一轮连接 | |
| this.epoch++; | |
| return this.connect(this.epoch); |
| private scheduleReconnect(): void { | ||
| if (this.isReconnecting) return; | ||
| // 如果不允许重连,或者已经在重连中,或者 Socket 还是开启状态,则跳过 | ||
| if (!this.currentParams?.reconnect || this.reconnectTimer) return; | ||
| const sessionEpoch = this.epoch; // 锁定当前的 epoch | ||
| this.isReconnecting = true; // 上锁 | ||
| this.logger.debug(`Scheduling reconnect in ${this.reconnectDelay}ms`); | ||
|
|
There was a problem hiding this comment.
scheduleReconnect() 的注释提到“Socket 还是开启状态则跳过”,但实际未检查 this.ws.readyState。更关键的是:handleError() 可能在 socket 仍处于 OPEN/CONNECTING 时触发,此时会直接 schedule reconnect,后续 connect() 会覆盖 this.ws 而不关闭旧连接,旧连接回调仍会以相同 epoch 生效,可能引发并发双连接/重复安装等竞态。建议在 scheduleReconnect 前先关闭并清理现有 ws(或仅在 readyState 为 CLOSED/CLOSING 时才重连),并确保旧回调失效。
| } | ||
|
|
||
| try { | ||
| const stableId = uuidv5(uri, CONFIG.NAMESPACE); |
| */ | ||
|
|
||
| /** | ||
| * VSCode ↔ ScriptCat 连接管理器 | ||
| * | ||
| * ⚠️ 维护者注意: | ||
| * 本类是一个「强状态 + 并发敏感」的 WebSocket 管理器。 | ||
| * 修改 epoch / timeout / cleanup 逻辑前,请完整理解事件顺序。 | ||
| */ |
There was a problem hiding this comment.
不写这堆的话不知道这个 vscode-connect.ts 是在干嘛
|
|
||
| public init(): void { | ||
| this.messageGroup.on("connect", (params: VSCodeConnectParam) => { | ||
| // this.logger.info("Received connect request", params); |
| private scheduleReconnect(): void { | ||
| if (this.isReconnecting) return; | ||
| // 如果不允许重连,或者已经在重连中,或者 Socket 还是开启状态,则跳过 | ||
| if (!this.currentParams?.reconnect || this.reconnectTimer) return; |
There was a problem hiding this comment.
可以删掉this.isReconnecting,用reconnectTimer来判断,两个有点重复冗余
[v1.3/1.4] VSCodeConnect 重构:提升 WebSocket 连接稳定性与可维护性
本次 PR 对
src/app/service/offscreen/vscode-connect.ts进行重构,目标是改善旧版在连接管理、重连逻辑与异常处理上的不稳定与难维护问题,使 VS Code 热重载流程在长时间运行与异常场景下表现更可靠。新旧对比一览
总结:旧版痛点 vs 新版价值
旧版主要问题:
新版带来的好处:
影响范围:仅限 VSCode 开发模式连接逻辑,不影响一般 ScriptCat 使用者。
测试建议
.user.js→ ScriptCat 是否即时更新