diff --git a/app/src/App.vue b/app/src/App.vue index 8465e98..85332d8 100644 --- a/app/src/App.vue +++ b/app/src/App.vue @@ -13,6 +13,22 @@ import Sidebar from '@/components/sidebar/index.vue'; import MainPanel from '@/components/main-panel/index.vue'; import { setDefaultCss } from './hook/css'; import { pinkLog } from './views/setting/util'; +import { useMessageBridge } from './api/message-bridge'; + +const { postMessage, onMessage, isConnected } = useMessageBridge(); + +// 监听所有消息 +onMessage((message) => { + console.log('Received:', message.command, message.payload); +}); + +// 发送消息 +const sendPing = () => { + postMessage({ + command: 'ping', + payload: { timestamp: Date.now() } + }); +}; onMounted(() => { @@ -22,6 +38,8 @@ onMounted(() => { }); pinkLog('OpenMCP Client 启动'); + + sendPing(); }) diff --git a/app/src/api/message-bridge.ts b/app/src/api/message-bridge.ts new file mode 100644 index 0000000..184d569 --- /dev/null +++ b/app/src/api/message-bridge.ts @@ -0,0 +1,110 @@ +import { pinkLog } from '@/views/setting/util'; +import { onUnmounted, ref } from 'vue'; + +export interface VSCodeMessage { + command: string; + payload?: unknown; + callbackId?: string; +} + +export type MessageHandler = (message: VSCodeMessage) => void; +export const acquireVsCodeApi = (window as any)['acquireVsCodeApi']; + +class MessageBridge { + private ws: WebSocket | null = null; + private handlers = new Set(); + public isConnected = ref(false); + + constructor(private wsUrl: string = 'ws://localhost:8080') { + this.init(); + } + + private init() { + // 环境检测优先级: + // 1. VS Code WebView 环境 + // 2. 浏览器 WebSocket 环境 + if (typeof acquireVsCodeApi !== 'undefined') { + this.setupVSCodeListener(); + pinkLog('当前模式:release'); + } else { + this.setupWebSocket(); + pinkLog('当前模式:debug'); + } + } + + // VS Code 环境监听 + private setupVSCodeListener() { + const vscode = acquireVsCodeApi(); + + window.addEventListener('message', (event: MessageEvent) => { + this.dispatchMessage(event.data); + }); + + this.postMessage = (message) => vscode.postMessage(message); + this.isConnected.value = true; + } + + // WebSocket 环境连接 + private setupWebSocket() { + this.ws = new WebSocket(this.wsUrl); + + this.ws.onopen = () => { + this.isConnected.value = true; + }; + + this.ws.onmessage = (event) => { + try { + const message = JSON.parse(event.data) as VSCodeMessage; + this.dispatchMessage(message); + } catch (err) { + console.error('Message parse error:', err); + } + }; + + this.ws.onclose = () => { + this.isConnected.value = false; + }; + + this.postMessage = (message) => { + if (this.ws?.readyState === WebSocket.OPEN) { + this.ws.send(JSON.stringify(message)); + } + }; + } + + private dispatchMessage(message: VSCodeMessage) { + this.handlers.forEach(handler => handler(message)); + } + + public postMessage(message: VSCodeMessage) { + throw new Error('PostMessage not initialized'); + } + + public onMessage(handler: MessageHandler) { + this.handlers.add(handler); + return () => this.handlers.delete(handler); + } + + public destroy() { + this.ws?.close(); + this.handlers.clear(); + } +} + +// 单例实例 +const messageBridge = new MessageBridge(); + +// 向外暴露一个独立函数,保证 MessageBridge 是单例的 +export function useMessageBridge() { + const bridge = messageBridge; + + onUnmounted(() => { + bridge.destroy(); + }); + + return { + postMessage: bridge.postMessage.bind(bridge), + onMessage: bridge.onMessage.bind(bridge), + isConnected: bridge.isConnected + }; +} \ No newline at end of file diff --git a/test/package-lock.json b/test/package-lock.json index 6ffc472..af23c5d 100644 --- a/test/package-lock.json +++ b/test/package-lock.json @@ -1,15 +1,20 @@ { - "name": "test", + "name": "openmcp-test-backend", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { + "name": "openmcp-test-backend", + "version": "1.0.0", + "license": "MIT", "dependencies": { "body-parser": "^1.20.3", "cors": "^2.8.5", "express": "^4.21.1", "morgan": "^1.10.0", - "pako": "^2.1.0" + "pako": "^2.1.0", + "ws": "^8.18.1" }, "devDependencies": { "@types/cors": "^2.8.17", @@ -227,9 +232,10 @@ }, "node_modules/@types/ws": { "version": "8.18.0", - "resolved": "https://registry.npmmirror.com/@types/ws/-/ws-8.18.0.tgz", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.0.tgz", "integrity": "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -1263,6 +1269,27 @@ "node": ">= 0.8" } }, + "node_modules/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/test/package.json b/test/package.json index 50d6863..6338936 100644 --- a/test/package.json +++ b/test/package.json @@ -1,4 +1,21 @@ { + "name": "openmcp-test-backend", + "version": "1.0.0", + "description": "", + "main": "dist/main.js", + "scripts": { + "dev": "ts-node src/main.ts", + "build": "tsc && cp -R src/public dist/ 2> /dev/null || :", + "build:watch": "tsc --watch", + "start": "node dist/main.js", + "start:prod": "NODE_ENV=production node dist/main.js", + "debug": "node --inspect -r ts-node/register src/main.ts", + "clean": "rm -rf dist", + "lint": "eslint src --ext .ts,.tsx", + "typecheck": "tsc --noEmit" + }, + "author": "LSTM-Kirigaya", + "license": "MIT", "devDependencies": { "@types/cors": "^2.8.17", "@types/express": "^5.0.0", @@ -14,6 +31,7 @@ "cors": "^2.8.5", "express": "^4.21.1", "morgan": "^1.10.0", - "pako": "^2.1.0" + "pako": "^2.1.0", + "ws": "^8.18.1" } } diff --git a/test/src/demo.ts b/test/src/demo.ts deleted file mode 100644 index 73a7621..0000000 --- a/test/src/demo.ts +++ /dev/null @@ -1,43 +0,0 @@ -import * as path from 'path'; -import * as fs from 'fs'; - -import { Request, Response } from 'express'; -import { showOpenViewDialog, showSaveViewDialog } from './windows'; -import * as pako from 'pako'; -import puppeteer, { LowerCasePaperFormat, PDFOptions } from 'puppeteer-core'; -import { PDFDocument } from 'pdf-lib'; - -export async function saveAsSvg(req: Request, res: Response) { - try { - const { svgBuffer, moduleName } = req.body; - const svgString = pako.ungzip(svgBuffer, { to: 'string' }); - // 询问新的路径 - const defaultFilename = moduleName + '.svg'; - const savePath = await showSaveViewDialog({ - title: 'Save As Svg', - defaultPath: path.resolve(__dirname, '../test', defaultFilename), - buttonLabel: 'Save', - filters: [ - { name: 'svg', extensions: ['svg'] }, - { name: 'All Files', extensions: ['*'] }, - ], - }); - - if (savePath) { - fs.writeFileSync(savePath, svgString); - res.send({ - savePath, - success: true - }); - } else { - res.send({ - success: false - }); - } - } catch (error) { - console.log('error happen in /save-as-svg, ' + error); - res.send({ - success: false - }); - } -} \ No newline at end of file diff --git a/test/src/main.ts b/test/src/main.ts index 4cde4cf..9f70240 100644 --- a/test/src/main.ts +++ b/test/src/main.ts @@ -1,19 +1,37 @@ -// server/src/server.ts -import express from 'express'; -import { WSServer } from './wsHandler'; +// server/wsServer.ts +import WebSocket from 'ws'; -const app = express(); -const PORT = 3000; +export interface VSCodeMessage { + command: string; + payload?: unknown; + callbackId?: string; +} -// 初始化 WebSocket 服务器 -const wsServer = new WSServer(8080); +export type MessageHandler = (message: VSCodeMessage) => void; -// HTTP 接口 -app.get('/', (req, res) => { - res.send('WebSocket Server is running'); -}); +const wss = new WebSocket.Server({ port: 8080 }); -// 启动 HTTP 服务器 -app.listen(PORT, () => { - console.log(`HTTP server running on port ${PORT}`); +wss.on('connection', (ws) => { + // 转换普通消息为 VS Code 格式 + ws.on('message', (data) => { + console.log('receive data from frontend: ' + data.toString()); + + // const rawMessage = data.toString(); + // const vscodeMessage: VSCodeMessage = { + // command: 'ws-message', + // payload: rawMessage, + // callbackId: Math.random().toString(36).slice(2) + // }; + // ws.send(JSON.stringify(vscodeMessage)); + }); + + // 连接后发送一个消息 + const vscodeMessage: VSCodeMessage = { + command: 'ws-message', + payload: { + text: 'connection completed' + }, + callbackId: Math.random().toString(36).slice(2) + }; + ws.send(JSON.stringify(vscodeMessage)); }); \ No newline at end of file