feat: update WebSocket connection and add system version sending functionality

- Changed WebSocket connection URL to localhost for testing.
- Added a function to send system version to the server upon connection and after a delay.
- Improved logging for received messages.

chore: update package dependencies

- Bump versions for several dependencies including @kevisual/query, lucide-react, vue, and others.
- Update package manager version to pnpm@10.26.1.

fix: adjust app initialization to use sessionStorage

- Modified the app initialization to use a custom sessionStorage implementation backed by an LRU cache.

feat: implement sessionStorage with LRU cache

- Added a new module for sessionStorage that utilizes LRUCache for efficient caching.
This commit is contained in:
2025-12-21 04:20:25 +08:00
parent b3c5e7d68d
commit 36628c8279
16 changed files with 323 additions and 112 deletions

View File

@@ -10,7 +10,7 @@
],
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
"license": "MIT",
"packageManager": "pnpm@10.26.0",
"packageManager": "pnpm@10.26.1",
"type": "module",
"files": [
"dist",
@@ -46,12 +46,12 @@
"@kevisual/load": "^0.0.6",
"@kevisual/local-app-manager": "^0.1.32",
"@kevisual/logger": "^0.0.4",
"@kevisual/query": "0.0.32",
"@kevisual/query": "0.0.33",
"@kevisual/query-login": "0.0.7",
"@kevisual/router": "^0.0.39",
"@kevisual/router": "^0.0.47",
"@kevisual/types": "^0.0.10",
"@kevisual/use-config": "^1.0.21",
"@types/bun": "^1.3.4",
"@types/bun": "^1.3.5",
"@types/lodash-es": "^4.17.12",
"@types/node": "^25.0.3",
"@types/send": "^1.2.1",

View File

@@ -41,7 +41,8 @@ export const app: App = useContextKey<App>('app', () => {
httpType: 'http',
cors: {
origin: '*',
}
},
io: true
},
});
}
@@ -54,7 +55,8 @@ export const app: App = useContextKey<App>('app', () => {
httpsKey: httpsPem.key,
cors: {
origin: '*',
}
},
io: true
},
});
});

View File

@@ -2,6 +2,7 @@ import http from 'node:http';
import https from 'node:https';
import { rewriteCookieDomain } from '../https/cookie-rewrite.ts';
import { ProxyInfo } from './proxy.ts';
import { pipeStream } from './pipe.ts';
export const defaultApiProxy = [
{
path: '/api/router',
@@ -109,5 +110,6 @@ export const httpProxy = (req: http.IncomingMessage, res: http.ServerResponse, p
});
// 处理 POST 请求的请求体(传递数据到目标服务器),end:true 表示当请求体结束时,关闭请求
req.pipe(proxyReq, { end: true });
return;
};
// @ts-ignore
// pipeStream(proxyReq, res);
}

View File

@@ -1,5 +1,4 @@
export * from './proxy.ts';
export * from './file-proxy.ts';
export { default as send } from 'send';
export * from './http-proxy.ts';
export * from './ws-proxy.ts';
export * from './http-proxy.ts';

View File

@@ -0,0 +1,19 @@
import * as http from 'http';
import * as fs from 'fs';
import { isBun } from './utils.ts';
export const pipeFileStream = (filePath: string, res: http.ServerResponse) => {
const readStream = fs.createReadStream(filePath);
if (isBun) {
res.pipe(readStream as any);
} else {
readStream.pipe(res, { end: true });
}
}
export const pipeStream = (readStream: fs.ReadStream, res: http.ServerResponse) => {
if (isBun) {
res.pipe(readStream as any);
} else {
readStream.pipe(res, { end: true });
}
}

View File

@@ -0,0 +1,6 @@
export const isBun = typeof Bun !== 'undefined' && Bun?.version != null;
export const isNode = typeof process !== 'undefined' && process?.versions != null && process.versions?.node != null;
// @ts-ignore
export const isDeno = typeof Deno !== 'undefined' && Deno?.version != null && Deno?.version?.deno != null;

View File

@@ -4,6 +4,7 @@ import type { Http2Server } from 'node:http2';
import { WebSocket } from 'ws';
import { ProxyInfo } from './proxy.ts';
import { WebSocketServer } from 'ws';
import { isBun, isNode } from './utils.ts';
export const wss = new WebSocketServer({
noServer: true,
@@ -97,7 +98,13 @@ export const wsProxy = (server: HttpServer | HttpsServer | Http2Server, config:
await wssApp.handleConnection(ws, req);
});
// 处理升级请求
server.on('upgrade', (request, socket, head) => {
wssApp.upgrade(request, socket, head);
});
if (!isBun) {
server.on('upgrade', (request, socket, head) => {
wssApp.upgrade(request, socket, head);
});
} else if (isBun) {
// @ts-ignore
console.warn('Bun WebSocket proxy is not implemented yet.');
console.log('server', server)
}
};

