agent-messenger 2.27.0 → 2.27.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.
Files changed (37) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dist/package.json +1 -1
  3. package/dist/src/platforms/discord/listener.d.ts +9 -1
  4. package/dist/src/platforms/discord/listener.d.ts.map +1 -1
  5. package/dist/src/platforms/discord/listener.js +79 -3
  6. package/dist/src/platforms/discord/listener.js.map +1 -1
  7. package/dist/src/platforms/discordbot/listener.d.ts +6 -0
  8. package/dist/src/platforms/discordbot/listener.d.ts.map +1 -1
  9. package/dist/src/platforms/discordbot/listener.js +78 -2
  10. package/dist/src/platforms/discordbot/listener.js.map +1 -1
  11. package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
  12. package/dist/src/platforms/kakaotalk/client.js +4 -4
  13. package/dist/src/platforms/kakaotalk/client.js.map +1 -1
  14. package/package.json +1 -1
  15. package/skills/agent-channeltalk/SKILL.md +1 -1
  16. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  17. package/skills/agent-discord/SKILL.md +1 -1
  18. package/skills/agent-discordbot/SKILL.md +1 -1
  19. package/skills/agent-instagram/SKILL.md +1 -1
  20. package/skills/agent-kakaotalk/SKILL.md +1 -1
  21. package/skills/agent-line/SKILL.md +1 -1
  22. package/skills/agent-slack/SKILL.md +1 -1
  23. package/skills/agent-slackbot/SKILL.md +1 -1
  24. package/skills/agent-teams/SKILL.md +1 -1
  25. package/skills/agent-telegram/SKILL.md +1 -1
  26. package/skills/agent-telegrambot/SKILL.md +1 -1
  27. package/skills/agent-webex/SKILL.md +1 -1
  28. package/skills/agent-webexbot/SKILL.md +1 -1
  29. package/skills/agent-wechatbot/SKILL.md +1 -1
  30. package/skills/agent-whatsapp/SKILL.md +1 -1
  31. package/skills/agent-whatsappbot/SKILL.md +1 -1
  32. package/src/platforms/discord/listener.test.ts +175 -143
  33. package/src/platforms/discord/listener.ts +87 -3
  34. package/src/platforms/discordbot/listener.test.ts +186 -184
  35. package/src/platforms/discordbot/listener.ts +83 -2
  36. package/src/platforms/kakaotalk/client.test.ts +40 -0
  37. package/src/platforms/kakaotalk/client.ts +6 -4
@@ -107,11 +107,40 @@ function createMockClient(overrides: Record<string, any> = {}) {
107
107
  } as any
108
108
  }
109
109
 
