-
-
Notifications
You must be signed in to change notification settings - Fork 77
Expand file tree
/
Copy pathws.ts
More file actions
127 lines (110 loc) · 4.26 KB
/
ws.ts
File metadata and controls
127 lines (110 loc) · 4.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/* eslint-disable no-console */
import type { ConnectionMeta, DevToolsNodeContext, DevToolsNodeRpcSession, DevToolsRpcClientFunctions, DevToolsRpcServerFunctions } from '@vitejs/devtools-kit'
import type { WebSocket } from 'ws'
import type { RpcFunctionsHost } from './host-functions'
import { AsyncLocalStorage } from 'node:async_hooks'
import process from 'node:process'
import { createWsRpcPreset } from '@vitejs/devtools-rpc/presets/ws/server'
import { createRpcServer } from '@vitejs/devtools-rpc/server'
import c from 'ansis'
import { getPort } from 'get-port-please'
import { createDebug } from 'obug'
import { MARK_INFO } from './constants'
import { getInternalContext } from './context-internal'
const debugInvoked = createDebug('vite:devtools:rpc:invoked')
export interface CreateWsServerOptions {
cwd: string
portWebSocket?: number
hostWebSocket: string
base?: string
context: DevToolsNodeContext
}
const ANONYMOUS_SCOPE = 'vite:anonymous:'
export async function createWsServer(options: CreateWsServerOptions) {
const rpcHost = options.context.rpc as unknown as RpcFunctionsHost
const port = options.portWebSocket ?? await getPort({ port: 7812, random: true })!
const host = options.hostWebSocket ?? 'localhost'
const wsClients = new Set<WebSocket>()
const context = options.context
const contextInternal = getInternalContext(context)
const isClientAuthDisabled = context.mode === 'build' || context.viteConfig.devtools?.clientAuth === false || process.env.VITE_DEVTOOLS_DISABLE_CLIENT_AUTH === 'true'
if (isClientAuthDisabled) {
console.warn('[Vite DevTools] Client authentication is disabled. Any browser can connect to the devtools and access to your server and filesystem.')
}
const preset = createWsRpcPreset({
port,
host,
onConnected: (ws, req, meta) => {
const url = new URL(req.url ?? '', 'http://localhost')
const authId = url.searchParams.get('vite_devtools_auth_id') ?? undefined
if (isClientAuthDisabled) {
meta.isTrusted = true
}
else if (authId && contextInternal.storage.auth.value().trusted[authId]) {
meta.isTrusted = true
meta.clientAuthId = authId
}
wsClients.add(ws)
const color = meta.isTrusted ? c.green : c.yellow
console.log(color`${MARK_INFO} Websocket client connected. [${meta.id}] [${meta.clientAuthId}] (${meta.isTrusted ? 'trusted' : 'untrusted'})`)
},
onDisconnected: (ws, meta) => {
wsClients.delete(ws)
console.log(c.red`${MARK_INFO} Websocket client disconnected. [${meta.id}]`)
},
})
const asyncStorage = new AsyncLocalStorage<DevToolsNodeRpcSession>()
const rpcGroup = createRpcServer<DevToolsRpcClientFunctions, DevToolsRpcServerFunctions>(
rpcHost.functions,
{
preset,
rpcOptions: {
onFunctionError(error, name) {
console.error(c.red`⬢ RPC error on executing "${c.bold(name)}":`)
console.error(error)
},
onGeneralError(error) {
console.error(c.red`⬢ RPC error on executing rpc`)
console.error(error)
},
resolver(name, fn) {
// eslint-disable-next-line ts/no-this-alias
const rpc = this
// Block unauthorized access to non-anonymous methods
if (!name.startsWith(ANONYMOUS_SCOPE) && !rpc.$meta.isTrusted) {
return () => {
throw new Error(`Unauthorized access to method ${JSON.stringify(name)} from client [${rpc.$meta.id}]`)
}
}
// If the function is not found, return undefined
if (!fn)
return undefined
// Register AsyncContext for the current RPC call
return async function (this: any, ...args) {
debugInvoked(`${JSON.stringify(name)} from #${rpc.$meta.id}`)
return await asyncStorage.run({
rpc,
meta: rpc.$meta,
}, async () => {
return (await fn).apply(this, args)
})
}
},
},
},
)
rpcHost._rpcGroup = rpcGroup
rpcHost._asyncStorage = asyncStorage
const getConnectionMeta = async (): Promise<ConnectionMeta> => {
return {
backend: 'websocket',
websocket: port,
}
}
return {
port,
rpc: rpcGroup,
rpcHost,
getConnectionMeta,
}
}