wspromisify 2.10.0 → 2.10.2
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/dist/bundle.cjs +1 -1
- package/dist/bundle.mjs +1 -1
- package/package.json +1 -1
- package/TODO +0 -9
- package/coverage.lcov +0 -212
- package/rollup.config.js +0 -38
- package/src/WSC.ts +0 -320
- package/src/config.ts +0 -57
- package/src/types.ts +0 -68
- package/src/utils.ts +0 -21
- package/test/index.ts +0 -24
- package/test/mock/WS.ts +0 -75
- package/test/mock/server.ts +0 -18
- package/test/specs/close.ts +0 -18
- package/test/specs/drops.ts +0 -29
- package/test/specs/echo.ts +0 -24
- package/test/specs/encode-decode.ts +0 -1
- package/test/specs/existing-socket.ts +0 -44
- package/test/specs/lazy-send-before-open.ts +0 -19
- package/test/specs/lazy.ts +0 -16
- package/test/specs/no-native-throws.ts +0 -18
- package/test/specs/ready.ts +0 -12
- package/test/specs/reconnect.ts +0 -41
- package/test/specs/socket.ts +0 -13
- package/test/specs/stream-comprehensive.ts +0 -74
- package/test/specs/stream-real.ts +0 -61
- package/test/specs/stream-simple.ts +0 -45
- package/test/suite.ts +0 -3
- package/test/utils.ts +0 -33
- package/tslint.json +0 -25
package/src/WSC.ts
DELETED
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
import { AnyFunc, AnyObject, both, callWith, F, isNil, notf, once, qfilter, T, tap, typeIs } from 'pepka'
|
|
2
|
-
import { Zipnum } from 'zipnum'
|
|
3
|
-
import { processConfig } from './config'
|
|
4
|
-
import './types'
|
|
5
|
-
import { add_event, rm_event, sett } from './utils'
|
|
6
|
-
|
|
7
|
-
const MAX_32 = 2**31 - 1
|
|
8
|
-
const { random } = Math
|
|
9
|
-
const zipnum = new Zipnum()
|
|
10
|
-
const callit = callWith([])
|
|
11
|
-
const isNumber = both(typeIs('Number'), notf(isNaN))
|
|
12
|
-
const ping_send_opts: wsc.SendOptions = {_is_ping: true}
|
|
13
|
-
const clearTO = (to: NodeJS.Timeout|null) => to && clearTimeout(to)
|
|
14
|
-
|
|
15
|
-
type EventHandler<T extends keyof WebSocketEventMap> = AnyFunc<any, [WebSocketEventMap[T]]>
|
|
16
|
-
type EventHandlers = {
|
|
17
|
-
open: EventHandler<'open'>[]
|
|
18
|
-
close: EventHandler<'close'>[]
|
|
19
|
-
error: EventHandler<'error'>[]
|
|
20
|
-
message: AnyFunc<any, [WebSocketEventMap['message'] & {data: any}]>[]
|
|
21
|
-
timeout: AnyFunc<any, [data: any]>[]
|
|
22
|
-
}
|
|
23
|
-
const genid = (q: AnyObject) => {
|
|
24
|
-
const id = zipnum.zip((random()*(MAX_32-10))|0)
|
|
25
|
-
return id in q ? genid(q) : id
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export class WebSocketClient {
|
|
29
|
-
private ws: wsc.Socket|null = null
|
|
30
|
-
private intentionally_closed = false
|
|
31
|
-
private reconnect_timeout: NodeJS.Timeout|null = null
|
|
32
|
-
private queue: Record<string, wsc.Message> = {}
|
|
33
|
-
private onReadyQueue: AnyFunc[] = []
|
|
34
|
-
private onCloseQueue: AnyFunc[] = []
|
|
35
|
-
private handlers: EventHandlers = { open: [], close: [], message: [], error: [], timeout: [] }
|
|
36
|
-
private config = <wsc.Config>{}
|
|
37
|
-
private ping_timer: NodeJS.Timeout|null = null
|
|
38
|
-
private idle_timer: NodeJS.Timeout|null = null
|
|
39
|
-
private get opened() { return this.ws?.readyState===1 } // The only opened state.
|
|
40
|
-
|
|
41
|
-
private init_flush(): void {
|
|
42
|
-
// TODO: reject them or save somehow ?..
|
|
43
|
-
qfilter(F, this.queue)
|
|
44
|
-
}
|
|
45
|
-
private call(event_name: wsc.WSEvent, ...args: any[]) {
|
|
46
|
-
for(const h of this.handlers[event_name]) h(...args)
|
|
47
|
-
}
|
|
48
|
-
private log(event: wsc.WSEvent, message: any = null, time: number|null = null): void {
|
|
49
|
-
const {config} = this
|
|
50
|
-
setTimeout(() => {
|
|
51
|
-
if(time === null)
|
|
52
|
-
if(config.timer) config.log(event, null, message)
|
|
53
|
-
else config.log(event, message)
|
|
54
|
-
else
|
|
55
|
-
config.log(event, time, message)
|
|
56
|
-
})
|
|
57
|
-
}
|
|
58
|
-
private resetPing() {
|
|
59
|
-
const {config: {ping}, ping_timer} = this
|
|
60
|
-
if(ping) {
|
|
61
|
-
if(!isNil(ping_timer))
|
|
62
|
-
clearTimeout(ping_timer as NodeJS.Timeout)
|
|
63
|
-
this.ping_timer = sett(ping.interval*1e3, async () => {
|
|
64
|
-
const {ping_timer, opened} = this
|
|
65
|
-
if(opened) {
|
|
66
|
-
await this.send(ping.content, ping_send_opts)
|
|
67
|
-
this.resetPing()
|
|
68
|
-
} else clearTimeout(ping_timer!)
|
|
69
|
-
})
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// FIXME: Make some version where it could work faster (for streaming).
|
|
74
|
-
private resetIdle() {
|
|
75
|
-
const {config: {max_idle_time: time}, idle_timer} = this
|
|
76
|
-
if(time!==Infinity) {
|
|
77
|
-
if(!isNil(idle_timer)) clearTimeout(idle_timer!)
|
|
78
|
-
this.idle_timer = sett(time*1e3, () => this.opened && this.close())
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
private initSocket(ws: wsc.Socket) {
|
|
83
|
-
const {queue, config} = this
|
|
84
|
-
this.ws = ws
|
|
85
|
-
this.onReadyQueue.forEach((fn: Function) => fn())
|
|
86
|
-
this.onReadyQueue.splice(0)
|
|
87
|
-
const {id_key, data_key} = config.server
|
|
88
|
-
// Works also on previously opened sockets that do not fire 'open' event.
|
|
89
|
-
this.call('open', ws)
|
|
90
|
-
for(const msg_id in queue) ws.send(queue[msg_id].msg)
|
|
91
|
-
if(this.reconnect_timeout !== null) {
|
|
92
|
-
clearInterval(this.reconnect_timeout)
|
|
93
|
-
this.reconnect_timeout = null
|
|
94
|
-
}
|
|
95
|
-
this.resetPing(); this.resetIdle()
|
|
96
|
-
add_event(ws, 'close', async (...e) => {
|
|
97
|
-
this.log('close')
|
|
98
|
-
this.ws = null
|
|
99
|
-
this.onCloseQueue.forEach(callit)
|
|
100
|
-
this.onCloseQueue.splice(0)
|
|
101
|
-
this.call('close', ...e)
|
|
102
|
-
// Auto reconnect.
|
|
103
|
-
let {reconnect, reconnection_attempts} = config
|
|
104
|
-
if(isNumber(reconnect)) {
|
|
105
|
-
const reconnectFunc = async () => {
|
|
106
|
-
if(this.intentionally_closed || !reconnection_attempts) return;
|
|
107
|
-
reconnection_attempts--
|
|
108
|
-
this.log('reconnect')
|
|
109
|
-
if(!isNil(this.ws)) {
|
|
110
|
-
this.ws!.close()
|
|
111
|
-
this.ws = null
|
|
112
|
-
}
|
|
113
|
-
// If some error occured, try again.
|
|
114
|
-
const status = await this.connect()
|
|
115
|
-
if(!isNil(status))
|
|
116
|
-
this.reconnect_timeout = setTimeout(reconnectFunc, reconnect*1e3)
|
|
117
|
-
}
|
|
118
|
-
// TODO: test normal close by server. Would it be infinite ?
|
|
119
|
-
reconnectFunc()
|
|
120
|
-
}
|
|
121
|
-
})
|
|
122
|
-
add_event(ws, 'message', (e) => {
|
|
123
|
-
try {
|
|
124
|
-
const data = config.decode(e.data)
|
|
125
|
-
this.call('message', {...e, data})
|
|
126
|
-
let internal = false
|
|
127
|
-
if(typeIs('Object', data) && id_key in data) {
|
|
128
|
-
const q = this.queue[data[id_key]]
|
|
129
|
-
if(q) {
|
|
130
|
-
// Debug, Log.
|
|
131
|
-
const time = q.sent_time ? (Date.now() - q.sent_time) : null
|
|
132
|
-
this.log('message', data[data_key], time)
|
|
133
|
-
// Play.
|
|
134
|
-
q.ff(data[data_key])
|
|
135
|
-
internal = true
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if(!internal) this.log('message-ext', data)
|
|
139
|
-
} catch (err) {
|
|
140
|
-
console.error(err, `WSP: Decode error. Got: ${e.data}`)
|
|
141
|
-
}
|
|
142
|
-
this.resetPing()
|
|
143
|
-
})
|
|
144
|
-
}
|
|
145
|
-
private opening = false
|
|
146
|
-
private connect() { // returns status if won't open or null if ok.
|
|
147
|
-
return new Promise<null|number>((ff) => {
|
|
148
|
-
if(this.opened||this.opening) return ff(null)
|
|
149
|
-
this.opening = true
|
|
150
|
-
const config = this.config
|
|
151
|
-
const ws = config.socket || config.adapter(config.url, config.protocols)
|
|
152
|
-
if(!ws || ws.readyState > 1) {
|
|
153
|
-
this.opening = false
|
|
154
|
-
this.ws = null
|
|
155
|
-
this.log('error', 'ready() on closing or closed state! status 2.')
|
|
156
|
-
return ff(2)
|
|
157
|
-
}
|
|
158
|
-
const ffo = once((s: null|number) => {this.opening=false; ff(s)})
|
|
159
|
-
add_event(ws, 'error', once((e) => {
|
|
160
|
-
this.ws = null
|
|
161
|
-
this.log('error', 'status 3. Err: '+e.message)
|
|
162
|
-
this.call('error', e)
|
|
163
|
-
// Some network error: Connection refused or so.
|
|
164
|
-
ffo(3)
|
|
165
|
-
}))
|
|
166
|
-
// Because 'open' won't be envoked on opened socket.
|
|
167
|
-
if(ws.readyState) {
|
|
168
|
-
this.initSocket(ws)
|
|
169
|
-
ffo(null)
|
|
170
|
-
} else {
|
|
171
|
-
add_event(ws, 'open', once(() => {
|
|
172
|
-
this.log('open')
|
|
173
|
-
this.initSocket(ws)
|
|
174
|
-
ffo(null)
|
|
175
|
-
}))
|
|
176
|
-
}
|
|
177
|
-
})
|
|
178
|
-
}
|
|
179
|
-
public get socket() { return this.ws }
|
|
180
|
-
public async ready() {
|
|
181
|
-
return new Promise<void>((ff) => {
|
|
182
|
-
if(this.config.lazy || this.opened) ff() // FIXME: (possibly) breaking change ?? At least minor ver bump with a notice!!!
|
|
183
|
-
else this.onReadyQueue.push(ff)
|
|
184
|
-
})
|
|
185
|
-
}
|
|
186
|
-
public on(
|
|
187
|
-
event_name: wsc.WSEvent,
|
|
188
|
-
handler: (data: any) => any,
|
|
189
|
-
predicate: (data: any) => boolean = T,
|
|
190
|
-
raw = false
|
|
191
|
-
) {
|
|
192
|
-
const _handler: wsc.EventHandler = (event) =>
|
|
193
|
-
predicate(event) && handler(event)
|
|
194
|
-
if(raw) add_event(this.ws as wsc.Socket, event_name, _handler)
|
|
195
|
-
else this.handlers[event_name].push(_handler)
|
|
196
|
-
return _handler
|
|
197
|
-
}
|
|
198
|
-
public off(
|
|
199
|
-
event_name: wsc.WSEvent,
|
|
200
|
-
handler: (data: any) => any,
|
|
201
|
-
raw = false
|
|
202
|
-
) {
|
|
203
|
-
if(raw) return rm_event(this.ws as wsc.Socket, event_name, handler)
|
|
204
|
-
const handlers = this.handlers[event_name]
|
|
205
|
-
const i = handlers.indexOf(handler)
|
|
206
|
-
if(~i) handlers.splice(i, 1)
|
|
207
|
-
}
|
|
208
|
-
public async close(): wsc.AsyncErrCode {
|
|
209
|
-
return new Promise((ff, rj) => {
|
|
210
|
-
if(this.ws === null) {
|
|
211
|
-
rj('WSP: closing a non-inited socket!')
|
|
212
|
-
} else {
|
|
213
|
-
this.onCloseQueue.push(() => {
|
|
214
|
-
this.init_flush()
|
|
215
|
-
ff(null)
|
|
216
|
-
})
|
|
217
|
-
this.ws.close()
|
|
218
|
-
this.ws = null
|
|
219
|
-
this.intentionally_closed = true
|
|
220
|
-
}
|
|
221
|
-
})
|
|
222
|
-
}
|
|
223
|
-
public open() {
|
|
224
|
-
if(!this.opened) {
|
|
225
|
-
this.intentionally_closed = false
|
|
226
|
-
return this.connect()
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
// TODO: Сделать сэттер элементов конфигурации чтобы двигать таймауты.
|
|
230
|
-
// И эвент, когда схема наша, а соответствующего элемента очереди не ма.
|
|
231
|
-
// Или добавить флажок к эвенту 'message'.F
|
|
232
|
-
// И событие 'line' со значением on: boolean. Критерии?
|
|
233
|
-
private async prepareMessage<RequestDataType = any>(
|
|
234
|
-
message_data: RequestDataType,
|
|
235
|
-
opts = <wsc.SendOptions>{}
|
|
236
|
-
) {
|
|
237
|
-
this.log(opts._is_ping ? 'ping' : 'send', message_data)
|
|
238
|
-
const {config, queue} = this
|
|
239
|
-
const {pipes, server: {data_key}} = config
|
|
240
|
-
const {top, _is_ping} = opts
|
|
241
|
-
const id = genid(queue)
|
|
242
|
-
if(typeof top === 'object') {
|
|
243
|
-
if(top[data_key]) {
|
|
244
|
-
throw new Error(`Attempting to set data key/token via ${opts._is_ping ? 'ping' : 'send'}() options!`)
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
for(const pipe of pipes) message_data = pipe(message_data)
|
|
248
|
-
const [msg, err] = await Promise.all([
|
|
249
|
-
config.encode(id, message_data, config),
|
|
250
|
-
this.connect()
|
|
251
|
-
])
|
|
252
|
-
if(err) throw new Error('ERR while opening connection #'+err)
|
|
253
|
-
const cleanup = tap(() => delete this.queue[id])
|
|
254
|
-
const timeout = (rj: AnyFunc) => sett(config.timeout, () => {
|
|
255
|
-
if(id in queue) {
|
|
256
|
-
this.call('timeout', message_data)
|
|
257
|
-
rj({'Websocket timeout expired': config.timeout, 'for the message': message_data})
|
|
258
|
-
cleanup()
|
|
259
|
-
}
|
|
260
|
-
})
|
|
261
|
-
if(this.opened) {
|
|
262
|
-
sett(0, () => this.ws!.send(msg))
|
|
263
|
-
this.resetPing()
|
|
264
|
-
if(!_is_ping) this.resetIdle()
|
|
265
|
-
}
|
|
266
|
-
return { id, msg, timeout, cleanup }
|
|
267
|
-
}
|
|
268
|
-
/** .send(your_data) wraps request to server with {id: `hash`, data: `actually your data`},
|
|
269
|
-
returns a Promise that will be rejected after a timeout or
|
|
270
|
-
resolved if server returns the same signature: {id: `same_hash`, data: `response data`}.
|
|
271
|
-
*/
|
|
272
|
-
public async send<RequestDataType = any, ResponseDataType = any>(
|
|
273
|
-
message_data: RequestDataType,
|
|
274
|
-
opts = <wsc.SendOptions>{}
|
|
275
|
-
): Promise<ResponseDataType> {
|
|
276
|
-
const {id, msg, timeout, cleanup} = await this.prepareMessage(message_data, opts)
|
|
277
|
-
const {queue, config} = this
|
|
278
|
-
return new Promise<ResponseDataType>((ff, rj) => {
|
|
279
|
-
const to = timeout(rj)
|
|
280
|
-
queue[id] = {
|
|
281
|
-
msg,
|
|
282
|
-
data_type: config.data_type,
|
|
283
|
-
sent_time: config.timer ? Date.now() : null,
|
|
284
|
-
ff(x: any) {
|
|
285
|
-
clearTO(to)
|
|
286
|
-
ff(x)
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}).finally(cleanup)
|
|
290
|
-
}
|
|
291
|
-
// FIXME: rejects into ff somehow.
|
|
292
|
-
public async *stream<RequestDataType = any, ResponseDataType = any>(
|
|
293
|
-
message_data: RequestDataType,
|
|
294
|
-
opts = <wsc.SendOptions>{}
|
|
295
|
-
): AsyncGenerator<ResponseDataType, void, unknown> {
|
|
296
|
-
const {id, msg, timeout, cleanup} = await this.prepareMessage(message_data, opts)
|
|
297
|
-
const {queue, config} = this
|
|
298
|
-
let done = false, fulfill: AnyFunc, to: NodeJS.Timeout|null = null
|
|
299
|
-
queue[id] = {
|
|
300
|
-
msg,
|
|
301
|
-
ff: (msg: ResponseDataType&{done?: boolean}) => {
|
|
302
|
-
fulfill(msg)
|
|
303
|
-
if(msg?.done) { cleanup(); done=true }
|
|
304
|
-
},
|
|
305
|
-
data_type: config.data_type,
|
|
306
|
-
sent_time: config.timer ? Date.now() : null
|
|
307
|
-
}
|
|
308
|
-
while(!done) yield await new Promise<ResponseDataType>((ff, rj) => {
|
|
309
|
-
to=timeout(rj); fulfill=ff
|
|
310
|
-
}).catch((e) => cleanup(e)).finally(() => {clearTO(to); to=null})
|
|
311
|
-
}
|
|
312
|
-
// TODO: Add .on handlers to config!
|
|
313
|
-
constructor(user_config: wsc.UserConfig = {}) {
|
|
314
|
-
this.config = processConfig(user_config)
|
|
315
|
-
if(!this.config.lazy) this.connect()
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/* TODO: v3: @.deprecated. Use named import { WebSocketClient } instead. */
|
|
320
|
-
export default WebSocketClient
|
package/src/config.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import './types'
|
|
2
|
-
import { native_ws } from './utils'
|
|
3
|
-
|
|
4
|
-
const default_config = <wsc.Config>{
|
|
5
|
-
data_type: 'json', // ToDo some other stuff maybe.
|
|
6
|
-
// Debug features.
|
|
7
|
-
log: (() => null),
|
|
8
|
-
timer: false,
|
|
9
|
-
// Set up.
|
|
10
|
-
url: 'localhost',
|
|
11
|
-
timeout: 1400,
|
|
12
|
-
reconnect: 2, // Reconnect timeout in seconds or null.
|
|
13
|
-
reconnection_attempts: Infinity,
|
|
14
|
-
max_idle_time: Infinity,
|
|
15
|
-
lazy: false,
|
|
16
|
-
socket: null,
|
|
17
|
-
adapter: ((host, protocols) => new WebSocket(host, protocols)),
|
|
18
|
-
encode: (key, data, { server }) => JSON.stringify({
|
|
19
|
-
[server.id_key]: key,
|
|
20
|
-
[server.data_key]: data
|
|
21
|
-
}),
|
|
22
|
-
decode: (rawMessage) => JSON.parse(rawMessage),
|
|
23
|
-
protocols: [],
|
|
24
|
-
pipes: [],
|
|
25
|
-
server: {
|
|
26
|
-
id_key: 'id',
|
|
27
|
-
data_key: 'data'
|
|
28
|
-
},
|
|
29
|
-
ping: {
|
|
30
|
-
interval: 55,
|
|
31
|
-
content: {}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export const processConfig = (config: wsc.UserConfig) => {
|
|
36
|
-
if(native_ws===null && !('adapter' in config)) throw new Error(`
|
|
37
|
-
This platform has no native WebSocket implementation.
|
|
38
|
-
Please use 'ws' package as an adapter.
|
|
39
|
-
See https://github.com/houd1ni/WebsocketPromisify/issues/23
|
|
40
|
-
`)
|
|
41
|
-
const full_config: wsc.Config = Object.assign(
|
|
42
|
-
{},
|
|
43
|
-
default_config,
|
|
44
|
-
config
|
|
45
|
-
)
|
|
46
|
-
const url = full_config.url
|
|
47
|
-
if(url[0] == '/') {
|
|
48
|
-
try {
|
|
49
|
-
const protocol = location.protocol.includes('s:') ? 'wss' : 'ws'
|
|
50
|
-
full_config.url = `${protocol}://${location.hostname}:${location.port}${url}`
|
|
51
|
-
} catch (e) {
|
|
52
|
-
throw new Error('WSP: URL starting with / in non-browser environment!')
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return full_config
|
|
57
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
declare namespace wsc {
|
|
2
|
-
|
|
3
|
-
interface DataObject {
|
|
4
|
-
[key: string]: any
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export type WSEvent = 'open' | 'message' | 'message-ext' | 'close' | 'error' | 'timeout' | 'reconnect' | 'send' | 'ping'
|
|
8
|
-
|
|
9
|
-
/** Minimal socket-like interface. */
|
|
10
|
-
interface Socket {
|
|
11
|
-
readyState: number
|
|
12
|
-
send(...any: any[]): void
|
|
13
|
-
close(): void
|
|
14
|
-
addEventListener: WebSocket["addEventListener"]
|
|
15
|
-
removeEventListener: WebSocket["removeEventListener"]
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export type AsyncErrCode = Promise<number | null | {}>
|
|
19
|
-
|
|
20
|
-
export type EventHandler = (e: any) => void
|
|
21
|
-
|
|
22
|
-
export type DataPipe = (message: any) => any
|
|
23
|
-
|
|
24
|
-
export type DataType = 'json' | 'string'
|
|
25
|
-
|
|
26
|
-
export interface Config {
|
|
27
|
-
data_type: DataType
|
|
28
|
-
log (event: string, time?: number|null, message?: any): void
|
|
29
|
-
log (event: string, message?: any): void
|
|
30
|
-
timer: boolean
|
|
31
|
-
url: string
|
|
32
|
-
timeout: number
|
|
33
|
-
reconnect: number
|
|
34
|
-
reconnection_attempts: number
|
|
35
|
-
max_idle_time: number
|
|
36
|
-
lazy: boolean
|
|
37
|
-
socket: Socket | null
|
|
38
|
-
adapter: (host: string, protocols?: string[]) => Socket
|
|
39
|
-
encode: (key: string, message: any, config: Config) => any
|
|
40
|
-
decode: (rawMessage: any) => {
|
|
41
|
-
[id_or_data_key: string]: string
|
|
42
|
-
}
|
|
43
|
-
protocols: string[]
|
|
44
|
-
pipes: DataPipe[]
|
|
45
|
-
server: {
|
|
46
|
-
id_key: string
|
|
47
|
-
data_key: string
|
|
48
|
-
},
|
|
49
|
-
ping: {
|
|
50
|
-
interval: number
|
|
51
|
-
content: any
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export type UserConfig = Partial<Config>
|
|
56
|
-
|
|
57
|
-
export type SendOptions = Partial<{
|
|
58
|
-
top: any
|
|
59
|
-
data_type: DataType
|
|
60
|
-
_is_ping: boolean
|
|
61
|
-
}>
|
|
62
|
-
|
|
63
|
-
export interface Message {
|
|
64
|
-
msg: any, ff(x: any): any,
|
|
65
|
-
data_type: DataType,
|
|
66
|
-
sent_time: number | null
|
|
67
|
-
}
|
|
68
|
-
}
|
package/src/utils.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import './types'
|
|
2
|
-
|
|
3
|
-
export const native_ws = (() => {try {return WebSocket||null}catch { return null }})()
|
|
4
|
-
|
|
5
|
-
export const add_event = (o: wsc.Socket, e: string, handler: wsc.EventHandler) => {
|
|
6
|
-
return o.addEventListener(e, handler)
|
|
7
|
-
}
|
|
8
|
-
export const rm_event = (o: wsc.Socket, e: string, handler: wsc.EventHandler) => {
|
|
9
|
-
return o.removeEventListener(e, handler)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const sett = (
|
|
13
|
-
a: number,
|
|
14
|
-
b: { (): void; (...args: any[]): void; }
|
|
15
|
-
) => setTimeout(b, a)
|
|
16
|
-
|
|
17
|
-
export const try_splice = <T = any>(arr: T[], el: T) => {
|
|
18
|
-
const i = arr.indexOf(el)
|
|
19
|
-
if(~i) return arr.splice(arr.indexOf(el), 1)
|
|
20
|
-
else return []
|
|
21
|
-
}
|
package/test/index.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import './specs/close'
|
|
2
|
-
import './specs/drops'
|
|
3
|
-
import './specs/echo'
|
|
4
|
-
import './specs/existing-socket'
|
|
5
|
-
import './specs/lazy'
|
|
6
|
-
import './specs/ready'
|
|
7
|
-
import './specs/reconnect'
|
|
8
|
-
import './specs/lazy-send-before-open'
|
|
9
|
-
import './specs/socket'
|
|
10
|
-
import './specs/no-native-throws'
|
|
11
|
-
import './specs/stream-simple'
|
|
12
|
-
import './specs/stream-comprehensive'
|
|
13
|
-
import './specs/stream-real'
|
|
14
|
-
import mockServer from './mock/server'
|
|
15
|
-
import {test} from './suite'
|
|
16
|
-
|
|
17
|
-
const {shutDown, isRunning} = await mockServer()
|
|
18
|
-
test.after(() => {
|
|
19
|
-
setTimeout(async () => {
|
|
20
|
-
if(isRunning()) await shutDown()
|
|
21
|
-
process.exit()
|
|
22
|
-
}, 100)
|
|
23
|
-
})
|
|
24
|
-
test.run()
|
package/test/mock/WS.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import WebSocket, { WebSocketServer } from 'ws'
|
|
3
|
-
import {add, compose, genBy, identity, noop, wait} from 'pepka'
|
|
4
|
-
|
|
5
|
-
let server: WebSocketServer|null = null
|
|
6
|
-
|
|
7
|
-
const createServer = (port = 40510) => new Promise<WebSocketServer>((ff, rj) => {
|
|
8
|
-
if(server) return rj('The server is already running!')
|
|
9
|
-
server = new WebSocketServer({ port }, () => {
|
|
10
|
-
server!.on('connection', (socket: WebSocket&{isAlive: boolean}) => {
|
|
11
|
-
socket.on('message', async (rawMessage: string) => {
|
|
12
|
-
// console.log({rawMessage: rawMessage.toString()})
|
|
13
|
-
const {id, data} = JSON.parse(rawMessage)
|
|
14
|
-
let response = ''
|
|
15
|
-
if(data.shut) {
|
|
16
|
-
socket.terminate()
|
|
17
|
-
socket.isAlive = false
|
|
18
|
-
socket.ping('', false, noop)
|
|
19
|
-
return null
|
|
20
|
-
} else if(data.echo) {
|
|
21
|
-
response = data
|
|
22
|
-
} else if(data.stream) {
|
|
23
|
-
// Handle streaming responses
|
|
24
|
-
const chunks = genBy(compose(add(1), identity), 20) // Generate 20 chunks
|
|
25
|
-
// console.log(chunks)
|
|
26
|
-
const delay = 2 // 20ms delay between chunks for reliable delivery
|
|
27
|
-
|
|
28
|
-
if(data.multi) {
|
|
29
|
-
// Multi-chunk streaming
|
|
30
|
-
for(const i in chunks) {
|
|
31
|
-
const chunk = chunks[i]
|
|
32
|
-
socket.send(JSON.stringify({
|
|
33
|
-
id,
|
|
34
|
-
data: {
|
|
35
|
-
...data, chunk,
|
|
36
|
-
done: +i === chunks.length - 1 // Last chunk gets done: true
|
|
37
|
-
}
|
|
38
|
-
}))
|
|
39
|
-
await wait(delay)
|
|
40
|
-
}
|
|
41
|
-
} else {
|
|
42
|
-
// Single response
|
|
43
|
-
socket.send(JSON.stringify({
|
|
44
|
-
id,
|
|
45
|
-
data: {
|
|
46
|
-
...data,
|
|
47
|
-
chunk: chunks[0],
|
|
48
|
-
done: true
|
|
49
|
-
}
|
|
50
|
-
}))
|
|
51
|
-
}
|
|
52
|
-
return null
|
|
53
|
-
}
|
|
54
|
-
socket.send(JSON.stringify({ id, data: response }))
|
|
55
|
-
return null
|
|
56
|
-
})
|
|
57
|
-
return true
|
|
58
|
-
})
|
|
59
|
-
return ff(server!)
|
|
60
|
-
})
|
|
61
|
-
server.on('', console.log)
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
const killServer = async () => new Promise<void>((ff, rj) => {
|
|
65
|
-
if(server) {
|
|
66
|
-
for(const socket of server.clients) socket.terminate()
|
|
67
|
-
server.close(() => {
|
|
68
|
-
server = null
|
|
69
|
-
ff()
|
|
70
|
-
})
|
|
71
|
-
} else
|
|
72
|
-
rj('The server is already down!')
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
export { createServer, killServer }
|
package/test/mock/server.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { WebSocketServer } from 'ws'
|
|
3
|
-
import { createServer, killServer } from './WS'
|
|
4
|
-
|
|
5
|
-
let port: number,
|
|
6
|
-
server: WebSocketServer|null
|
|
7
|
-
|
|
8
|
-
export default async (new_port?: number) => {
|
|
9
|
-
if(!server) {
|
|
10
|
-
port = new_port || 8000 + Math.ceil(Math.random()*500)
|
|
11
|
-
server = await createServer(port)
|
|
12
|
-
}
|
|
13
|
-
return {
|
|
14
|
-
server, port,
|
|
15
|
-
isRunning() {return Boolean(server)},
|
|
16
|
-
shutDown: async () => { await killServer(); server=null }
|
|
17
|
-
}
|
|
18
|
-
}
|
package/test/specs/close.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { test } from '../suite'
|
|
2
|
-
import { createNew } from '../utils.js'
|
|
3
|
-
import mockServer from '../mock/server'
|
|
4
|
-
|
|
5
|
-
/** Closes the connenction. */
|
|
6
|
-
test('close', () => new Promise<void>(async (ff, rj) => {
|
|
7
|
-
const {port} = await mockServer()
|
|
8
|
-
const ws = await createNew({}, port)
|
|
9
|
-
|
|
10
|
-
setTimeout(async () => {
|
|
11
|
-
try {
|
|
12
|
-
await ws.close()
|
|
13
|
-
if(ws.socket === null) ff(); else rj()
|
|
14
|
-
} catch(e) {
|
|
15
|
-
rj()
|
|
16
|
-
}
|
|
17
|
-
}, 500)
|
|
18
|
-
}))
|
package/test/specs/drops.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { createNew } from '../utils'
|
|
2
|
-
import mockServer from '../mock/server'
|
|
3
|
-
import { test } from '../suite'
|
|
4
|
-
import { wait } from 'pepka'
|
|
5
|
-
|
|
6
|
-
/** Rejects messages by timout */
|
|
7
|
-
test('drops', () => new Promise(async (ff, rj) => {
|
|
8
|
-
const {port, shutDown} = await mockServer()
|
|
9
|
-
const ws = await createNew({timeout: 500}, port)
|
|
10
|
-
|
|
11
|
-
await shutDown()
|
|
12
|
-
await wait(200)
|
|
13
|
-
|
|
14
|
-
const msg = {echo: true, msg: 'hello!'}
|
|
15
|
-
let to: NodeJS.Timeout
|
|
16
|
-
try {
|
|
17
|
-
to = setTimeout(() => {
|
|
18
|
-
return rj()
|
|
19
|
-
}, 600)
|
|
20
|
-
await ws.send(msg)
|
|
21
|
-
if(to) clearTimeout(to)
|
|
22
|
-
await mockServer(port)
|
|
23
|
-
return rj()
|
|
24
|
-
} catch(e) {
|
|
25
|
-
if(to!) clearTimeout(to)
|
|
26
|
-
await mockServer(port)
|
|
27
|
-
return ff()
|
|
28
|
-
}
|
|
29
|
-
}))
|
package/test/specs/echo.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { equals } from 'pepka'
|
|
2
|
-
import { createNew, timeout } from '../utils'
|
|
3
|
-
import mockServer from '../mock/server'
|
|
4
|
-
import { test } from '../suite'
|
|
5
|
-
|
|
6
|
-
/** Proof of work. */
|
|
7
|
-
test('echo', timeout(5e3, () => new Promise<void>(async (ff, rj) => {
|
|
8
|
-
const {port} = await mockServer()
|
|
9
|
-
let to = setTimeout(() => rj('cannot create'), 2e2)
|
|
10
|
-
const ws = await createNew({}, port)
|
|
11
|
-
clearTimeout(to)
|
|
12
|
-
|
|
13
|
-
to = setTimeout(() => rj('cannot ready'), 2e2)
|
|
14
|
-
await ws.ready()
|
|
15
|
-
clearTimeout(to)
|
|
16
|
-
|
|
17
|
-
const msg = {echo: true, msg: 'hello!'}
|
|
18
|
-
to = setTimeout(() => rj('cannot send'), 2e2)
|
|
19
|
-
const response = await ws.send(msg)
|
|
20
|
-
clearTimeout(to)
|
|
21
|
-
|
|
22
|
-
if(equals(response, msg)) ff(); else rj('echo msg is not equal.')
|
|
23
|
-
})
|
|
24
|
-
))
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
// TODO with mock server with specific port.
|