@vobs/http 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +57 -0
- package/package.json +21 -0
- package/src/debug.ts +64 -0
- package/src/index.test.ts +499 -0
- package/src/index.ts +847 -0
- package/src/stream.ts +128 -0
package/src/stream.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
export interface SSEOptions {
|
|
2
|
+
readonly withCredentials?: boolean
|
|
3
|
+
readonly eventSource?: SSEConstructor
|
|
4
|
+
readonly onOpen?: (event: Event) => void
|
|
5
|
+
readonly onMessage?: (event: MessageEvent) => void
|
|
6
|
+
readonly onError?: (event: Event) => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface SSEClient {
|
|
10
|
+
readonly source: EventSource
|
|
11
|
+
close(): void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type SSEConstructor = new (url: string, options?: EventSourceInit) => EventSource
|
|
15
|
+
|
|
16
|
+
export function createSSE(url: string, options: SSEOptions = {}): SSEClient {
|
|
17
|
+
const EventSourceImpl = options.eventSource ?? globalThis.EventSource
|
|
18
|
+
if (!EventSourceImpl) throw new Error('HTTP: 当前环境没有可用的 EventSource')
|
|
19
|
+
const source = new EventSourceImpl(url, { withCredentials: options.withCredentials ?? false })
|
|
20
|
+
if (options.onOpen) source.addEventListener('open', options.onOpen)
|
|
21
|
+
if (options.onMessage) source.addEventListener('message', options.onMessage)
|
|
22
|
+
if (options.onError) source.addEventListener('error', options.onError)
|
|
23
|
+
return {
|
|
24
|
+
source,
|
|
25
|
+
close: () => source.close()
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface WebSocketOptions {
|
|
30
|
+
readonly protocols?: string | readonly string[]
|
|
31
|
+
readonly webSocket?: WebSocketConstructor
|
|
32
|
+
readonly autoReconnect?: boolean
|
|
33
|
+
readonly reconnectDelay?: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface WebSocketClient {
|
|
37
|
+
readonly socket: WebSocket | null
|
|
38
|
+
readonly state: WebSocketState
|
|
39
|
+
connect(): void
|
|
40
|
+
send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void
|
|
41
|
+
close(code?: number, reason?: string): void
|
|
42
|
+
on<K extends WebSocketEventName>(event: K, listener: WebSocketEventListener<K>): () => void
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type WebSocketState = 'idle' | 'connecting' | 'open' | 'closed'
|
|
46
|
+
export type WebSocketEventName = 'open' | 'message' | 'error' | 'close'
|
|
47
|
+
export type WebSocketEventListener<K extends WebSocketEventName> =
|
|
48
|
+
K extends 'message' ? (event: MessageEvent) => void
|
|
49
|
+
: K extends 'close' ? (event: CloseEvent) => void
|
|
50
|
+
: (event: Event) => void
|
|
51
|
+
export type WebSocketConstructor = new (
|
|
52
|
+
url: string,
|
|
53
|
+
protocols?: string | string[]
|
|
54
|
+
) => WebSocket
|
|
55
|
+
|
|
56
|
+
export function createWebSocket(url: string, options: WebSocketOptions = {}): WebSocketClient {
|
|
57
|
+
const WebSocketImpl = options.webSocket ?? globalThis.WebSocket
|
|
58
|
+
if (!WebSocketImpl) throw new Error('HTTP: 当前环境没有可用的 WebSocket')
|
|
59
|
+
const reconnectDelay = options.reconnectDelay ?? 1_000
|
|
60
|
+
if (!Number.isFinite(reconnectDelay) || reconnectDelay < 0) {
|
|
61
|
+
throw new Error('HTTP: reconnectDelay 必须是大于等于 0 的有限数字')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const listeners = new Map<WebSocketEventName, Set<(event: Event) => void>>()
|
|
65
|
+
let socket: WebSocket | null = null
|
|
66
|
+
let state: WebSocketState = 'idle'
|
|
67
|
+
let manuallyClosed = false
|
|
68
|
+
let reconnectTimer: ReturnType<typeof setTimeout> | undefined
|
|
69
|
+
|
|
70
|
+
const client: WebSocketClient = {
|
|
71
|
+
get socket(): WebSocket | null { return socket },
|
|
72
|
+
get state(): WebSocketState { return state },
|
|
73
|
+
connect,
|
|
74
|
+
send(data) {
|
|
75
|
+
if (!socket || state !== 'open') throw new Error('HTTP: WebSocket 尚未连接')
|
|
76
|
+
socket.send(data)
|
|
77
|
+
},
|
|
78
|
+
close(code, reason) {
|
|
79
|
+
manuallyClosed = true
|
|
80
|
+
if (reconnectTimer !== undefined) clearTimeout(reconnectTimer)
|
|
81
|
+
socket?.close(code, reason)
|
|
82
|
+
if (!socket) state = 'closed'
|
|
83
|
+
},
|
|
84
|
+
on(event, listener) {
|
|
85
|
+
let eventListeners = listeners.get(event)
|
|
86
|
+
if (!eventListeners) {
|
|
87
|
+
eventListeners = new Set()
|
|
88
|
+
listeners.set(event, eventListeners)
|
|
89
|
+
}
|
|
90
|
+
eventListeners.add(listener as (event: Event) => void)
|
|
91
|
+
return () => eventListeners?.delete(listener as (event: Event) => void)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
connect()
|
|
96
|
+
return client
|
|
97
|
+
|
|
98
|
+
function connect(): void {
|
|
99
|
+
if (state === 'connecting' || state === 'open') return
|
|
100
|
+
manuallyClosed = false
|
|
101
|
+
state = 'connecting'
|
|
102
|
+
const protocols = options.protocols === undefined
|
|
103
|
+
? undefined
|
|
104
|
+
: typeof options.protocols === 'string' ? options.protocols : [...options.protocols]
|
|
105
|
+
socket = new WebSocketImpl(url, protocols)
|
|
106
|
+
socket.addEventListener('open', event => {
|
|
107
|
+
state = 'open'
|
|
108
|
+
notify('open', event)
|
|
109
|
+
})
|
|
110
|
+
socket.addEventListener('message', event => notify('message', event))
|
|
111
|
+
socket.addEventListener('error', event => notify('error', event))
|
|
112
|
+
socket.addEventListener('close', event => {
|
|
113
|
+
state = 'closed'
|
|
114
|
+
socket = null
|
|
115
|
+
notify('close', event)
|
|
116
|
+
if (options.autoReconnect && !manuallyClosed) {
|
|
117
|
+
reconnectTimer = setTimeout(() => {
|
|
118
|
+
reconnectTimer = undefined
|
|
119
|
+
connect()
|
|
120
|
+
}, reconnectDelay)
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function notify(event: WebSocketEventName, value: Event): void {
|
|
126
|
+
for (const listener of [...(listeners.get(event) ?? [])]) listener(value)
|
|
127
|
+
}
|
|
128
|
+
}
|