diff --git a/packages/gpt-runner-shared/package.json b/packages/gpt-runner-shared/package.json index 82ea66a..489a6e6 100644 --- a/packages/gpt-runner-shared/package.json +++ b/packages/gpt-runner-shared/package.json @@ -62,6 +62,8 @@ "find-free-ports": "*", "ip": "*", "minimatch": "*", + "socket.io": "*", + "socket.io-client": "*", "zod": "*" }, "dependencies": { @@ -72,6 +74,8 @@ "find-free-ports": "^3.1.1", "ip": "^1.1.8", "minimatch": "^9.0.1", + "socket.io": "^4.6.2", + "socket.io-client": "^4.6.2", "zod": "^3.21.4" }, "devDependencies": { @@ -79,4 +83,4 @@ "@types/ip": "^1.1.0", "express": "^4.18.2" } -} +} \ No newline at end of file diff --git a/packages/gpt-runner-shared/src/common/helpers/common.ts b/packages/gpt-runner-shared/src/common/helpers/common.ts index 980974b..9779a6a 100644 --- a/packages/gpt-runner-shared/src/common/helpers/common.ts +++ b/packages/gpt-runner-shared/src/common/helpers/common.ts @@ -86,10 +86,21 @@ export function tryParseJson(str: string) { return JSON.parse(str) } catch (e) { + console.error('tryParseJson error: ', e) return {} } } +export function tryStringifyJson(obj: any) { + try { + return JSON.stringify(obj) + } + catch (e) { + console.error('tryStringifyJson error: ', e) + return '' + } +} + export function debounce any>(callback: T, wait: number) { let timeout: ReturnType | undefined diff --git a/packages/gpt-runner-shared/src/common/helpers/env-config.ts b/packages/gpt-runner-shared/src/common/helpers/env-config.ts index be0c760..b8984f7 100644 --- a/packages/gpt-runner-shared/src/common/helpers/env-config.ts +++ b/packages/gpt-runner-shared/src/common/helpers/env-config.ts @@ -15,7 +15,7 @@ interface EnvVarConfig { /** * if true, this env var will only be available on server side - * window.__config__ will not have this env var + * window.__env__ will not have this env var * * @default false */ @@ -26,7 +26,9 @@ const config: Record = { NODE_ENV: { defaultValue: 'production', }, - OPENAI_KEY: {}, + OPENAI_KEY: { + serverSideOnly: true, + }, GPTR_BASE_SERVER_URL: { defaultValue: 'http://localhost:3003', }, @@ -42,7 +44,7 @@ export class EnvConfig { // client side if (typeof window !== 'undefined' && !serverSideOnly) - return window?.__config__?.[key] ?? defaultValue ?? '' + return window?.__env__?.[key] ?? defaultValue ?? '' // server side return process.env[key] ?? defaultValue ?? '' @@ -51,7 +53,7 @@ export class EnvConfig { /** * get all env vars on server or client side * @param type server or client, get all allowed env vars on that scope - * @param getWays all or process, get env vars both on process and window.__config__ or only process.env + * @param getWays all or process, get env vars both on process and window.__env__ or only process.env * @returns env vars key value map */ static getAllEnvVarsOnScopes( @@ -93,7 +95,7 @@ export class EnvConfig { /** * for /api/config - * @returns env vars key value map for window.__config__ + * @returns env vars key value map for window.__env__ */ static getClientEnvVarsInServerSide(): Partial> { return EnvConfig.getAllEnvVarsOnScopes('client', 'process') @@ -106,6 +108,6 @@ declare global { } interface Window { - __config__?: Partial + __env__?: Partial } } diff --git a/packages/gpt-runner-shared/src/common/helpers/index.ts b/packages/gpt-runner-shared/src/common/helpers/index.ts index 4e55f40..86a99fd 100644 --- a/packages/gpt-runner-shared/src/common/helpers/index.ts +++ b/packages/gpt-runner-shared/src/common/helpers/index.ts @@ -4,4 +4,6 @@ export * from './create-filter-pattern' export * from './debug' export * from './env-config' export * from './is' +export * from './request' +export * from './socket' export * from './verify-zod' diff --git a/packages/gpt-runner-shared/src/common/helpers/request.ts b/packages/gpt-runner-shared/src/common/helpers/request.ts new file mode 100644 index 0000000..3f66d9c --- /dev/null +++ b/packages/gpt-runner-shared/src/common/helpers/request.ts @@ -0,0 +1,17 @@ +import type { FailResponse, SuccessResponse } from '../types' + +export function buildSuccessResponse(options: Omit, 'type'>): SuccessResponse { + return { + type: 'Success', + status: options.status || 200, + ...options, + } +} + +export function buildFailResponse(options: Omit, 'type'>): FailResponse { + return { + type: 'Fail', + status: options.status || 400, + ...options, + } +} diff --git a/packages/gpt-runner-shared/src/common/helpers/socket.ts b/packages/gpt-runner-shared/src/common/helpers/socket.ts new file mode 100644 index 0000000..0a5f4df --- /dev/null +++ b/packages/gpt-runner-shared/src/common/helpers/socket.ts @@ -0,0 +1,178 @@ +import * as uuid from 'uuid' +import type { BrowserSocket, MaybePromise, NodeServerSocket, Socket, WssActionName, WssActionNameRequestMap } from '../types' +import { EnvConfig } from './env-config' + +type SocketQueueFn = (socket: Socket) => void +export class WssUtils { + static _instance: WssUtils | undefined + static defaultWssUrl = `http://${new URL(EnvConfig.get('GPTR_BASE_SERVER_URL')).host}` + #wssUrl: string + #socketQueue: SocketQueueFn[] = [] + #hasConnected = false + + static get instance() { + if (!this._instance) + this._instance = new WssUtils() + + return this._instance + } + + constructor(wssUrl?: string) { + this.#wssUrl = wssUrl ?? WssUtils.defaultWssUrl + } + + static get isBrowser() { + return typeof window !== 'undefined' + } + + static isNodeServerSocket(socket: Socket | undefined): socket is NodeServerSocket { + return typeof window === 'undefined' && Boolean(socket) + } + + static isBrowserSocket(socket: Socket | undefined): socket is BrowserSocket { + return WssUtils.isBrowser && Boolean(socket) + } + + get wsUrl() { + return this.#wssUrl + } + + #wss: Socket | undefined + + #setWss = (socket: Socket) => { + this.#wss = socket + this.#socketQueue.forEach(fn => fn(socket)) + this.#socketQueue = [] + } + + get wss() { + return this.#wss + } + + connect = async (params?: { + server: any // http.createServer(expressApp); + }) => { + if (this.wss || this.#hasConnected) + return this.wss + + console.log('Connecting to WS...') + const { server } = params || {} + + try { + if (WssUtils.isBrowser) + await this.#connectBrowserSocket() + else + server && await this.#connectNodeSocket(server) + + this.#hasConnected = true + + return this.wss + } + catch (error) { + console.error('Error connecting to WS', error) + throw error + } + } + + // for nodejs + #connectNodeSocket = async (server: any) => { + if (WssUtils.isBrowser || this.wss) + return + + const { Server } = await import('socket.io') + + const serverSocket = new Server(server, { + cors: { + origin: '*', + }, + }) + + serverSocket.on('connection', (socket) => { + this.#setWss(socket) + this.#handleConnection() + }) + } + + // for browser + #connectBrowserSocket = async () => { + if (!WssUtils.isBrowser || this.wss) + return + + const { io } = await import('socket.io-client') + const socket = io(this.wsUrl) + this.#setWss(socket) + + // if (!WssUtils.isBrowserSocket(this.wss)) + // return + + // socket.on('connect', () => { + // this.#handleConnection() + // }) + } + + #handleConnection = async () => { + console.log('Connected to Socket server') + } + + on = (eventName: T, callback: (message: WssActionNameRequestMap[T]) => MaybePromise) => { + if (!this.wss) { + this.#socketQueue.push((socket) => { + socket.on(eventName, callback as any) + }) + return + } + + (this.wss as NodeServerSocket).on(eventName, callback as any) + } + + emit = (eventName: T, message: WssActionNameRequestMap[T]) => { + if (!this.wss) { + this.#socketQueue.push((socket) => { + socket.emit(eventName, message) + }) + return + } + + this.wss.emit(eventName, message) + } + + off = (eventName: T, callback: (message: WssActionNameRequestMap[T]) => MaybePromise) => { + if (!this.wss) { + this.#socketQueue.push((socket) => { + socket.off(eventName, callback as any) + }) + return + } + + this.wss.off(eventName, callback as any) + } + + emitAndWaitForRes = async (eventName: T, message: WssActionNameRequestMap[T]) => { + return new Promise((resolve, reject) => { + let destroyFn: () => void + + const timeout = setTimeout(() => { + destroyFn?.() + reject(new Error(`WS timeout, actionName: ${eventName}`)) + }, 10000) + + const __id__ = uuid.v4() + this.emit(eventName, { ...message, __id__ }) + + const handler = (message: WssActionNameRequestMap[T]) => { + if (message.__id__ !== __id__) + return + + destroyFn?.() + resolve(message) + } + + this.on(eventName, handler) + + destroyFn = () => { + clearTimeout(timeout) + this.off(eventName, handler) + } + }) + } +} diff --git a/packages/gpt-runner-shared/src/common/types/enum.ts b/packages/gpt-runner-shared/src/common/types/enum.ts index 9ec5908..118cea3 100644 --- a/packages/gpt-runner-shared/src/common/types/enum.ts +++ b/packages/gpt-runner-shared/src/common/types/enum.ts @@ -26,3 +26,11 @@ export enum ServerStorageName { FrontendState = 'frontend-state', WebPreset = 'web-preset', } + +export enum WssActionName { + Error = 'error', + StorageGetItem = 'storageGetItem', + StorageSetItem = 'storageSetItem', + StorageRemoveItem = 'storageRemoveItem', + StorageClear = 'storageClear', +} diff --git a/packages/gpt-runner-shared/src/common/types/index.ts b/packages/gpt-runner-shared/src/common/types/index.ts index 2cbf5a2..d1f13e7 100644 --- a/packages/gpt-runner-shared/src/common/types/index.ts +++ b/packages/gpt-runner-shared/src/common/types/index.ts @@ -4,3 +4,4 @@ export * from './config' export * from './enum' export * from './eventemitter' export * from './server' +export * from './socket' diff --git a/packages/gpt-runner-shared/src/common/types/server.ts b/packages/gpt-runner-shared/src/common/types/server.ts index d12030a..8ec1f5a 100644 --- a/packages/gpt-runner-shared/src/common/types/server.ts +++ b/packages/gpt-runner-shared/src/common/types/server.ts @@ -36,22 +36,35 @@ export interface GetUserConfigResData { userConfig: UserConfig } -export interface GetStorageReqParams { +export interface StorageGetItemReqParams { storageName: ServerStorageName key: string } export type ServerStorageValue = Record | null | undefined -export interface GetStorageResData { +export interface StorageGetItemResData { value: ServerStorageValue cacheDir: string } -export interface SaveStorageReqParams { +export interface StorageSetItemReqParams { storageName: ServerStorageName key: string value?: ServerStorageValue } -export type SaveStorageResData = null +export type StorageSetItemResData = null + +export interface StorageRemoveItemReqParams { + storageName: ServerStorageName + key: string +} + +export type StorageRemoveItemResData = null + +export interface StorageClearReqParams { + storageName: ServerStorageName +} + +export type StorageClearResData = null diff --git a/packages/gpt-runner-shared/src/common/types/socket.ts b/packages/gpt-runner-shared/src/common/types/socket.ts new file mode 100644 index 0000000..e3d7021 --- /dev/null +++ b/packages/gpt-runner-shared/src/common/types/socket.ts @@ -0,0 +1,67 @@ +import type { Socket as BrowserSocket } from 'socket.io-client' +import type { Socket as NodeServerSocket } from 'socket.io' +import type { MaybePromise } from './common' +import type { WssActionName } from './enum' + +import type { + BaseResponse, + StorageClearReqParams, + StorageClearResData, + StorageGetItemReqParams, + StorageGetItemResData, + StorageRemoveItemReqParams, + StorageRemoveItemResData, + StorageSetItemReqParams, + StorageSetItemResData, +} from './server' + +export interface IWssActionNameRequestMap extends Record + resData?: any +}> { + [WssActionName.Error]: { + reqParams?: { + error: Error + } + resData?: Error + } + + [WssActionName.StorageGetItem]: { + reqParams?: StorageGetItemReqParams + resData?: StorageGetItemResData + } + + [WssActionName.StorageSetItem]: { + reqParams?: StorageSetItemReqParams + resData?: StorageSetItemResData + } + + [WssActionName.StorageRemoveItem]: { + reqParams?: StorageRemoveItemReqParams + resData?: StorageRemoveItemResData + } + + [WssActionName.StorageClear]: { + reqParams?: StorageClearReqParams + resData?: StorageClearResData + } +} + +export type WssActionNameRequestMap = { + [K in keyof IWssActionNameRequestMap]: { + __id__?: string + reqParams?: IWssActionNameRequestMap[K]['reqParams'] + res?: BaseResponse + } +} + +export type WssEventsMap = { + [K in keyof WssActionNameRequestMap]: (message: WssActionNameRequestMap[K]) => MaybePromise; +} + +// export type NodeServerSocket = InstanceType> +// export type BrowserSocket = InstanceType> +// export type Socket = NodeServerSocket | BrowserSocket + +export type { BrowserSocket, NodeServerSocket } +export type Socket = BrowserSocket | NodeServerSocket diff --git a/packages/gpt-runner-shared/src/common/zod/server.zod.ts b/packages/gpt-runner-shared/src/common/zod/server.zod.ts index ab70936..7e65822 100644 --- a/packages/gpt-runner-shared/src/common/zod/server.zod.ts +++ b/packages/gpt-runner-shared/src/common/zod/server.zod.ts @@ -1,5 +1,5 @@ import { z } from 'zod' -import type { ChatStreamReqParams, GetGptFilesReqParams, GetStorageReqParams, GetUserConfigReqParams, SaveStorageReqParams } from '../types' +import type { ChatStreamReqParams, GetGptFilesReqParams, GetUserConfigReqParams, StorageClearReqParams, StorageGetItemReqParams, StorageRemoveItemReqParams, StorageSetItemReqParams } from '../types' import { SingleChatMessageSchema, SingleFileConfigSchema } from './config.zod' import { ServerStorageNameSchema } from './enum.zod' @@ -19,13 +19,22 @@ export const GetUserConfigReqParamsSchema = z.object({ rootPath: z.string(), }) satisfies z.ZodType -export const GetStorageReqParamsSchema = z.object({ +export const StorageGetItemReqParamsSchema = z.object({ storageName: ServerStorageNameSchema, key: z.string(), -}) satisfies z.ZodType +}) satisfies z.ZodType -export const SaveStorageReqParamsSchema = z.object({ +export const StorageSetItemReqParamsSchema = z.object({ storageName: ServerStorageNameSchema, key: z.string(), value: z.record(z.any()).nullable().optional(), -}) satisfies z.ZodType +}) satisfies z.ZodType + +export const StorageRemoveItemReqParamsSchema = z.object({ + storageName: ServerStorageNameSchema, + key: z.string(), +}) satisfies z.ZodType + +export const StorageClearReqParamsSchema = z.object({ + storageName: ServerStorageNameSchema, +}) satisfies z.ZodType diff --git a/packages/gpt-runner-shared/src/node/helpers/request.ts b/packages/gpt-runner-shared/src/node/helpers/request.ts index 9c97403..b33298c 100644 --- a/packages/gpt-runner-shared/src/node/helpers/request.ts +++ b/packages/gpt-runner-shared/src/node/helpers/request.ts @@ -1,23 +1,7 @@ import type { Response } from 'express' import type { z } from 'zod' import type { FailResponse, SuccessResponse } from '../../common' -import { verifyZod } from '../../common' - -export function buildSuccessResponse(options: Omit, 'type'>): SuccessResponse { - return { - type: 'Success', - status: options.status || 200, - ...options, - } -} - -export function buildFailResponse(options: Omit, 'type'>): FailResponse { - return { - type: 'Fail', - status: options.status || 400, - ...options, - } -} +import { buildFailResponse, buildSuccessResponse, verifyZod } from '../../common' export function sendSuccessResponse(res: Response, options: Omit, 'type'>): Response { return res.status(options.status || 200).json(buildSuccessResponse(options)) diff --git a/packages/gpt-runner-vscode/package.json b/packages/gpt-runner-vscode/package.json index 2d3daa0..26f189b 100644 --- a/packages/gpt-runner-vscode/package.json +++ b/packages/gpt-runner-vscode/package.json @@ -50,7 +50,13 @@ }, { "command": "gpt-runner.restartServer", - "title": "GPT Runner Restart Server", + "title": "Restart GPT Runner Server", + "category": "GPT Runner" + }, + { + "command": "gpt-runner.openChat", + "title": "Open GPT Runner Chat", + "icon": "res/logo.svg", "category": "GPT Runner" } ], @@ -64,6 +70,15 @@ "description": "Disable the GPT Runner extension" } } + }, + "menus": { + "editor/title": [ + { + "command": "gpt-runner.openChat", + "group": "navigation", + "icon": "res/logo.svg" + } + ] } }, "scripts": { @@ -84,4 +99,4 @@ "execa": "^7.1.1", "fs-extra": "^11.1.1" } -} +} \ No newline at end of file diff --git a/packages/gpt-runner-vscode/res/logo.png b/packages/gpt-runner-vscode/res/logo.png index d8ff49b..1b78178 100644 Binary files a/packages/gpt-runner-vscode/res/logo.png and b/packages/gpt-runner-vscode/res/logo.png differ diff --git a/packages/gpt-runner-vscode/res/logo.svg b/packages/gpt-runner-vscode/res/logo.svg new file mode 100644 index 0000000..e6b13ab --- /dev/null +++ b/packages/gpt-runner-vscode/res/logo.svg @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/packages/gpt-runner-vscode/res/sidebar-icon.png b/packages/gpt-runner-vscode/res/sidebar-icon.png index d8ff49b..1b78178 100644 Binary files a/packages/gpt-runner-vscode/res/sidebar-icon.png and b/packages/gpt-runner-vscode/res/sidebar-icon.png differ diff --git a/packages/gpt-runner-vscode/src/constant.ts b/packages/gpt-runner-vscode/src/constant.ts index 230f443..7ec3cc7 100644 --- a/packages/gpt-runner-vscode/src/constant.ts +++ b/packages/gpt-runner-vscode/src/constant.ts @@ -4,6 +4,7 @@ export const EXT_DISPLAY_NAME = 'GPT Runner' export enum Commands { Reload = `${EXT_NAME}.reload`, RestartServer = `${EXT_NAME}.restartServer`, + OpenChat = `${EXT_NAME}.openChat`, OpenInBrowser = `${EXT_NAME}.openInBrowser`, InsertCodes = `${EXT_NAME}.insertCodes`, DiffCodes = `${EXT_NAME}.diffCodes`, diff --git a/packages/gpt-runner-vscode/src/register/webview.ts b/packages/gpt-runner-vscode/src/register/webview.ts index f7ac380..024e49a 100644 --- a/packages/gpt-runner-vscode/src/register/webview.ts +++ b/packages/gpt-runner-vscode/src/register/webview.ts @@ -2,8 +2,9 @@ import fs from 'fs' import path from 'path' import type { ExtensionContext } from 'vscode' import * as vscode from 'vscode' +import * as uuid from 'uuid' import type { ContextLoader } from '../contextLoader' -import { EXT_NAME } from '../constant' +import { Commands, EXT_DISPLAY_NAME, EXT_NAME } from '../constant' import { createHash, getServerBaseUrl } from '../utils' import { state } from '../state' import { EventType, emitter } from '../emitter' @@ -29,28 +30,50 @@ class ChatViewProvider implements vscode.WebviewViewProvider { _context: vscode.WebviewViewResolveContext, _token: vscode.CancellationToken, ) { - const { extensionUri } = this.#extContext this.#view = webviewView state.sidebarWebviewView = webviewView - webviewView.webview.onDidReceiveMessage(({ eventName, eventData }) => { + ChatViewProvider.updateWebview(webviewView.webview, this.#extContext, this.#projectPath) + } + + static createWebviewPanel(extContext: ExtensionContext, projectPath: string): vscode.WebviewPanel { + const panel = vscode.window.createWebviewPanel( + uuid.v4(), + EXT_DISPLAY_NAME, + { + viewColumn: vscode.ViewColumn.Two, + }, + { retainContextWhenHidden: true }, + ) + + state.webviewPanel = panel + + ChatViewProvider.updateWebview(panel.webview, extContext, projectPath) + + return panel + } + + static updateWebview(webview: vscode.Webview, extContext: ExtensionContext, projectPath: string) { + const { extensionUri } = extContext + + webview.onDidReceiveMessage(({ eventName, eventData }) => { emitter.emit(eventName, eventData, EventType.ReceiveMessage) }) const baseUri = vscode.Uri.joinPath(extensionUri, './node_modules/@nicepkg/gpt-runner-web/dist/browser') - webviewView.webview.options = { + webview.options = { // Allow scripts in the webview enableScripts: true, localResourceRoots: [baseUri], } - webviewView.webview.html = this.#getHtmlForWebview(webviewView.webview) + webview.html = ChatViewProvider.getHtmlForWebview(webview, extContext, projectPath) } - #getHtmlForWebview(webview: vscode.Webview) { - const { extensionUri } = this.#extContext + static getHtmlForWebview(webview: vscode.Webview, extContext: ExtensionContext, projectPath: string) { + const { extensionUri } = extContext const baseUri = vscode.Uri.joinPath(extensionUri, './node_modules/@nicepkg/gpt-runner-web/dist/browser') @@ -69,7 +92,7 @@ class ChatViewProvider implements vscode.WebviewViewProvider { window.vscode = acquireVsCodeApi() window.__GLOBAL_CONFIG__ = { - rootPath: '${this.#projectPath}', + rootPath: '${projectPath}', serverBaseUrl: '${getServerBaseUrl()}', initialRoutePath: '/chat', showDiffCodesBtn: true, @@ -106,16 +129,25 @@ export async function registerWebview( ext: ExtensionContext, ) { const provider = new ChatViewProvider(ext, cwd) - let webviewDisposer: vscode.Disposable | undefined + let sidebarWebviewDisposer: vscode.Disposable | undefined + let webviewPanelDisposer: vscode.Disposable | undefined const dispose = () => { - webviewDisposer?.dispose?.() + sidebarWebviewDisposer?.dispose?.() + webviewPanelDisposer?.dispose?.() } const registerProvider = () => { dispose() - webviewDisposer = vscode.window.registerWebviewViewProvider(ChatViewProvider.viewType, provider) - return webviewDisposer + + sidebarWebviewDisposer = vscode.window.registerWebviewViewProvider(ChatViewProvider.viewType, provider) + webviewPanelDisposer = vscode.commands.registerCommand(Commands.OpenChat, () => { + ChatViewProvider.createWebviewPanel(ext, cwd) + }) + + return vscode.Disposable.from({ + dispose, + }) } ext.subscriptions.push(registerProvider()) diff --git a/packages/gpt-runner-vscode/src/state.ts b/packages/gpt-runner-vscode/src/state.ts index b47436c..03708b2 100644 --- a/packages/gpt-runner-vscode/src/state.ts +++ b/packages/gpt-runner-vscode/src/state.ts @@ -4,6 +4,7 @@ export interface State { serverPort: number | null statusBarItem: vscode.StatusBarItem | null sidebarWebviewView: vscode.WebviewView | null + webviewPanel: vscode.WebviewPanel | null insertCodes: string diffCodes: string } @@ -12,6 +13,7 @@ export const state: State = { serverPort: null, statusBarItem: null, sidebarWebviewView: null, + webviewPanel: null, insertCodes: '', diffCodes: 'aaa', } diff --git a/packages/gpt-runner-web/client/index.html b/packages/gpt-runner-web/client/index.html index b9ac780..d81e144 100644 --- a/packages/gpt-runner-web/client/index.html +++ b/packages/gpt-runner-web/client/index.html @@ -23,6 +23,22 @@ + + @@ -35,4 +51,4 @@ - + \ No newline at end of file diff --git a/packages/gpt-runner-web/client/src/helpers/global-config.ts b/packages/gpt-runner-web/client/src/helpers/global-config.ts index 408d2af..c28bd2f 100644 --- a/packages/gpt-runner-web/client/src/helpers/global-config.ts +++ b/packages/gpt-runner-web/client/src/helpers/global-config.ts @@ -1,4 +1,5 @@ import { getSearchParams } from '@nicepkg/gpt-runner-shared/browser' +import { EnvConfig } from '@nicepkg/gpt-runner-shared/common' export interface GlobalConfig { rootPath: string @@ -11,7 +12,7 @@ export interface GlobalConfig { window.__DEFAULT_GLOBAL_CONFIG__ = { rootPath: getSearchParams('rootPath') || '/Users/yangxiaoming/Documents/codes/gpt-runner', initialRoutePath: '/', - serverBaseUrl: '', + serverBaseUrl: EnvConfig.get('GPTR_BASE_SERVER_URL'), showDiffCodesBtn: false, showInsertCodesBtn: false, } diff --git a/packages/gpt-runner-web/client/src/networks/server-storage.ts b/packages/gpt-runner-web/client/src/networks/server-storage.ts index c1219c5..a71bf9c 100644 --- a/packages/gpt-runner-web/client/src/networks/server-storage.ts +++ b/packages/gpt-runner-web/client/src/networks/server-storage.ts @@ -1,10 +1,10 @@ -import type { BaseResponse, GetStorageReqParams, GetStorageResData, SaveStorageReqParams, SaveStorageResData } from '@nicepkg/gpt-runner-shared/common' +import type { BaseResponse, StorageGetItemReqParams, StorageGetItemResData, StorageSetItemReqParams, StorageSetItemResData } from '@nicepkg/gpt-runner-shared/common' import { getGlobalConfig } from '../helpers/global-config' -export interface GetServerStorageParams extends GetStorageReqParams { +export interface GetServerStorageParams extends StorageGetItemReqParams { } -export type GetServerStorageRes = BaseResponse +export type GetServerStorageRes = BaseResponse export async function getServerStorage(params: GetServerStorageParams): Promise { const { storageName, key } = params @@ -19,10 +19,10 @@ export async function getServerStorage(params: GetServerStorageParams): Promise< return data } -export interface SaveServerStorageParams extends SaveStorageReqParams { +export interface SaveServerStorageParams extends StorageSetItemReqParams { } -export type SaveServerStorageRes = BaseResponse +export type SaveServerStorageRes = BaseResponse export async function saveServerStorage(params: SaveServerStorageParams): Promise { const { storageName, key, value } = params diff --git a/packages/gpt-runner-web/client/src/store/zustand/global/sidebar-tree.slice.ts b/packages/gpt-runner-web/client/src/store/zustand/global/sidebar-tree.slice.ts index b09415a..bd98f74 100644 --- a/packages/gpt-runner-web/client/src/store/zustand/global/sidebar-tree.slice.ts +++ b/packages/gpt-runner-web/client/src/store/zustand/global/sidebar-tree.slice.ts @@ -141,7 +141,9 @@ export const createSidebarTreeSlice: StateCreator< if (item.type === GptFileTreeItemType.File) { gptFileIds.push(item.id) + const chatIds = currentGptFileIdChatIdsMap.get(item.id) || [] + result.children = chatIds.map((chatId) => { const chatInfo = state.getChatInfo(chatId) chatInfo.parentId = item.id diff --git a/packages/gpt-runner-web/client/src/store/zustand/storage.ts b/packages/gpt-runner-web/client/src/store/zustand/storage.ts index fed3c57..fb08f16 100644 --- a/packages/gpt-runner-web/client/src/store/zustand/storage.ts +++ b/packages/gpt-runner-web/client/src/store/zustand/storage.ts @@ -20,7 +20,7 @@ async function getStateFromServerOnce(key: string) { } // will save each action state to server -const debounceSaveStateToServer = debounce(async (key: string, value: ServerStorageValue) => { +const debounceSaveStateToServerFn = debounce(async (key: string, value: ServerStorageValue) => { if (hasUpdateStateFromRemote !== 'finish') return @@ -31,6 +31,13 @@ const debounceSaveStateToServer = debounce(async (key: string, value: ServerStor }) }, 1000) +function debounceSaveStateToServer(key: string, value: ServerStorageValue) { + if (hasUpdateStateFromRemote !== 'finish') + return + + debounceSaveStateToServerFn(key, value) +} + export class CustomStorage implements StateStorage { #storage: Storage diff --git a/packages/gpt-runner-web/client/src/store/zustand/utils.ts b/packages/gpt-runner-web/client/src/store/zustand/utils.ts index 2fd285c..35997f6 100644 --- a/packages/gpt-runner-web/client/src/store/zustand/utils.ts +++ b/packages/gpt-runner-web/client/src/store/zustand/utils.ts @@ -18,7 +18,6 @@ export function resetAllState() { export function createStore(devtoolsName: string) { const newCreate = (store: any) => { - const defaultState = create(store).getState() let result: any // https://github.com/pmndrs/zustand/issues/852#issuecomment-1059783350 @@ -30,11 +29,13 @@ export function createStore(devtoolsName: string) { }), ) } - - result = create(store) + else { + result = create(store) + } // reset state of this store result.resetState = () => { + const defaultState = create(store).getState() result.setState(cloneDeep(defaultState), true) } diff --git a/packages/gpt-runner-web/package.json b/packages/gpt-runner-web/package.json index efb4e0d..3d587af 100644 --- a/packages/gpt-runner-web/package.json +++ b/packages/gpt-runner-web/package.json @@ -57,7 +57,7 @@ "build:server": "unbuild", "dev": "pnpm dev:server & pnpm dev:client", "dev:client": "vite --config ./client/vite.config.ts", - "dev:server": "cross-env NODE_OPTIONS='--experimental-fetch' NODE_NO_WARNINGS='1' DEBUG='enabled' pnpm esno server/start-server.ts --auto-free-port", + "dev:server": "cross-env NODE_ENV=development NODE_OPTIONS='--experimental-fetch' NODE_NO_WARNINGS='1' DEBUG='enabled' pnpm esno server/start-server.ts --auto-free-port", "start": "cross-env NODE_OPTIONS='--experimental-fetch' NODE_NO_WARNINGS='1' DEBUG='enabled' node dist/start-server.cjs --auto-free-port", "stub": "unbuild --stub" }, @@ -104,4 +104,4 @@ "@vitejs/plugin-react": "^4.0.0", "vite": "^4.3.9" } -} +} \ No newline at end of file diff --git a/packages/gpt-runner-web/server/index.ts b/packages/gpt-runner-web/server/index.ts index 862faa6..4baa78c 100644 --- a/packages/gpt-runner-web/server/index.ts +++ b/packages/gpt-runner-web/server/index.ts @@ -1,5 +1,6 @@ import './src/proxy' import path from 'node:path' +import http from 'node:http' import type { Express } from 'express' import express from 'express' import cors from 'cors' @@ -49,7 +50,11 @@ export async function startServer(props: StartServerProps): Promise { app.use(errorHandlerMiddleware) - app.listen(finalPort, () => console.log(`Server is running on port ${finalPort}`)) + const server = http.createServer(app) + + // await processWsControllers(server) + + server.listen(finalPort, () => console.log(`Server is running on port ${finalPort}`)) return app } diff --git a/packages/gpt-runner-web/server/src/controllers/chatgpt.controller.ts b/packages/gpt-runner-web/server/src/controllers/chatgpt.controller.ts index 9da70d8..6a4c43f 100644 --- a/packages/gpt-runner-web/server/src/controllers/chatgpt.controller.ts +++ b/packages/gpt-runner-web/server/src/controllers/chatgpt.controller.ts @@ -1,7 +1,7 @@ import type { Request, Response } from 'express' import type { ChatStreamReqParams, FailResponse, SuccessResponse } from '@nicepkg/gpt-runner-shared/common' -import { ChatStreamReqParamsSchema, EnvConfig } from '@nicepkg/gpt-runner-shared/common' -import { PathUtils, buildFailResponse, buildSuccessResponse, sendFailResponse, sendSuccessResponse, verifyParamsByZod } from '@nicepkg/gpt-runner-shared/node' +import { ChatStreamReqParamsSchema, EnvConfig, buildFailResponse, buildSuccessResponse } from '@nicepkg/gpt-runner-shared/common' +import { PathUtils, sendFailResponse, sendSuccessResponse, verifyParamsByZod } from '@nicepkg/gpt-runner-shared/node' import { loadUserConfig } from '@nicepkg/gpt-runner-core' import { chatgptChain } from '../services' import type { ControllerConfig } from './../types' diff --git a/packages/gpt-runner-web/server/src/controllers/config.controller.ts b/packages/gpt-runner-web/server/src/controllers/config.controller.ts index 5103e9e..60bd6a6 100644 --- a/packages/gpt-runner-web/server/src/controllers/config.controller.ts +++ b/packages/gpt-runner-web/server/src/controllers/config.controller.ts @@ -1,6 +1,6 @@ import { PathUtils, sendFailResponse, sendSuccessResponse, verifyParamsByZod } from '@nicepkg/gpt-runner-shared/node' import type { GetUserConfigReqParams, GetUserConfigResData } from '@nicepkg/gpt-runner-shared/common' -import { GetUserConfigReqParamsSchema, resetUserConfigUnsafeKey } from '@nicepkg/gpt-runner-shared/common' +import { EnvConfig, GetUserConfigReqParamsSchema, resetUserConfigUnsafeKey } from '@nicepkg/gpt-runner-shared/common' import { loadUserConfig } from '@nicepkg/gpt-runner-core' import pkg from '../../../package.json' import type { ControllerConfig } from '../types' @@ -19,6 +19,17 @@ export const configControllers: ControllerConfig = { }) }, }, + { + url: '/env.js', + method: 'get', + handler: async (req, res) => { + const envMap = EnvConfig.getClientEnvVarsInServerSide() + + // response a javascript file + res.setHeader('Content-Type', 'application/javascript') + res.send(`window.__env__ = ${JSON.stringify(envMap)}`) + }, + }, { url: '/user-config', method: 'get', diff --git a/packages/gpt-runner-web/server/src/controllers/index.ts b/packages/gpt-runner-web/server/src/controllers/index.ts index 7e7ab4a..584c63f 100644 --- a/packages/gpt-runner-web/server/src/controllers/index.ts +++ b/packages/gpt-runner-web/server/src/controllers/index.ts @@ -1,9 +1,11 @@ import type { NextFunction, Router } from 'express' +import { WssActionName, WssUtils, buildFailResponse } from '@nicepkg/gpt-runner-shared/common' import type { Controller, ControllerConfig } from '../types' import { chatgptControllers } from './chatgpt.controller' import { configControllers } from './config.controller' import { gptFilesControllers } from './gpt-files.controller' import { storageControllers } from './storage.controller' +import { allWsControllersConfig } from './ws' export function processControllers(router: Router) { const allControllersConfig: ControllerConfig[] = [ @@ -33,3 +35,34 @@ export function processControllers(router: Router) { }) }) } + +export async function processWsControllers(server: any) { + await WssUtils.instance.connect({ server }) + + allWsControllersConfig.forEach((controllerConfig) => { + const { controllers } = controllerConfig + + controllers.forEach((controller) => { + const { actionName, handler } = controller + + WssUtils.instance.on(actionName, async (params: Record) => { + console.log(`[WS] ${actionName} params:`, params) + try { + return await handler(params as any) + } + catch (error: any) { + const errRes = buildFailResponse({ data: error, message: error?.message || String(error) }) + + WssUtils.instance.emit(actionName, { + reqParams: params as any, + res: errRes, + }) + + WssUtils.instance.emit(WssActionName.Error, { + res: errRes, + }) + } + }) + }) + }) +} diff --git a/packages/gpt-runner-web/server/src/controllers/storage.controller.ts b/packages/gpt-runner-web/server/src/controllers/storage.controller.ts index 0207541..62c9ee2 100644 --- a/packages/gpt-runner-web/server/src/controllers/storage.controller.ts +++ b/packages/gpt-runner-web/server/src/controllers/storage.controller.ts @@ -1,6 +1,6 @@ import { getStorage, sendSuccessResponse, verifyParamsByZod } from '@nicepkg/gpt-runner-shared/node' -import type { GetStorageReqParams, GetStorageResData, SaveStorageReqParams, SaveStorageResData } from '@nicepkg/gpt-runner-shared/common' -import { GetStorageReqParamsSchema, SaveStorageReqParamsSchema } from '@nicepkg/gpt-runner-shared/common' +import type { StorageClearReqParams, StorageClearResData, StorageGetItemReqParams, StorageGetItemResData, StorageRemoveItemReqParams, StorageRemoveItemResData, StorageSetItemReqParams, StorageSetItemResData } from '@nicepkg/gpt-runner-shared/common' +import { StorageClearReqParamsSchema, StorageGetItemReqParamsSchema, StorageRemoveItemReqParamsSchema, StorageSetItemReqParamsSchema } from '@nicepkg/gpt-runner-shared/common' import type { ControllerConfig } from '../types' export const storageControllers: ControllerConfig = { @@ -10,9 +10,9 @@ export const storageControllers: ControllerConfig = { url: '/', method: 'get', handler: async (req, res) => { - const query = req.query as GetStorageReqParams + const query = req.query as StorageGetItemReqParams - verifyParamsByZod(query, GetStorageReqParamsSchema) + verifyParamsByZod(query, StorageGetItemReqParamsSchema) const { key, storageName } = query @@ -23,7 +23,7 @@ export const storageControllers: ControllerConfig = { data: { value, cacheDir, - } satisfies GetStorageResData, + } satisfies StorageGetItemResData, }) }, }, @@ -31,27 +31,53 @@ export const storageControllers: ControllerConfig = { url: '/', method: 'post', handler: async (req, res) => { - const body = req.body as SaveStorageReqParams + const body = req.body as StorageSetItemReqParams - verifyParamsByZod(body, SaveStorageReqParamsSchema) + verifyParamsByZod(body, StorageSetItemReqParamsSchema) const { storageName, key, value } = body - const { storage } = await getStorage(storageName) - switch (value) { - case undefined: - // remove - await storage.delete(key) - break - default: - // set - await storage.set(key, value) - break - } + await storage.set(key, value) sendSuccessResponse(res, { - data: null satisfies SaveStorageResData, + data: null satisfies StorageSetItemResData, + }) + }, + }, + { + url: '/', + method: 'delete', + handler: async (req, res) => { + const body = req.body as StorageRemoveItemReqParams + + verifyParamsByZod(body, StorageRemoveItemReqParamsSchema) + + const { key, storageName } = body + const { storage } = await getStorage(storageName) + + await storage.delete(key) + + sendSuccessResponse(res, { + data: null satisfies StorageRemoveItemResData, + }) + }, + }, + { + url: '/clear', + method: 'post', + handler: async (req, res) => { + const body = req.body as StorageClearReqParams + + verifyParamsByZod(body, StorageClearReqParamsSchema) + + const { storageName } = body + const { storage } = await getStorage(storageName) + + await storage.clear() + + sendSuccessResponse(res, { + data: null satisfies StorageClearResData, }) }, }, diff --git a/packages/gpt-runner-web/server/src/controllers/ws/index.ts b/packages/gpt-runner-web/server/src/controllers/ws/index.ts new file mode 100644 index 0000000..a82c18f --- /dev/null +++ b/packages/gpt-runner-web/server/src/controllers/ws/index.ts @@ -0,0 +1,4 @@ +import type { WssControllerConfig } from '../../types' +import { storageControllers } from './storage.ws-controller' + +export const allWsControllersConfig: WssControllerConfig[] = [storageControllers] diff --git a/packages/gpt-runner-web/server/src/controllers/ws/storage.ws-controller.ts b/packages/gpt-runner-web/server/src/controllers/ws/storage.ws-controller.ts new file mode 100644 index 0000000..9f9c068 --- /dev/null +++ b/packages/gpt-runner-web/server/src/controllers/ws/storage.ws-controller.ts @@ -0,0 +1,84 @@ +import type { StorageClearResData, StorageGetItemReqParams, StorageGetItemResData, StorageRemoveItemReqParams, StorageRemoveItemResData, StorageSetItemReqParams, StorageSetItemResData } from '@nicepkg/gpt-runner-shared/common' +import { StorageClearReqParamsSchema, StorageGetItemReqParamsSchema, StorageRemoveItemReqParamsSchema, StorageSetItemReqParamsSchema, WssActionName, WssUtils, buildSuccessResponse } from '@nicepkg/gpt-runner-shared/common' +import { getStorage, verifyParamsByZod } from '@nicepkg/gpt-runner-shared/node' +import type { WssControllerConfig } from '../../types' + +export const storageControllers: WssControllerConfig = { + controllers: [ + { + actionName: WssActionName.StorageGetItem, + handler: async (params: StorageGetItemReqParams) => { + verifyParamsByZod(params, StorageGetItemReqParamsSchema) + + const { key, storageName } = params + + const { storage, cacheDir } = await getStorage(storageName) + const value = await storage.get(key) + + WssUtils.instance.emit(WssActionName.StorageGetItem, { + reqParams: params, + res: buildSuccessResponse({ + data: { + value, + cacheDir, + } satisfies StorageGetItemResData, + }), + }) + }, + }, + { + actionName: WssActionName.StorageSetItem, + handler: async (params: StorageSetItemReqParams) => { + verifyParamsByZod(params, StorageSetItemReqParamsSchema) + + const { storageName, key, value } = params + + const { storage } = await getStorage(storageName) + await storage.set(key, value) + + WssUtils.instance.emit(WssActionName.StorageSetItem, { + reqParams: params, + res: buildSuccessResponse({ + data: null satisfies StorageSetItemResData, + }), + }) + }, + }, + { + actionName: WssActionName.StorageRemoveItem, + handler: async (params: StorageRemoveItemReqParams) => { + verifyParamsByZod(params, StorageRemoveItemReqParamsSchema) + + const { key, storageName } = params + + const { storage } = await getStorage(storageName) + await storage.delete(key) + + WssUtils.instance.emit(WssActionName.StorageRemoveItem, { + reqParams: params, + res: buildSuccessResponse({ + data: null satisfies StorageRemoveItemResData, + }), + }) + }, + }, + { + actionName: WssActionName.StorageClear, + handler: async (params) => { + verifyParamsByZod(params, StorageClearReqParamsSchema) + + const { storageName } = params + + const { storage } = await getStorage(storageName) + await storage.clear() + + WssUtils.instance.emit(WssActionName.StorageClear, { + reqParams: params, + res: buildSuccessResponse({ + data: null satisfies StorageClearResData, + }), + }) + }, + }, + ], +} diff --git a/packages/gpt-runner-web/server/src/types.ts b/packages/gpt-runner-web/server/src/types.ts index 6dbe927..963776e 100644 --- a/packages/gpt-runner-web/server/src/types.ts +++ b/packages/gpt-runner-web/server/src/types.ts @@ -1,11 +1,21 @@ +import type { WssActionName } from '@nicepkg/gpt-runner-shared/common' import type { NextFunction, Request, Response } from 'express' -export interface ControllerConfig { - namespacePath: string - controllers: Controller[] -} export interface Controller { url: string method: 'all' | 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options' | 'head' handler: (req: Request, any, any, Record>, res: Response, next: NextFunction) => Promise } +export interface ControllerConfig { + namespacePath: string + controllers: Controller[] +} + +export interface WssController { + actionName: WssActionName + handler: (params: any) => Promise +} + +export interface WssControllerConfig { + controllers: WssController[] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 55eca34..d748efc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -335,6 +335,12 @@ importers: minimatch: specifier: ^9.0.1 version: 9.0.1 + socket.io: + specifier: ^4.6.2 + version: 4.6.2 + socket.io-client: + specifier: ^4.6.2 + version: 4.6.2 zod: specifier: ^3.21.4 version: 3.21.4 @@ -3968,6 +3974,10 @@ packages: webpack-sources: 3.2.3 dev: false + /@socket.io/component-emitter@3.1.0: + resolution: {integrity: sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==} + dev: false + /@surma/rollup-plugin-off-main-thread@2.2.3: resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} dependencies: @@ -4354,13 +4364,11 @@ packages: /@types/cookie@0.4.1: resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} - dev: true /@types/cors@2.8.13: resolution: {integrity: sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==} dependencies: '@types/node': 18.16.9 - dev: true /@types/debug@4.1.7: resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==} @@ -5524,6 +5532,11 @@ packages: /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + /base64id@2.0.0: + resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} + engines: {node: ^4.5.0 || >= 5.9} + dev: false + /batch@0.6.1: resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} dev: false @@ -6295,7 +6308,6 @@ packages: /cookie@0.4.2: resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} engines: {node: '>= 0.6'} - dev: true /cookie@0.5.0: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} @@ -7368,6 +7380,45 @@ packages: once: 1.4.0 dev: false + /engine.io-client@6.4.0: + resolution: {integrity: sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g==} + dependencies: + '@socket.io/component-emitter': 3.1.0 + debug: 4.3.4 + engine.io-parser: 5.0.7 + ws: 8.11.0 + xmlhttprequest-ssl: 2.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + + /engine.io-parser@5.0.7: + resolution: {integrity: sha512-P+jDFbvK6lE3n1OL+q9KuzdOFWkkZ/cMV9gol/SbVfpyqfvrfrFTOFJ6fQm2VC3PZHlU3QPhVwmbsCnauHF2MQ==} + engines: {node: '>=10.0.0'} + dev: false + + /engine.io@6.4.2: + resolution: {integrity: sha512-FKn/3oMiJjrOEOeUub2WCox6JhxBXq/Zn3fZOMCBxKnNYtsdKjxhl7yR3fZhM9PV+rdE75SU5SYMc+2PGzo+Tg==} + engines: {node: '>=10.0.0'} + dependencies: + '@types/cookie': 0.4.1 + '@types/cors': 2.8.13 + '@types/node': 18.16.9 + accepts: 1.3.8 + base64id: 2.0.0 + cookie: 0.4.2 + cors: 2.8.5 + debug: 4.3.4 + engine.io-parser: 5.0.7 + ws: 8.11.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + /enhanced-resolve@5.14.1: resolution: {integrity: sha512-Vklwq2vDKtl0y/vtwjSesgJ5MYS7Etuk5txS8VdKL4AOS1aUlD96zqIfsOSLQsdv3xgMRbtkWM8eG9XDfKUPow==} engines: {node: '>=10.13.0'} @@ -14459,6 +14510,55 @@ packages: engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} dev: true + /socket.io-adapter@2.5.2: + resolution: {integrity: sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==} + dependencies: + ws: 8.11.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + + /socket.io-client@4.6.2: + resolution: {integrity: sha512-OwWrMbbA8wSqhBAR0yoPK6EdQLERQAYjXb3A0zLpgxfM1ZGLKoxHx8gVmCHA6pcclRX5oA/zvQf7bghAS11jRA==} + engines: {node: '>=10.0.0'} + dependencies: + '@socket.io/component-emitter': 3.1.0 + debug: 4.3.4 + engine.io-client: 6.4.0 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + + /socket.io-parser@4.2.4: + resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} + engines: {node: '>=10.0.0'} + dependencies: + '@socket.io/component-emitter': 3.1.0 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + + /socket.io@4.6.2: + resolution: {integrity: sha512-Vp+lSks5k0dewYTfwgPT9UeGGd+ht7sCpB7p0e83VgO4X/AHYWhXITMrNk/pg8syY2bpx23ptClCQuHhqi2BgQ==} + engines: {node: '>=10.0.0'} + dependencies: + accepts: 1.3.8 + base64id: 2.0.0 + debug: 4.3.4 + engine.io: 6.4.2 + socket.io-adapter: 2.5.2 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + /sockjs@0.3.24: resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==} dependencies: @@ -16827,6 +16927,19 @@ packages: optional: true dev: false + /ws@8.11.0: + resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==} + 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 + dev: false + /ws@8.13.0: resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} engines: {node: '>=10.0.0'} @@ -16860,6 +16973,11 @@ packages: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} dev: true + /xmlhttprequest-ssl@2.0.0: + resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==} + engines: {node: '>=0.4.0'} + dev: false + /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'}