-
Notifications
You must be signed in to change notification settings - Fork 49
/
ws.ts
121 lines (101 loc) · 3.64 KB
/
ws.ts
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
import { Adapter, Context, Logger, Quester, Schema, Time, WebSocketLayer } from '@satorijs/satori'
import { OneBotBot } from './bot'
import { dispatchSession, Response, TimeoutError } from './utils'
const logger = new Logger('onebot')
interface SharedConfig<T = 'ws' | 'ws-reverse'> {
protocol: T
responseTimeout?: number
}
export class WsClient extends Adapter.WsClient<OneBotBot> {
protected accept = accept
prepare(bot: OneBotBot<OneBotBot.BaseConfig & WsClient.Config>) {
const { token, endpoint } = bot.config
const http = this.ctx.http.extend(bot.config)
if (token) http.config.headers.Authorization = `Bearer ${token}`
return http.ws(endpoint)
}
}
export namespace WsClient {
export interface Config extends SharedConfig<'ws'>, Quester.Config, Adapter.WsClient.Config {}
export const Config: Schema<Config> = Schema.intersect([
Schema.object({
protocol: Schema.const('ws' as const),
responseTimeout: Schema.natural().role('time').default(Time.minute).description('等待响应的时间 (单位为毫秒)。'),
}).description('连接设置'),
Quester.createConfig(true),
Adapter.WsClient.Config,
])
}
export class WsServer extends Adapter.Server<OneBotBot<OneBotBot.BaseConfig & WsServer.Config>> {
public wsServer?: WebSocketLayer
constructor(ctx: Context, bot: OneBotBot) {
super()
const { path = '/onebot' } = bot.config as WsServer.Config
this.wsServer = ctx.router.ws(path, (socket, { headers }) => {
logger.debug('connected with', headers)
if (headers['x-client-role'] !== 'Universal') {
return socket.close(1008, 'invalid x-client-role')
}
const selfId = headers['x-self-id'].toString()
const bot = this.bots.find(bot => bot.selfId === selfId)
if (!bot) return socket.close(1008, 'invalid x-self-id')
bot.socket = socket
accept(bot)
})
ctx.on('dispose', () => {
logger.debug('ws server closing')
this.wsServer.close()
})
}
async stop(bot: OneBotBot) {
bot.socket?.close()
bot.socket = null
}
}
export namespace WsServer {
export interface Config extends SharedConfig<'ws-reverse'> {
path?: string
}
export const Config: Schema<Config> = Schema.object({
protocol: Schema.const('ws-reverse' as const),
path: Schema.string().description('服务器监听的路径。').default('/onebot'),
responseTimeout: Schema.natural().role('time').default(Time.minute).description('等待响应的时间 (单位为毫秒)。'),
}).description('连接设置')
}
let counter = 0
const listeners: Record<number, (response: Response) => void> = {}
export function accept(bot: OneBotBot<OneBotBot.BaseConfig & SharedConfig>) {
bot.socket.on('message', (data) => {
let parsed: any
try {
parsed = JSON.parse(data.toString())
} catch (error) {
return logger.warn('cannot parse message', data)
}
if ('post_type' in parsed) {
logger.debug('receive %o', parsed)
dispatchSession(bot, parsed)
} else if (parsed.echo in listeners) {
listeners[parsed.echo](parsed)
delete listeners[parsed.echo]
}
})
bot.socket.on('close', () => {
delete bot.internal._request
})
bot.internal._request = (action, params) => {
const data = { action, params, echo: ++counter }
data.echo = ++counter
return new Promise((resolve, reject) => {
listeners[data.echo] = resolve
setTimeout(() => {
delete listeners[data.echo]
reject(new TimeoutError(params, action))
}, bot.config.responseTimeout)
bot.socket.send(JSON.stringify(data), (error) => {
if (error) reject(error)
})
})
}
bot.initialize()
}