-
Notifications
You must be signed in to change notification settings - Fork 0
/
data.ts
109 lines (94 loc) · 3.12 KB
/
data.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
import type { ClientConfig, Console, DataService, Events, WebSocket } from '@cordisjs/plugin-webui'
import type { Promisify } from 'cosmokit'
import { markRaw, reactive, ref } from 'vue'
import { Context } from './context'
export type Store = {
[K in keyof Console.Services]?: Console.Services[K] extends DataService<infer T> ? T : never
}
declare const KOISHI_CONFIG: ClientConfig
export const global = KOISHI_CONFIG
export const store = reactive<Store>({})
export function withProxy(url: string) {
return (global.proxyBase || '') + url
}
export const socket = ref<WebSocket>(null)
const listeners: Record<string, (data: any) => void> = {}
const responseHooks: Record<string, [Function, Function]> = {}
export function send<T extends keyof Events>(type: T, ...args: Parameters<Events[T]>): Promisify<ReturnType<Events[T]>>
export function send(type: string, ...args: any[]) {
if (!socket.value) return
console.debug('↑%c', 'color:brown', type, args)
const id = Math.random().toString(36).slice(2, 9)
socket.value.send(JSON.stringify({ id, type, args }))
return new Promise((resolve, reject) => {
responseHooks[id] = [resolve, reject]
setTimeout(() => {
delete responseHooks[id]
reject(new Error('timeout'))
}, 60000)
})
}
export function receive<T = any>(event: string, listener: (data: T) => void) {
listeners[event] = listener
}
receive('data', ({ key, value }) => {
store[key] = value
})
receive('patch', ({ key, value }) => {
if (Array.isArray(store[key])) {
store[key].push(...value)
} else if (store[key]) {
Object.assign(store[key], value)
}
})
receive('response', ({ id, value, error }) => {
if (!responseHooks[id]) return
const [resolve, reject] = responseHooks[id]
delete responseHooks[id]
if (error) {
reject(error)
} else {
resolve(value)
}
})
export function connect(ctx: Context, callback: () => WebSocket) {
const value = callback()
let sendTimer: number
let closeTimer: number
const refresh = () => {
if (!global.heartbeat) return
clearTimeout(sendTimer)
clearTimeout(closeTimer)
sendTimer = +setTimeout(() => send('ping'), global.heartbeat.interval)
closeTimer = +setTimeout(() => value?.close(), global.heartbeat.timeout)
}
const reconnect = () => {
socket.value = null
for (const key in store) {
store[key] = undefined
}
console.log('[cordis] websocket disconnected, will retry in 1s...')
setTimeout(() => {
connect(ctx, callback).then(location.reload, () => {
console.log('[cordis] websocket disconnected, will retry in 1s...')
})
}, 1000)
}
value.addEventListener('message', (ev) => {
refresh()
const data = JSON.parse(ev.data)
console.debug('↓%c', 'color:purple', data.type, data.body)
if (data.type in listeners) {
listeners[data.type](data.body)
}
ctx.emit(data.type, data.body)
})
value.addEventListener('close', reconnect)
return new Promise<WebSocket.Event>((resolve, reject) => {
value.addEventListener('open', (event) => {
socket.value = markRaw(value)
resolve(event)
})
value.addEventListener('error', reject)
})
}