View File

@@ -38,8 +38,12 @@ export const runServer = async (port: number = 51015, listenPath = '127.0.0.1')
console.log(`Server is running on ${protocol}://${listenPath}:${_port}`);
});
}
app.server.on(proxyRoute);
proxyWs();
app.server.on([{
id: 'all',
func: proxyRoute as any
},
...proxyWs()
]);
const manager = new AssistantApp(assistantConfig, app);
setTimeout(() => {
manager.load({ runtime: 'client' }).then(() => {

View File

@@ -1,10 +1,12 @@
import { fileProxy, httpProxy, createApiProxy, wsProxy } from '@/module/assistant/index.ts';
import { fileProxy, httpProxy, createApiProxy, ProxyInfo } from '@/module/assistant/index.ts';
import http from 'node:http';
import { LocalProxy } from './local-proxy.ts';
import { assistantConfig, app } from '@/app.ts';
import { log, logger } from '@/module/logger.ts';
import { getToken } from '@/module/http-token.ts';
import { getTokenUserCache } from '@/routes/index.ts';
import type { WebSocketListenerFun } from "@kevisual/router";
import WebSocket from 'ws';
const localProxy = new LocalProxy({});
localProxy.initFromAssistantConfig(assistantConfig);
@@ -175,8 +177,63 @@ export const proxyWs = () => {
const apiProxy = assistantConfig.getCacheAssistantConfig()?.api?.proxy || [];
const proxy = assistantConfig.getCacheAssistantConfig()?.proxy || [];
const proxyApi = [...apiProxy, ...proxy].filter((item) => item.ws);
log.debug('proxyApi ', proxyApi);
wsProxy(app.server.server, {
apiList: proxyApi,
});
const demoProxy = [
{
path: '/api/ws/demo',
target: 'https://kevisual.xiongxiao.me',
pathname: '/api/router',
ws: true,
}
]
return proxyApi.map(createProxyInfo);
};
export const createProxyInfo = (proxyApiItem: ProxyInfo) => {
const func: WebSocketListenerFun = async (req, res) => {
const { ws, emitter, id, data } = req;
if (!id) {
ws.send(JSON.stringify({ type: 'error', message: 'not found id' }));
ws.close();
return;
}
// @ts-ignore
let _proxySocket = ws.data.proxySocket;
if (!_proxySocket) {
const _u = new URL(proxyApiItem.path, `${proxyApiItem.target}`);
if (proxyApiItem.pathname) {
_u.pathname = proxyApiItem.pathname;
}
const isHttps = _u.protocol === 'https:';
const wsProtocol = isHttps ? 'wss' : 'ws';
const wsUrl = `${wsProtocol}://${_u.host}${_u.pathname}`;
console.log('WebSocket proxy URL', { wsUrl });
const proxySocket = new WebSocket(wsUrl);
proxySocket.on('open', () => {
proxySocket.on('message', (message) => {
ws.send(message);
});
});
proxySocket.on('error', (err) => {
console.error(`WebSocket proxy error: ${err.message}`);
});
proxySocket.on('close', () => {
console.log('WebSocket proxy closed');
});
emitter.once('close--' + id, () => {
console.log('WebSocket client closed');
proxySocket?.close?.();
});
// @ts-ignore
ws.data.proxySocket = proxySocket;
return;
}
console.log('ws.data', data);
_proxySocket.send(JSON.stringify(data))
}
return {
path: proxyApiItem.path,
io: true,
func: func
}
}

View File

@@ -5,11 +5,20 @@ const testRouter = () => {
// const ws = new WebSocket('wss://kevisual.xiongxiao.me/ws/proxy');
// const ws = new WebSocket('wss://kevisual.xiongxiao.me/api/router');
const ws = new WebSocket('ws://118.196.32.29:3005/api/router');
// const ws = new WebSocket('ws://118.196.32.29:3005/api/router');
// const ws = new WebSocket('wss://kevisual.cn/api/router');
const ws = new WebSocket('ws://localhost:51015/api/ws/demo?id=12345');
console.log('Connecting to WebSocket server...');
ws.on('open', () => {
console.log('WebSocket connection opened');
sendSystemVersion();
setTimeout(() => {
sendSystemVersion();
console.log('第二次发送');
}, 3000);
});
const sendSystemVersion = () => {
ws.send(
JSON.stringify({
"type": "router",
@@ -19,10 +28,10 @@ const testRouter = () => {
}
}),
);
});
}
ws.on('message', (data) => {
console.log('Received message from server:', data.toString());
console.log('Received message from server e:', data.toString());
});
ws.on('close', () => {
console.log('WebSocket connection closed');