@owlmeans/client-socket 0.1.18-rc.5 → 0.1.18-rc.50
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/README.md +22 -16
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/client-socket/SKILL.md +173 -8
- package/build/consts.d.ts +8 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +17 -0
- package/build/consts.js.map +1 -0
- package/build/consts.local.d.ts +3 -0
- package/build/consts.local.d.ts.map +1 -0
- package/build/consts.local.js +2 -0
- package/build/consts.local.js.map +1 -0
- package/build/helper/types.d.ts +22 -0
- package/build/helper/types.d.ts.map +1 -0
- package/build/helper/types.js +2 -0
- package/build/helper/types.js.map +1 -0
- package/build/helper.d.ts +8 -4
- package/build/helper.d.ts.map +1 -1
- package/build/helper.js +79 -49
- package/build/helper.js.map +1 -1
- package/build/hooks.d.ts +18 -0
- package/build/hooks.d.ts.map +1 -0
- package/build/hooks.js +66 -0
- package/build/hooks.js.map +1 -0
- package/build/index.d.ts +4 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3 -0
- package/build/index.js.map +1 -1
- package/build/status.d.ts +30 -0
- package/build/status.d.ts.map +1 -0
- package/build/status.js +151 -0
- package/build/status.js.map +1 -0
- package/build/types.d.ts +75 -0
- package/build/types.d.ts.map +1 -1
- package/build/utils/connection.d.ts +12 -3
- package/build/utils/connection.d.ts.map +1 -1
- package/build/utils/connection.js +264 -28
- package/build/utils/connection.js.map +1 -1
- package/build/utils/consts.local.d.ts +6 -0
- package/build/utils/consts.local.d.ts.map +1 -0
- package/build/utils/consts.local.js +6 -0
- package/build/utils/consts.local.js.map +1 -0
- package/build/utils/index.d.ts +1 -0
- package/build/utils/index.d.ts.map +1 -1
- package/build/utils/types.d.ts +31 -0
- package/build/utils/types.d.ts.map +1 -0
- package/build/utils/types.js +2 -0
- package/build/utils/types.js.map +1 -0
- package/package.json +15 -11
- package/src/consts.local.ts +3 -0
- package/src/consts.ts +20 -0
- package/src/helper/types.ts +20 -0
- package/src/helper.ts +95 -53
- package/src/hooks.ts +75 -0
- package/src/index.ts +4 -0
- package/src/status.ts +162 -0
- package/src/types.ts +84 -1
- package/src/utils/connection.ts +256 -33
- package/src/utils/consts.local.ts +5 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/types.ts +33 -0
- package/tests/context.ts +20 -0
- package/tests/reconnect.spec.ts +372 -0
- package/tests/tsconfig.json +12 -0
- package/tsconfig.json +1 -1
- package/build/.gitkeep +0 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from 'bun:test'
|
|
2
|
+
import type { Server } from 'bun'
|
|
3
|
+
import { socketClientHelper } from '../src/helper.js'
|
|
4
|
+
import { makeConnection } from '../src/utils/connection.js'
|
|
5
|
+
import { appendSocketStatus } from '../src/status.js'
|
|
6
|
+
import { SocketSystemEvent } from '@owlmeans/socket'
|
|
7
|
+
import type { Connection, EventMessage } from '@owlmeans/socket'
|
|
8
|
+
import { makeTestContext } from './context.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Real `Bun.serve` WebSocket servers and the real carrier — no mocks. Every server here answers
|
|
12
|
+
* every upgrade and otherwise does nothing; the point is the CLIENT's behaviour when the server
|
|
13
|
+
* disappears and comes back (or never comes back at all).
|
|
14
|
+
*/
|
|
15
|
+
let server: Server | null = null
|
|
16
|
+
|
|
17
|
+
const startServer = (port: number, echo: boolean = true): Server => {
|
|
18
|
+
server = Bun.serve({
|
|
19
|
+
port,
|
|
20
|
+
fetch(req, srv) {
|
|
21
|
+
if (srv.upgrade(req)) return undefined
|
|
22
|
+
return new Response('upgrade failed', { status: 400 })
|
|
23
|
+
},
|
|
24
|
+
// Answers a heartbeat ping with a pong, exactly as `server-socket` does, and echoes every
|
|
25
|
+
// other frame — enough for a test to prove a frame sent after a reconnect reaches the SAME
|
|
26
|
+
// pre-drop `observe` handler. `echo: false` upgrades and then answers nothing at all: a
|
|
27
|
+
// half-open connection, from the client's side.
|
|
28
|
+
websocket: {
|
|
29
|
+
open() { },
|
|
30
|
+
message(ws, data) {
|
|
31
|
+
if (!echo) return
|
|
32
|
+
try {
|
|
33
|
+
if (typeof data === 'string' && JSON.parse(data)?.type === 'ping') {
|
|
34
|
+
ws.send(JSON.stringify({ type: 'pong' }))
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
// Not JSON at all — echoed below like any other frame.
|
|
39
|
+
}
|
|
40
|
+
ws.send(data)
|
|
41
|
+
},
|
|
42
|
+
close() { },
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
return server
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const stopServer = () => {
|
|
49
|
+
server?.stop(true)
|
|
50
|
+
server = null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
afterEach(() => {
|
|
54
|
+
stopServer()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const waitFor = async (check: () => boolean, timeout: number, step = 20): Promise<void> => {
|
|
58
|
+
const startedAt = Date.now()
|
|
59
|
+
while (!check()) {
|
|
60
|
+
if (Date.now() - startedAt > timeout) {
|
|
61
|
+
throw new Error('waitFor: condition never became true')
|
|
62
|
+
}
|
|
63
|
+
await Bun.sleep(step)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const systemEvents = (connection: Connection): string[] => {
|
|
68
|
+
const seen: string[] = []
|
|
69
|
+
connection.listen(async message => {
|
|
70
|
+
if (typeof message === 'object' && (message as EventMessage<unknown>).type === 'system') {
|
|
71
|
+
seen.push((message as EventMessage<unknown>).event)
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
return seen
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
describe('@owlmeans/client-socket — reconnect', () => {
|
|
78
|
+
test('reconnects when the server restarts on the same port, on the SAME connection', async () => {
|
|
79
|
+
const initial = startServer(0)
|
|
80
|
+
const port = initial.port
|
|
81
|
+
|
|
82
|
+
const ctx = makeTestContext({ reconnect: { minDelay: 50, maxDelay: 150, stableAfter: 200, budget: 30_000 } })
|
|
83
|
+
const connection = await socketClientHelper.connect(() => `ws://localhost:${port}`, ctx)
|
|
84
|
+
const events = systemEvents(connection)
|
|
85
|
+
|
|
86
|
+
// Register an observer BEFORE the drop — it must still be the one that fires after the
|
|
87
|
+
// reconnect, because the model is never recreated.
|
|
88
|
+
let observed = 0
|
|
89
|
+
connection.observe('probe', async () => { observed += 1 })
|
|
90
|
+
|
|
91
|
+
stopServer()
|
|
92
|
+
await waitFor(() => events.includes(SocketSystemEvent.Disconnected), 2_000)
|
|
93
|
+
|
|
94
|
+
startServer(port)
|
|
95
|
+
await waitFor(() => events.includes(SocketSystemEvent.Reconnected), 5_000)
|
|
96
|
+
|
|
97
|
+
expect(events).not.toContain(SocketSystemEvent.Close)
|
|
98
|
+
|
|
99
|
+
// The pre-drop observer is still live on the reconnected socket.
|
|
100
|
+
await connection.notify('probe', {})
|
|
101
|
+
await waitFor(() => observed === 1, 1_000)
|
|
102
|
+
|
|
103
|
+
await connection.close()
|
|
104
|
+
}, 10_000)
|
|
105
|
+
|
|
106
|
+
test('backoff grows geometrically and is capped at maxDelay', async () => {
|
|
107
|
+
// Drive `makeConnection` directly — `connect()`/`ws()` only hand back a `Connection` once
|
|
108
|
+
// `ready` settles, and here we need to observe frames WHILE every attempt is still failing.
|
|
109
|
+
const policy = {
|
|
110
|
+
minDelay: 200, maxDelay: 3_000, factor: 2, jitter: 0.1,
|
|
111
|
+
budget: 60_000, reviveBudget: 60_000, stableAfter: 999_999, heartbeat: 999_999, pongTimeout: 999_999,
|
|
112
|
+
}
|
|
113
|
+
const delays: number[] = []
|
|
114
|
+
const { connection, ready } = makeConnection({
|
|
115
|
+
policy, retry: true, open: () => Promise.reject(new Error('never up')),
|
|
116
|
+
})
|
|
117
|
+
ready.catch(() => void 0)
|
|
118
|
+
connection.listen(async message => {
|
|
119
|
+
const msg = message as EventMessage<{ attempt: number, delay: number }>
|
|
120
|
+
if (typeof message === 'object' && msg.type === 'system' && msg.event === SocketSystemEvent.Reconnecting) {
|
|
121
|
+
delays.push(msg.payload.delay)
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
await waitFor(() => delays.length >= 5, 10_000)
|
|
126
|
+
await connection.close()
|
|
127
|
+
|
|
128
|
+
expect(delays[0]).toBeGreaterThanOrEqual(180)
|
|
129
|
+
expect(delays[0]).toBeLessThanOrEqual(220)
|
|
130
|
+
expect(delays[1]).toBeGreaterThanOrEqual(360)
|
|
131
|
+
expect(delays[1]).toBeLessThanOrEqual(440)
|
|
132
|
+
expect(delays[2]).toBeGreaterThanOrEqual(720)
|
|
133
|
+
expect(delays[2]).toBeLessThanOrEqual(880)
|
|
134
|
+
expect(delays[3]).toBeGreaterThanOrEqual(1_440)
|
|
135
|
+
expect(delays[3]).toBeLessThanOrEqual(1_760)
|
|
136
|
+
// The 5th attempt's raw geometric value (3200) already exceeds maxDelay, so it is capped.
|
|
137
|
+
expect(delays[4]).toBeGreaterThanOrEqual(2_700)
|
|
138
|
+
expect(delays[4]).toBeLessThanOrEqual(3_000)
|
|
139
|
+
}, 15_000)
|
|
140
|
+
|
|
141
|
+
test('client-initiated close never retries and reports exactly one close frame', async () => {
|
|
142
|
+
const initial = startServer(0)
|
|
143
|
+
const port = initial.port
|
|
144
|
+
|
|
145
|
+
const ctx = makeTestContext({ reconnect: { minDelay: 50, maxDelay: 150 } })
|
|
146
|
+
const connection = await socketClientHelper.connect(() => `ws://localhost:${port}`, ctx)
|
|
147
|
+
const events = systemEvents(connection)
|
|
148
|
+
|
|
149
|
+
await connection.close()
|
|
150
|
+
await Bun.sleep(100)
|
|
151
|
+
|
|
152
|
+
expect(events.filter(e => e === SocketSystemEvent.Close).length).toBe(1)
|
|
153
|
+
expect(events).not.toContain(SocketSystemEvent.Disconnected)
|
|
154
|
+
expect(events).not.toContain(SocketSystemEvent.Reconnecting)
|
|
155
|
+
}, 5_000)
|
|
156
|
+
|
|
157
|
+
test('a socket that answers nothing at all is force-closed by the heartbeat', async () => {
|
|
158
|
+
const silent = startServer(0, false)
|
|
159
|
+
|
|
160
|
+
const ctx = makeTestContext({
|
|
161
|
+
reconnect: { heartbeat: 60, pongTimeout: 30, minDelay: 20, maxDelay: 40, budget: 10_000 }
|
|
162
|
+
})
|
|
163
|
+
const connection = await socketClientHelper.connect(() => `ws://localhost:${silent.port}`, ctx)
|
|
164
|
+
const events = systemEvents(connection)
|
|
165
|
+
|
|
166
|
+
await waitFor(() => events.includes(SocketSystemEvent.Disconnected), 3_000)
|
|
167
|
+
|
|
168
|
+
await connection.close()
|
|
169
|
+
}, 10_000)
|
|
170
|
+
|
|
171
|
+
test('a heartbeat wake-up long after the previous one keeps a socket the server answers', async () => {
|
|
172
|
+
const initial = startServer(0)
|
|
173
|
+
const port = initial.port
|
|
174
|
+
const realNow = Date.now.bind(Date)
|
|
175
|
+
|
|
176
|
+
const ctx = makeTestContext({
|
|
177
|
+
reconnect: { heartbeat: 100, pongTimeout: 50, minDelay: 20, maxDelay: 40, budget: 10_000 }
|
|
178
|
+
})
|
|
179
|
+
const connection = await socketClientHelper.connect(() => `ws://localhost:${port}`, ctx)
|
|
180
|
+
const events = systemEvents(connection)
|
|
181
|
+
let observed = 0
|
|
182
|
+
connection.observe('probe', async () => { observed += 1 })
|
|
183
|
+
|
|
184
|
+
// What a backgrounded tab looks like: real timers keep firing, but every wake-up lands a
|
|
185
|
+
// minute later than the last as far as the clock is concerned.
|
|
186
|
+
let skew = 0
|
|
187
|
+
Date.now = () => realNow() + skew
|
|
188
|
+
const throttle = setInterval(() => { skew += 60_000 }, 50)
|
|
189
|
+
const wait = async (ms: number) => {
|
|
190
|
+
const until = realNow() + ms
|
|
191
|
+
while (realNow() < until) await Bun.sleep(20)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
await wait(800)
|
|
196
|
+
// Several heartbeat intervals with an apparent minute between each: nothing dropped.
|
|
197
|
+
expect(events).toEqual([])
|
|
198
|
+
|
|
199
|
+
await connection.notify('probe', {})
|
|
200
|
+
const deadline = realNow() + 2_000
|
|
201
|
+
while (observed === 0 && realNow() < deadline) await Bun.sleep(20)
|
|
202
|
+
expect(observed).toBe(1)
|
|
203
|
+
} finally {
|
|
204
|
+
clearInterval(throttle)
|
|
205
|
+
Date.now = realNow
|
|
206
|
+
await connection.close()
|
|
207
|
+
}
|
|
208
|
+
}, 15_000)
|
|
209
|
+
|
|
210
|
+
test('gives up after the retry budget elapses: status reads lost, connect() rejects', async () => {
|
|
211
|
+
const ctx = makeTestContext({ reconnect: { minDelay: 20, maxDelay: 40, budget: 200, stableAfter: 50 } })
|
|
212
|
+
appendSocketStatus(ctx)
|
|
213
|
+
ctx.configure()
|
|
214
|
+
await ctx.init()
|
|
215
|
+
|
|
216
|
+
await expect(socketClientHelper.connect(() => 'ws://127.0.0.1:1', ctx)).rejects.toBeTruthy()
|
|
217
|
+
|
|
218
|
+
expect(ctx.socketStatus().state()).toBe('lost')
|
|
219
|
+
}, 5_000)
|
|
220
|
+
|
|
221
|
+
test('retry() revives a lost connection in place: no close frame, reconnected on the SAME connection', async () => {
|
|
222
|
+
const initial = startServer(0)
|
|
223
|
+
const port = initial.port
|
|
224
|
+
|
|
225
|
+
const ctx = makeTestContext({
|
|
226
|
+
reconnect: { minDelay: 20, maxDelay: 40, budget: 300, reviveBudget: 2_000, stableAfter: 50 }
|
|
227
|
+
})
|
|
228
|
+
appendSocketStatus(ctx)
|
|
229
|
+
ctx.configure()
|
|
230
|
+
await ctx.init()
|
|
231
|
+
|
|
232
|
+
const connection = await socketClientHelper.connect(() => `ws://localhost:${port}`, ctx)
|
|
233
|
+
const events = systemEvents(connection)
|
|
234
|
+
let observed = 0
|
|
235
|
+
connection.observe('probe', async () => { observed += 1 })
|
|
236
|
+
|
|
237
|
+
stopServer()
|
|
238
|
+
await waitFor(() => events.includes(SocketSystemEvent.Lost), 3_000)
|
|
239
|
+
expect(ctx.socketStatus().state()).toBe('lost')
|
|
240
|
+
// `close` means gone for good — a lost connection is still revivable.
|
|
241
|
+
expect(events).not.toContain(SocketSystemEvent.Close)
|
|
242
|
+
|
|
243
|
+
startServer(port)
|
|
244
|
+
let retried = 0
|
|
245
|
+
ctx.socketStatus().onRetry(() => { retried += 1 })
|
|
246
|
+
expect(ctx.socketStatus().retry()).toBe(true)
|
|
247
|
+
expect(retried).toBe(1)
|
|
248
|
+
expect(ctx.socketStatus().state()).toBe('reconnecting')
|
|
249
|
+
|
|
250
|
+
await waitFor(() => events.includes(SocketSystemEvent.Reconnected), 3_000)
|
|
251
|
+
expect(ctx.socketStatus().state()).toBe('online')
|
|
252
|
+
|
|
253
|
+
await connection.notify('probe', {})
|
|
254
|
+
await waitFor(() => observed === 1, 1_000)
|
|
255
|
+
|
|
256
|
+
// Nothing is lost any more — a second retry has nothing to do and tells nobody.
|
|
257
|
+
expect(ctx.socketStatus().retry()).toBe(false)
|
|
258
|
+
expect(retried).toBe(1)
|
|
259
|
+
|
|
260
|
+
await connection.close()
|
|
261
|
+
}, 10_000)
|
|
262
|
+
|
|
263
|
+
test('a revive that cannot succeed reports lost again once reviveBudget elapses', async () => {
|
|
264
|
+
const initial = startServer(0)
|
|
265
|
+
const port = initial.port
|
|
266
|
+
|
|
267
|
+
const ctx = makeTestContext({
|
|
268
|
+
reconnect: { minDelay: 20, maxDelay: 40, budget: 200, reviveBudget: 200, stableAfter: 50 }
|
|
269
|
+
})
|
|
270
|
+
appendSocketStatus(ctx)
|
|
271
|
+
ctx.configure()
|
|
272
|
+
await ctx.init()
|
|
273
|
+
|
|
274
|
+
const connection = await socketClientHelper.connect(() => `ws://localhost:${port}`, ctx)
|
|
275
|
+
const events = systemEvents(connection)
|
|
276
|
+
|
|
277
|
+
stopServer()
|
|
278
|
+
await waitFor(() => events.includes(SocketSystemEvent.Lost), 3_000)
|
|
279
|
+
|
|
280
|
+
ctx.socketStatus().retry()
|
|
281
|
+
expect(ctx.socketStatus().state()).toBe('reconnecting')
|
|
282
|
+
await waitFor(() => events.filter(e => e === SocketSystemEvent.Lost).length === 2, 3_000)
|
|
283
|
+
expect(ctx.socketStatus().state()).toBe('lost')
|
|
284
|
+
expect(events).not.toContain(SocketSystemEvent.Close)
|
|
285
|
+
|
|
286
|
+
await connection.close()
|
|
287
|
+
expect(ctx.socketStatus().state()).toBe('online')
|
|
288
|
+
}, 10_000)
|
|
289
|
+
|
|
290
|
+
test('revive() on a connection still retrying attempts at once and outlives its old budget', async () => {
|
|
291
|
+
// Its own budget alone would report lost ~400ms in; the revive at ~50ms must push that out.
|
|
292
|
+
const policy = {
|
|
293
|
+
minDelay: 200, maxDelay: 200, factor: 1, jitter: 0,
|
|
294
|
+
budget: 300, reviveBudget: 1_500, stableAfter: 999_999, heartbeat: 999_999, pongTimeout: 999_999,
|
|
295
|
+
}
|
|
296
|
+
let opens = 0
|
|
297
|
+
const events: string[] = []
|
|
298
|
+
const { connection, ready, revive } = makeConnection({
|
|
299
|
+
policy, retry: true, open: () => { opens += 1; return Promise.reject(new Error('down')) },
|
|
300
|
+
})
|
|
301
|
+
ready.catch(() => void 0)
|
|
302
|
+
connection.listen(async message => {
|
|
303
|
+
const msg = message as EventMessage<unknown>
|
|
304
|
+
if (typeof message === 'object' && msg.type === 'system') events.push(msg.event)
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
await waitFor(() => opens === 1, 1_000)
|
|
308
|
+
await Bun.sleep(50)
|
|
309
|
+
revive()
|
|
310
|
+
// At once — not after the 200ms backoff the failed first attempt scheduled.
|
|
311
|
+
expect(opens).toBe(2)
|
|
312
|
+
|
|
313
|
+
await Bun.sleep(600)
|
|
314
|
+
expect(events).not.toContain(SocketSystemEvent.Lost)
|
|
315
|
+
await waitFor(() => events.includes(SocketSystemEvent.Lost), 3_000)
|
|
316
|
+
|
|
317
|
+
await connection.close()
|
|
318
|
+
}, 10_000)
|
|
319
|
+
|
|
320
|
+
test('revive() during an in-flight attempt still extends the budget that attempt will face', async () => {
|
|
321
|
+
// The 2nd attempt hangs 400ms and fails at ~450ms, past the 300ms budget — lost, unless the
|
|
322
|
+
// revive at ~100ms moved the budget first.
|
|
323
|
+
const policy = {
|
|
324
|
+
minDelay: 50, maxDelay: 50, factor: 1, jitter: 0,
|
|
325
|
+
budget: 300, reviveBudget: 1_500, stableAfter: 999_999, heartbeat: 999_999, pongTimeout: 999_999,
|
|
326
|
+
}
|
|
327
|
+
let opens = 0
|
|
328
|
+
const events: string[] = []
|
|
329
|
+
const { connection, ready, revive } = makeConnection({
|
|
330
|
+
policy, retry: true, open: async () => {
|
|
331
|
+
opens += 1
|
|
332
|
+
if (opens === 2) await Bun.sleep(400)
|
|
333
|
+
throw new Error('down')
|
|
334
|
+
},
|
|
335
|
+
})
|
|
336
|
+
ready.catch(() => void 0)
|
|
337
|
+
connection.listen(async message => {
|
|
338
|
+
const msg = message as EventMessage<unknown>
|
|
339
|
+
if (typeof message === 'object' && msg.type === 'system') events.push(msg.event)
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
await waitFor(() => opens === 2, 1_000)
|
|
343
|
+
await Bun.sleep(50)
|
|
344
|
+
revive()
|
|
345
|
+
// No second attempt on top of the one in flight.
|
|
346
|
+
expect(opens).toBe(2)
|
|
347
|
+
|
|
348
|
+
await Bun.sleep(600)
|
|
349
|
+
expect(events).not.toContain(SocketSystemEvent.Lost)
|
|
350
|
+
await waitFor(() => events.includes(SocketSystemEvent.Lost), 3_000)
|
|
351
|
+
|
|
352
|
+
await connection.close()
|
|
353
|
+
}, 10_000)
|
|
354
|
+
|
|
355
|
+
test('retry() drops a connection that never opened and tells its owner to dial again', async () => {
|
|
356
|
+
const ctx = makeTestContext({ reconnect: { minDelay: 20, maxDelay: 40, budget: 200, stableAfter: 50 } })
|
|
357
|
+
appendSocketStatus(ctx)
|
|
358
|
+
ctx.configure()
|
|
359
|
+
await ctx.init()
|
|
360
|
+
|
|
361
|
+
await expect(socketClientHelper.connect(() => 'ws://127.0.0.1:1', ctx)).rejects.toBeTruthy()
|
|
362
|
+
expect(ctx.socketStatus().state()).toBe('lost')
|
|
363
|
+
|
|
364
|
+
let retried = 0
|
|
365
|
+
ctx.socketStatus().onRetry(() => { retried += 1 })
|
|
366
|
+
expect(ctx.socketStatus().retry()).toBe(true)
|
|
367
|
+
|
|
368
|
+
// There was no `Connection` to revive, so nothing is left reporting — the owner re-dials.
|
|
369
|
+
expect(ctx.socketStatus().state()).toBe('online')
|
|
370
|
+
expect(retried).toBe(1)
|
|
371
|
+
}, 5_000)
|
|
372
|
+
})
|
package/tsconfig.json
CHANGED
package/build/.gitkeep
DELETED
|
File without changes
|