Skip to content

Commit e84ddec

Browse files
committed
fix(gpt-runner-web): state not share different host
1 parent 1fbd594 commit e84ddec

35 files changed

+766
-89
lines changed

packages/gpt-runner-shared/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@
6262
"find-free-ports": "*",
6363
"ip": "*",
6464
"minimatch": "*",
65+
"socket.io": "*",
66+
"socket.io-client": "*",
6567
"zod": "*"
6668
},
6769
"dependencies": {
@@ -72,11 +74,13 @@
7274
"find-free-ports": "^3.1.1",
7375
"ip": "^1.1.8",
7476
"minimatch": "^9.0.1",
77+
"socket.io": "^4.6.2",
78+
"socket.io-client": "^4.6.2",
7579
"zod": "^3.21.4"
7680
},
7781
"devDependencies": {
7882
"@types/express": "^4.17.17",
7983
"@types/ip": "^1.1.0",
8084
"express": "^4.18.2"
8185
}
82-
}
86+
}

packages/gpt-runner-shared/src/common/helpers/common.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,21 @@ export function tryParseJson(str: string) {
8686
return JSON.parse(str)
8787
}
8888
catch (e) {
89+
console.error('tryParseJson error: ', e)
8990
return {}
9091
}
9192
}
9293

94+
export function tryStringifyJson(obj: any) {
95+
try {
96+
return JSON.stringify(obj)
97+
}
98+
catch (e) {
99+
console.error('tryStringifyJson error: ', e)
100+
return ''
101+
}
102+
}
103+
93104
export function debounce<T extends (...args: any[]) => any>(callback: T, wait: number) {
94105
let timeout: ReturnType<typeof setTimeout> | undefined
95106

packages/gpt-runner-shared/src/common/helpers/env-config.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ interface EnvVarConfig {
1515

1616
/**
1717
* if true, this env var will only be available on server side
18-
* window.__config__ will not have this env var
18+
* window.__env__ will not have this env var
1919
*
2020
* @default false
2121
*/
@@ -26,7 +26,9 @@ const config: Record<EnvName, EnvVarConfig> = {
2626
NODE_ENV: {
2727
defaultValue: 'production',
2828
},
29-
OPENAI_KEY: {},
29+
OPENAI_KEY: {
30+
serverSideOnly: true,
31+
},
3032
GPTR_BASE_SERVER_URL: {
3133
defaultValue: 'http://localhost:3003',
3234
},
@@ -42,7 +44,7 @@ export class EnvConfig {
4244

4345
// client side
4446
if (typeof window !== 'undefined' && !serverSideOnly)
45-
return window?.__config__?.[key] ?? defaultValue ?? ''
47+
return window?.__env__?.[key] ?? defaultValue ?? ''
4648

4749
// server side
4850
return process.env[key] ?? defaultValue ?? ''
@@ -51,7 +53,7 @@ export class EnvConfig {
5153
/**
5254
* get all env vars on server or client side
5355
* @param type server or client, get all allowed env vars on that scope
54-
* @param getWays all or process, get env vars both on process and window.__config__ or only process.env
56+
* @param getWays all or process, get env vars both on process and window.__env__ or only process.env
5557
* @returns env vars key value map
5658
*/
5759
static getAllEnvVarsOnScopes(
@@ -93,7 +95,7 @@ export class EnvConfig {
9395

9496
/**
9597
* for /api/config
96-
* @returns env vars key value map for window.__config__
98+
* @returns env vars key value map for window.__env__
9799
*/
98100
static getClientEnvVarsInServerSide(): Partial<Record<EnvName, string>> {
99101
return EnvConfig.getAllEnvVarsOnScopes('client', 'process')
@@ -106,6 +108,6 @@ declare global {
106108
}
107109

108110
interface Window {
109-
__config__?: Partial<Env>
111+
__env__?: Partial<Env>
110112
}
111113
}

packages/gpt-runner-shared/src/common/helpers/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ export * from './create-filter-pattern'
44
export * from './debug'
55
export * from './env-config'
66
export * from './is'
7+
export * from './request'
8+
export * from './socket'
79
export * from './verify-zod'
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { FailResponse, SuccessResponse } from '../types'
2+
3+
export function buildSuccessResponse<T>(options: Omit<SuccessResponse<T>, 'type'>): SuccessResponse<T> {
4+
return {
5+
type: 'Success',
6+
status: options.status || 200,
7+
...options,
8+
}
9+
}
10+
11+
export function buildFailResponse<T>(options: Omit<FailResponse<T>, 'type'>): FailResponse<T> {
12+
return {
13+
type: 'Fail',
14+
status: options.status || 400,
15+
...options,
16+
}
17+
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import * as uuid from 'uuid'
2+
import type { BrowserSocket, MaybePromise, NodeServerSocket, Socket, WssActionName, WssActionNameRequestMap } from '../types'
3+
import { EnvConfig } from './env-config'
4+
5+
type SocketQueueFn = (socket: Socket) => void
6+
export class WssUtils {
7+
static _instance: WssUtils | undefined
8+
static defaultWssUrl = `http://${new URL(EnvConfig.get('GPTR_BASE_SERVER_URL')).host}`
9+
#wssUrl: string
10+
#socketQueue: SocketQueueFn[] = []
11+
#hasConnected = false
12+
13+
static get instance() {
14+
if (!this._instance)
15+
this._instance = new WssUtils()
16+
17+
return this._instance
18+
}
19+
20+
constructor(wssUrl?: string) {
21+
this.#wssUrl = wssUrl ?? WssUtils.defaultWssUrl
22+
}
23+
24+
static get isBrowser() {
25+
return typeof window !== 'undefined'
26+
}
27+
28+
static isNodeServerSocket(socket: Socket | undefined): socket is NodeServerSocket {
29+
return typeof window === 'undefined' && Boolean(socket)
30+
}
31+
32+
static isBrowserSocket(socket: Socket | undefined): socket is BrowserSocket {
33+
return WssUtils.isBrowser && Boolean(socket)
34+
}
35+
36+
get wsUrl() {
37+
return this.#wssUrl
38+
}
39+
40+
#wss: Socket | undefined
41+
42+
#setWss = (socket: Socket) => {
43+
this.#wss = socket
44+
this.#socketQueue.forEach(fn => fn(socket))
45+
this.#socketQueue = []
46+
}
47+
48+
get wss() {
49+
return this.#wss
50+
}
51+
52+
connect = async (params?: {
53+
server: any // http.createServer(expressApp);
54+
}) => {
55+
if (this.wss || this.#hasConnected)
56+
return this.wss
57+
58+
console.log('Connecting to WS...')
59+
const { server } = params || {}
60+
61+
try {
62+
if (WssUtils.isBrowser)
63+
await this.#connectBrowserSocket()
64+
else
65+
server && await this.#connectNodeSocket(server)
66+
67+
this.#hasConnected = true
68+
69+
return this.wss
70+
}
71+
catch (error) {
72+
console.error('Error connecting to WS', error)
73+
throw error
74+
}
75+
}
76+
77+
// for nodejs
78+
#connectNodeSocket = async (server: any) => {
79+
if (WssUtils.isBrowser || this.wss)
80+
return
81+
82+
const { Server } = await import('socket.io')
83+
84+
const serverSocket = new Server(server, {
85+
cors: {
86+
origin: '*',
87+
},
88+
})
89+
90+
serverSocket.on('connection', (socket) => {
91+
this.#setWss(socket)
92+
this.#handleConnection()
93+
})
94+
}
95+
96+
// for browser
97+
#connectBrowserSocket = async () => {
98+
if (!WssUtils.isBrowser || this.wss)
99+
return
100+
101+
const { io } = await import('socket.io-client')
102+
const socket = io(this.wsUrl)
103+
this.#setWss(socket)
104+
105+
// if (!WssUtils.isBrowserSocket(this.wss))
106+
// return
107+
108+
// socket.on('connect', () => {
109+
// this.#handleConnection()
110+
// })
111+
}
112+
113+
#handleConnection = async () => {
114+
console.log('Connected to Socket server')
115+
}
116+
117+
on = <T extends WssActionName>(eventName: T, callback: (message: WssActionNameRequestMap[T]) => MaybePromise<void>) => {
118+
if (!this.wss) {
119+
this.#socketQueue.push((socket) => {
120+
socket.on(eventName, callback as any)
121+
})
122+
return
123+
}
124+
125+
(this.wss as NodeServerSocket).on(eventName, callback as any)
126+
}
127+
128+
emit = <T extends WssActionName>(eventName: T, message: WssActionNameRequestMap[T]) => {
129+
if (!this.wss) {
130+
this.#socketQueue.push((socket) => {
131+
socket.emit(eventName, message)
132+
})
133+
return
134+
}
135+
136+
this.wss.emit(eventName, message)
137+
}
138+
139+
off = <T extends WssActionName>(eventName: T, callback: (message: WssActionNameRequestMap[T]) => MaybePromise<void>) => {
140+
if (!this.wss) {
141+
this.#socketQueue.push((socket) => {
142+
socket.off(eventName, callback as any)
143+
})
144+
return
145+
}
146+
147+
this.wss.off(eventName, callback as any)
148+
}
149+
150+
emitAndWaitForRes = async <T extends WssActionName>(eventName: T, message: WssActionNameRequestMap[T]) => {
151+
return new Promise<WssActionNameRequestMap[T]>((resolve, reject) => {
152+
let destroyFn: () => void
153+
154+
const timeout = setTimeout(() => {
155+
destroyFn?.()
156+
reject(new Error(`WS timeout, actionName: ${eventName}`))
157+
}, 10000)
158+
159+
const __id__ = uuid.v4()
160+
this.emit(eventName, { ...message, __id__ })
161+
162+
const handler = (message: WssActionNameRequestMap[T]) => {
163+
if (message.__id__ !== __id__)
164+
return
165+
166+
destroyFn?.()
167+
resolve(message)
168+
}
169+
170+
this.on(eventName, handler)
171+
172+
destroyFn = () => {
173+
clearTimeout(timeout)
174+
this.off(eventName, handler)
175+
}
176+
})
177+
}
178+
}

