diff --git a/.gitignore b/.gitignore index eae58d7..9c55a6c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ cache-file /apps -logs \ No newline at end of file +logs +root/ diff --git a/.npmrc b/.npmrc index 7446745..0a2c6cd 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1,3 @@ //npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN} -//registry.npmjs.org/:_authToken=${NPM_TOKEN} \ No newline at end of file +//registry.npmjs.org/:_authToken=${NPM_TOKEN} +ignore-workspace-root-check=true \ No newline at end of file diff --git a/assistant-module/.npmrc b/assistant-module/.npmrc new file mode 100644 index 0000000..7446745 --- /dev/null +++ b/assistant-module/.npmrc @@ -0,0 +1,2 @@ +//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN} +//registry.npmjs.org/:_authToken=${NPM_TOKEN} \ No newline at end of file diff --git a/assistant-module/package.json b/assistant-module/package.json new file mode 100644 index 0000000..ed2f6c1 --- /dev/null +++ b/assistant-module/package.json @@ -0,0 +1,49 @@ +{ + "name": "@kevisual/assistant-module", + "version": "0.0.3", + "description": "assistant module", + "main": "dist/assistant-module.mjs", + "types": "dist/assistant-module.d.ts", + "scripts": { + "dev": "rollup -c -w", + "build": "npm run clean && rollup -c", + "clean": "rm -rf dist" + }, + "keywords": [], + "author": "abearxiong ", + "license": "MIT", + "type": "module", + "devDependencies": { + "@types/node": "^22.13.10", + "@types/send": "^0.17.4", + "@types/ws": "^8.18.0" + }, + "publishConfig": { + "access": "public" + }, + "files": [ + "dist" + ], + "exports": { + ".": { + "import": "./dist/assistant-module.mjs", + "types": "./dist/assistant-module.d.ts" + }, + "./proxy": { + "import": "./dist/assistant-proxy.mjs", + "types": "./dist/assistant-proxy.d.ts" + }, + "./assistant-config": { + "import": "./dist/assistant-config.mjs", + "types": "./dist/assistant-config.d.ts" + }, + "./assistant-process": { + "import": "./dist/assistant-process.mjs", + "types": "./dist/assistant-process.d.ts" + } + }, + "dependencies": { + "send": "^1.1.0", + "ws": "^8.18.1" + } +} \ No newline at end of file diff --git a/assistant-module/pnpm-lock.yaml b/assistant-module/pnpm-lock.yaml new file mode 100644 index 0000000..9491809 --- /dev/null +++ b/assistant-module/pnpm-lock.yaml @@ -0,0 +1,29 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@types/node': + specifier: ^22.13.10 + version: 22.13.10 + +packages: + + '@types/node@22.13.10': + resolution: {integrity: sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==} + + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + +snapshots: + + '@types/node@22.13.10': + dependencies: + undici-types: 6.20.0 + + undici-types@6.20.0: {} diff --git a/assistant-module/rollup.config.mjs b/assistant-module/rollup.config.mjs new file mode 100644 index 0000000..bbce792 --- /dev/null +++ b/assistant-module/rollup.config.mjs @@ -0,0 +1,172 @@ +import resolve from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import json from '@rollup/plugin-json'; +import path from 'path'; +import esbuild from 'rollup-plugin-esbuild'; +import alias from '@rollup/plugin-alias'; +import replace from '@rollup/plugin-replace'; +import dts from 'rollup-plugin-dts'; +// @ts-ignore +import pkgs from './package.json' with {type: 'json'}; + +const isDev = process.env.NODE_ENV === 'development'; +const input = './src/index.ts'; +/** + * @type {import('rollup').RollupOptions} + */ +const config = { + input, + output: { + dir: './dist', + entryFileNames: 'assistant-module.mjs', + chunkFileNames: '[name]-[hash].mjs', + format: 'esm', + }, + plugins: [ + replace({ + preventAssignment: true, // 防止意外赋值 + DEV_SERVER: JSON.stringify(isDev), // 替换 process.env.NODE_ENV + VERSION: JSON.stringify(pkgs.version), + }), + alias({ + // only esbuild needs to be configured + entries: [ + { find: '@', replacement: path.resolve('src') }, // 配置 @ 为 src 目录 + { find: 'http', replacement: 'node:http' }, + { find: 'https', replacement: 'node:https' }, + { find: 'fs', replacement: 'node:fs' }, + { find: 'path', replacement: 'node:path' }, + { find: 'crypto', replacement: 'node:crypto' }, + { find: 'zlib', replacement: 'node:zlib' }, + { find: 'stream', replacement: 'node:stream' }, + { find: 'net', replacement: 'node:net' }, + { find: 'tty', replacement: 'node:tty' }, + { find: 'tls', replacement: 'node:tls' }, + { find: 'buffer', replacement: 'node:buffer' }, + { find: 'timers', replacement: 'node:timers' }, + // { find: 'string_decoder', replacement: 'node:string_decoder' }, + { find: 'dns', replacement: 'node:dns' }, + { find: 'domain', replacement: 'node:domain' }, + { find: 'os', replacement: 'node:os' }, + { find: 'events', replacement: 'node:events' }, + { find: 'url', replacement: 'node:url' }, + { find: 'assert', replacement: 'node:assert' }, + { find: 'util', replacement: 'node:util' }, + ], + }), + resolve({ + preferBuiltins: true, // 强制优先使用内置模块 + }), + commonjs(), + esbuild({ + target: 'node22', // + minify: false, // 启用代码压缩 + tsconfig: 'tsconfig.json', + }), + json(), + ], + external: [ + /@kevisual\/router(\/.*)?/, //, // 路由 + /@kevisual\/use-config(\/.*)?/, // + ], +}; +const dtsConfig = [{ + input, + output: { + file: 'dist/assistant-module.d.ts', + format: 'esm', + }, + plugins: [dts()], +}]; + +const moduleConfig = { + input: './src/assistant-proxy.ts', + output: { + file: 'dist/assistant-proxy.mjs', + format: 'esm', + }, + plugins: [ + alias({ + entries: [{ find: '@', replacement: path.resolve('src') }], + }), + resolve({ + preferBuiltins: true, // 强制优先使用内置模块 + }), + commonjs(), + esbuild({ + target: 'node22', // + }), + json(), + ], + +}; +const moduleDtsConfig = [{ + input: './src/assistant-proxy.ts', + output: { + file: 'dist/assistant-proxy.d.ts', + format: 'esm', + }, + plugins: [dts()], +}]; + +const assistantConfigConfig = { + input: './src/assistant-config.ts', + output: { + file: 'dist/assistant-config.mjs', + format: 'esm', + }, + plugins: [ + alias({ + entries: [{ find: '@', replacement: path.resolve('src') }], + }), + resolve({ + preferBuiltins: true, // 强制优先使用内置模块 + }), + commonjs(), + esbuild({ + target: 'node22', // + }), + json(), + ], +}; +const assistantConfigDtsConfig = [{ + input: './src/assistant-config.ts', + output: { + file: 'dist/assistant-config.d.ts', + format: 'esm', + }, + plugins: [dts()], +}]; + +const assistantProcessConfig = { + input: './src/assistant-process.ts', + output: { + file: 'dist/assistant-process.mjs', + format: 'esm', + }, + plugins: [ + alias({ + entries: [{ find: '@', replacement: path.resolve('src') }], + }), + resolve({ + preferBuiltins: true, // 强制优先使用内置模块 + }), + commonjs(), + esbuild({ + target: 'node22', // + }), + json(), + ], +}; +const assistantProcessDtsConfig = [{ + input: './src/assistant-process.ts', + output: { + file: 'dist/assistant-process.d.ts', + format: 'esm', + }, + plugins: [dts()], +}]; + + + +export default [config, ...dtsConfig, moduleConfig, ...moduleDtsConfig, assistantConfigConfig, ...assistantConfigDtsConfig, assistantProcessConfig, ...assistantProcessDtsConfig]; \ No newline at end of file diff --git a/assistant-module/src/assistant-config.ts b/assistant-module/src/assistant-config.ts new file mode 100644 index 0000000..29a6f01 --- /dev/null +++ b/assistant-module/src/assistant-config.ts @@ -0,0 +1 @@ +export * from './config/index.ts'; diff --git a/assistant-module/src/assistant-process.ts b/assistant-module/src/assistant-process.ts new file mode 100644 index 0000000..b40110a --- /dev/null +++ b/assistant-module/src/assistant-process.ts @@ -0,0 +1 @@ +export * from './process/index.ts'; \ No newline at end of file diff --git a/assistant-module/src/assistant-proxy.ts b/assistant-module/src/assistant-proxy.ts new file mode 100644 index 0000000..14eea5d --- /dev/null +++ b/assistant-module/src/assistant-proxy.ts @@ -0,0 +1 @@ +export * from './proxy/index.ts'; diff --git a/assistant-module/src/config/index.ts b/assistant-module/src/config/index.ts new file mode 100644 index 0000000..4cbf773 --- /dev/null +++ b/assistant-module/src/config/index.ts @@ -0,0 +1,111 @@ +import path from 'path'; +import { homedir } from 'os'; +import fs from 'fs'; +import { checkFileExists, createDir } from '../file/index.ts'; +import { ProxyInfo } from '../proxy/proxy.ts'; + +export const kevisualUrl = 'https://kevisual.xiongxiao.me'; +const configDir = createDir(path.join(homedir(), '.config/envision')); +export const configPath = path.join(configDir, 'assistant-config.json'); +export const appConfigPath = path.join(configDir, 'assistant-app-config.json'); +export const appDir = createDir(path.join(configDir, 'assistant-app/frontend')); +export const appPidPath = path.join(configDir, 'assistant-app.pid'); +export const LocalElectronAppUrl = 'https://assistant.app/user/tiptap/'; + +type AssistantConfig = { + pageApi?: string; // https://kevisual.silkyai.cn + loadURL?: string; // https://assistant.app/user/tiptap/ + proxy?: { user: string; key: string; path: string }[]; + apiProxyList?: ProxyInfo[]; +}; +let assistantConfig: AssistantConfig; +export const getConfig = () => { + try { + if (!checkFileExists(configPath)) { + fs.writeFileSync(configPath, JSON.stringify({ proxy: [] }, null, 2)); + return { + loadURL: LocalElectronAppUrl, + pageApi: '', + proxy: [], + }; + } + assistantConfig = JSON.parse(fs.readFileSync(configPath, 'utf8')); + return assistantConfig; + } catch (error) { + console.error(error); + return { + loadURL: LocalElectronAppUrl, + pageApi: '', + proxy: [], + }; + } +}; +export const getCacheAssistantConfig = () => { + if (assistantConfig) { + return assistantConfig; + } + return getConfig(); +}; + +export const setConfig = (config?: AssistantConfig) => { + if (!config) { + return assistantConfig; + } + assistantConfig = config; + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); + return assistantConfig; +}; +type AppConfig = { + list: any[]; +}; +/** + * 应用配置 + * @returns + */ +export const getAppConfig = (): AppConfig => { + if (!checkFileExists(appConfigPath)) { + return { + list: [], + }; + } + return JSON.parse(fs.readFileSync(appConfigPath, 'utf8')); +}; + +export const setAppConfig = (config: AppConfig) => { + fs.writeFileSync(appConfigPath, JSON.stringify(config, null, 2)); + return config; +}; + +export const addAppConfig = (app: any) => { + const config = getAppConfig(); + const assistantConfig = getCacheAssistantConfig(); + const _apps = config.list; + const _proxy = assistantConfig.proxy || []; + const { user, key } = app; + const newProxyInfo = { + user, + key, + path: `/${user}/${key}`, + }; + const _proxyIndex = _proxy.findIndex((_proxy: any) => _proxy.path === newProxyInfo.path); + if (_proxyIndex !== -1) { + _proxy[_proxyIndex] = newProxyInfo; + } else { + _proxy.push(newProxyInfo); + } + + const _app = _apps.findIndex((_app: any) => _app.id === app.id); + if (_app !== -1) { + _apps[_app] = app; + } else { + _apps.push(app); + } + setAppConfig({ ...config, list: _apps }); + setConfig({ ...assistantConfig, proxy: _proxy }); + return config; +}; + +export const getAppList = () => { + const config = getAppConfig(); + return config.list || []; +}; diff --git a/assistant-module/src/file/index.ts b/assistant-module/src/file/index.ts new file mode 100644 index 0000000..f1f45bb --- /dev/null +++ b/assistant-module/src/file/index.ts @@ -0,0 +1,20 @@ +import fs from 'fs'; + +export const checkFileExists = (filePath: string, checkIsFile = false) => { + try { + fs.accessSync(filePath); + if (checkIsFile) { + return fs.statSync(filePath).isFile(); + } + return true; + } catch (error) { + return false; + } +}; + +export const createDir = (dirPath: string) => { + if (!checkFileExists(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } + return dirPath; +}; diff --git a/assistant-module/src/index.ts b/assistant-module/src/index.ts new file mode 100644 index 0000000..fb5cf55 --- /dev/null +++ b/assistant-module/src/index.ts @@ -0,0 +1,2 @@ +export * from './install/index.ts'; +export * from './config/index.ts'; \ No newline at end of file diff --git a/assistant-module/src/install/index.ts b/assistant-module/src/install/index.ts new file mode 100644 index 0000000..48981e3 --- /dev/null +++ b/assistant-module/src/install/index.ts @@ -0,0 +1,127 @@ +import path from 'path'; +import fs from 'fs'; + +type DownloadTask = { + downloadPath: string; + downloadUrl: string; + user: string; + key: string; + version: string; +}; +export type Package = { + id: string; + name?: string; + version?: string; + description?: string; + title?: string; + user?: string; + key?: string; + [key: string]: any; +}; +type InstallAppOpts = { + appDir?: string; + kevisualUrl?: string; + /** + * 是否是客户端, 下载到 assistant-config的下面 + */ +}; +export const installApp = async (app: Package, opts: InstallAppOpts = {}) => { + // const _app = demoData; + const { appDir = '', kevisualUrl = 'https://kevisual.cn' } = opts; + const _app = app; + try { + let files = _app.data.files || []; + const version = _app.version; + const user = _app.user; + const key = _app.key; + + const downFiles = files.map((file: any) => { + const noVersionPath = file.path.replace(`/${version}`, ''); + return { + ...file, + downloadPath: path.join(appDir, noVersionPath), + downloadUrl: `${kevisualUrl}/${noVersionPath}`, + }; + }); + const downloadTasks: DownloadTask[] = downFiles as any; + for (const file of downloadTasks) { + const downloadPath = file.downloadPath; + const downloadUrl = file.downloadUrl; + const dir = path.dirname(downloadPath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + const res = await fetch(downloadUrl); + const blob = await res.blob(); + fs.writeFileSync(downloadPath, Buffer.from(await blob.arrayBuffer())); + } + let indexHtml = files.find((file: any) => file.name === 'index.html'); + if (!indexHtml) { + files.push({ + name: 'index.html', + path: `${user}/${key}/index.html`, + }); + fs.writeFileSync(path.join(appDir, `${user}/${key}/index.html`), JSON.stringify(app, null, 2)); + } + _app.data.files = files; + return { + code: 200, + data: _app, + message: 'Install app success', + }; + } catch (error) { + console.error(error); + return { + code: 500, + message: 'Install app failed', + }; + } +}; +export const checkAppDir = (appDir: string) => { + const files = fs.readdirSync(appDir); + if (files.length === 0) { + fs.rmSync(appDir, { recursive: true }); + } +}; +export const checkFileExists = (path: string) => { + try { + fs.accessSync(path); + return true; + } catch (error) { + return false; + } +}; +type UninstallAppOpts = { + appDir?: string; +}; +export const uninstallApp = async (app: Partial, opts: UninstallAppOpts = {}) => { + const { appDir = '' } = opts; + try { + const { user, key } = app; + const keyDir = path.join(appDir, user, key); + const parentDir = path.join(appDir, user); + if (!checkFileExists(appDir) || !checkFileExists(keyDir)) { + return { + code: 200, + message: 'uninstall app success', + }; + } + try { + // 删除appDir和文件 + fs.rmSync(keyDir, { recursive: true }); + } catch (error) { + console.error(error); + } + checkAppDir(parentDir); + return { + code: 200, + message: 'Uninstall app success', + }; + } catch (error) { + console.error(error); + return { + code: 500, + message: 'Uninstall app failed', + }; + } +}; diff --git a/assistant-module/src/process/index.ts b/assistant-module/src/process/index.ts new file mode 100644 index 0000000..93aa0f2 --- /dev/null +++ b/assistant-module/src/process/index.ts @@ -0,0 +1,70 @@ +import { ChildProcess, fork } from 'child_process'; + +export const runProcess = (appPath: string) => { + const process = fork(appPath); + process.on('exit', (code) => { + console.log(`Process exited with code ${code}`); + }); + + process.on('message', (message) => { + console.log('Message from child:', message); + }); + + // Example of sending a message to the child process + // process.send({ hello: 'world' }); +}; +class BaseProcess { + private process: ChildProcess; + status: 'running' | 'stopped' | 'error' = 'stopped'; + appPath: string; + constructor(appPath: string) { + this.appPath = appPath; + // this.createProcess(appPath); + } + createProcess(appPath: string = this.appPath) { + if (this.process) { + this.process.kill(); + } + this.appPath = appPath; + this.process = fork(appPath); + return this; + } + kill(signal?: NodeJS.Signals | number) { + if (this.process) { + this.process.kill(signal); + } + return this; + } + public send(message: any) { + this.process.send(message); + } + + public on(event: string, callback: (message: any) => void) { + this.process.on(event, callback); + } + + public onExit(callback: (code: number) => void) { + this.process.on('exit', callback); + } + + public onError(callback: (error: Error) => void) { + this.process.on('error', callback); + } + + public onMessage(callback: (message: any) => void) { + this.process.on('message', callback); + } + + public onClose(callback: () => void) { + this.process.on('close', callback); + } + + public onDisconnect(callback: () => void) { + this.process.on('disconnect', callback); + } +} +export class AssistantProcess extends BaseProcess { + constructor(appPath: string) { + super(appPath); + } +} diff --git a/assistant-module/src/proxy/api-proxy.ts b/assistant-module/src/proxy/api-proxy.ts new file mode 100644 index 0000000..92bc364 --- /dev/null +++ b/assistant-module/src/proxy/api-proxy.ts @@ -0,0 +1,82 @@ +import http from 'http'; +import https from 'https'; + +import { ProxyInfo } from './proxy.ts'; +export const defaultApiProxy = [ + { + path: '/api/router', + target: 'https://kevisual.xiongxiao.me', + }, + { + path: '/v1', + target: 'https://kevisual.xiongxiao.me', + }, +]; +/** + * 创建api代理 + * @param api + * @param paths ['/api/router', '/v1' ] + * @returns + */ +export const createApiProxy = (api: string, paths: string[] = ['/api/router', '/v1']) => { + const pathList = paths.map((item) => { + return { + path: item, + target: new URL(api).origin, + }; + }); + return pathList; +}; + +export const apiProxy = (req: http.IncomingMessage, res: http.ServerResponse, proxyApi: ProxyInfo) => { + const _u = new URL(req.url, `${proxyApi.target}`); + console.log('proxyApi', req.url, _u.href); + // 设置代理请求的目标 URL 和请求头 + let header: any = {}; + if (req.headers?.['Authorization'] && !req.headers?.['authorization']) { + header.authorization = req.headers['Authorization']; + } + // 提取req的headers中的非HOST的header + const headers = Object.keys(req.headers).filter((item) => item && item.toLowerCase() !== 'host'); + headers.forEach((item) => { + if (item.toLowerCase() === 'origin') { + header.origin = new URL(proxyApi.target).origin; + return; + } + if (item.toLowerCase() === 'referer') { + header.referer = new URL(req.url, proxyApi.target).href; + return; + } + header[item] = req.headers[item]; + }); + const options = { + host: _u.hostname, + path: req.url, + method: req.method, + headers: { + ...header, + }, + }; + console.log('options', JSON.stringify(options, null, 2)); + if (_u.port) { + // @ts-ignore + options.port = _u.port; + } + const httpProxy = _u.protocol === 'https:' ? https : http; + // 创建代理请求 + const proxyReq = httpProxy.request(options, (proxyRes) => { + // 将代理服务器的响应头和状态码返回给客户端 + res.writeHead(proxyRes.statusCode, proxyRes.headers); + // 将代理响应流写入客户端响应 + proxyRes.pipe(res, { end: true }); + }); + // 处理代理请求的错误事件 + proxyReq.on('error', (err) => { + console.error(`Proxy request error: ${err.message}`); + res.writeHead(500, { 'Content-Type': 'text/plain' }); + res.write(`Proxy request error: ${err.message}`); + }); + // 处理 POST 请求的请求体(传递数据到目标服务器),end:true 表示当请求体结束时,关闭请求 + req.pipe(proxyReq, { end: true }); + return; +}; diff --git a/assistant-module/src/proxy/file-proxy.ts b/assistant-module/src/proxy/file-proxy.ts new file mode 100644 index 0000000..57ea1f4 --- /dev/null +++ b/assistant-module/src/proxy/file-proxy.ts @@ -0,0 +1,47 @@ +import http from 'http'; +import send from 'send'; +import fs from 'fs'; +import { fileIsExist } from '@kevisual/use-config'; +import path from 'path'; +import { ProxyInfo } from './proxy.ts'; + +export const fileProxy = (req: http.IncomingMessage, res: http.ServerResponse, proxyApi: ProxyInfo) => { + // url开头的文件 + const url = new URL(req.url, 'http://localhost'); + let pathname = url.pathname.slice(1); + const { indexPath = '', target = '', rootPath = process.cwd() } = proxyApi; + try { + if (pathname.endsWith('/')) { + pathname = pathname + 'index.html'; + } + // 检测文件是否存在,如果文件不存在,则返回404 + let filePath = path.join(rootPath, target, pathname); + let exist = fileIsExist(filePath); + if (!exist) { + filePath = path.join(rootPath, target, '/' + indexPath); + exist = fileIsExist(filePath); + } + console.log('filePath', filePath, exist); + + if (!exist) { + res.statusCode = 404; + res.end('Not Found File'); + return; + } + const ext = path.extname(filePath); + let maxAge = 24 * 60 * 60 * 1000; // 24小时 + if (ext === '.html') { + maxAge = 0; + } + let sendFilePath = filePath.replace(rootPath + '/', ''); + const file = send(req, sendFilePath, { + root: rootPath, + maxAge, + }); + file.pipe(res); + } catch (error) { + res.statusCode = 404; + res.end('Error:Not Found File'); + return; + } +}; diff --git a/assistant-module/src/proxy/index.ts b/assistant-module/src/proxy/index.ts new file mode 100644 index 0000000..c5405f5 --- /dev/null +++ b/assistant-module/src/proxy/index.ts @@ -0,0 +1,5 @@ +export * from './proxy.ts'; +export * from './file-proxy.ts'; +export { default as send } from 'send'; +export * from './api-proxy.ts'; +export * from './wx-proxy.ts'; \ No newline at end of file diff --git a/assistant-module/src/proxy/proxy.ts b/assistant-module/src/proxy/proxy.ts new file mode 100644 index 0000000..a7a3c0e --- /dev/null +++ b/assistant-module/src/proxy/proxy.ts @@ -0,0 +1,35 @@ +export type ProxyInfo = { + path?: string; + target?: string; + type?: 'static' | 'dynamic' | 'minio'; + /** + * 首要文件,比如index.html, 设置了首要文件,如果文件不存在,则访问首要文件 + */ + indexPath?: string; + /** + * 根路径, 默认是process.cwd() + */ + rootPath?: string; +}; +export type ApiList = { + path: string; + /** + * url或者相对路径 + */ + target: string; + /** + * 类型 + */ + type?: 'static' | 'dynamic' | 'minio'; +}[]; + +/** + +[ + { + path: '/api/v1/user', + target: 'http://localhost:3000/api/v1/user', + type: 'dynamic', + }, +] + */ diff --git a/assistant-module/src/proxy/wx-proxy.ts b/assistant-module/src/proxy/wx-proxy.ts new file mode 100644 index 0000000..1001bb1 --- /dev/null +++ b/assistant-module/src/proxy/wx-proxy.ts @@ -0,0 +1,48 @@ +import { Server } from 'http'; +import WebSocket from 'ws'; +/** + * websocket代理 + * apiList: [{ path: '/api/router', target: 'https://kevisual.xiongxiao.me' }] + * @param server + * @param config + */ +export const wsProxy = (server: Server, config: { apiList: any[] }) => { + console.log('Upgrade initialization started'); + + server.on('upgrade', (req, socket, head) => { + const proxyApiList = config?.apiList || []; + const proxyApi = proxyApiList.find((item) => req.url.startsWith(item.path)); + + if (proxyApi) { + const _u = new URL(req.url, `${proxyApi.target}`); + const isHttps = _u.protocol === 'https:'; + const wsProtocol = isHttps ? 'wss' : 'ws'; + const wsUrl = `${wsProtocol}://${_u.hostname}${_u.pathname}`; + + const proxySocket = new WebSocket(wsUrl, { + headers: req.headers, + }); + + proxySocket.on('open', () => { + socket.on('data', (data) => { + proxySocket.send(data); + }); + + proxySocket.on('message', (message) => { + socket.write(message); + }); + }); + + proxySocket.on('error', (err) => { + console.error(`WebSocket proxy error: ${err.message}`); + socket.end(); + }); + + socket.on('error', () => { + proxySocket.close(); + }); + } else { + socket.end(); + } + }); +}; diff --git a/assistant-module/tsconfig.json b/assistant-module/tsconfig.json new file mode 100644 index 0000000..0333030 --- /dev/null +++ b/assistant-module/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "module": "nodenext", + "target": "esnext", + "noImplicitAny": false, + "outDir": "./dist", + "sourceMap": false, + "allowJs": true, + "newLine": "LF", + "baseUrl": "./", + "typeRoots": [ + "node_modules/@types", + "node_modules/@kevisual/types" + ], + "declaration": true, + "noEmit": false, + "allowImportingTsExtensions": true, + "emitDeclarationOnly": true, + "moduleResolution": "NodeNext", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "esModuleInterop": true, + "paths": { + "@/*": [ + "src/*" + ] + } + }, + "include": [ + "src/**/*.ts", + ], + "exclude": [], +} \ No newline at end of file diff --git a/package.json b/package.json index d891985..0bd7e15 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,21 @@ { - "name": "demo-app", + "name": "assistant-center", "version": "0.0.1", "description": "", "main": "index.js", "app": { - "key": "demo-app", + "key": "assistant-center", "entry": "dist/app.mjs", "type": "system-app", "files": [ - "dist" + "dist", + "pem" ] }, "scripts": { "watch": "rollup -c rollup.config.mjs -w", "dev": "cross-env NODE_ENV=development nodemon --delay 2.5 -e js,cjs,mjs --exec node dist/app.mjs", + "build": "rollup -c rollup.config.mjs", "test": "tsx test/**/*.ts", "dev:watch": "cross-env NODE_ENV=development concurrently -n \"Watch,Dev\" -c \"green,blue\" \"npm run watch\" \"sleep 1 && npm run dev\" ", "clean": "rm -rf dist", @@ -31,20 +33,22 @@ "src" ], "dependencies": { - "@kevisual/code-center-module": "0.0.11-alpha.1", - "@kevisual/mark": "0.0.6", - "@kevisual/router": "0.0.8-alpha.3", + "@kevisual/code-center-module": "0.0.13", + "@kevisual/mark": "0.0.7", + "@kevisual/router": "0.0.9", "cookie": "^1.0.2", "dayjs": "^1.11.13", "formidable": "^3.5.2", "json5": "^2.2.3", - "lodash-es": "^4.17.21" + "lodash-es": "^4.17.21", + "ws": "^8.18.1" }, "devDependencies": { + "@kevisual/assistant-module": "workspace:*", "@kevisual/types": "^0.0.6", "@kevisual/use-config": "^1.0.9", "@rollup/plugin-alias": "^5.1.1", - "@rollup/plugin-commonjs": "^28.0.2", + "@rollup/plugin-commonjs": "^28.0.3", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-replace": "^6.0.2", @@ -52,7 +56,8 @@ "@types/crypto-js": "^4.2.2", "@types/formidable": "^3.4.5", "@types/lodash-es": "^4.17.12", - "@types/node": "^22.13.8", + "@types/node": "^22.13.9", + "@types/ws": "^8.18.0", "concurrently": "^9.1.2", "cross-env": "^7.0.3", "nodemon": "^3.1.9", diff --git a/pem/https-cert.pem b/pem/https-cert.pem new file mode 100644 index 0000000..68f4fce --- /dev/null +++ b/pem/https-cert.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICXTCCAcagAwIBAgIJHsP036vqWER/MA0GCSqGSIb3DQEBBQUAMF8xCjAIBgNV +BAMTASoxCzAJBgNVBAYTAkNOMREwDwYDVQQIEwhaaGVKaWFuZzERMA8GA1UEBxMI +SGFuZ3pob3UxETAPBgNVBAoTCEVudmlzaW9uMQswCQYDVQQLEwJJVDAeFw0yNTAz +MDcxNDIwMTJaFw0yNjAzMDcxNDIwMTJaMF8xCjAIBgNVBAMTASoxCzAJBgNVBAYT +AkNOMREwDwYDVQQIEwhaaGVKaWFuZzERMA8GA1UEBxMISGFuZ3pob3UxETAPBgNV +BAoTCEVudmlzaW9uMQswCQYDVQQLEwJJVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEAquA2XnwduVSJHvnTW4r5yodz/joTPUi+r8kS/KJyR/NQ5xovtDY2gJoO +nJk8qekcLKuofskIIu4HFsCE7AYBkQGaYmc+0cCQCmEpwivesbeMB0ydz+6NwLQn +32HVjtMtx3gUcywGdMntiQb/P9FIhtE132wOmW9PeSl0dx/nyrUCAwEAAaMhMB8w +HQYDVR0RBBYwFIIBKoIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBBQUAA4GB +AJsjIZgb6iE4OTXoEDiBPmHM+byWs20K2eCvi79V9/vns90IroBQfGirIsovv923 +SqjmdAFsZkRUbZvX99lBX0mmZK9KTE4K9YUm7bv+d8+fBPxAgNFSTRiSNBeNh0Lh +HdJUiI/tzIfI6RRg1pFDC1tOG083Cl/YElN879w3Iipi +-----END CERTIFICATE----- diff --git a/pem/https-key.pem b/pem/https-key.pem new file mode 100644 index 0000000..7263037 --- /dev/null +++ b/pem/https-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCq4DZefB25VIke+dNbivnKh3P+OhM9SL6vyRL8onJH81DnGi+0 +NjaAmg6cmTyp6Rwsq6h+yQgi7gcWwITsBgGRAZpiZz7RwJAKYSnCK96xt4wHTJ3P +7o3AtCffYdWO0y3HeBRzLAZ0ye2JBv8/0UiG0TXfbA6Zb095KXR3H+fKtQIDAQAB +AoGADDEbL/qjFEoXzoH8tpdf4zdu60CxhrneASTTmfrtNH0D1LlllfIYSWy0hi/Y +yDa9r+I/j2xAjF13XAQ4d66mBdjCRATLx/aL495o+e6NkIBEAgdP88hHm13F6gg+ +h8iMixs5mkwU41sghnCYeBqlziKPi8fsoTmhK0VETFUtDQECQQDT0kZ7OCEVNcz0 +LAUPO7ukeHAYnGYns+Q3F3kgonzHPGflClH5dsg0NS1HFQj6Ny2oyUupjNePOCJK +88zNehIlAkEAzoO9zrE+AoTPleVpe7TAUlZB1YMa7W1C5owjyEkv4TjIe8mpwWM/ +9vVe+SGUnc6DZy6xkk5zWmA2w18SexXJUQJBAJQbcyy1EmzCMYyJOwBrw8g8biTH +NqaMIgZjY05uTtEAa6S6kpbbdyEKDZ6mFqDd9A8QsNbco9yAY3oE/i6uLAECQHOt +a9aphZiXmEfYl3uJxejZFEtrAtxXxY+qlCiOhllcG0Drt0DyPVQyIZ7fZoX2tbhI +eYMAmrDXEBXj3VBA5eECQCLGpQKqo06QwP2qZ9mEaPB9KvVcABo97b9Lf7VUqcJx +tFWRSlpeICpDQZHqX92nwoD/2fGCH3br3o94k1oyApI= +-----END RSA PRIVATE KEY----- diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 665a17b..985e263 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,14 +9,14 @@ importers: .: dependencies: '@kevisual/code-center-module': - specifier: 0.0.11-alpha.1 - version: 0.0.11-alpha.1(@kevisual/auth@1.0.5)(@kevisual/router@0.0.8-alpha.3)(@kevisual/use-config@1.0.9)(ioredis@5.5.0)(pg@8.13.3)(sequelize@6.37.5(pg@8.13.3)) + specifier: 0.0.13 + version: 0.0.13(@kevisual/auth@1.0.5)(@kevisual/router@0.0.9)(@kevisual/use-config@1.0.9)(ioredis@5.5.0)(pg@8.13.3)(sequelize@6.37.5(pg@8.13.3)) '@kevisual/mark': - specifier: 0.0.6 - version: 0.0.6(esbuild@0.25.0) + specifier: 0.0.7 + version: 0.0.7(esbuild@0.25.0) '@kevisual/router': - specifier: 0.0.8-alpha.3 - version: 0.0.8-alpha.3 + specifier: 0.0.9 + version: 0.0.9 cookie: specifier: ^1.0.2 version: 1.0.2 @@ -32,7 +32,13 @@ importers: lodash-es: specifier: ^4.17.21 version: 4.17.21 + ws: + specifier: ^8.18.1 + version: 8.18.1 devDependencies: + '@kevisual/assistant-module': + specifier: workspace:* + version: link:assistant-module '@kevisual/types': specifier: ^0.0.6 version: 0.0.6 @@ -43,8 +49,8 @@ importers: specifier: ^5.1.1 version: 5.1.1(rollup@4.34.9) '@rollup/plugin-commonjs': - specifier: ^28.0.2 - version: 28.0.2(rollup@4.34.9) + specifier: ^28.0.3 + version: 28.0.3(rollup@4.34.9) '@rollup/plugin-json': specifier: ^6.1.0 version: 6.1.0(rollup@4.34.9) @@ -67,8 +73,11 @@ importers: specifier: ^4.17.12 version: 4.17.12 '@types/node': - specifier: ^22.13.8 - version: 22.13.8 + specifier: ^22.13.9 + version: 22.13.9 + '@types/ws': + specifier: ^8.18.0 + version: 8.18.0 concurrently: specifier: ^9.1.2 version: 9.1.2 @@ -106,6 +115,25 @@ importers: specifier: ^5.8.2 version: 5.8.2 + assistant-module: + dependencies: + send: + specifier: ^1.1.0 + version: 1.1.0 + ws: + specifier: ^8.18.1 + version: 8.18.1 + devDependencies: + '@types/node': + specifier: ^22.13.10 + version: 22.13.10 + '@types/send': + specifier: ^0.17.4 + version: 0.17.4 + '@types/ws': + specifier: ^8.18.0 + version: 8.18.0 + packages: '@babel/code-frame@7.26.2': @@ -279,8 +307,8 @@ packages: '@kevisual/auth@1.0.5': resolution: {integrity: sha512-GwsLj7unKXi7lmMiIIgdig4LwwLiDJnOy15HHZR5gMbyK6s5/uJiMY5RXPB2+onGzTNDqFo/hXjsD2wkerHPVg==} - '@kevisual/code-center-module@0.0.11-alpha.1': - resolution: {integrity: sha512-0HPSZw4PmhejE7p4cBIe174/h434XE3dgrwHoRZLYZSZyJ/aaBfR+3RybdvDN5dnyusLkPdgRq+Qern53Lqp1A==} + '@kevisual/code-center-module@0.0.13': + resolution: {integrity: sha512-A82sX8rdG2igyVLIF+0dagcUsGfk2b0JAga1BDDr9mrChrG1HbG1uYN7JJdjJbGE6zGYqGxRZwxKZmzB/+KMnw==} peerDependencies: '@kevisual/auth': ^1.0.5 '@kevisual/router': ^0.0.7 @@ -292,8 +320,8 @@ packages: '@kevisual/load@0.0.4': resolution: {integrity: sha512-TJBieKsEoEPfP4+tDyhNZdMX2LMAGiDZ/IrAXPFWB4jeFP0Ywm1W5xDV52LhhHq4nwTmuhyTVmPxJYiEVYTHtA==} - '@kevisual/mark@0.0.6': - resolution: {integrity: sha512-QhJXeJbeQIbouitqE3s67G92tkx44XoC4dDZUXCd28xGJyeCegkkQ6n4uxKPLBCh3XhDtek1e2EZjhK97wyZJA==} + '@kevisual/mark@0.0.7': + resolution: {integrity: sha512-PiEEy4yvWEpixw76PzgrIWeNelzm+FrhtzFmqJU92o5GkgawaFwighcvIxqcVZRKeEFF4uvlTjFrGeQvXw6F4A==} '@kevisual/rollup-tools@0.0.1': resolution: {integrity: sha512-TdCN+IU0fyHudiiqYvobXQ8r5MltfM/cKmSS59iopyL8YYwXwcipOS4S24NWA79g7uwJfSUNk5lg3yVhom79fQ==} @@ -302,8 +330,8 @@ packages: '@kevisual/router@0.0.7': resolution: {integrity: sha512-4n1Tp4YLoraJv7jtfy7jbuLGyAj0B2QkTlnlEDHCUTlEUOvOkjtf7DHAe2SL92fTgXhSbod0I/0vUcDF85oj/w==} - '@kevisual/router@0.0.8-alpha.3': - resolution: {integrity: sha512-iWFatFe0ggBTpSmCqtRIVbMq8YnekM2beIciWmoE9kIfadosdyKW/vWoK5wfTUu4nB4GORCiDO3YrAYnoCaQug==} + '@kevisual/router@0.0.9': + resolution: {integrity: sha512-qPyC2GVJ7iOIdJCCKNDsWMAKOQeSJW9HBpL5ZWKHTbi+t4jJBGTzIlXmjKeMHRd0lr/Qq1imQvlkSh4hlrbodA==} '@kevisual/types@0.0.6': resolution: {integrity: sha512-7yxe1QmuC5g7lI/1Hm+zXly8if0z+ZqGM1SVOVv2VNRwRAVYBJDc365zWCCfRwE+5YaB2daWTe5zBOU4EkltkQ==} @@ -354,8 +382,8 @@ packages: rollup: optional: true - '@rollup/plugin-commonjs@28.0.2': - resolution: {integrity: sha512-BEFI2EDqzl+vA1rl97IDRZ61AIwGH093d9nz8+dThxJNH8oSoB7MjWvPCX3dkaK1/RCJ/1v/R1XB15FuSs0fQw==} + '@rollup/plugin-commonjs@28.0.3': + resolution: {integrity: sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ==} engines: {node: '>=16.0.0 || 14 >= 14.17'} peerDependencies: rollup: ^2.68.0||^3.0.0||^4.0.0 @@ -549,6 +577,9 @@ packages: '@types/lodash@4.17.15': resolution: {integrity: sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==} + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + '@types/minimatch@5.1.2': resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} @@ -558,15 +589,24 @@ packages: '@types/node-forge@1.3.11': resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} - '@types/node@22.13.8': - resolution: {integrity: sha512-G3EfaZS+iOGYWLLRCEAXdWK9my08oHNZ+FHluRiggIYJPOXzhOiDgpVCUHaUvyIC5/fj7C/p637jdzC666AOKQ==} + '@types/node@22.13.10': + resolution: {integrity: sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==} + + '@types/node@22.13.9': + resolution: {integrity: sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==} '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + '@types/send@0.17.4': + resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} + '@types/validator@13.12.2': resolution: {integrity: sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==} + '@types/ws@8.18.0': + resolution: {integrity: sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -854,6 +894,14 @@ packages: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dezalgo@1.0.4: resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} @@ -875,12 +923,19 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + engine.io-parser@5.2.3: resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} engines: {node: '>=10.0.0'} @@ -932,6 +987,9 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -960,6 +1018,10 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + eventemitter2@0.4.14: resolution: {integrity: sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==} @@ -1020,6 +1082,10 @@ packages: formidable@3.5.2: resolution: {integrity: sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==} + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + fs-extra@8.1.0: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} @@ -1147,6 +1213,10 @@ packages: resolution: {integrity: sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==} engines: {node: '>=8'} + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -1491,6 +1561,10 @@ packages: resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -1650,6 +1724,10 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + read@1.0.7: resolution: {integrity: sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==} engines: {node: '>=0.8'} @@ -1780,6 +1858,10 @@ packages: engines: {node: '>=10'} hasBin: true + send@1.1.0: + resolution: {integrity: sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==} + engines: {node: '>= 18'} + sequelize-pool@7.1.0: resolution: {integrity: sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==} engines: {node: '>= 10.0.0'} @@ -1829,6 +1911,9 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1922,6 +2007,10 @@ packages: standard-as-callback@2.1.0: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} @@ -1984,6 +2073,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + toposort-class@1.0.1: resolution: {integrity: sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==} @@ -2127,8 +2220,8 @@ packages: utf-8-validate: optional: true - ws@8.18.0: - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + ws@8.18.1: + resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -2263,10 +2356,10 @@ snapshots: '@kevisual/auth@1.0.5': {} - '@kevisual/code-center-module@0.0.11-alpha.1(@kevisual/auth@1.0.5)(@kevisual/router@0.0.8-alpha.3)(@kevisual/use-config@1.0.9)(ioredis@5.5.0)(pg@8.13.3)(sequelize@6.37.5(pg@8.13.3))': + '@kevisual/code-center-module@0.0.13(@kevisual/auth@1.0.5)(@kevisual/router@0.0.9)(@kevisual/use-config@1.0.9)(ioredis@5.5.0)(pg@8.13.3)(sequelize@6.37.5(pg@8.13.3))': dependencies: '@kevisual/auth': 1.0.5 - '@kevisual/router': 0.0.8-alpha.3 + '@kevisual/router': 0.0.9 '@kevisual/use-config': 1.0.9 ioredis: 5.5.0 nanoid: 5.1.2 @@ -2283,13 +2376,14 @@ snapshots: dependencies: eventemitter3: 5.0.1 - '@kevisual/mark@0.0.6(esbuild@0.25.0)': + '@kevisual/mark@0.0.7(esbuild@0.25.0)': dependencies: '@kevisual/auth': 1.0.5 '@kevisual/rollup-tools': 0.0.1(esbuild@0.25.0) '@kevisual/router': 0.0.7 '@kevisual/use-config': 1.0.9 cookie: 1.0.2 + nanoid: 5.1.2 pg: 8.13.3 sequelize: 6.37.5(pg@8.13.3) transitivePeerDependencies: @@ -2310,12 +2404,12 @@ snapshots: '@kevisual/rollup-tools@0.0.1(esbuild@0.25.0)': dependencies: '@rollup/plugin-alias': 5.1.1(rollup@4.34.9) - '@rollup/plugin-commonjs': 28.0.2(rollup@4.34.9) + '@rollup/plugin-commonjs': 28.0.3(rollup@4.34.9) '@rollup/plugin-json': 6.1.0(rollup@4.34.9) '@rollup/plugin-node-resolve': 15.3.1(rollup@4.34.9) '@rollup/plugin-replace': 6.0.2(rollup@4.34.9) '@rollup/plugin-typescript': 12.1.2(rollup@4.34.9)(tslib@2.8.1)(typescript@5.8.2) - '@types/node': 22.13.8 + '@types/node': 22.13.10 chalk: 5.4.1 commander: 12.1.0 glob: 11.0.1 @@ -2334,16 +2428,16 @@ snapshots: dependencies: path-to-regexp: 8.2.0 selfsigned: 2.4.1 - ws: 8.18.0 + ws: 8.18.1 transitivePeerDependencies: - bufferutil - utf-8-validate - '@kevisual/router@0.0.8-alpha.3': + '@kevisual/router@0.0.9': dependencies: path-to-regexp: 8.2.0 selfsigned: 2.4.1 - ws: 8.18.0 + ws: 8.18.1 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -2430,7 +2524,7 @@ snapshots: optionalDependencies: rollup: 4.34.9 - '@rollup/plugin-commonjs@28.0.2(rollup@4.34.9)': + '@rollup/plugin-commonjs@28.0.3(rollup@4.34.9)': dependencies: '@rollup/pluginutils': 5.1.4(rollup@4.34.9) commondir: 1.0.1 @@ -2555,7 +2649,7 @@ snapshots: '@types/cors@2.8.17': dependencies: - '@types/node': 22.13.8 + '@types/node': 22.13.10 '@types/crypto-js@4.2.2': {} @@ -2567,16 +2661,16 @@ snapshots: '@types/formidable@3.4.5': dependencies: - '@types/node': 22.13.8 + '@types/node': 22.13.10 '@types/fs-extra@8.1.5': dependencies: - '@types/node': 22.13.8 + '@types/node': 22.13.10 '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 22.13.8 + '@types/node': 22.13.10 '@types/lodash-es@4.17.12': dependencies: @@ -2584,22 +2678,37 @@ snapshots: '@types/lodash@4.17.15': {} + '@types/mime@1.3.5': {} + '@types/minimatch@5.1.2': {} '@types/ms@2.1.0': {} '@types/node-forge@1.3.11': dependencies: - '@types/node': 22.13.8 + '@types/node': 22.13.10 - '@types/node@22.13.8': + '@types/node@22.13.10': + dependencies: + undici-types: 6.20.0 + + '@types/node@22.13.9': dependencies: undici-types: 6.20.0 '@types/resolve@1.20.2': {} + '@types/send@0.17.4': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 22.13.10 + '@types/validator@13.12.2': {} + '@types/ws@8.18.0': + dependencies: + '@types/node': 22.13.10 + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -2889,6 +2998,10 @@ snapshots: denque@2.1.0: {} + depd@2.0.0: {} + + destroy@1.2.0: {} + dezalgo@1.0.4: dependencies: asap: 2.0.6 @@ -2912,16 +3025,20 @@ snapshots: eastasianwidth@0.2.0: {} + ee-first@1.1.1: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} + encodeurl@2.0.0: {} + engine.io-parser@5.2.3: {} engine.io@6.6.4: dependencies: '@types/cors': 2.8.17 - '@types/node': 22.13.8 + '@types/node': 22.13.10 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.7.2 @@ -3057,6 +3174,8 @@ snapshots: escalade@3.2.0: {} + escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} escodegen@2.1.0: @@ -3077,6 +3196,8 @@ snapshots: esutils@2.0.3: {} + etag@1.8.1: {} + eventemitter2@0.4.14: {} eventemitter2@5.0.1: {} @@ -3134,6 +3255,8 @@ snapshots: hexoid: 2.0.0 once: 1.4.0 + fresh@0.5.2: {} + fs-extra@8.1.0: dependencies: graceful-fs: 4.2.11 @@ -3278,6 +3401,14 @@ snapshots: hexoid@2.0.0: {} + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 @@ -3626,6 +3757,10 @@ snapshots: has-symbols: 1.1.0 object-keys: 1.1.1 + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -3834,6 +3969,8 @@ snapshots: queue-microtask@1.2.3: {} + range-parser@1.2.1: {} + read@1.0.7: dependencies: mute-stream: 0.0.8 @@ -4009,6 +4146,23 @@ snapshots: semver@7.7.1: {} + send@1.1.0: + dependencies: + debug: 4.4.0(supports-color@5.5.0) + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime-types: 2.1.35 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + sequelize-pool@7.1.0: {} sequelize@6.37.5(pg@8.13.3): @@ -4056,6 +4210,8 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 + setprototypeof@1.2.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -4166,6 +4322,8 @@ snapshots: standard-as-callback@2.1.0: {} + statuses@2.0.1: {} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -4260,6 +4418,8 @@ snapshots: dependencies: is-number: 7.0.0 + toidentifier@1.0.1: {} + toposort-class@1.0.1: {} touch@3.1.1: {} @@ -4396,7 +4556,7 @@ snapshots: wkx@0.5.0: dependencies: - '@types/node': 22.13.8 + '@types/node': 22.13.10 wrap-ansi@7.0.0: dependencies: @@ -4416,7 +4576,7 @@ snapshots: ws@8.17.1: {} - ws@8.18.0: {} + ws@8.18.1: {} xtend@4.0.2: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..7b0b4b6 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'assistant-module' \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index cd3952c..3844f27 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,8 +1,19 @@ import { App } from '@kevisual/router'; import { useContextKey } from '@kevisual/use-config/context'; +import { httpsConfig } from './modules/config.ts'; const init = () => { - return new App(); + const app = new App({ + serverOptions: { + path: '/client/router', + httpType: 'https', + httpsCert: httpsConfig.cert.toString(), + httpsKey: httpsConfig.key.toString(), + }, + }); + return app; }; export const app = useContextKey('app', init); + + diff --git a/src/demo-route.ts b/src/demo-route.ts deleted file mode 100644 index 651a013..0000000 --- a/src/demo-route.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { app } from './app.ts'; -import { useConfig } from '@kevisual/use-config'; - -app - .route({ - path: 'demo', - key: 'demo', - }) - .define(async (ctx) => { - ctx.body = '123'; - }) - .addTo(app); - -const config = useConfig(); - -console.log('run demo: http://localhost:' + config.port + '/api/router?path=demo&key=demo'); diff --git a/src/dev.ts b/src/dev.ts index 818c369..20198d4 100644 --- a/src/dev.ts +++ b/src/dev.ts @@ -1,8 +1,18 @@ -import { useConfig } from '@kevisual/use-config'; import { app } from './index.ts'; +import { proxyRoute } from './proxy-route/index.ts'; -const config = useConfig(); +app + .route({ + path: 'demo', + }) + .define(async (ctx) => { + ctx.body = 'hello world'; + }) + .addTo(app); -app.listen(config.port, () => { - console.log(`server is running at http://localhost:${config.port}`); +console.log('httpsConfig', `https://localhost:51015/client/router?path=demo`); +app.listen(51015, () => { + console.log('Router App is running on https://localhost:51015'); }); + +app.server.on(proxyRoute); diff --git a/src/index.ts b/src/index.ts index d7c7c49..6b89bcb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ import { app } from './app.ts'; -import './demo-route.ts'; +import './route/index.ts'; export { app }; \ No newline at end of file diff --git a/src/modules/config.ts b/src/modules/config.ts new file mode 100644 index 0000000..43b127c --- /dev/null +++ b/src/modules/config.ts @@ -0,0 +1,9 @@ +import fs from 'fs'; +import path from 'path'; + +const pemDir = path.join(process.cwd(), 'pem'); + +export const httpsConfig = { + key: fs.readFileSync(path.join(pemDir, 'https-key.pem')), + cert: fs.readFileSync(path.join(pemDir, 'https-cert.pem')), +}; diff --git a/src/modules/config/index.ts b/src/modules/config/index.ts new file mode 100644 index 0000000..c64d304 --- /dev/null +++ b/src/modules/config/index.ts @@ -0,0 +1,108 @@ +import path from 'path'; +import { homedir } from 'os'; +import fs from 'fs'; +import { checkFileExists, createDir } from '../file/index.ts'; + +export const kevisualUrl = 'https://kevisual.xiongxiao.me'; +const configDir = createDir(path.join(homedir(), '.config/envision')); +export const configPath = path.join(configDir, 'assistant-config.json'); +export const appConfigPath = path.join(configDir, 'assistant-app-config.json'); +export const appDir = createDir(path.join(configDir, 'assistant-app/frontend')); +export const LocalElectronAppUrl = 'https://assistant.app/user/tiptap/'; + +type AssistantConfig = { + pageApi?: string; // https://kevisual.silkyai.cn + loadURL?: string; // https://assistant.app/user/tiptap/ + proxy?: { user: string; key: string; path: string }[]; +}; +let assistantConfig: AssistantConfig; +export const getConfig = () => { + try { + if (!checkFileExists(configPath)) { + fs.writeFileSync(configPath, JSON.stringify({ proxy: [] }, null, 2)); + return { + loadURL: LocalElectronAppUrl, + pageApi: '', + proxy: [], + }; + } + assistantConfig = JSON.parse(fs.readFileSync(configPath, 'utf8')); + return assistantConfig; + } catch (error) { + console.error(error); + return { + loadURL: LocalElectronAppUrl, + pageApi: '', + proxy: [], + }; + } +}; +export const getCacheAssistantConfig = () => { + if (assistantConfig) { + return assistantConfig; + } + return getConfig(); +}; + +export const setConfig = (config?: AssistantConfig) => { + if (!config) { + return assistantConfig; + } + assistantConfig = config; + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); + return assistantConfig; +}; +type AppConfig = { + list: any[]; +}; +/** + * 应用配置 + * @returns + */ +export const getAppConfig = (): AppConfig => { + if (!checkFileExists(appConfigPath)) { + return { + list: [], + }; + } + return JSON.parse(fs.readFileSync(appConfigPath, 'utf8')); +}; + +export const setAppConfig = (config: AppConfig) => { + fs.writeFileSync(appConfigPath, JSON.stringify(config, null, 2)); + return config; +}; + +export const addAppConfig = (app: any) => { + const config = getAppConfig(); + const assistantConfig = getCacheAssistantConfig(); + const _apps = config.list; + const _proxy = assistantConfig.proxy || []; + const { user, key } = app; + const newProxyInfo = { + user, + key, + path: `/${user}/${key}`, + }; + const _proxyIndex = _proxy.findIndex((_proxy: any) => _proxy.path === newProxyInfo.path); + if (_proxyIndex !== -1) { + _proxy[_proxyIndex] = newProxyInfo; + } else { + _proxy.push(newProxyInfo); + } + + const _app = _apps.findIndex((_app: any) => _app.id === app.id); + if (_app !== -1) { + _apps[_app] = app; + } else { + _apps.push(app); + } + setAppConfig({ ...config, list: _apps }); + setConfig({ ...assistantConfig, proxy: _proxy }); + return config; +}; + +export const getAppList = () => { + const config = getAppConfig(); + return config.list || []; +}; diff --git a/src/modules/file/index.ts b/src/modules/file/index.ts new file mode 100644 index 0000000..f1f45bb --- /dev/null +++ b/src/modules/file/index.ts @@ -0,0 +1,20 @@ +import fs from 'fs'; + +export const checkFileExists = (filePath: string, checkIsFile = false) => { + try { + fs.accessSync(filePath); + if (checkIsFile) { + return fs.statSync(filePath).isFile(); + } + return true; + } catch (error) { + return false; + } +}; + +export const createDir = (dirPath: string) => { + if (!checkFileExists(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } + return dirPath; +}; diff --git a/src/modules/install.ts b/src/modules/install.ts new file mode 100644 index 0000000..e66562a --- /dev/null +++ b/src/modules/install.ts @@ -0,0 +1,156 @@ +import path from 'path'; +import fs from 'fs'; +import { appDir, kevisualUrl, addAppConfig, getAppConfig, setAppConfig, getCacheAssistantConfig, setConfig } from './config/index.ts'; + +export const demoData = { + id: '471ee96f-d7d8-4da1-b84f-4a34f4732f16', + title: 'tiptap', + description: '', + data: { + files: [ + { + name: 'README.md', + path: 'user/tiptap/0.0.1/README.md', + }, + { + name: 'app.css', + path: 'user/tiptap/0.0.1/app.css', + }, + { + name: 'app.js', + path: 'user/tiptap/0.0.1/app.js', + }, + { + name: 'create-BxEwtceK.js', + path: 'user/tiptap/0.0.1/create-BxEwtceK.js', + }, + { + name: 'index.CrTXFMOJ.js', + path: 'user/tiptap/0.0.1/index.CrTXFMOJ.js', + }, + { + name: 'index.html', + path: 'user/tiptap/0.0.1/index.html', + }, + ], + }, + version: '0.0.1', + domain: '', + appType: '', + key: 'tiptap', + type: '', + uid: '2bebe6a0-3c64-4a64-89f9-cc47fd082a07', + pid: null, + proxy: false, + user: 'user', + status: 'running', + createdAt: '2024-12-14T15:39:30.684Z', + updatedAt: '2024-12-14T15:39:55.714Z', + deletedAt: null, +}; + +type DownloadTask = { + downloadPath: string; + downloadUrl: string; + user: string; + key: string; + version: string; +}; +export type Package = { + id: string; + name?: string; + version?: string; + description?: string; + title?: string; + user?: string; + key?: string; + [key: string]: any; +}; +export const installApp = async (app: Package) => { + // const _app = demoData; + const _app = app; + try { + let files = _app.data.files || []; + const version = _app.version; + const user = _app.user; + const key = _app.key; + + const downFiles = files.map((file: any) => { + const noVersionPath = file.path.replace(`/${version}`, ''); + return { + ...file, + downloadPath: path.join(appDir, noVersionPath), + downloadUrl: `${kevisualUrl}/${noVersionPath}`, + }; + }); + const downloadTasks: DownloadTask[] = downFiles as any; + for (const file of downloadTasks) { + const downloadPath = file.downloadPath; + const downloadUrl = file.downloadUrl; + const dir = path.dirname(downloadPath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + const res = await fetch(downloadUrl); + const blob = await res.blob(); + fs.writeFileSync(downloadPath, Buffer.from(await blob.arrayBuffer())); + } + let indexHtml = files.find((file: any) => file.name === 'index.html'); + if (!indexHtml) { + files.push({ + name: 'index.html', + path: `${user}/${key}/index.html`, + }); + fs.writeFileSync(path.join(appDir, `${user}/${key}/index.html`), JSON.stringify(app, null, 2)); + } + _app.data.files = files; + addAppConfig(_app); + return { + code: 200, + data: _app, + message: 'Install app success', + }; + } catch (error) { + console.error(error); + return { + code: 500, + message: 'Install app failed', + }; + } +}; + +export const uninstallApp = async (app: Package) => { + try { + const { user, key } = app; + const appConfig = getAppConfig(); + const index = appConfig.list.findIndex((item: any) => item.user === user && item.key === key); + if (index !== -1) { + appConfig.list.splice(index, 1); + setAppConfig(appConfig); + // 删除appDir和文件 + fs.rmSync(path.join(appDir, user, key), { recursive: true }); + // 删除proxy + const proxyConfig = getCacheAssistantConfig(); + const proxyIndex = proxyConfig.proxy.findIndex((item: any) => item.user === user && item.key === key); + if (proxyIndex !== -1) { + proxyConfig.proxy.splice(proxyIndex, 1); + setConfig(proxyConfig); + } + } + return { + code: 200, + message: 'Uninstall app success', + }; + } catch (error) { + console.error(error); + return { + code: 500, + message: 'Uninstall app failed', + }; + } +}; + +export const getInstallList = async () => { + const appConfig = getAppConfig(); + return appConfig.list; +}; diff --git a/src/proxy-route/index.ts b/src/proxy-route/index.ts new file mode 100644 index 0000000..e7420c9 --- /dev/null +++ b/src/proxy-route/index.ts @@ -0,0 +1,61 @@ +import { fileProxy, apiProxy, createApiProxy } from '@kevisual/assistant-module/proxy'; +import { getCacheAssistantConfig, appDir } from '@kevisual/assistant-module'; +import http from 'http'; + +// https://localhost:51015/user/tiptap/ +export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResponse) => { + const assistantConfig = getCacheAssistantConfig(); + // const { apiList } = assistantConfig; + const url = new URL(req.url, 'http://localhost'); + const pathname = url.pathname; + if (pathname.startsWith('/favicon.ico')) { + res.statusCode = 404; + res.end('Not Found Favicon'); + return; + } + if (pathname.startsWith('/client')) { + console.log('handle by router'); + return; + } + const apiProxyList = assistantConfig?.apiProxyList || []; + const defaultApiProxy = createApiProxy(assistantConfig?.pageApi || 'https://kevisual.xiongxiao.me'); + const apiBackendProxy = [...apiProxyList, ...defaultApiProxy].find((item) => pathname.startsWith(item.path)); + if (apiBackendProxy) { + console.log('apiBackendProxy', apiBackendProxy); + return apiProxy(req, res, { + path: apiBackendProxy.path, + target: apiBackendProxy.target, + }); + } + // client, api, v1, serve 开头的拦截 + const proxyApiList = assistantConfig?.proxy || []; + const proxyApi = proxyApiList.find((item) => pathname.startsWith(item.path)); + if (proxyApi) { + console.log('proxyApi', proxyApi, pathname); + const { user, key } = proxyApi; + return fileProxy(req, res, { + path: proxyApi.path, + rootPath: appDir, + indexPath: `${user}/${key}/index.html`, + }); + } + const localProxyProxy = localProxyProxyList.find((item) => pathname.startsWith(item.path)); + if (localProxyProxy) { + return fileProxy(req, res, { + path: localProxyProxy.path, + rootPath: process.cwd(), + indexPath: localProxyProxy.indexPath, + }); + } + console.log('handle by router 404'); + res.statusCode = 404; + res.end('Not Found Proxy'); +}; +const localProxyProxyList = [ + { + user: 'root', + key: 'assistant-base-app', + path: '/root/assistant-base-app', + indexPath: 'root/assistant-base-app/index.html', + }, +]; diff --git a/src/proxy-route/ws-proxy.ts b/src/proxy-route/ws-proxy.ts new file mode 100644 index 0000000..09c6541 --- /dev/null +++ b/src/proxy-route/ws-proxy.ts @@ -0,0 +1,42 @@ +import net from 'net'; +import { App } from '@kevisual/router'; + +export const wsProxy = (app: App, config: { apiList: any[] }) => { + console.log('Upgrade initialization started'); + + app.server.server.on('upgrade', (req, socket, head) => { + const proxyApiList = config?.apiList || []; + const proxyApi = proxyApiList.find((item) => req.url.startsWith(item.path)); + + if (proxyApi) { + const _u = new URL(req.url, `${proxyApi.target}`); + const options = { + hostname: _u.hostname, + port: Number(_u.port) || 80, + path: _u.pathname, + headers: req.headers, + }; + + const proxySocket = net.connect(options.port, options.hostname, () => { + proxySocket.write( + `GET ${options.path} HTTP/1.1\r\n` + + `Host: ${options.hostname}\r\n` + + `Connection: Upgrade\r\n` + + `Upgrade: websocket\r\n` + + `Sec-WebSocket-Key: ${req.headers['sec-websocket-key']}\r\n` + + `Sec-WebSocket-Version: ${req.headers['sec-websocket-version']}\r\n` + + `\r\n`, + ); + proxySocket.pipe(socket); + socket.pipe(proxySocket); + }); + + proxySocket.on('error', (err) => { + console.error(`WebSocket proxy error: ${err.message}`); + socket.end(); + }); + } else { + socket.end(); + } + }); +}; diff --git a/src/route/client/check.ts b/src/route/client/check.ts new file mode 100644 index 0000000..e036974 --- /dev/null +++ b/src/route/client/check.ts @@ -0,0 +1,10 @@ +import { app } from '@/app.ts'; + +app + .route({ + path: 'check', + }) + .define(async (ctx) => { + ctx.body = 'ok'; + }) + .addTo(app); diff --git a/src/route/config/index.ts b/src/route/config/index.ts new file mode 100644 index 0000000..6afd088 --- /dev/null +++ b/src/route/config/index.ts @@ -0,0 +1,25 @@ +import { app } from '@/app.ts'; +import { getCacheAssistantConfig, setConfig } from '@/modules/config/index.ts'; + +app + .route({ + path: 'config', + description: '获取配置', + }) + .define(async (ctx) => { + ctx.body = getCacheAssistantConfig(); + }) + .addTo(app); + +app + .route({ + path: 'config', + key: 'set', + description: '设置配置', + }) + .define(async (ctx) => { + const { data } = ctx.query; + + ctx.body = setConfig(data); + }) + .addTo(app); diff --git a/src/route/index.ts b/src/route/index.ts new file mode 100644 index 0000000..efc5f7a --- /dev/null +++ b/src/route/index.ts @@ -0,0 +1,3 @@ +import './shop-install/index.ts'; +import './client/check.ts'; +import './config/index.ts'; diff --git a/src/route/shop-install/index.ts b/src/route/shop-install/index.ts new file mode 100644 index 0000000..42ef96a --- /dev/null +++ b/src/route/shop-install/index.ts @@ -0,0 +1,40 @@ +import { app } from '@/app.ts'; +import { getInstallList, installApp, uninstallApp } from '@/modules/install.ts'; + +app + .route({ + path: 'shop', + key: 'list-installed', + }) + .define(async (ctx) => { + // https://localhost:51015/client/router?path=shop&key=list-installed + const list = await getInstallList(); + ctx.body = list; + }) + .addTo(app); + +app + .route({ + path: 'shop', + key: 'install', + }) + .define(async (ctx) => { + // https://localhost:51015/client/router?path=shop&key=install + const { pkg } = ctx.query.data; + const res = await installApp(pkg); + ctx.body = res; + }) + .addTo(app); + +app + .route({ + path: 'shop', + key: 'uninstall', + }) + .define(async (ctx) => { + // https://localhost:51015/client/router?path=shop&key=uninstall + const { pkg } = ctx.query.data; + const res = await uninstallApp(pkg); + ctx.body = res; + }) + .addTo(app); diff --git a/src/scripts/assistant-config.ts b/src/scripts/assistant-config.ts new file mode 100644 index 0000000..3efd569 --- /dev/null +++ b/src/scripts/assistant-config.ts @@ -0,0 +1,8 @@ +import { getCacheAssistantConfig, appConfigPath, appDir } from '@kevisual/assistant-module'; +import fs from 'fs'; + +const assistantConfig = getCacheAssistantConfig(); +console.log(assistantConfig); + +console.log('appConfigPath', appConfigPath); +console.log('appDir', appDir);