-
Notifications
You must be signed in to change notification settings - Fork 308
[v1.3?] VSCodeConnect 代码优化 #947
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
cyfung1031
wants to merge
8
commits into
scriptscat:release/v1.3
Choose a base branch
from
cyfung1031:pr-vscodeConnect-001
base: release/v1.3
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
a849db0
VSCodeConnect 代码优化
cyfung1031 41f5486
调整一下代码风格
cyfung1031 064cd30
Merge branch 'release/v1.3' into pr-vscodeConnect-001
cyfung1031 9a9e9b6
统一接口返回值
cyfung1031 ee6afa2
代码调整
cyfung1031 a28102e
Service 应该在构造函数中接收 Group 对象作为依赖注入
cyfung1031 ecde7d2
代码调整
cyfung1031 bb17bb1
vsCodeConnect -> vscodeConnect
cyfung1031 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,103 +1,203 @@ | ||
| import LoggerCore from "@App/app/logger/core"; | ||
| import Logger from "@App/app/logger/logger"; | ||
| import { type Group } from "@Packages/message/server"; | ||
| import type { Group } from "@Packages/message/server"; | ||
| import type { MessageSend } from "@Packages/message/types"; | ||
| import { ScriptClient } from "../service_worker/client"; | ||
| import { v5 as uuidv5 } from "uuid"; | ||
|
|
||
| // 在offscreen下与scriptcat-vscode建立websocket连接 | ||
| // 需要在vscode中安装scriptcat-vscode插件 | ||
| /* ---------- 类型定义 ---------- */ | ||
| export type VSCodeConnectParam = { url: string; reconnect: boolean }; // 连接参数:WebSocket地址和是否自动重连 | ||
|
|
||
| /** 从VSCode WebSocket接收的动作类型 */ | ||
| enum VSCodeAction { | ||
| Hello = "hello", // VSCode问候消息 | ||
| OnChange = "onchange", // 文件变更通知 | ||
| } | ||
|
|
||
| class WebSocketExtended extends WebSocket { | ||
| _handlers: Record<string, (...args: any) => void> = {}; | ||
| _isConnected: boolean = false; | ||
| addEventListeners() { | ||
| for (const [eventName, handler] of Object.entries(this._handlers)) { | ||
| this.addEventListener(eventName, handler); | ||
| } | ||
| } | ||
| removeEventListeners() { | ||
| for (const [eventName, handler] of Object.entries(this._handlers)) { | ||
| this.removeEventListener(eventName, handler); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /* ---------- 主类 ---------- */ | ||
| // 在offscreen文档中与scriptcat-vscode插件建立WebSocket连接 | ||
| // 前提:VSCode需安装scriptcat-vscode扩展 | ||
| export class VSCodeConnect { | ||
| logger: Logger = LoggerCore.logger().with({ service: "VSCodeConnect" }); | ||
| private readonly logger: Logger = LoggerCore.logger().with({ service: "VSCodeConnect" }); | ||
|
|
||
| reconnect: boolean = false; | ||
| private ws: WebSocketExtended | undefined; // 当前WebSocket实例 | ||
|
|
||
| wsConnect: WebSocket | undefined; | ||
| private timerId: ReturnType<typeof setTimeout> | undefined; // 连接超时定时器 | ||
|
|
||
| connectVSCodeTimer: any; | ||
| private readonly scriptClient: ScriptClient; // 用于安装脚本的客户端 | ||
|
|
||
| scriptClient: ScriptClient; | ||
| private readonly vscodeConnectGroup: Group; // 消息分组,用于接收连接指令 | ||
|
|
||
| constructor( | ||
| private group: Group, | ||
| private msgSender: MessageSend | ||
| ) { | ||
| this.scriptClient = new ScriptClient(this.msgSender); | ||
| private mParam: VSCodeConnectParam | undefined; | ||
|
|
||
| constructor(vscodeConnectGroup: Group, msgSender: MessageSend) { | ||
| this.vscodeConnectGroup = vscodeConnectGroup; | ||
| this.scriptClient = new ScriptClient(msgSender); | ||
| } | ||
|
|
||
| connect({ url, reconnect }: { url: string; reconnect: boolean }) { | ||
| // 如果已经连接,断开重连 | ||
| if (this.wsConnect) { | ||
| this.wsConnect.close(); | ||
| } | ||
| // 清理老的定时器 | ||
| if (this.connectVSCodeTimer) { | ||
| clearInterval(this.connectVSCodeTimer); | ||
| this.connectVSCodeTimer = undefined; | ||
| } | ||
| const handler = () => { | ||
| if (!this.wsConnect) { | ||
| return this.connectVSCode({ url }); | ||
| } | ||
| return Promise.resolve(); | ||
| }; | ||
| if (reconnect) { | ||
| this.connectVSCodeTimer = setInterval(() => { | ||
| handler(); | ||
| }, 30 * 1000); | ||
| } | ||
| return handler(); | ||
| /** 初始化消息监听 */ | ||
| init() { | ||
| this.vscodeConnectGroup.on("connect", (param: VSCodeConnectParam) => this.connect(param)); | ||
| } | ||
| doReconnect(): void { | ||
| this.clearTimer(); | ||
| this.closeExisting(); // 如果已经连接,先关闭已有连接 | ||
| // 旧连接已清除 | ||
| this.timerId = setTimeout(() => this.connectVSCode(), 100); // 稍后重试 | ||
| } | ||
|
|
||
| // 连接到vscode | ||
| connectVSCode({ url }: { url: string }) { | ||
| connectVSCode(): Promise<void> { | ||
| const { url, reconnect } = this.mParam!; // 在初次连接 / 重连接时,取最后 mParam 的值。 | ||
| return new Promise<void>((resolve, reject) => { | ||
| // 如果已经连接,断开重连 | ||
| if (this.wsConnect) { | ||
| this.wsConnect.close(); | ||
| if (this.ws) { | ||
| this.logger.debug("unexpected error: vscode was connected."); | ||
| reject("vscode was connected"); | ||
| return; | ||
| } | ||
| try { | ||
| this.wsConnect = new WebSocket(url); | ||
| this.ws = new WebSocketExtended(url); | ||
| } catch (e: any) { | ||
| this.logger.debug("connect vscode faild", Logger.E(e)); | ||
| this.logger.debug("connect vscode failed", Logger.E(e)); // 连接VSCode失败 | ||
| reject(e); | ||
| return; | ||
| } | ||
| let ok = false; | ||
| this.wsConnect.addEventListener("open", () => { | ||
| this.wsConnect!.send('{"action":"hello"}'); | ||
| ok = true; | ||
| resolve(); | ||
| }); | ||
| this.wsConnect.addEventListener("message", async (ev) => { | ||
| const data = JSON.parse(ev.data); | ||
| switch (data.action) { | ||
| case "onchange": { | ||
| // 调用安装脚本接口 | ||
| const code = data.data.script; | ||
| this.scriptClient.installByCode(uuidv5(data.data.uri, uuidv5.URL), code, "vscode"); | ||
| break; | ||
| this.ws._handlers = { | ||
| open: () => { | ||
| if (this.ws) { | ||
| this.clearTimer(); // 已触发 open, 清除30秒超时器 | ||
| this.ws.send('{"action":"hello"}'); // 发送问候 | ||
| this.ws._isConnected = true; | ||
| resolve(); | ||
| } | ||
| default: | ||
| } | ||
| }); | ||
| }, | ||
| message: (ev: MessageEvent) => { | ||
| if (this.ws) { | ||
| this.handleMessage(ev).catch((err) => { | ||
| this.logger.error("message handler error", Logger.E(err)); // 处理消息出错 | ||
| }); | ||
| } | ||
| }, | ||
| error: (e: Event) => { | ||
| if (this.ws) { | ||
| const connectOK = this.ws._isConnected; // 已触发 open | ||
| this.clearTimer(); // 已触发 error, 清除30秒超时器 | ||
| this.ws.removeEventListeners(); | ||
| this.ws = undefined; // error / close / timeout 时清除 this.ws | ||
| this.logger.debug("connect vscode failed", Logger.E(e)); // 连接错误 | ||
| if (!connectOK) { | ||
| // 未触发 open | ||
| reject(new Error("connect fail before open")); | ||
| } | ||
| if (reconnect) this.doReconnect(); | ||
| } | ||
| }, | ||
| close: () => { | ||
| if (this.ws) { | ||
| const connectOK = this.ws._isConnected; // 已触发 open | ||
| this.clearTimer(); // 已触发 close, 清除30秒超时器 | ||
| this.ws.removeEventListeners(); | ||
| this.ws = undefined; // error / close / timeout 时清除 this.ws | ||
| this.logger.debug("vscode connection closed"); // VSCode连接已关闭 | ||
| if (!connectOK) { | ||
| // 未触发 open | ||
| reject(new Error("connect closed before open")); | ||
| } | ||
| if (reconnect) this.doReconnect(); | ||
| } | ||
| }, | ||
| }; | ||
| this.ws.addEventListeners(); | ||
|
|
||
| this.wsConnect.addEventListener("error", (e) => { | ||
| this.wsConnect = undefined; | ||
| this.logger.debug("connect vscode faild", Logger.E(e)); | ||
| if (!ok) { | ||
| reject(new Error("connect fail")); | ||
| // 30秒超时处理: 如 open, close, error 都不发生,30 秒后reject | ||
| this.clearTimer(); | ||
| this.timerId = setTimeout(() => { | ||
| if (!this.ws) { | ||
| this.logger.debug("unexpected error: vscode connection is undefined."); | ||
| return; | ||
| } | ||
| }); | ||
|
|
||
| this.wsConnect.addEventListener("close", () => { | ||
| this.wsConnect = undefined; | ||
| this.logger.debug("vscode connection closed"); | ||
| }); | ||
| if (this.ws?._isConnected) { | ||
| this.logger.debug("unexpected error: vscode was connected."); | ||
| return; | ||
| } | ||
| this.ws.removeEventListeners(); // 浏览器触发的 close 动作不需要消息处理 | ||
| try { | ||
| this.ws.close(); | ||
| } catch (e) { | ||
| console.error(e); | ||
| } | ||
| this.ws = undefined; // error / close / timeout 时清除 this.ws | ||
| this.logger.debug("vscode connection timeout"); // VSCode连接Timeout | ||
| reject(new Error("Timeout")); | ||
| if (reconnect) this.doReconnect(); | ||
| }, 30_000); | ||
| }); | ||
| } | ||
|
|
||
| init() { | ||
| this.group.on("connect", this.connect.bind(this)); | ||
| /* ---------- 公共方法 ---------- */ | ||
| /** 连接(或重连)到VSCode的WebSocket服务 */ | ||
| public connect({ url, reconnect }: VSCodeConnectParam): Promise<void> { | ||
| this.mParam = { url, reconnect }; | ||
| this.clearTimer(); // 清理老的定时器 | ||
| this.closeExisting(); // 如果已经连接,连接前先关闭旧连接 | ||
| // 旧连接已清除 | ||
| return this.connectVSCode(); | ||
| } | ||
|
|
||
| /* ---------- 消息处理 ---------- */ | ||
| /** 处理从VSCode收到的消息 */ | ||
| private async handleMessage(ev: MessageEvent): Promise<void> { | ||
| let data: any; | ||
| const evData = ev.data; | ||
| if (typeof evData !== "string") return; | ||
| try { | ||
| data = JSON.parse(evData); | ||
| } catch { | ||
| return; // 忽略格式错误的JSON | ||
| } | ||
| switch (data.action as VSCodeAction) { | ||
| case VSCodeAction.OnChange: { | ||
| // 当VSCode通知脚本文件变更时,自动安装/更新脚本 | ||
| const { script, uri } = data.data; | ||
| const id = uuidv5(uri, uuidv5.URL); // 用uri生成稳定脚本ID | ||
| await this.scriptClient.installByCode(id, script, "vscode"); | ||
| break; | ||
| } | ||
| default: | ||
| // 忽略未知动作 | ||
| } | ||
| } | ||
|
|
||
| /* ---------- 辅助方法 ---------- */ | ||
| /** 关闭已有WebSocket连接 */ | ||
| private closeExisting(): void { | ||
| this.ws?.removeEventListeners(); // 浏览器触发的 close 动作不需要消息处理 | ||
| try { | ||
| this.ws?.close(); | ||
| } catch (e: any) { | ||
| console.error(e); | ||
| } | ||
| this.ws = undefined; | ||
| } | ||
| /** 清除超时定时器 */ | ||
| private clearTimer(): void { | ||
| if (this.timerId) { | ||
| clearTimeout(this.timerId); | ||
cyfung1031 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| this.timerId = undefined; | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.