packages/gpt-runner-shared/src/common/types/enum.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,11 @@ export enum ServerStorageName {
2626
FrontendState = 'frontend-state',
2727
WebPreset = 'web-preset',
2828
}
29+
30+
export enum WssActionName {
31+
Error = 'error',
32+
StorageGetItem = 'storageGetItem',
33+
StorageSetItem = 'storageSetItem',
34+
StorageRemoveItem = 'storageRemoveItem',
35+
StorageClear = 'storageClear',
36+
}

packages/gpt-runner-shared/src/common/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export * from './config'
44
export * from './enum'
55
export * from './eventemitter'
66
export * from './server'
7+
export * from './socket'

packages/gpt-runner-shared/src/common/types/server.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,35 @@ export interface GetUserConfigResData {
3636
userConfig: UserConfig
3737
}
3838

39-
export interface GetStorageReqParams {
39+
export interface StorageGetItemReqParams {
4040
storageName: ServerStorageName
4141
key: string
4242
}
4343

4444
export type ServerStorageValue = Record<string, any> | null | undefined
4545

46-
export interface GetStorageResData {
46+
export interface StorageGetItemResData {
4747
value: ServerStorageValue
4848
cacheDir: string
4949
}
5050

51-
export interface SaveStorageReqParams {
51+
export interface StorageSetItemReqParams {
5252
storageName: ServerStorageName
5353
key: string
5454
value?: ServerStorageValue
5555
}
5656

57-
export type SaveStorageResData = null
57+
export type StorageSetItemResData = null
58+
59+
export interface StorageRemoveItemReqParams {
60+
storageName: ServerStorageName
61+
key: string
62+
}
63+
64+
export type StorageRemoveItemResData = null
65+
66+
export interface StorageClearReqParams {
67+
storageName: ServerStorageName
68+
}
69+
70+
export type StorageClearResData = null

0 commit comments

Comments
 (0)