110
+ // start() now resolves only once the gateway emits READY, so the handshake must be
111
+ // driven concurrently while start() is pending. This waits for connect() to create the
112
+ // socket (after the awaited gatewayConnect microtask) before the caller drives it.
113
+ async function waitForSocket(): Promise<MockWs> {
114
+ for (let i = 0; i < 50 && !mockWsInstance; i++) {
115
+ await Promise.resolve()
116
+ }
117
+ return mockWsInstance
118
+ }
119
+
120
+ async function startAndReady(listener: DiscordBotListener): Promise<void> {
121
+ const started = listener.start()
122
+ const ws = await waitForSocket()
123
+ ws.simulateOpen()
124
+ ws.simulateHello()
125
+ ws.simulateReady()
126
+ await started
127
+ }
128
+
129
+ // For tests that exercise pre-READY behavior (Identify/heartbeat/close paths): start() stays
130
+ // pending here. The start promise's rejection is swallowed; afterEach stop() clears its timer.
131
+ async function startConnecting(listener: DiscordBotListener): Promise<MockWs> {
132
+ listener.start().catch(() => {})
133
+ const ws = await waitForSocket()
134
+ ws.simulateOpen()
135
+ return ws
136
+ }
137
+
110
138
  describe('DiscordBotListener', () => {
111
139
  let listener: DiscordBotListener
112
140
 
113
141
  afterEach(() => {
114
142
  listener?.stop()
143
+ mockWsInstance = undefined as unknown as MockWs
115
144
  })
116
145
 
117
146
  describe('start', () => {
@@ -119,21 +148,75 @@ describe('DiscordBotListener', () => {
119
148
  const client = createMockClient()
120
149
  listener = new DiscordBotListener(client)
121
150
 
122
- await listener.start()
123
- mockWsInstance.simulateOpen()
151
+ await startAndReady(listener)
124
152
 
125
153
  expect(client.gatewayConnect).toHaveBeenCalledTimes(1)
126
154
  })
127
155
 
156
+ it('does not resolve until a READY dispatch is delivered', async () => {
157
+ const client = createMockClient()
158
+ listener = new DiscordBotListener(client)
159
+
160
+ let resolved = false
161
+ const started = listener.start().then(() => {
162
+ resolved = true
163
+ })
164
+
165
+ const ws = await waitForSocket()
166
+ ws.simulateOpen()
167
+ ws.simulateHello()
168
+
169
+ // given: socket open + Hello (Identify sent) but no READY yet
170
+ await Promise.resolve()
171
+ expect(resolved).toBe(false)
172
+
173
+ // when: READY arrives
174
+ ws.simulateReady()
175
+ await started
176
+
177
+ expect(resolved).toBe(true)
178
+ })
179
+
128
180
  it('is idempotent', async () => {
129
181
  const client = createMockClient()
130
182
  listener = new DiscordBotListener(client)
131
183
 
132
- await listener.start()
184
+ await startAndReady(listener)
133
185
  await listener.start()
134
186
 
135
187
  expect(client.gatewayConnect).toHaveBeenCalledTimes(1)
136
188
  })
189
+
190
+ it('shares one readiness promise across concurrent start() calls', async () => {
191
+ const client = createMockClient()
192
+ listener = new DiscordBotListener(client)
193
+
194
+ const first = listener.start()
195
+ const second = listener.start()
196
+
197
+ const ws = await waitForSocket()
198
+ ws.simulateOpen()
199
+ ws.simulateHello()
200
+ ws.simulateReady()
201
+
202
+ await Promise.all([first, second])
203
+ expect(client.gatewayConnect).toHaveBeenCalledTimes(1)
204
+ })
205
+
206
+ it('rejects an in-flight start() when stop() is called before READY', async () => {
207
+ const client = createMockClient()
208
+ listener = new DiscordBotListener(client)
209
+
210
+ const started = listener.start()
211
+ const ws = await waitForSocket()
212
+ ws.simulateOpen()
213
+ ws.simulateHello()
214
+
215
+ // given: start() is still awaiting READY when the consumer stops the listener
216
+ listener.stop()
217
+
218
+ await expect(started).rejects.toThrow(/stopped before becoming ready/)
219
+ })
137
220
  })
138
221
 
139
222
  describe('connected event', () => {
@@ -144,10 +227,7 @@ describe('DiscordBotListener', () => {
144
227
  const connected: any[] = []
145
228
  listener.on('connected', (info) => connected.push(info))
146
229
 
147
- await listener.start()
148
- mockWsInstance.simulateOpen()
149
- mockWsInstance.simulateHello()
150
- mockWsInstance.simulateReady()
230
+ await startAndReady(listener)
151
231
 
152
232
  expect(connected.length).toBe(1)
153
233
  expect(connected[0].user.id).toBe('BOT_SELF')
@@ -160,9 +240,8 @@ describe('DiscordBotListener', () => {
160
240
  const client = createMockClient()
161
241
  listener = new DiscordBotListener(client)
162
242
 
163
- await listener.start()
164
- mockWsInstance.simulateOpen()
165
- mockWsInstance.simulateHello()
243
+ const ws = await startConnecting(listener)
244
+ ws.simulateHello()
166
245
 
167
246
  const sentMessages = mockWsInstance.sent.map((s) => JSON.parse(s))
168
247
  const identifyMsg = sentMessages.find((m) => m.op === 2)
@@ -176,9 +255,8 @@ describe('DiscordBotListener', () => {
176
255
  const customIntents = (1 << 9) | (1 << 15) // GuildMessages | MessageContent
177
256
  listener = new DiscordBotListener(client, { intents: customIntents })
178
257
 
179
- await listener.start()
180
- mockWsInstance.simulateOpen()
181
- mockWsInstance.simulateHello()
258
+ const ws = await startConnecting(listener)
259
+ ws.simulateHello()
182
260
 
183
261
  const sentMessages = mockWsInstance.sent.map((s) => JSON.parse(s))
184
262
  const identifyMsg = sentMessages.find((m) => m.op === 2)
@@ -191,9 +269,8 @@ describe('DiscordBotListener', () => {
191
269
  const client = createMockClient()
192
270
  listener = new DiscordBotListener(client)
193
271
 
194
- await listener.start()
195
- mockWsInstance.simulateOpen()
196
- mockWsInstance.simulateHello()
272
+ const ws = await startConnecting(listener)
273
+ ws.simulateHello()
197
274
 
198
275
  const sentMessages = mockWsInstance.sent.map((s) => JSON.parse(s))
199
276
  const identifyMsg = sentMessages.find((m) => m.op === 2)
@@ -214,10 +291,7 @@ describe('DiscordBotListener', () => {
214
291
  const messages: DiscordGatewayMessageCreateEvent[] = []
215
292
  listener.on('message_create', (event) => messages.push(event))
216
293
 
217
- await listener.start()
218
- mockWsInstance.simulateOpen()
219
- mockWsInstance.simulateHello()
220
- mockWsInstance.simulateReady()
294
+ await startAndReady(listener)
221
295
  mockWsInstance.simulateDispatch(
222
296
  'MESSAGE_CREATE',
223
297
  {
@@ -242,10 +316,7 @@ describe('DiscordBotListener', () => {
242
316
  const messages: DiscordGatewayMessageCreateEvent[] = []
243
317
  listener.on('message_create', (event) => messages.push(event))
244
318
 
245
- await listener.start()
246
- mockWsInstance.simulateOpen()
247
- mockWsInstance.simulateHello()
248
- mockWsInstance.simulateReady()
319
+ await startAndReady(listener)
249
320
  mockWsInstance.simulateDispatch(
250
321
  'MESSAGE_CREATE',
251
322
  {
@@ -283,10 +354,7 @@ describe('DiscordBotListener', () => {
283
354
  const updates: DiscordGatewayMessageUpdateEvent[] = []
284
355
  listener.on('message_update', (event) => updates.push(event))
285
356
 
286
- await listener.start()
287
- mockWsInstance.simulateOpen()
288
- mockWsInstance.simulateHello()
289
- mockWsInstance.simulateReady()
357
+ await startAndReady(listener)
290
358
  mockWsInstance.simulateDispatch(
291
359
  'MESSAGE_UPDATE',
292
360
  { id: 'msg_1', channel_id: 'C123', content: 'edited', edited_timestamp: '2024-01-01T00:01:00Z' },
@@ -305,10 +373,7 @@ describe('DiscordBotListener', () => {
305
373
  const deletes: DiscordGatewayMessageDeleteEvent[] = []
306
374
  listener.on('message_delete', (event) => deletes.push(event))
307
375
 
308
- await listener.start()
309
- mockWsInstance.simulateOpen()
310
- mockWsInstance.simulateHello()
311
- mockWsInstance.simulateReady()
376
+ await startAndReady(listener)
312
377
  mockWsInstance.simulateDispatch('MESSAGE_DELETE', { id: 'msg_1', channel_id: 'C123' }, 2)
313
378
 
314
379
  expect(deletes.length).toBe(1)
@@ -324,10 +389,7 @@ describe('DiscordBotListener', () => {
324
389
  const reactions: DiscordGatewayReactionEvent[] = []
325
390
  listener.on('message_reaction_add', (event) => reactions.push(event))
326
391
 
327
- await listener.start()
328
- mockWsInstance.simulateOpen()
329
- mockWsInstance.simulateHello()
330
- mockWsInstance.simulateReady()
392
+ await startAndReady(listener)
331
393
  mockWsInstance.simulateDispatch(
332
394
  'MESSAGE_REACTION_ADD',
333
395
  {
@@ -353,10 +415,7 @@ describe('DiscordBotListener', () => {
353
415
  const interactions: any[] = []
354
416
  listener.on('interaction_create', (event) => interactions.push(event))
355
417
 
356
- await listener.start()
357
- mockWsInstance.simulateOpen()
358
- mockWsInstance.simulateHello()
359
- mockWsInstance.simulateReady()
418
+ await startAndReady(listener)
360
419
  mockWsInstance.simulateDispatch(
361
420
  'INTERACTION_CREATE',
362
421
  {
@@ -383,10 +442,7 @@ describe('DiscordBotListener', () => {
383
442
  const events: DiscordGatewayGenericEvent[] = []
384
443
  listener.on('discord_event', (event) => events.push(event))
385
444
 
386
- await listener.start()
387
- mockWsInstance.simulateOpen()
388
- mockWsInstance.simulateHello()
389
- mockWsInstance.simulateReady()
445
+ await startAndReady(listener)
390
446
  mockWsInstance.simulateDispatch(
391
447
  'MESSAGE_CREATE',
392
448
  { id: 'm1', channel_id: 'C1', content: 'hi', timestamp: 't', author: { id: 'U1', username: 'u' } },
@@ -409,9 +465,8 @@ describe('DiscordBotListener', () => {
409
465
  const client = createMockClient()
410
466
  listener = new DiscordBotListener(client)
411
467
 
412
- await listener.start()
413
- mockWsInstance.simulateOpen()
414
- mockWsInstance.simulateHello(50)
468
+ const ws = await startConnecting(listener)
469
+ ws.simulateHello(50)
415
470
 
416
471
  await new Promise((r) => setTimeout(r, 150))
417
472
 
@@ -431,10 +486,7 @@ describe('DiscordBotListener', () => {
431
486
  const events: DiscordGatewayGenericEvent[] = []
432
487
  listener.on('discord_event', (event) => events.push(event))
433
488
 
434
- await listener.start()
435
- mockWsInstance.simulateOpen()
436
- mockWsInstance.simulateHello()
437
- mockWsInstance.simulateReady()
489
+ await startAndReady(listener)
438
490
  mockWsInstance.simulateHeartbeatACK()
439
491
 
440
492
  expect(events.length).toBe(0)
@@ -451,9 +503,8 @@ describe('DiscordBotListener', () => {
451
503
  const disconnected: boolean[] = []
452
504
  listener.on('disconnected', () => disconnected.push(true))
453
505
 
454
- await listener.start()
455
- mockWsInstance.simulateOpen()
456
- mockWsInstance.simulateHello(50)
506
+ const ws = await startConnecting(listener)
507
+ ws.simulateHello(50)
457
508
 
458
509
  await new Promise((r) => setTimeout(r, 300))
459
510
 
@@ -469,8 +520,7 @@ describe('DiscordBotListener', () => {
469
520
  const client = createMockClient()
470
521
  listener = new DiscordBotListener(client)
471
522
 
472
- await listener.start()
473
- mockWsInstance.simulateOpen()
523
+ await startConnecting(listener)
474
524
 
475
525
  listener.stop()
476
526
 
@@ -487,9 +537,8 @@ describe('DiscordBotListener', () => {
487
537
  const disconnected: boolean[] = []
488
538
  listener.on('disconnected', () => disconnected.push(true))
489
539
 
490
- await listener.start()
491
- mockWsInstance.simulateOpen()
492
- mockWsInstance.simulateClose()
540
+ const ws = await startConnecting(listener)
541
+ ws.simulateClose()
493
542
 
494
543
  expect(disconnected.length).toBe(1)
495
544
 
@@ -497,28 +546,20 @@ describe('DiscordBotListener', () => {
497
546
  expect(client.gatewayConnect.mock.calls.length).toBeGreaterThanOrEqual(2)
498
547
  })
499
548
 
500
- it('emits error and reconnects on gatewayConnect failure', async () => {
501
- let callCount = 0
549
+ it('rejects start() and stops on gatewayConnect failure during initial connect', async () => {
502
550
  const client = createMockClient({
503
- gatewayConnect: mock(() => {
504
- callCount++
505
- if (callCount === 1) return Promise.reject(new Error('network_error'))
506
- return Promise.resolve({ token: 'fake-bot-token' })
507
- }),
551
+ gatewayConnect: mock(() => Promise.reject(new Error('network_error'))),
508
552
  })
509
553
 
510
554
  listener = new DiscordBotListener(client)
555
+ listener.on('error', () => {})
511
556
 
512
- const errors: Error[] = []
513
- listener.on('error', (err) => errors.push(err))
514
-
515
- await listener.start()
557
+ await expect(listener.start()).rejects.toThrow('network_error')
516
558
 
559
+ // given: a pre-READY connect error tears down the listener (no resurrected reconnect loop)
517
560
  await new Promise((r) => setTimeout(r, 1500))
518
-
519
- expect(errors.length).toBe(1)
520
- expect(errors[0].message).toBe('network_error')
521
- expect(client.gatewayConnect.mock.calls.length).toBeGreaterThanOrEqual(2)
561
+ expect((listener as any).running).toBe(false)
562
+ expect(client.gatewayConnect).toHaveBeenCalledTimes(1)
522
563
  })
523
564
  })
524
565
 
@@ -531,10 +572,7 @@ describe('DiscordBotListener', () => {
531
572
  const handler = (event: DiscordGatewayMessageCreateEvent) => messages.push(event)
532
573
  listener.on('message_create', handler)
533
574
 
534
- await listener.start()
535
- mockWsInstance.simulateOpen()
536
- mockWsInstance.simulateHello()
537
- mockWsInstance.simulateReady()
575
+ await startAndReady(listener)
538
576
  mockWsInstance.simulateDispatch(
539
577
  'MESSAGE_CREATE',
540
578
  { id: 'm1', channel_id: 'C1', content: 'a', timestamp: 't', author: { id: 'U1', username: 'u' } },
@@ -559,10 +597,7 @@ describe('DiscordBotListener', () => {
559
597
  const messages: DiscordGatewayMessageCreateEvent[] = []
560
598
  listener.once('message_create', (event) => messages.push(event))
561
599
 
562
- await listener.start()
563
- mockWsInstance.simulateOpen()
564
- mockWsInstance.simulateHello()
565
- mockWsInstance.simulateReady()
600
+ await startAndReady(listener)
566
601
  mockWsInstance.simulateDispatch(
567
602
  'MESSAGE_CREATE',
568
603
  { id: 'm1', channel_id: 'C1', content: 'first', timestamp: 't', author: { id: 'U1', username: 'u' } },
@@ -584,10 +619,7 @@ describe('DiscordBotListener', () => {
584
619
  const client = createMockClient()
585
620
  listener = new DiscordBotListener(client)
586
621
 
587
- await listener.start()
588
- mockWsInstance.simulateOpen()
589
- mockWsInstance.simulateHello()
590
- mockWsInstance.simulateReady()
622
+ await startAndReady(listener)
591
623
 
592
624
  ;(listener as any).reconnectAttempts = 5
593
625
  mockWsInstance.simulateReconnect()
@@ -601,10 +633,7 @@ describe('DiscordBotListener', () => {
601
633
  const client = createMockClient()
602
634
  listener = new DiscordBotListener(client)
603
635
 
604
- await listener.start()
605
- mockWsInstance.simulateOpen()
606
- mockWsInstance.simulateHello()
607
- mockWsInstance.simulateReady()
636
+ await startAndReady(listener)
608
637
 
609
638
  expect((listener as any).sessionId).toBe('session_123')
610
639
 
@@ -619,10 +648,7 @@ describe('DiscordBotListener', () => {
619
648
  const client = createMockClient()
620
649
  listener = new DiscordBotListener(client)
621
650
 
622
- await listener.start()
623
- mockWsInstance.simulateOpen()
624
- mockWsInstance.simulateHello()
625
- mockWsInstance.simulateReady()
651
+ await startAndReady(listener)
626
652
 
627
653
  const sessionIdBefore = (listener as any).sessionId
628
654
 
@@ -637,10 +663,7 @@ describe('DiscordBotListener', () => {
637
663
  const client = createMockClient()
638
664
  listener = new DiscordBotListener(client)
639
665
 
640
- await listener.start()
641
- mockWsInstance.simulateOpen()
642
- mockWsInstance.simulateHello()
643
- mockWsInstance.simulateReady()
666
+ await startAndReady(listener)
644
667
 
645
668
  mockWsInstance.simulateClose()
646
669
 
@@ -658,39 +681,38 @@ describe('DiscordBotListener', () => {
658
681
  })
659
682
 
660
683
  describe('non-recoverable close codes', () => {
661
- it('emits error and stops on code 4014 (privileged intent not approved)', async () => {
662
- const client = createMockClient()
663
- listener = new DiscordBotListener(client)
664
-
665
- const errors: Error[] = []
666
- listener.on('error', (err) => errors.push(err))
667
-
668
- await listener.start()
669
- mockWsInstance.simulateOpen()
670
- mockWsInstance.simulateClose(4014)
684
+ for (const code of [4004, 4013, 4014]) {
685
+ it(`rejects start() and stops on non-recoverable close ${code}`, async () => {
686
+ const client = createMockClient()
687
+ listener = new DiscordBotListener(client)
688
+ listener.on('error', () => {})
671
689
 
672
- await new Promise((r) => setTimeout(r, 50))
690
+ const started = listener.start()
691
+ const ws = await waitForSocket()
692
+ ws.simulateOpen()
693
+ ws.simulateClose(code)
673
694
 
674
- expect(errors.length).toBe(1)
675
- expect(errors[0].message).toContain('4014')
676
- expect(client.gatewayConnect).toHaveBeenCalledTimes(1)
677
- })
695
+ await expect(started).rejects.toThrow(String(code))
696
+ expect((listener as any).running).toBe(false)
697
+ expect(client.gatewayConnect).toHaveBeenCalledTimes(1)
698
+ })
699
+ }
700
+ })
678
701
 
679
- it('emits error and stops on code 4004 (invalid token)', async () => {
702
+ describe('connect timeout', () => {
703
+ it('rejects start() and tears down when READY never arrives', async () => {
680
704
  const client = createMockClient()
681
- listener = new DiscordBotListener(client)
682
-
683
- const errors: Error[] = []
684
- listener.on('error', (err) => errors.push(err))
685
-
686
- await listener.start()
687
- mockWsInstance.simulateOpen()
688
- mockWsInstance.simulateClose(4004)
705
+ listener = new DiscordBotListener(client, { connectTimeoutMs: 100 })
706
+ listener.on('error', () => {})
689
707
 
690
- await new Promise((r) => setTimeout(r, 50))
708
+ const started = listener.start()
709
+ const ws = await waitForSocket()
710
+ ws.simulateOpen()
711
+ ws.simulateHello()
712
+ // given: Identify sent but the gateway never delivers READY
691
713
 
692
- expect(errors.length).toBe(1)
693
- expect(errors[0].message).toContain('4004')
714
+ await expect(started).rejects.toThrow(/did not become ready/)
715
+ expect((listener as any).running).toBe(false)
694
716
  })
695
717
  })
696
718
 
@@ -699,10 +721,7 @@ describe('DiscordBotListener', () => {
699
721
  const client = createMockClient()
700
722
  listener = new DiscordBotListener(client)
701
723
 
702
- await listener.start()
703
- mockWsInstance.simulateOpen()
704
- mockWsInstance.simulateHello()
705
- mockWsInstance.simulateReady()
724
+ await startAndReady(listener)
706
725
 
707
726
  expect((listener as any).sessionId).toBe('session_123')
708
727
 
@@ -729,10 +748,7 @@ describe('DiscordBotListener', () => {
729
748
  const client = createMockClient()
730
749
  listener = new DiscordBotListener(client)
731
750
 
732
- await listener.start()
733
- mockWsInstance.simulateOpen()
734
- mockWsInstance.simulateHello()
735
- mockWsInstance.simulateReady()
751
+ await startAndReady(listener)
736
752
 
737
753
  mockWsInstance.simulateClose(4009)
738
754
 
@@ -749,10 +765,12 @@ describe('DiscordBotListener', () => {
749
765
  const client = createMockClient()
750
766
  listener = new DiscordBotListener(client)
751
767
 
752
- await listener.start()
753
- mockWsInstance.simulateOpen()
754
- mockWsInstance.simulateHello(50)
755
- mockWsInstance.simulateReady()
768
+ const started = listener.start()
769
+ const ws = await waitForSocket()
770
+ ws.simulateOpen()
771
+ ws.simulateHello(50)
772
+ ws.simulateReady()
773
+ await started
756
774
 
757
775
  mockWsInstance.simulateHello(50)
758
776
 
@@ -777,10 +795,7 @@ describe('DiscordBotListener', () => {
777
795
  const client = createMockClient()
778
796
  listener = new DiscordBotListener(client)
779
797
 
780
- await listener.start()
781
- mockWsInstance.simulateOpen()
782
- mockWsInstance.simulateHello()
783
- mockWsInstance.simulateReady()
798
+ await startAndReady(listener)
784
799
 
785
800
  mockWsInstance.simulateInvalidSession(true)
786
801
 
@@ -798,10 +813,7 @@ describe('DiscordBotListener', () => {
798
813
  const client = createMockClient()
799
814
  listener = new DiscordBotListener(client)
800
815
 
801
- await listener.start()
802
- mockWsInstance.simulateOpen()
803
- mockWsInstance.simulateHello()
804
- mockWsInstance.simulateReady()
816
+ await startAndReady(listener)
805
817
 
806
818
  const sentBefore = mockWsInstance.sent.length
807
819
  mockWsInstance.simulateMessage({ op: 1 })
@@ -818,10 +830,7 @@ describe('DiscordBotListener', () => {
818
830
  const client = createMockClient()
819
831
  listener = new DiscordBotListener(client)
820
832
 
821
- await listener.start()
822
- mockWsInstance.simulateOpen()
823
- mockWsInstance.simulateHello()
824
- mockWsInstance.simulateReady()
833
+ await startAndReady(listener)
825
834
 
826
835
  mockWsInstance.simulateDispatch(
827
836
  'MESSAGE_CREATE',
@@ -836,10 +845,7 @@ describe('DiscordBotListener', () => {
836
845
  const client = createMockClient()
837
846
  listener = new DiscordBotListener(client)
838
847
 
839
- await listener.start()
840
- mockWsInstance.simulateOpen()
841
- mockWsInstance.simulateHello()
842
- mockWsInstance.simulateReady()
848
+ await startAndReady(listener)
843
849
 
844
850
  mockWsInstance.simulateDispatch(
845
851
  'MESSAGE_CREATE',
@@ -863,11 +869,11 @@ describe('DiscordBotListener', () => {
863
869
  const client = createMockClient()
864
870
  listener = new DiscordBotListener(client)
865
871
 
866
- await listener.start()
872
+ await startConnecting(listener)
867
873
  ;(listener as any).reconnectAttempts = 5
868
874
  listener.stop()
869
875
 
870
- await listener.start()
876
+ await startConnecting(listener)
871
877
  expect((listener as any).reconnectAttempts).toBe(0)
872
878
  })
873
879
  })
@@ -877,10 +883,11 @@ describe('DiscordBotListener', () => {
877
883
  const client = createMockClient()
878
884
  listener = new DiscordBotListener(client)
879
885
 
880
- await listener.start()
881
- mockWsInstance.simulateOpen()
882
- mockWsInstance.simulateHello()
883
- mockWsInstance.simulateMessage({
886
+ const started = listener.start()
887
+ const ws = await waitForSocket()
888
+ ws.simulateOpen()
889
+ ws.simulateHello()
890
+ ws.simulateMessage({
884
891
  op: 0,
885
892
  t: 'READY',
886
893
  s: 1,
@@ -890,6 +897,7 @@ describe('DiscordBotListener', () => {
890
897
  user: { id: 'BOT', username: 'bot' },
891
898
  },
892
899
  })
900
+ await started
893
901
 
894
902
  mockWsInstance.simulateClose()
895
903
  await new Promise((r) => setTimeout(r, 1500))
@@ -901,7 +909,8 @@ describe('DiscordBotListener', () => {
901
909
  const client = createMockClient()
902
910
  listener = new DiscordBotListener(client)
903
911
 
904
- await listener.start()
912
+ listener.start().catch(() => {})
913
+ await waitForSocket()
905
914
 
906
915
  expect(MockWs.lastUrl).toBe('wss://gateway.discord.gg/?v=10&encoding=json')
907
916
  })
@@ -912,10 +921,11 @@ describe('DiscordBotListener', () => {
912
921
  const client = createMockClient()
913
922
  listener = new DiscordBotListener(client)
914
923
 
915
- await listener.start()
924
+ listener.start().catch(() => {})
925
+ const ws = await waitForSocket()
916
926
  ;(listener as any).reconnectAttempts = 5
917
927
 
918
- mockWsInstance.simulateOpen()
928
+ ws.simulateOpen()
919
929
 
920
930
  // given: a socket opens but no READY received yet
921
931
  // then: reconnectAttempts must NOT be reset (open alone is not a successful session)
@@ -926,12 +936,14 @@ describe('DiscordBotListener', () => {
926
936
  const client = createMockClient()
927
937
  listener = new DiscordBotListener(client)
928
938
 
929
- await listener.start()
939
+ const started = listener.start()
940
+ const ws = await waitForSocket()
930
941
  ;(listener as any).reconnectAttempts = 5
931
942
 
932
- mockWsInstance.simulateOpen()
933
- mockWsInstance.simulateHello()
934
- mockWsInstance.simulateReady()
943
+ ws.simulateOpen()
944
+ ws.simulateHello()
945
+ ws.simulateReady()
946
+ await started
935
947
 
936
948
  expect((listener as any).reconnectAttempts).toBe(0)
937
949
  })
@@ -940,10 +952,7 @@ describe('DiscordBotListener', () => {
940
952
  const client = createMockClient()
941
953
  listener = new DiscordBotListener(client)
942
954
 
943
- await listener.start()
944
- mockWsInstance.simulateOpen()
945
- mockWsInstance.simulateHello()
946
- mockWsInstance.simulateReady()
955
+ await startAndReady(listener)
947
956
 
948
957
  mockWsInstance.simulateClose()
949
958
  await new Promise((r) => setTimeout(r, 1500))
@@ -966,10 +975,7 @@ describe('DiscordBotListener', () => {
966
975
  const connectedEvents: any[] = []
967
976
  listener.on('connected', (info) => connectedEvents.push(info))
968
977
 
969
- await listener.start()
970
- mockWsInstance.simulateOpen()
971
- mockWsInstance.simulateHello()
972
- mockWsInstance.simulateReady()
978
+ await startAndReady(listener)
973
979
 
974
980
  expect(connectedEvents.length).toBe(1)
975
981
 
@@ -991,15 +997,13 @@ describe('DiscordBotListener', () => {
991
997
  const client = createMockClient()
992
998
  listener = new DiscordBotListener(client)
993
999
 
994
- await listener.start()
995
- mockWsInstance.simulateOpen()
996
- mockWsInstance.simulateHello()
997
- mockWsInstance.simulateReady()
1000
+ await startAndReady(listener)
998
1001
 
999
1002
  const oldWs = mockWsInstance
1000
1003
 
1001
1004
  listener.stop()
1002
- await listener.start()
1005
+ mockWsInstance = undefined as unknown as MockWs
1006
+ await startConnecting(listener)
1003
1007
 
1004
1008
  // given: a fresh socket from the second start()
1005
1009
  // when: the OLD socket fires a stale close event
@@ -1018,10 +1022,7 @@ describe('DiscordBotListener', () => {
1018
1022
  const client = createMockClient()
1019
1023
  listener = new DiscordBotListener(client)
1020
1024
 
1021
- await listener.start()
1022
- mockWsInstance.simulateOpen()
1023
- mockWsInstance.simulateHello()
1024
- mockWsInstance.simulateReady()
1025
+ await startAndReady(listener)
1025
1026
 
1026
1027
  const initialGeneration = (listener as any).generation
1027
1028
 
@@ -1029,7 +1030,8 @@ describe('DiscordBotListener', () => {
1029
1030
 
1030
1031
  // when: stop()+start() before the d=true delay fires
1031
1032
  listener.stop()
1032
- await listener.start()
1033
+ mockWsInstance = undefined as unknown as MockWs
1034
+ await startConnecting(listener)
1033
1035
 
1034
1036
  await new Promise((r) => setTimeout(r, 1500))
1035
1037