@dxos/messaging 0.5.9-main.07b4bad → 0.5.9-main.1c1903d

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 (44) hide show
  1. package/dist/lib/browser/index.mjs +812 -559
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +778 -545
  5. package/dist/lib/node/index.cjs.map +4 -4
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/types/src/messenger-monitor.d.ts +8 -0
  8. package/dist/types/src/messenger-monitor.d.ts.map +1 -0
  9. package/dist/types/src/messenger.d.ts +1 -0
  10. package/dist/types/src/messenger.d.ts.map +1 -1
  11. package/dist/types/src/signal-client/signal-client-monitor.d.ts +30 -0
  12. package/dist/types/src/signal-client/signal-client-monitor.d.ts.map +1 -0
  13. package/dist/types/src/signal-client/signal-client.d.ts +25 -50
  14. package/dist/types/src/signal-client/signal-client.d.ts.map +1 -1
  15. package/dist/types/src/signal-client/signal-local-state.d.ts +46 -0
  16. package/dist/types/src/signal-client/signal-local-state.d.ts.map +1 -0
  17. package/dist/types/src/signal-client/signal-rpc-client-monitor.d.ts +6 -0
  18. package/dist/types/src/signal-client/signal-rpc-client-monitor.d.ts.map +1 -0
  19. package/dist/types/src/signal-client/signal-rpc-client.d.ts +4 -2
  20. package/dist/types/src/signal-client/signal-rpc-client.d.ts.map +1 -1
  21. package/dist/types/src/signal-manager/memory-signal-manager.d.ts +0 -2
  22. package/dist/types/src/signal-manager/memory-signal-manager.d.ts.map +1 -1
  23. package/dist/types/src/signal-manager/signal-manager.d.ts +0 -2
  24. package/dist/types/src/signal-manager/signal-manager.d.ts.map +1 -1
  25. package/dist/types/src/signal-manager/websocket-signal-manager-monitor.d.ts +8 -0
  26. package/dist/types/src/signal-manager/websocket-signal-manager-monitor.d.ts.map +1 -0
  27. package/dist/types/src/signal-manager/websocket-signal-manager.d.ts +7 -3
  28. package/dist/types/src/signal-manager/websocket-signal-manager.d.ts.map +1 -1
  29. package/dist/types/src/signal-methods.d.ts +6 -4
  30. package/dist/types/src/signal-methods.d.ts.map +1 -1
  31. package/package.json +13 -12
  32. package/src/messenger-monitor.ts +20 -0
  33. package/src/messenger.ts +16 -5
  34. package/src/signal-client/signal-client-monitor.ts +111 -0
  35. package/src/signal-client/signal-client.test.ts +111 -259
  36. package/src/signal-client/signal-client.ts +141 -252
  37. package/src/signal-client/signal-local-state.ts +156 -0
  38. package/src/signal-client/signal-rpc-client-monitor.ts +15 -0
  39. package/src/signal-client/signal-rpc-client.ts +38 -21
  40. package/src/signal-manager/memory-signal-manager.ts +0 -2
  41. package/src/signal-manager/signal-manager.ts +0 -2
  42. package/src/signal-manager/websocket-signal-manager-monitor.ts +20 -0
  43. package/src/signal-manager/websocket-signal-manager.ts +48 -26
  44. package/src/signal-methods.ts +6 -4
@@ -13,9 +13,25 @@ import { Context } from "@dxos/context";
13
13
  import { invariant } from "@dxos/invariant";
14
14
  import { PublicKey } from "@dxos/keys";
15
15
  import { log } from "@dxos/log";
16
- import { TimeoutError as ProtocolTimeoutError, schema, trace } from "@dxos/protocols";
16
+ import { TimeoutError as ProtocolTimeoutError, schema, trace as trace2 } from "@dxos/protocols";
17
17
  import { ComplexMap, ComplexSet } from "@dxos/util";
18
18
 
19
+ // packages/core/mesh/messaging/src/messenger-monitor.ts
20
+ import { trace } from "@dxos/tracing";
21
+ var MessengerMonitor = class {
22
+ recordMessageAckFailed() {
23
+ trace.metrics.increment("mesh.signal.messenger.failed-ack", 1);
24
+ }
25
+ recordReliableMessage(params) {
26
+ trace.metrics.increment("mesh.signal.messenger.reliable-send", 1, {
27
+ tags: {
28
+ success: params.sent,
29
+ attempts: params.sendAttempts
30
+ }
31
+ });
32
+ }
33
+ };
34
+
19
35
  // packages/core/mesh/messaging/src/timeouts.ts
20
36
  var MESSAGE_TIMEOUT = 1e4;
21
37
 
@@ -26,6 +42,7 @@ var Acknowledgement = schema.getCodecForType("dxos.mesh.messaging.Acknowledgemen
26
42
  var RECEIVED_MESSAGES_GC_INTERVAL = 12e4;
27
43
  var Messenger = class {
28
44
  constructor({ signalManager, retryDelay = 300 }) {
45
+ this._monitor = new MessengerMonitor();
29
46
  // { peerId, payloadType } => listeners set
30
47
  this._listeners = new ComplexMap(({ peerId, payloadType }) => peerId.toHex() + payloadType);
31
48
  // peerId => listeners set
@@ -46,18 +63,18 @@ var Messenger = class {
46
63
  return;
47
64
  }
48
65
  const traceId = PublicKey.random().toHex();
49
- log.trace("dxos.mesh.messenger.open", trace.begin({
66
+ log.trace("dxos.mesh.messenger.open", trace2.begin({
50
67
  id: traceId
51
68
  }), {
52
69
  F: __dxlog_file,
53
- L: 69,
70
+ L: 71,
54
71
  S: this,
55
72
  C: (f, a) => f(...a)
56
73
  });
57
74
  this._ctx = new Context({
58
75
  onError: (err) => log.catch(err, void 0, {
59
76
  F: __dxlog_file,
60
- L: 71,
77
+ L: 73,
61
78
  S: this,
62
79
  C: (f, a) => f(...a)
63
80
  })
@@ -67,7 +84,7 @@ var Messenger = class {
67
84
  from: message.author
68
85
  }, {
69
86
  F: __dxlog_file,
70
- L: 75,
87
+ L: 77,
71
88
  S: this,
72
89
  C: (f, a) => f(...a)
73
90
  });
@@ -77,11 +94,11 @@ var Messenger = class {
77
94
  this._performGc();
78
95
  }, RECEIVED_MESSAGES_GC_INTERVAL);
79
96
  this._closed = false;
80
- log.trace("dxos.mesh.messenger.open", trace.end({
97
+ log.trace("dxos.mesh.messenger.open", trace2.end({
81
98
  id: traceId
82
99
  }), {
83
100
  F: __dxlog_file,
84
- L: 90,
101
+ L: 92,
85
102
  S: this,
86
103
  C: (f, a) => f(...a)
87
104
  });
@@ -96,7 +113,7 @@ var Messenger = class {
96
113
  async sendMessage({ author, recipient, payload }) {
97
114
  invariant(!this._closed, "Closed", {
98
115
  F: __dxlog_file,
99
- L: 102,
116
+ L: 104,
100
117
  S: this,
101
118
  A: [
102
119
  "!this._closed",
@@ -110,7 +127,7 @@ var Messenger = class {
110
127
  };
111
128
  invariant(!this._onAckCallbacks.has(reliablePayload.messageId), void 0, {
112
129
  F: __dxlog_file,
113
- L: 109,
130
+ L: 111,
114
131
  S: this,
115
132
  A: [
116
133
  "!this._onAckCallbacks.has(reliablePayload.messageId!)",
@@ -123,12 +140,13 @@ var Messenger = class {
123
140
  recipient
124
141
  }, {
125
142
  F: __dxlog_file,
126
- L: 110,
143
+ L: 112,
127
144
  S: this,
128
145
  C: (f, a) => f(...a)
129
146
  });
130
147
  let messageReceived;
131
148
  let timeoutHit;
149
+ let sendAttempts = 0;
132
150
  const promise = new Promise((resolve, reject) => {
133
151
  messageReceived = resolve;
134
152
  timeoutHit = reject;
@@ -138,10 +156,11 @@ var Messenger = class {
138
156
  messageId: reliablePayload.messageId
139
157
  }, {
140
158
  F: __dxlog_file,
141
- L: 124,
159
+ L: 127,
142
160
  S: this,
143
161
  C: (f, a) => f(...a)
144
162
  });
163
+ sendAttempts++;
145
164
  await this._encodeAndSend({
146
165
  author,
147
166
  recipient,
@@ -150,7 +169,7 @@ var Messenger = class {
150
169
  err
151
170
  }, {
152
171
  F: __dxlog_file,
153
- L: 126,
172
+ L: 130,
154
173
  S: this,
155
174
  C: (f, a) => f(...a)
156
175
  }));
@@ -160,18 +179,26 @@ var Messenger = class {
160
179
  messageId: reliablePayload.messageId
161
180
  }, {
162
181
  F: __dxlog_file,
163
- L: 135,
182
+ L: 139,
164
183
  S: this,
165
184
  C: (f, a) => f(...a)
166
185
  });
167
186
  this._onAckCallbacks.delete(reliablePayload.messageId);
168
187
  timeoutHit(new ProtocolTimeoutError("signaling message not delivered", new TimeoutError(MESSAGE_TIMEOUT, "Message not delivered")));
169
188
  void messageContext.dispose();
189
+ this._monitor.recordReliableMessage({
190
+ sendAttempts,
191
+ sent: false
192
+ });
170
193
  }, MESSAGE_TIMEOUT);
171
194
  this._onAckCallbacks.set(reliablePayload.messageId, () => {
172
195
  messageReceived();
173
196
  this._onAckCallbacks.delete(reliablePayload.messageId);
174
197
  void messageContext.dispose();
198
+ this._monitor.recordReliableMessage({
199
+ sendAttempts,
200
+ sent: true
201
+ });
175
202
  });
176
203
  await this._encodeAndSend({
177
204
  author,
@@ -187,7 +214,7 @@ var Messenger = class {
187
214
  async listen({ peerId, payloadType, onMessage }) {
188
215
  invariant(!this._closed, "Closed", {
189
216
  F: __dxlog_file,
190
- L: 171,
217
+ L: 177,
191
218
  S: this,
192
219
  A: [
193
220
  "!this._closed",
@@ -251,7 +278,7 @@ var Messenger = class {
251
278
  async _handleReliablePayload({ author, recipient, payload }) {
252
279
  invariant(payload.type_url === "dxos.mesh.messaging.ReliablePayload", void 0, {
253
280
  F: __dxlog_file,
254
- L: 232,
281
+ L: 238,
255
282
  S: this,
256
283
  A: [
257
284
  "payload.type_url === 'dxos.mesh.messaging.ReliablePayload'",
@@ -265,15 +292,20 @@ var Messenger = class {
265
292
  messageId: reliablePayload.messageId
266
293
  }, {
267
294
  F: __dxlog_file,
268
- L: 235,
295
+ L: 241,
269
296
  S: this,
270
297
  C: (f, a) => f(...a)
271
298
  });
272
- await this._sendAcknowledgement({
273
- author,
274
- recipient,
275
- messageId: reliablePayload.messageId
276
- });
299
+ try {
300
+ await this._sendAcknowledgement({
301
+ author,
302
+ recipient,
303
+ messageId: reliablePayload.messageId
304
+ });
305
+ } catch (err) {
306
+ this._monitor.recordMessageAckFailed();
307
+ throw err;
308
+ }
277
309
  if (this._receivedMessages.has(reliablePayload.messageId)) {
278
310
  return;
279
311
  }
@@ -287,7 +319,7 @@ var Messenger = class {
287
319
  async _handleAcknowledgement({ payload }) {
288
320
  invariant(payload.type_url === "dxos.mesh.messaging.Acknowledgement", void 0, {
289
321
  F: __dxlog_file,
290
- L: 258,
322
+ L: 269,
291
323
  S: this,
292
324
  A: [
293
325
  "payload.type_url === 'dxos.mesh.messaging.Acknowledgement'",
@@ -303,7 +335,7 @@ var Messenger = class {
303
335
  to: author
304
336
  }, {
305
337
  F: __dxlog_file,
306
- L: 271,
338
+ L: 282,
307
339
  S: this,
308
340
  C: (f, a) => f(...a)
309
341
  });
@@ -354,7 +386,7 @@ var Messenger = class {
354
386
  elapsed
355
387
  }, {
356
388
  F: __dxlog_file,
357
- L: 319,
389
+ L: 330,
358
390
  S: this,
359
391
  C: (f, a) => f(...a)
360
392
  });
@@ -363,37 +395,310 @@ var Messenger = class {
363
395
  };
364
396
 
365
397
  // packages/core/mesh/messaging/src/signal-client/signal-client.ts
366
- import { DeferredTask, Event, Trigger as Trigger2, asyncTimeout, scheduleTask as scheduleTask2, scheduleTaskInterval as scheduleTaskInterval3, sleep } from "@dxos/async";
367
- import { Context as Context3, cancelWithContext } from "@dxos/context";
398
+ import { DeferredTask, Event as Event2, Trigger as Trigger2, scheduleTask as scheduleTask2, scheduleTaskInterval as scheduleTaskInterval3, sleep } from "@dxos/async";
399
+ import { cancelWithContext as cancelWithContext2, Resource } from "@dxos/context";
368
400
  import { invariant as invariant3 } from "@dxos/invariant";
369
- import { PublicKey as PublicKey3 } from "@dxos/keys";
370
- import { log as log3 } from "@dxos/log";
371
- import { trace as trace3 } from "@dxos/protocols";
401
+ import { PublicKey as PublicKey4 } from "@dxos/keys";
402
+ import { log as log4 } from "@dxos/log";
403
+ import { trace as trace6 } from "@dxos/protocols";
372
404
  import { SignalState } from "@dxos/protocols/proto/dxos/mesh/signal";
373
- import { ComplexMap as ComplexMap2, ComplexSet as ComplexSet2 } from "@dxos/util";
405
+
406
+ // packages/core/mesh/messaging/src/signal-client/signal-client-monitor.ts
407
+ import { trace as trace3 } from "@dxos/tracing";
408
+ var SignalClientMonitor = class {
409
+ constructor() {
410
+ this._performance = {
411
+ sentMessages: 0,
412
+ receivedMessages: 0,
413
+ reconnectCounter: 0,
414
+ joinCounter: 0,
415
+ leaveCounter: 0
416
+ };
417
+ /**
418
+ * Timestamp of when the connection attempt was began.
419
+ */
420
+ this._connectionStarted = /* @__PURE__ */ new Date();
421
+ /**
422
+ * Timestamp of last state change.
423
+ */
424
+ this._lastStateChange = /* @__PURE__ */ new Date();
425
+ }
426
+ getRecordedTimestamps() {
427
+ return {
428
+ connectionStarted: this._connectionStarted,
429
+ lastStateChange: this._lastStateChange
430
+ };
431
+ }
432
+ recordStateChangeTime() {
433
+ this._lastStateChange = /* @__PURE__ */ new Date();
434
+ }
435
+ recordConnectionStartTime() {
436
+ this._connectionStarted = /* @__PURE__ */ new Date();
437
+ }
438
+ recordReconnect(params) {
439
+ this._performance.reconnectCounter++;
440
+ trace3.metrics.increment("mesh.signal.signal-client.reconnect", 1, {
441
+ tags: {
442
+ success: params.success
443
+ }
444
+ });
445
+ }
446
+ recordJoin() {
447
+ this._performance.joinCounter++;
448
+ }
449
+ recordLeave() {
450
+ this._performance.leaveCounter++;
451
+ }
452
+ recordMessageReceived(message) {
453
+ this._performance.receivedMessages++;
454
+ trace3.metrics.increment("mesh.signal.signal-client.received-total", 1, {
455
+ tags: createIdentityTags(message)
456
+ });
457
+ trace3.metrics.distribution("mesh.signal.signal-client.bytes-in", getByteCount(message), {
458
+ tags: createIdentityTags(message)
459
+ });
460
+ }
461
+ async recordMessageSending(message, sendMessage) {
462
+ this._performance.sentMessages++;
463
+ const tags = createIdentityTags(message);
464
+ let success = true;
465
+ try {
466
+ const reqStart = Date.now();
467
+ await sendMessage();
468
+ const reqDuration = Date.now() - reqStart;
469
+ trace3.metrics.distribution("mesh.signal.signal-client.send-duration", reqDuration, {
470
+ tags
471
+ });
472
+ trace3.metrics.distribution("mesh.signal.signal-client.bytes-out", getByteCount(message), {
473
+ tags
474
+ });
475
+ } catch (err) {
476
+ success = false;
477
+ }
478
+ trace3.metrics.increment("mesh.signal.signal-client.sent-total", 1, {
479
+ tags: {
480
+ ...tags,
481
+ success
482
+ }
483
+ });
484
+ }
485
+ recordStreamCloseErrors(count) {
486
+ trace3.metrics.increment("mesh.signal.signal-client.stream-close-errors", count);
487
+ }
488
+ recordReconciliation(params) {
489
+ trace3.metrics.increment("mesh.signal.signal-client.reconciliation", 1, {
490
+ tags: {
491
+ success: params.success
492
+ }
493
+ });
494
+ }
495
+ };
496
+ var getByteCount = (message) => {
497
+ return message.author.asUint8Array().length + message.recipient.asUint8Array().length + message.payload.type_url.length + message.payload.value.length;
498
+ };
499
+ var createIdentityTags = (message) => {
500
+ return {
501
+ peer: message.author.toHex()
502
+ };
503
+ };
504
+
505
+ // packages/core/mesh/messaging/src/signal-client/signal-local-state.ts
506
+ import { asyncTimeout, Event } from "@dxos/async";
507
+ import { cancelWithContext } from "@dxos/context";
508
+ import { PublicKey as PublicKey2 } from "@dxos/keys";
509
+ import { log as log2 } from "@dxos/log";
510
+ import { ComplexMap as ComplexMap2, ComplexSet as ComplexSet2, safeAwaitAll } from "@dxos/util";
511
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/core/mesh/messaging/src/signal-client/signal-local-state.ts";
512
+ var SignalLocalState = class {
513
+ constructor(_onMessage, _onSwarmEvent) {
514
+ this._onMessage = _onMessage;
515
+ this._onSwarmEvent = _onSwarmEvent;
516
+ this._swarmStreams = new ComplexMap2(({ topic, peerId }) => topic.toHex() + peerId.toHex());
517
+ this._joinedTopics = new ComplexSet2(({ topic, peerId }) => topic.toHex() + peerId.toHex());
518
+ this._subscribedMessages = new ComplexSet2(({ peerId }) => peerId.toHex());
519
+ this.messageStreams = new ComplexMap2((key) => key.toHex());
520
+ this.reconciled = new Event();
521
+ }
522
+ async safeCloseStreams() {
523
+ const streams = [
524
+ ...this._swarmStreams.values()
525
+ ].concat([
526
+ ...this.messageStreams.values()
527
+ ]);
528
+ this._swarmStreams.clear();
529
+ this.messageStreams.clear();
530
+ const failureCount = (await safeAwaitAll(streams, (s) => s.close())).length;
531
+ return {
532
+ failureCount
533
+ };
534
+ }
535
+ join({ topic, peerId }) {
536
+ this._joinedTopics.add({
537
+ topic,
538
+ peerId
539
+ });
540
+ }
541
+ leave({ topic, peerId }) {
542
+ void this._swarmStreams.get({
543
+ topic,
544
+ peerId
545
+ })?.close();
546
+ this._swarmStreams.delete({
547
+ topic,
548
+ peerId
549
+ });
550
+ this._joinedTopics.delete({
551
+ topic,
552
+ peerId
553
+ });
554
+ }
555
+ subscribeMessages(peerId) {
556
+ this._subscribedMessages.add({
557
+ peerId
558
+ });
559
+ }
560
+ unsubscribeMessages(peerId) {
561
+ log2("unsubscribing from messages", {
562
+ peerId
563
+ }, {
564
+ F: __dxlog_file2,
565
+ L: 76,
566
+ S: this,
567
+ C: (f, a) => f(...a)
568
+ });
569
+ this._subscribedMessages.delete({
570
+ peerId
571
+ });
572
+ void this.messageStreams.get(peerId)?.close();
573
+ this.messageStreams.delete(peerId);
574
+ }
575
+ async reconcile(ctx, client) {
576
+ await this._reconcileSwarmSubscriptions(ctx, client);
577
+ await this._reconcileMessageSubscriptions(ctx, client);
578
+ this.reconciled.emit();
579
+ }
580
+ async _reconcileSwarmSubscriptions(ctx, client) {
581
+ for (const { topic, peerId } of this._swarmStreams.keys()) {
582
+ if (this._joinedTopics.has({
583
+ topic,
584
+ peerId
585
+ })) {
586
+ continue;
587
+ }
588
+ void this._swarmStreams.get({
589
+ topic,
590
+ peerId
591
+ })?.close();
592
+ this._swarmStreams.delete({
593
+ topic,
594
+ peerId
595
+ });
596
+ }
597
+ for (const { topic, peerId } of this._joinedTopics.values()) {
598
+ if (this._swarmStreams.has({
599
+ topic,
600
+ peerId
601
+ })) {
602
+ continue;
603
+ }
604
+ const swarmStream = await asyncTimeout(cancelWithContext(ctx, client.join({
605
+ topic,
606
+ peerId
607
+ })), 5e3);
608
+ swarmStream.subscribe(async (swarmEvent) => {
609
+ if (this._joinedTopics.has({
610
+ topic,
611
+ peerId
612
+ })) {
613
+ log2("swarm event", {
614
+ swarmEvent
615
+ }, {
616
+ F: __dxlog_file2,
617
+ L: 112,
618
+ S: this,
619
+ C: (f, a) => f(...a)
620
+ });
621
+ await this._onSwarmEvent({
622
+ topic,
623
+ swarmEvent
624
+ });
625
+ }
626
+ });
627
+ this._swarmStreams.set({
628
+ topic,
629
+ peerId
630
+ }, swarmStream);
631
+ }
632
+ }
633
+ async _reconcileMessageSubscriptions(ctx, client) {
634
+ for (const peerId of this.messageStreams.keys()) {
635
+ if (this._subscribedMessages.has({
636
+ peerId
637
+ })) {
638
+ continue;
639
+ }
640
+ void this.messageStreams.get(peerId)?.close();
641
+ this.messageStreams.delete(peerId);
642
+ }
643
+ for (const { peerId } of this._subscribedMessages.values()) {
644
+ if (this.messageStreams.has(peerId)) {
645
+ continue;
646
+ }
647
+ const messageStream = await asyncTimeout(cancelWithContext(ctx, client.receiveMessages(peerId)), 5e3);
648
+ messageStream.subscribe(async (signalMessage) => {
649
+ if (this._subscribedMessages.has({
650
+ peerId
651
+ })) {
652
+ const message = {
653
+ author: PublicKey2.from(signalMessage.author),
654
+ recipient: PublicKey2.from(signalMessage.recipient),
655
+ payload: signalMessage.payload
656
+ };
657
+ await this._onMessage(message);
658
+ }
659
+ });
660
+ this.messageStreams.set(peerId, messageStream);
661
+ }
662
+ }
663
+ };
374
664
 
375
665
  // packages/core/mesh/messaging/src/signal-client/signal-rpc-client.ts
376
666
  import WebSocket from "isomorphic-ws";
377
- import { scheduleTaskInterval as scheduleTaskInterval2, Trigger } from "@dxos/async";
667
+ import { scheduleTaskInterval as scheduleTaskInterval2, TimeoutError as TimeoutError2, Trigger } from "@dxos/async";
378
668
  import { Context as Context2 } from "@dxos/context";
379
669
  import { invariant as invariant2 } from "@dxos/invariant";
380
- import { PublicKey as PublicKey2 } from "@dxos/keys";
381
- import { log as log2 } from "@dxos/log";
382
- import { schema as schema2, trace as trace2 } from "@dxos/protocols";
670
+ import { PublicKey as PublicKey3 } from "@dxos/keys";
671
+ import { log as log3 } from "@dxos/log";
672
+ import { schema as schema2, trace as trace5 } from "@dxos/protocols";
383
673
  import { createProtoRpcPeer } from "@dxos/rpc";
384
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/core/mesh/messaging/src/signal-client/signal-rpc-client.ts";
674
+
675
+ // packages/core/mesh/messaging/src/signal-client/signal-rpc-client-monitor.ts
676
+ import { trace as trace4 } from "@dxos/tracing";
677
+ var SignalRpcClientMonitor = class {
678
+ recordClientCloseFailure(params) {
679
+ trace4.metrics.increment("mesh.signal.signal-rpc-client.close-failure", 1, {
680
+ tags: {
681
+ reason: params.failureReason
682
+ }
683
+ });
684
+ }
685
+ };
686
+
687
+ // packages/core/mesh/messaging/src/signal-client/signal-rpc-client.ts
688
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/mesh/messaging/src/signal-client/signal-rpc-client.ts";
385
689
  var SIGNAL_KEEPALIVE_INTERVAL = 1e4;
386
690
  var SignalRPCClient = class {
387
691
  constructor({ url, callbacks = {} }) {
388
692
  this._connectTrigger = new Trigger();
389
693
  this._closed = false;
390
694
  this._closeComplete = new Trigger();
391
- const traceId = PublicKey2.random().toHex();
392
- log2.trace("dxos.mesh.signal-rpc-client.constructor", trace2.begin({
695
+ this._monitor = new SignalRpcClientMonitor();
696
+ const traceId = PublicKey3.random().toHex();
697
+ log3.trace("dxos.mesh.signal-rpc-client.constructor", trace5.begin({
393
698
  id: traceId
394
699
  }), {
395
- F: __dxlog_file2,
396
- L: 55,
700
+ F: __dxlog_file3,
701
+ L: 60,
397
702
  S: this,
398
703
  C: (f, a) => f(...a)
399
704
  });
@@ -413,9 +718,9 @@ var SignalRPCClient = class {
413
718
  try {
414
719
  this._socket.send(msg);
415
720
  } catch (err) {
416
- log2.warn("send error", err, {
417
- F: __dxlog_file2,
418
- L: 74,
721
+ log3.warn("send error", err, {
722
+ F: __dxlog_file3,
723
+ L: 79,
419
724
  S: this,
420
725
  C: (f, a) => f(...a)
421
726
  });
@@ -438,9 +743,13 @@ var SignalRPCClient = class {
438
743
  this._socket.onopen = async () => {
439
744
  try {
440
745
  await this._rpc.open();
441
- log2(`RPC open ${this._url}`, void 0, {
442
- F: __dxlog_file2,
443
- L: 95,
746
+ if (this._closed) {
747
+ await this._safeCloseRpc();
748
+ return;
749
+ }
750
+ log3(`RPC open ${this._url}`, void 0, {
751
+ F: __dxlog_file3,
752
+ L: 104,
444
753
  S: this,
445
754
  C: (f, a) => f(...a)
446
755
  });
@@ -452,12 +761,14 @@ var SignalRPCClient = class {
452
761
  }, SIGNAL_KEEPALIVE_INTERVAL);
453
762
  } catch (err) {
454
763
  this._callbacks.onError?.(err);
764
+ this._socket.close();
765
+ this._closed = true;
455
766
  }
456
767
  };
457
768
  this._socket.onclose = async () => {
458
- log2(`Disconnected ${this._url}`, void 0, {
459
- F: __dxlog_file2,
460
- L: 116,
769
+ log3(`Disconnected ${this._url}`, void 0, {
770
+ F: __dxlog_file3,
771
+ L: 127,
461
772
  S: this,
462
773
  C: (f, a) => f(...a)
463
774
  });
@@ -467,89 +778,73 @@ var SignalRPCClient = class {
467
778
  };
468
779
  this._socket.onerror = async (event) => {
469
780
  if (this._closed) {
781
+ this._socket.close();
470
782
  return;
471
783
  }
472
- this._callbacks.onError?.(event.error ?? new Error(event.message));
473
- this._connectTrigger.reset();
474
- try {
475
- await this._rpc?.close();
476
- } catch (err) {
477
- log2.catch(err, void 0, {
478
- F: __dxlog_file2,
479
- L: 134,
480
- S: this,
481
- C: (f, a) => f(...a)
482
- });
483
- }
484
784
  this._closed = true;
485
- log2.warn(event.message ?? "Socket error", {
785
+ this._callbacks.onError?.(event.error ?? new Error(event.message));
786
+ await this._safeCloseRpc();
787
+ log3.warn(`Socket ${event.type ?? "unknown"} error`, {
788
+ message: event.message,
486
789
  url: this._url
487
790
  }, {
488
- F: __dxlog_file2,
489
- L: 138,
791
+ F: __dxlog_file3,
792
+ L: 143,
490
793
  S: this,
491
794
  C: (f, a) => f(...a)
492
795
  });
493
796
  };
494
- log2.trace("dxos.mesh.signal-rpc-client.constructor", trace2.end({
797
+ log3.trace("dxos.mesh.signal-rpc-client.constructor", trace5.end({
495
798
  id: traceId
496
799
  }), {
497
- F: __dxlog_file2,
498
- L: 141,
800
+ F: __dxlog_file3,
801
+ L: 146,
499
802
  S: this,
500
803
  C: (f, a) => f(...a)
501
804
  });
502
805
  }
503
806
  async close() {
504
- await this._keepaliveCtx?.dispose();
807
+ if (this._closed) {
808
+ return;
809
+ }
505
810
  this._closed = true;
811
+ await this._keepaliveCtx?.dispose();
506
812
  try {
507
- await this._rpc?.close();
508
- if (this._socket?.readyState === WebSocket.OPEN || this._socket?.readyState === WebSocket.CONNECTING) {
813
+ await this._safeCloseRpc();
814
+ if (this._socket.readyState === WebSocket.OPEN || this._socket.readyState === WebSocket.CONNECTING) {
509
815
  this._socket.close();
510
816
  }
511
817
  await this._closeComplete.wait({
512
818
  timeout: 1e3
513
819
  });
514
820
  } catch (err) {
515
- log2.warn("close error", err, {
516
- F: __dxlog_file2,
517
- L: 156,
518
- S: this,
519
- C: (f, a) => f(...a)
821
+ const failureReason = err instanceof TimeoutError2 ? "timeout" : err?.constructor?.name ?? "unknown";
822
+ this._monitor.recordClientCloseFailure({
823
+ failureReason
520
824
  });
521
825
  }
522
826
  }
523
827
  async join({ topic, peerId }) {
524
- log2("join", {
828
+ log3("join", {
525
829
  topic,
526
830
  peerId,
527
831
  metadata: this._callbacks?.getMetadata?.()
528
832
  }, {
529
- F: __dxlog_file2,
530
- L: 161,
833
+ F: __dxlog_file3,
834
+ L: 172,
531
835
  S: this,
532
836
  C: (f, a) => f(...a)
533
837
  });
534
- await this._connectTrigger.wait();
535
838
  invariant2(!this._closed, "SignalRPCClient is closed", {
536
- F: __dxlog_file2,
537
- L: 163,
839
+ F: __dxlog_file3,
840
+ L: 173,
538
841
  S: this,
539
842
  A: [
540
843
  "!this._closed",
541
844
  "'SignalRPCClient is closed'"
542
845
  ]
543
846
  });
544
- invariant2(this._rpc, "Rpc is not initialized", {
545
- F: __dxlog_file2,
546
- L: 164,
547
- S: this,
548
- A: [
549
- "this._rpc",
550
- "'Rpc is not initialized'"
551
- ]
552
- });
847
+ await this._connectTrigger.wait();
553
848
  const swarmStream = this._rpc.rpc.Signal.join({
554
849
  swarm: topic.asUint8Array(),
555
850
  peer: peerId.asUint8Array(),
@@ -559,17 +854,17 @@ var SignalRPCClient = class {
559
854
  return swarmStream;
560
855
  }
561
856
  async receiveMessages(peerId) {
562
- log2("receiveMessages", {
857
+ log3("receiveMessages", {
563
858
  peerId
564
859
  }, {
565
- F: __dxlog_file2,
566
- L: 175,
860
+ F: __dxlog_file3,
861
+ L: 185,
567
862
  S: this,
568
863
  C: (f, a) => f(...a)
569
864
  });
570
865
  invariant2(!this._closed, "SignalRPCClient is closed", {
571
- F: __dxlog_file2,
572
- L: 176,
866
+ F: __dxlog_file3,
867
+ L: 186,
573
868
  S: this,
574
869
  A: [
575
870
  "!this._closed",
@@ -577,15 +872,6 @@ var SignalRPCClient = class {
577
872
  ]
578
873
  });
579
874
  await this._connectTrigger.wait();
580
- invariant2(this._rpc, "Rpc is not initialized", {
581
- F: __dxlog_file2,
582
- L: 178,
583
- S: this,
584
- A: [
585
- "this._rpc",
586
- "'Rpc is not initialized'"
587
- ]
588
- });
589
875
  const messageStream = this._rpc.rpc.Signal.receiveMessages({
590
876
  peer: peerId.asUint8Array()
591
877
  });
@@ -593,20 +879,20 @@ var SignalRPCClient = class {
593
879
  return messageStream;
594
880
  }
595
881
  async sendMessage({ author, recipient, payload }) {
596
- log2("sendMessage", {
882
+ log3("sendMessage", {
597
883
  author,
598
884
  recipient,
599
885
  payload,
600
886
  metadata: this._callbacks?.getMetadata?.()
601
887
  }, {
602
- F: __dxlog_file2,
603
- L: 187,
888
+ F: __dxlog_file3,
889
+ L: 196,
604
890
  S: this,
605
891
  C: (f, a) => f(...a)
606
892
  });
607
893
  invariant2(!this._closed, "SignalRPCClient is closed", {
608
- F: __dxlog_file2,
609
- L: 188,
894
+ F: __dxlog_file3,
895
+ L: 197,
610
896
  S: this,
611
897
  A: [
612
898
  "!this._closed",
@@ -614,15 +900,6 @@ var SignalRPCClient = class {
614
900
  ]
615
901
  });
616
902
  await this._connectTrigger.wait();
617
- invariant2(this._rpc, "Rpc is not initialized", {
618
- F: __dxlog_file2,
619
- L: 190,
620
- S: this,
621
- A: [
622
- "this._rpc",
623
- "'Rpc is not initialized'"
624
- ]
625
- });
626
903
  await this._rpc.rpc.Signal.sendMessage({
627
904
  author: author.asUint8Array(),
628
905
  recipient: recipient.asUint8Array(),
@@ -630,53 +907,59 @@ var SignalRPCClient = class {
630
907
  metadata: this._callbacks?.getMetadata?.()
631
908
  });
632
909
  }
910
+ async _safeCloseRpc() {
911
+ try {
912
+ this._connectTrigger.reset();
913
+ await this._rpc.close();
914
+ } catch (err) {
915
+ log3.catch(err, void 0, {
916
+ F: __dxlog_file3,
917
+ L: 212,
918
+ S: this,
919
+ C: (f, a) => f(...a)
920
+ });
921
+ }
922
+ }
633
923
  };
634
924
 
635
925
  // packages/core/mesh/messaging/src/signal-client/signal-client.ts
636
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/mesh/messaging/src/signal-client/signal-client.ts";
926
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/core/mesh/messaging/src/signal-client/signal-client.ts";
637
927
  var DEFAULT_RECONNECT_TIMEOUT = 100;
638
928
  var MAX_RECONNECT_TIMEOUT = 5e3;
639
929
  var ERROR_RECONCILE_DELAY = 1e3;
640
930
  var RECONCILE_INTERVAL = 5e3;
641
- var SignalClient = class {
931
+ var SignalClient = class extends Resource {
642
932
  /**
643
933
  * @param _host Signal server websocket URL.
934
+ * @param onMessage called when a new message is received.
935
+ * @param onSwarmEvent called when a new swarm event is received.
936
+ * @param _getMetadata signal-message metadata provider, called for every message.
644
937
  */
645
- constructor(_host, _onMessage, _onSwarmEvent, _getMetadata) {
938
+ constructor(_host, onMessage, onSwarmEvent, _getMetadata) {
939
+ super();
646
940
  this._host = _host;
647
- this._onMessage = _onMessage;
648
- this._onSwarmEvent = _onSwarmEvent;
649
941
  this._getMetadata = _getMetadata;
942
+ this._monitor = new SignalClientMonitor();
650
943
  this._state = SignalState.CLOSED;
651
- this._reconnectAfter = DEFAULT_RECONNECT_TIMEOUT;
652
- this._connectionStarted = /* @__PURE__ */ new Date();
653
- this._lastStateChange = /* @__PURE__ */ new Date();
944
+ this._lastReconciliationFailed = false;
654
945
  this._clientReady = new Trigger2();
655
- this.statusChanged = new Event();
656
- this.commandTrace = new Event();
657
- this._swarmStreams = new ComplexMap2(({ topic, peerId }) => topic.toHex() + peerId.toHex());
658
- this._joinedTopics = new ComplexSet2(({ topic, peerId }) => topic.toHex() + peerId.toHex());
659
- this._messageStreams = new ComplexMap2((key) => key.toHex());
660
- this._subscribedMessages = new ComplexSet2(({ peerId }) => peerId.toHex());
661
- this._reconciled = new Event();
662
- this._instanceId = PublicKey3.random().toHex();
663
- this._performance = {
664
- sentMessages: 0,
665
- receivedMessages: 0,
666
- reconnectCounter: 0,
667
- joinCounter: 0,
668
- leaveCounter: 0
669
- };
946
+ this._reconnectAfter = DEFAULT_RECONNECT_TIMEOUT;
947
+ this._instanceId = PublicKey4.random().toHex();
948
+ this.statusChanged = new Event2();
670
949
  if (!this._host.startsWith("wss://") && !this._host.startsWith("ws://")) {
671
950
  throw new Error(`Signal server requires a websocket URL. Provided: ${this._host}`);
672
951
  }
952
+ this.localState = new SignalLocalState((message) => {
953
+ this._monitor.recordMessageReceived(message);
954
+ return onMessage(message);
955
+ }, onSwarmEvent);
673
956
  }
674
- async open() {
675
- log3.trace("dxos.mesh.signal-client.open", trace3.begin({
957
+ async _open() {
958
+ log4.trace("dxos.mesh.signal-client.open", trace6.begin({
676
959
  id: this._instanceId
677
960
  }), {
678
- F: __dxlog_file3,
679
- L: 128,
961
+ F: __dxlog_file4,
962
+ L: 83,
680
963
  S: this,
681
964
  C: (f, a) => f(...a)
682
965
  });
@@ -686,26 +969,33 @@ var SignalClient = class {
686
969
  ].includes(this._state)) {
687
970
  return;
688
971
  }
689
- this._ctx = new Context3({
690
- onError: (err) => {
691
- if (this._state === SignalState.CLOSED || this._ctx?.disposed) {
692
- return;
693
- }
694
- if (this._state === SignalState.CONNECTED) {
695
- log3.warn("SignalClient error:", err, {
696
- F: __dxlog_file3,
697
- L: 140,
698
- S: this,
699
- C: (f, a) => f(...a)
700
- });
701
- }
702
- this._scheduleReconcileAfterError();
703
- }
704
- });
972
+ this._setState(SignalState.CONNECTING);
705
973
  this._reconcileTask = new DeferredTask(this._ctx, async () => {
706
- await this._reconcileSwarmSubscriptions();
707
- await this._reconcileMessageSubscriptions();
708
- this._reconciled.emit();
974
+ try {
975
+ await cancelWithContext2(this._connectionCtx, this._clientReady.wait({
976
+ timeout: 5e3
977
+ }));
978
+ invariant3(this._state === SignalState.CONNECTED, "Not connected to Signal Server", {
979
+ F: __dxlog_file4,
980
+ L: 93,
981
+ S: this,
982
+ A: [
983
+ "this._state === SignalState.CONNECTED",
984
+ "'Not connected to Signal Server'"
985
+ ]
986
+ });
987
+ await this.localState.reconcile(this._connectionCtx, this._client);
988
+ this._monitor.recordReconciliation({
989
+ success: true
990
+ });
991
+ this._lastReconciliationFailed = false;
992
+ } catch (err) {
993
+ this._lastReconciliationFailed = true;
994
+ this._monitor.recordReconciliation({
995
+ success: false
996
+ });
997
+ throw err;
998
+ }
709
999
  });
710
1000
  scheduleTaskInterval3(this._ctx, async () => {
711
1001
  if (this._state === SignalState.CONNECTED) {
@@ -713,23 +1003,46 @@ var SignalClient = class {
713
1003
  }
714
1004
  }, RECONCILE_INTERVAL);
715
1005
  this._reconnectTask = new DeferredTask(this._ctx, async () => {
716
- await this._reconnect();
1006
+ try {
1007
+ await this._reconnect();
1008
+ this._monitor.recordReconnect({
1009
+ success: true
1010
+ });
1011
+ } catch (err) {
1012
+ this._monitor.recordReconnect({
1013
+ success: false
1014
+ });
1015
+ throw err;
1016
+ }
717
1017
  });
718
- this._setState(SignalState.CONNECTING);
719
1018
  this._createClient();
720
- log3.trace("dxos.mesh.signal-client.open", trace3.end({
1019
+ log4.trace("dxos.mesh.signal-client.open", trace6.end({
721
1020
  id: this._instanceId
722
1021
  }), {
723
- F: __dxlog_file3,
724
- L: 169,
1022
+ F: __dxlog_file4,
1023
+ L: 126,
725
1024
  S: this,
726
1025
  C: (f, a) => f(...a)
727
1026
  });
728
1027
  }
729
- async close() {
730
- log3("closing...", void 0, {
731
- F: __dxlog_file3,
732
- L: 173,
1028
+ async _catch(err) {
1029
+ if (this._state === SignalState.CLOSED || this._ctx.disposed) {
1030
+ return;
1031
+ }
1032
+ if (this._state === SignalState.CONNECTED && !this._lastReconciliationFailed) {
1033
+ log4.warn("SignalClient error:", err, {
1034
+ F: __dxlog_file4,
1035
+ L: 135,
1036
+ S: this,
1037
+ C: (f, a) => f(...a)
1038
+ });
1039
+ }
1040
+ this._scheduleReconcileAfterError();
1041
+ }
1042
+ async _close() {
1043
+ log4("closing...", void 0, {
1044
+ F: __dxlog_file4,
1045
+ L: 141,
733
1046
  S: this,
734
1047
  C: (f, a) => f(...a)
735
1048
  });
@@ -738,14 +1051,11 @@ var SignalClient = class {
738
1051
  ].includes(this._state)) {
739
1052
  return;
740
1053
  }
741
- await this._ctx?.dispose();
742
- this._clientReady.reset();
743
- await this._client?.close();
744
- this._client = void 0;
745
1054
  this._setState(SignalState.CLOSED);
746
- log3("closed", void 0, {
747
- F: __dxlog_file3,
748
- L: 184,
1055
+ await this._safeResetClient();
1056
+ log4("closed", void 0, {
1057
+ F: __dxlog_file4,
1058
+ L: 149,
749
1059
  S: this,
750
1060
  C: (f, a) => f(...a)
751
1061
  });
@@ -756,228 +1066,181 @@ var SignalClient = class {
756
1066
  state: this._state,
757
1067
  error: this._lastError?.message,
758
1068
  reconnectIn: this._reconnectAfter,
759
- connectionStarted: this._connectionStarted,
760
- lastStateChange: this._lastStateChange
1069
+ ...this._monitor.getRecordedTimestamps()
761
1070
  };
762
1071
  }
763
- async join({ topic, peerId }) {
764
- log3("joining", {
765
- topic,
766
- peerId
1072
+ async join(args) {
1073
+ log4("joining", {
1074
+ topic: args.topic,
1075
+ peerId: args.peerId
767
1076
  }, {
768
- F: __dxlog_file3,
769
- L: 199,
1077
+ F: __dxlog_file4,
1078
+ L: 163,
770
1079
  S: this,
771
1080
  C: (f, a) => f(...a)
772
1081
  });
773
- this._performance.joinCounter++;
774
- this._joinedTopics.add({
775
- topic,
776
- peerId
777
- });
778
- this._reconcileTask.schedule();
1082
+ this._monitor.recordJoin();
1083
+ this.localState.join(args);
1084
+ this._reconcileTask?.schedule();
779
1085
  }
780
- async leave({ topic, peerId }) {
781
- this._performance.leaveCounter++;
782
- log3("leaving", {
783
- topic,
784
- peerId
1086
+ async leave(args) {
1087
+ log4("leaving", {
1088
+ topic: args.topic,
1089
+ peerId: args.peerId
785
1090
  }, {
786
- F: __dxlog_file3,
787
- L: 207,
1091
+ F: __dxlog_file4,
1092
+ L: 170,
788
1093
  S: this,
789
1094
  C: (f, a) => f(...a)
790
1095
  });
791
- void this._swarmStreams.get({
792
- topic,
793
- peerId
794
- })?.close();
795
- this._swarmStreams.delete({
796
- topic,
797
- peerId
798
- });
799
- this._joinedTopics.delete({
800
- topic,
801
- peerId
802
- });
1096
+ this._monitor.recordLeave();
1097
+ this.localState.leave(args);
803
1098
  }
804
1099
  async sendMessage(msg) {
805
- this._performance.sentMessages++;
806
- await this._clientReady.wait();
807
- invariant3(this._state === SignalState.CONNECTED, "Not connected to Signal Server", {
808
- F: __dxlog_file3,
809
- L: 216,
810
- S: this,
811
- A: [
812
- "this._state === SignalState.CONNECTED",
813
- "'Not connected to Signal Server'"
814
- ]
1100
+ return this._monitor.recordMessageSending(msg, async () => {
1101
+ await this._clientReady.wait();
1102
+ invariant3(this._state === SignalState.CONNECTED, "Not connected to Signal Server", {
1103
+ F: __dxlog_file4,
1104
+ L: 178,
1105
+ S: this,
1106
+ A: [
1107
+ "this._state === SignalState.CONNECTED",
1108
+ "'Not connected to Signal Server'"
1109
+ ]
1110
+ });
1111
+ await this._client.sendMessage(msg);
815
1112
  });
816
- await this._client.sendMessage(msg);
817
1113
  }
818
1114
  async subscribeMessages(peerId) {
819
- log3("subscribing to messages", {
1115
+ log4("subscribing to messages", {
820
1116
  peerId
821
1117
  }, {
822
- F: __dxlog_file3,
823
- L: 221,
1118
+ F: __dxlog_file4,
1119
+ L: 184,
824
1120
  S: this,
825
1121
  C: (f, a) => f(...a)
826
1122
  });
827
- this._subscribedMessages.add({
828
- peerId
829
- });
830
- this._reconcileTask.schedule();
1123
+ this.localState.subscribeMessages(peerId);
1124
+ this._reconcileTask?.schedule();
831
1125
  }
832
1126
  async unsubscribeMessages(peerId) {
833
- log3("unsubscribing from messages", {
1127
+ log4("unsubscribing from messages", {
834
1128
  peerId
835
1129
  }, {
836
- F: __dxlog_file3,
837
- L: 227,
1130
+ F: __dxlog_file4,
1131
+ L: 190,
838
1132
  S: this,
839
1133
  C: (f, a) => f(...a)
840
1134
  });
841
- this._subscribedMessages.delete({
842
- peerId
843
- });
844
- void this._messageStreams.get(peerId)?.close();
845
- this._messageStreams.delete(peerId);
1135
+ this.localState.unsubscribeMessages(peerId);
846
1136
  }
847
1137
  _scheduleReconcileAfterError() {
848
- scheduleTask2(this._ctx, () => {
849
- this._reconcileTask.schedule();
850
- }, ERROR_RECONCILE_DELAY);
851
- }
852
- _setState(newState) {
853
- this._state = newState;
854
- this._lastStateChange = /* @__PURE__ */ new Date();
855
- log3("signal state changed", {
856
- status: this.getStatus()
857
- }, {
858
- F: __dxlog_file3,
859
- L: 246,
860
- S: this,
861
- C: (f, a) => f(...a)
862
- });
863
- this.statusChanged.emit(this.getStatus());
1138
+ scheduleTask2(this._ctx, () => this._reconcileTask.schedule(), ERROR_RECONCILE_DELAY);
864
1139
  }
865
1140
  _createClient() {
866
- log3("creating client", {
1141
+ log4("creating client", {
867
1142
  host: this._host,
868
1143
  state: this._state
869
1144
  }, {
870
- F: __dxlog_file3,
871
- L: 251,
1145
+ F: __dxlog_file4,
1146
+ L: 199,
872
1147
  S: this,
873
1148
  C: (f, a) => f(...a)
874
1149
  });
875
1150
  invariant3(!this._client, "Client already created", {
876
- F: __dxlog_file3,
877
- L: 252,
1151
+ F: __dxlog_file4,
1152
+ L: 200,
878
1153
  S: this,
879
1154
  A: [
880
1155
  "!this._client",
881
1156
  "'Client already created'"
882
1157
  ]
883
1158
  });
884
- this._connectionStarted = /* @__PURE__ */ new Date();
1159
+ this._monitor.recordConnectionStartTime();
885
1160
  this._connectionCtx = this._ctx.derive();
886
1161
  this._connectionCtx.onDispose(async () => {
887
- log3("connection context disposed", void 0, {
888
- F: __dxlog_file3,
889
- L: 259,
1162
+ log4("connection context disposed", void 0, {
1163
+ F: __dxlog_file4,
1164
+ L: 207,
890
1165
  S: this,
891
1166
  C: (f, a) => f(...a)
892
1167
  });
893
- await Promise.all(Array.from(this._swarmStreams.values()).map((stream) => stream.close()));
894
- await Promise.all(Array.from(this._messageStreams.values()).map((stream) => stream.close()));
895
- this._swarmStreams.clear();
896
- this._messageStreams.clear();
1168
+ const { failureCount } = await this.localState.safeCloseStreams();
1169
+ this._monitor.recordStreamCloseErrors(failureCount);
897
1170
  });
898
1171
  try {
899
- this._client = new SignalRPCClient({
1172
+ const client = new SignalRPCClient({
900
1173
  url: this._host,
901
1174
  callbacks: {
902
1175
  onConnected: () => {
903
- log3("socket connected", void 0, {
904
- F: __dxlog_file3,
905
- L: 271,
906
- S: this,
907
- C: (f, a) => f(...a)
908
- });
909
- this._lastError = void 0;
910
- this._reconnectAfter = DEFAULT_RECONNECT_TIMEOUT;
911
- this._setState(SignalState.CONNECTED);
912
- this._clientReady.wake();
913
- this._reconcileTask.schedule();
1176
+ if (client === this._client) {
1177
+ log4("socket connected", void 0, {
1178
+ F: __dxlog_file4,
1179
+ L: 218,
1180
+ S: this,
1181
+ C: (f, a) => f(...a)
1182
+ });
1183
+ this._onConnected();
1184
+ }
914
1185
  },
915
1186
  onDisconnected: () => {
916
- log3("socket disconnected", {
1187
+ if (client !== this._client) {
1188
+ return;
1189
+ }
1190
+ log4("socket disconnected", {
917
1191
  state: this._state
918
1192
  }, {
919
- F: __dxlog_file3,
920
- L: 280,
1193
+ F: __dxlog_file4,
1194
+ L: 227,
921
1195
  S: this,
922
1196
  C: (f, a) => f(...a)
923
1197
  });
924
1198
  if (this._state === SignalState.ERROR) {
925
1199
  this._setState(SignalState.DISCONNECTED);
926
- return;
927
- }
928
- if (this._state !== SignalState.CONNECTED && this._state !== SignalState.CONNECTING) {
929
- this._incrementReconnectTimeout();
1200
+ } else {
1201
+ this._onDisconnected();
930
1202
  }
931
- this._setState(SignalState.DISCONNECTED);
932
- this._reconnectTask.schedule();
933
1203
  },
934
1204
  onError: (error) => {
935
- log3("socket error", {
936
- error,
937
- state: this._state
938
- }, {
939
- F: __dxlog_file3,
940
- L: 295,
941
- S: this,
942
- C: (f, a) => f(...a)
943
- });
944
- this._lastError = error;
945
- if (this._state !== SignalState.CONNECTED && this._state !== SignalState.CONNECTING) {
946
- this._incrementReconnectTimeout();
1205
+ if (client === this._client) {
1206
+ log4("socket error", {
1207
+ error,
1208
+ state: this._state
1209
+ }, {
1210
+ F: __dxlog_file4,
1211
+ L: 239,
1212
+ S: this,
1213
+ C: (f, a) => f(...a)
1214
+ });
1215
+ this._onDisconnected({
1216
+ error
1217
+ });
947
1218
  }
948
- this._setState(SignalState.ERROR);
949
- this._reconnectTask.schedule();
950
1219
  },
951
1220
  getMetadata: this._getMetadata
952
1221
  }
953
1222
  });
954
- } catch (err) {
955
- if (this._state !== SignalState.CONNECTED && this._state !== SignalState.CONNECTING) {
956
- this._incrementReconnectTimeout();
957
- }
958
- this._lastError = err;
959
- this._setState(SignalState.DISCONNECTED);
960
- this._reconnectTask.schedule();
1223
+ this._client = client;
1224
+ } catch (error) {
1225
+ this._client = void 0;
1226
+ this._onDisconnected({
1227
+ error
1228
+ });
961
1229
  }
962
1230
  }
963
- _incrementReconnectTimeout() {
964
- this._reconnectAfter *= 2;
965
- this._reconnectAfter = Math.min(this._reconnectAfter, MAX_RECONNECT_TIMEOUT);
966
- }
967
1231
  async _reconnect() {
968
- log3(`reconnecting in ${this._reconnectAfter}ms`, {
1232
+ log4(`reconnecting in ${this._reconnectAfter}ms`, {
969
1233
  state: this._state
970
1234
  }, {
971
- F: __dxlog_file3,
972
- L: 323,
1235
+ F: __dxlog_file4,
1236
+ L: 254,
973
1237
  S: this,
974
1238
  C: (f, a) => f(...a)
975
1239
  });
976
- this._performance.reconnectCounter++;
977
1240
  if (this._state === SignalState.RECONNECTING) {
978
- log3.warn("Signal api already reconnecting.", void 0, {
979
- F: __dxlog_file3,
980
- L: 327,
1241
+ log4.info("Signal api already reconnecting.", void 0, {
1242
+ F: __dxlog_file4,
1243
+ L: 257,
981
1244
  S: this,
982
1245
  C: (f, a) => f(...a)
983
1246
  });
@@ -986,149 +1249,96 @@ var SignalClient = class {
986
1249
  if (this._state === SignalState.CLOSED) {
987
1250
  return;
988
1251
  }
989
- this._clientReady.reset();
990
- await this._connectionCtx?.dispose();
991
- this._client?.close().catch(() => {
992
- });
993
- this._client = void 0;
994
- await cancelWithContext(this._ctx, sleep(this._reconnectAfter));
995
1252
  this._setState(SignalState.RECONNECTING);
1253
+ await this._safeResetClient();
1254
+ await cancelWithContext2(this._ctx, sleep(this._reconnectAfter));
996
1255
  this._createClient();
997
1256
  }
998
- async _reconcileSwarmSubscriptions() {
999
- await asyncTimeout(cancelWithContext(this._connectionCtx, this._clientReady.wait()), 5e3);
1000
- const client = this._client;
1001
- invariant3(this._state === SignalState.CONNECTED, "Not connected to Signal Server", {
1002
- F: __dxlog_file3,
1003
- L: 352,
1004
- S: this,
1005
- A: [
1006
- "this._state === SignalState.CONNECTED",
1007
- "'Not connected to Signal Server'"
1008
- ]
1009
- });
1010
- for (const { topic, peerId } of this._swarmStreams.keys()) {
1011
- if (this._joinedTopics.has({
1012
- topic,
1013
- peerId
1014
- })) {
1015
- continue;
1016
- }
1017
- void this._swarmStreams.get({
1018
- topic,
1019
- peerId
1020
- })?.close();
1021
- this._swarmStreams.delete({
1022
- topic,
1023
- peerId
1024
- });
1257
+ _onConnected() {
1258
+ this._lastError = void 0;
1259
+ this._lastReconciliationFailed = false;
1260
+ this._reconnectAfter = DEFAULT_RECONNECT_TIMEOUT;
1261
+ this._setState(SignalState.CONNECTED);
1262
+ this._clientReady.wake();
1263
+ this._reconcileTask.schedule();
1264
+ }
1265
+ _onDisconnected(options) {
1266
+ this._updateReconnectTimeout();
1267
+ if (this._state === SignalState.CLOSED) {
1268
+ return;
1025
1269
  }
1026
- for (const { topic, peerId } of this._joinedTopics.values()) {
1027
- if (this._swarmStreams.has({
1028
- topic,
1029
- peerId
1030
- })) {
1031
- continue;
1032
- }
1033
- const swarmStream = await asyncTimeout(cancelWithContext(this._connectionCtx, client.join({
1034
- topic,
1035
- peerId
1036
- })), 5e3);
1037
- swarmStream.subscribe(async (swarmEvent) => {
1038
- log3("swarm event", {
1039
- swarmEvent
1040
- }, {
1041
- F: __dxlog_file3,
1042
- L: 379,
1043
- S: this,
1044
- C: (f, a) => f(...a)
1045
- });
1046
- await this._onSwarmEvent({
1047
- topic,
1048
- swarmEvent
1049
- });
1050
- });
1051
- this._swarmStreams.set({
1052
- topic,
1053
- peerId
1054
- }, swarmStream);
1270
+ if (options?.error) {
1271
+ this._lastError = options.error;
1272
+ this._setState(SignalState.ERROR);
1273
+ } else {
1274
+ this._setState(SignalState.DISCONNECTED);
1055
1275
  }
1276
+ this._reconnectTask.schedule();
1056
1277
  }
1057
- async _reconcileMessageSubscriptions() {
1058
- await asyncTimeout(cancelWithContext(this._connectionCtx, this._clientReady.wait()), 5e3);
1059
- const client = this._client;
1060
- invariant3(this._state === SignalState.CONNECTED, "Not connected to Signal Server", {
1061
- F: __dxlog_file3,
1062
- L: 392,
1278
+ _setState(newState) {
1279
+ this._state = newState;
1280
+ this._monitor.recordStateChangeTime();
1281
+ log4("signal state changed", {
1282
+ status: this.getStatus()
1283
+ }, {
1284
+ F: __dxlog_file4,
1285
+ L: 298,
1063
1286
  S: this,
1064
- A: [
1065
- "this._state === SignalState.CONNECTED",
1066
- "'Not connected to Signal Server'"
1067
- ]
1287
+ C: (f, a) => f(...a)
1068
1288
  });
1069
- for (const peerId of this._messageStreams.keys()) {
1070
- if (this._subscribedMessages.has({
1071
- peerId
1072
- })) {
1073
- continue;
1074
- }
1075
- void this._messageStreams.get(peerId)?.close();
1076
- this._messageStreams.delete(peerId);
1077
- }
1078
- for (const { peerId } of this._subscribedMessages.values()) {
1079
- if (this._messageStreams.has(peerId)) {
1080
- continue;
1081
- }
1082
- const messageStream = await asyncTimeout(cancelWithContext(this._connectionCtx, client.receiveMessages(peerId)), 5e3);
1083
- messageStream.subscribe(async (message) => {
1084
- this._performance.receivedMessages++;
1085
- await this._onMessage({
1086
- author: PublicKey3.from(message.author),
1087
- recipient: PublicKey3.from(message.recipient),
1088
- payload: message.payload
1089
- });
1090
- });
1091
- this._messageStreams.set(peerId, messageStream);
1289
+ this.statusChanged.emit(this.getStatus());
1290
+ }
1291
+ _updateReconnectTimeout() {
1292
+ if (this._state !== SignalState.CONNECTED && this._state !== SignalState.CONNECTING) {
1293
+ this._reconnectAfter *= 2;
1294
+ this._reconnectAfter = Math.min(this._reconnectAfter, MAX_RECONNECT_TIMEOUT);
1092
1295
  }
1093
1296
  }
1297
+ async _safeResetClient() {
1298
+ await this._connectionCtx?.dispose();
1299
+ this._connectionCtx = void 0;
1300
+ this._clientReady.reset();
1301
+ await this._client?.close().catch(() => {
1302
+ });
1303
+ this._client = void 0;
1304
+ }
1094
1305
  };
1095
1306
 
1096
1307
  // packages/core/mesh/messaging/src/signal-manager/memory-signal-manager.ts
1097
- import { Event as Event2, Trigger as Trigger3 } from "@dxos/async";
1098
- import { Context as Context4 } from "@dxos/context";
1308
+ import { Event as Event3, Trigger as Trigger3 } from "@dxos/async";
1309
+ import { Context as Context3 } from "@dxos/context";
1099
1310
  import { invariant as invariant4 } from "@dxos/invariant";
1100
- import { PublicKey as PublicKey4 } from "@dxos/keys";
1101
- import { log as log4 } from "@dxos/log";
1311
+ import { PublicKey as PublicKey5 } from "@dxos/keys";
1312
+ import { log as log5 } from "@dxos/log";
1102
1313
  import { schema as schema3 } from "@dxos/protocols";
1103
1314
  import { ComplexMap as ComplexMap3, ComplexSet as ComplexSet3 } from "@dxos/util";
1104
- var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/core/mesh/messaging/src/signal-manager/memory-signal-manager.ts";
1315
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/core/mesh/messaging/src/signal-manager/memory-signal-manager.ts";
1105
1316
  var MemorySignalManagerContext = class {
1106
1317
  constructor() {
1107
1318
  // Swarm messages.
1108
- this.swarmEvent = new Event2();
1319
+ this.swarmEvent = new Event3();
1109
1320
  // Mapping from topic to set of peers.
1110
- this.swarms = new ComplexMap3(PublicKey4.hash);
1321
+ this.swarms = new ComplexMap3(PublicKey5.hash);
1111
1322
  // Map of connections for each peer for signaling.
1112
- this.connections = new ComplexMap3(PublicKey4.hash);
1323
+ this.connections = new ComplexMap3(PublicKey5.hash);
1113
1324
  }
1114
1325
  };
1115
1326
  var MemorySignalManager = class {
1116
1327
  constructor(_context) {
1117
1328
  this._context = _context;
1118
- this.statusChanged = new Event2();
1119
- this.commandTrace = new Event2();
1120
- this.swarmEvent = new Event2();
1121
- this.onMessage = new Event2();
1329
+ this.statusChanged = new Event3();
1330
+ this.swarmEvent = new Event3();
1331
+ this.onMessage = new Event3();
1122
1332
  this._joinedSwarms = new ComplexSet3(({ topic, peerId }) => topic.toHex() + peerId.toHex());
1123
1333
  this._freezeTrigger = new Trigger3().wake();
1124
- this._ctx = new Context4();
1334
+ this._ctx = new Context3();
1125
1335
  this._ctx.onDispose(this._context.swarmEvent.on((data) => this.swarmEvent.emit(data)));
1126
1336
  }
1127
1337
  async open() {
1128
1338
  if (!this._ctx.disposed) {
1129
1339
  return;
1130
1340
  }
1131
- this._ctx = new Context4();
1341
+ this._ctx = new Context3();
1132
1342
  this._ctx.onDispose(this._context.swarmEvent.on((data) => this.swarmEvent.emit(data)));
1133
1343
  await Promise.all([
1134
1344
  ...this._joinedSwarms.values()
@@ -1152,8 +1362,8 @@ var MemorySignalManager = class {
1152
1362
  }
1153
1363
  async join({ topic, peerId }) {
1154
1364
  invariant4(!this._ctx.disposed, "Closed", {
1155
- F: __dxlog_file4,
1156
- L: 102,
1365
+ F: __dxlog_file5,
1366
+ L: 100,
1157
1367
  S: this,
1158
1368
  A: [
1159
1369
  "!this._ctx.disposed",
@@ -1165,7 +1375,7 @@ var MemorySignalManager = class {
1165
1375
  peerId
1166
1376
  });
1167
1377
  if (!this._context.swarms.has(topic)) {
1168
- this._context.swarms.set(topic, new ComplexSet3(PublicKey4.hash));
1378
+ this._context.swarms.set(topic, new ComplexSet3(PublicKey5.hash));
1169
1379
  }
1170
1380
  this._context.swarms.get(topic).add(peerId);
1171
1381
  this._context.swarmEvent.emit({
@@ -1193,8 +1403,8 @@ var MemorySignalManager = class {
1193
1403
  }
1194
1404
  async leave({ topic, peerId }) {
1195
1405
  invariant4(!this._ctx.disposed, "Closed", {
1196
- F: __dxlog_file4,
1197
- L: 138,
1406
+ F: __dxlog_file5,
1407
+ L: 136,
1198
1408
  S: this,
1199
1409
  A: [
1200
1410
  "!this._ctx.disposed",
@@ -1206,7 +1416,7 @@ var MemorySignalManager = class {
1206
1416
  peerId
1207
1417
  });
1208
1418
  if (!this._context.swarms.has(topic)) {
1209
- this._context.swarms.set(topic, new ComplexSet3(PublicKey4.hash));
1419
+ this._context.swarms.set(topic, new ComplexSet3(PublicKey5.hash));
1210
1420
  }
1211
1421
  this._context.swarms.get(topic).delete(peerId);
1212
1422
  const swarmEvent = {
@@ -1220,19 +1430,19 @@ var MemorySignalManager = class {
1220
1430
  });
1221
1431
  }
1222
1432
  async sendMessage({ author, recipient, payload }) {
1223
- log4("send message", {
1433
+ log5("send message", {
1224
1434
  author,
1225
1435
  recipient,
1226
1436
  ...dec(payload)
1227
1437
  }, {
1228
- F: __dxlog_file4,
1229
- L: 158,
1438
+ F: __dxlog_file5,
1439
+ L: 156,
1230
1440
  S: this,
1231
1441
  C: (f, a) => f(...a)
1232
1442
  });
1233
1443
  invariant4(recipient, void 0, {
1234
- F: __dxlog_file4,
1235
- L: 160,
1444
+ F: __dxlog_file5,
1445
+ L: 158,
1236
1446
  S: this,
1237
1447
  A: [
1238
1448
  "recipient",
@@ -1240,8 +1450,8 @@ var MemorySignalManager = class {
1240
1450
  ]
1241
1451
  });
1242
1452
  invariant4(!this._ctx.disposed, "Closed", {
1243
- F: __dxlog_file4,
1244
- L: 161,
1453
+ F: __dxlog_file5,
1454
+ L: 159,
1245
1455
  S: this,
1246
1456
  A: [
1247
1457
  "!this._ctx.disposed",
@@ -1251,24 +1461,24 @@ var MemorySignalManager = class {
1251
1461
  await this._freezeTrigger.wait();
1252
1462
  const remote = this._context.connections.get(recipient);
1253
1463
  if (!remote) {
1254
- log4.warn("recipient is not subscribed for messages", {
1464
+ log5.warn("recipient is not subscribed for messages", {
1255
1465
  author,
1256
1466
  recipient
1257
1467
  }, {
1258
- F: __dxlog_file4,
1259
- L: 167,
1468
+ F: __dxlog_file5,
1469
+ L: 165,
1260
1470
  S: this,
1261
1471
  C: (f, a) => f(...a)
1262
1472
  });
1263
1473
  return;
1264
1474
  }
1265
1475
  if (remote._ctx.disposed) {
1266
- log4.warn("recipient is disposed", {
1476
+ log5.warn("recipient is disposed", {
1267
1477
  author,
1268
1478
  recipient
1269
1479
  }, {
1270
- F: __dxlog_file4,
1271
- L: 172,
1480
+ F: __dxlog_file5,
1481
+ L: 170,
1272
1482
  S: this,
1273
1483
  C: (f, a) => f(...a)
1274
1484
  });
@@ -1276,24 +1486,24 @@ var MemorySignalManager = class {
1276
1486
  }
1277
1487
  remote._freezeTrigger.wait().then(() => {
1278
1488
  if (remote._ctx.disposed) {
1279
- log4.warn("recipient is disposed", {
1489
+ log5.warn("recipient is disposed", {
1280
1490
  author,
1281
1491
  recipient
1282
1492
  }, {
1283
- F: __dxlog_file4,
1284
- L: 180,
1493
+ F: __dxlog_file5,
1494
+ L: 178,
1285
1495
  S: this,
1286
1496
  C: (f, a) => f(...a)
1287
1497
  });
1288
1498
  return;
1289
1499
  }
1290
- log4("receive message", {
1500
+ log5("receive message", {
1291
1501
  author,
1292
1502
  recipient,
1293
1503
  ...dec(payload)
1294
1504
  }, {
1295
- F: __dxlog_file4,
1296
- L: 184,
1505
+ F: __dxlog_file5,
1506
+ L: 182,
1297
1507
  S: this,
1298
1508
  C: (f, a) => f(...a)
1299
1509
  });
@@ -1303,33 +1513,33 @@ var MemorySignalManager = class {
1303
1513
  payload
1304
1514
  });
1305
1515
  }).catch((err) => {
1306
- log4.error("error while waiting for freeze", {
1516
+ log5.error("error while waiting for freeze", {
1307
1517
  err
1308
1518
  }, {
1309
- F: __dxlog_file4,
1310
- L: 189,
1519
+ F: __dxlog_file5,
1520
+ L: 187,
1311
1521
  S: this,
1312
1522
  C: (f, a) => f(...a)
1313
1523
  });
1314
1524
  });
1315
1525
  }
1316
1526
  async subscribeMessages(peerId) {
1317
- log4("subscribing", {
1527
+ log5("subscribing", {
1318
1528
  peerId
1319
1529
  }, {
1320
- F: __dxlog_file4,
1321
- L: 194,
1530
+ F: __dxlog_file5,
1531
+ L: 192,
1322
1532
  S: this,
1323
1533
  C: (f, a) => f(...a)
1324
1534
  });
1325
1535
  this._context.connections.set(peerId, this);
1326
1536
  }
1327
1537
  async unsubscribeMessages(peerId) {
1328
- log4("unsubscribing", {
1538
+ log5("unsubscribing", {
1329
1539
  peerId
1330
1540
  }, {
1331
- F: __dxlog_file4,
1332
- L: 199,
1541
+ F: __dxlog_file5,
1542
+ L: 197,
1333
1543
  S: this,
1334
1544
  C: (f, a) => f(...a)
1335
1545
  });
@@ -1357,12 +1567,31 @@ var dec = (payload) => {
1357
1567
  };
1358
1568
 
1359
1569
  // packages/core/mesh/messaging/src/signal-manager/websocket-signal-manager.ts
1360
- import { Event as Event3, sleep as sleep2, synchronized } from "@dxos/async";
1361
- import { Context as Context5 } from "@dxos/context";
1570
+ import { Event as Event4, sleep as sleep2, synchronized } from "@dxos/async";
1571
+ import { Context as Context4 } from "@dxos/context";
1362
1572
  import { invariant as invariant5 } from "@dxos/invariant";
1363
- import { PublicKey as PublicKey5 } from "@dxos/keys";
1364
- import { log as log5 } from "@dxos/log";
1365
- import { RateLimitExceededError, TimeoutError as TimeoutError2, trace as trace4 } from "@dxos/protocols";
1573
+ import { PublicKey as PublicKey6 } from "@dxos/keys";
1574
+ import { log as log6 } from "@dxos/log";
1575
+ import { RateLimitExceededError, TimeoutError as TimeoutError3, trace as trace8 } from "@dxos/protocols";
1576
+ import { BitField, safeAwaitAll as safeAwaitAll2 } from "@dxos/util";
1577
+
1578
+ // packages/core/mesh/messaging/src/signal-manager/websocket-signal-manager-monitor.ts
1579
+ import { trace as trace7 } from "@dxos/tracing";
1580
+ var WebsocketSignalManagerMonitor = class {
1581
+ recordRateLimitExceeded() {
1582
+ trace7.metrics.increment("mesh.signal.signal-manager.rate-limit-hit", 1);
1583
+ }
1584
+ recordServerFailure(params) {
1585
+ trace7.metrics.increment("mesh.signal.signal-manager.server-failure", 1, {
1586
+ tags: {
1587
+ server: params.serverName,
1588
+ restarted: params.willRestart
1589
+ }
1590
+ });
1591
+ }
1592
+ };
1593
+
1594
+ // packages/core/mesh/messaging/src/signal-manager/websocket-signal-manager.ts
1366
1595
  function _ts_decorate(decorators, target, key, desc) {
1367
1596
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1368
1597
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
@@ -1373,7 +1602,7 @@ function _ts_decorate(decorators, target, key, desc) {
1373
1602
  r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1374
1603
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1375
1604
  }
1376
- var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/core/mesh/messaging/src/signal-manager/websocket-signal-manager.ts";
1605
+ var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/core/mesh/messaging/src/signal-manager/websocket-signal-manager.ts";
1377
1606
  var MAX_SERVER_FAILURES = 5;
1378
1607
  var WSS_SIGNAL_SERVER_REBOOT_DELAY = 3e3;
1379
1608
  var WebsocketSignalManager = class {
@@ -1381,18 +1610,18 @@ var WebsocketSignalManager = class {
1381
1610
  this._hosts = _hosts;
1382
1611
  this._getMetadata = _getMetadata;
1383
1612
  this._servers = /* @__PURE__ */ new Map();
1613
+ this._monitor = new WebsocketSignalManagerMonitor();
1384
1614
  this._opened = false;
1385
1615
  this.failureCount = /* @__PURE__ */ new Map();
1386
- this.statusChanged = new Event3();
1387
- this.commandTrace = new Event3();
1388
- this.swarmEvent = new Event3();
1389
- this.onMessage = new Event3();
1390
- this._instanceId = PublicKey5.random().toHex();
1391
- log5("Created WebsocketSignalManager", {
1616
+ this.statusChanged = new Event4();
1617
+ this.swarmEvent = new Event4();
1618
+ this.onMessage = new Event4();
1619
+ this._instanceId = PublicKey6.random().toHex();
1620
+ log6("Created WebsocketSignalManager", {
1392
1621
  hosts: this._hosts
1393
1622
  }, {
1394
- F: __dxlog_file5,
1395
- L: 51,
1623
+ F: __dxlog_file6,
1624
+ L: 58,
1396
1625
  S: this,
1397
1626
  C: (f, a) => f(...a)
1398
1627
  });
@@ -1404,39 +1633,37 @@ var WebsocketSignalManager = class {
1404
1633
  server.statusChanged.on(() => this.statusChanged.emit(this.getStatus()));
1405
1634
  this._servers.set(host.server, server);
1406
1635
  this.failureCount.set(host.server, 0);
1407
- server.commandTrace.on((trace5) => this.commandTrace.emit(trace5));
1408
1636
  }
1637
+ this._failedServersBitfield = BitField.zeros(this._hosts.length);
1409
1638
  }
1410
1639
  async open() {
1411
1640
  if (this._opened) {
1412
1641
  return;
1413
1642
  }
1414
- log5("open signal manager", {
1643
+ log6("open signal manager", {
1415
1644
  hosts: this._hosts
1416
1645
  }, {
1417
- F: __dxlog_file5,
1418
- L: 78,
1646
+ F: __dxlog_file6,
1647
+ L: 85,
1419
1648
  S: this,
1420
1649
  C: (f, a) => f(...a)
1421
1650
  });
1422
- log5.trace("dxos.mesh.websocket-signal-manager.open", trace4.begin({
1651
+ log6.trace("dxos.mesh.websocket-signal-manager.open", trace8.begin({
1423
1652
  id: this._instanceId
1424
1653
  }), {
1425
- F: __dxlog_file5,
1426
- L: 79,
1654
+ F: __dxlog_file6,
1655
+ L: 86,
1427
1656
  S: this,
1428
1657
  C: (f, a) => f(...a)
1429
1658
  });
1430
1659
  this._initContext();
1431
- [
1432
- ...this._servers.values()
1433
- ].forEach((server) => server.open());
1660
+ await safeAwaitAll2(this._servers.values(), (server) => server.open());
1434
1661
  this._opened = true;
1435
- log5.trace("dxos.mesh.websocket-signal-manager.open", trace4.end({
1662
+ log6.trace("dxos.mesh.websocket-signal-manager.open", trace8.end({
1436
1663
  id: this._instanceId
1437
1664
  }), {
1438
- F: __dxlog_file5,
1439
- L: 87,
1665
+ F: __dxlog_file6,
1666
+ L: 93,
1440
1667
  S: this,
1441
1668
  C: (f, a) => f(...a)
1442
1669
  });
@@ -1447,20 +1674,20 @@ var WebsocketSignalManager = class {
1447
1674
  }
1448
1675
  this._opened = false;
1449
1676
  await this._ctx.dispose();
1450
- await Promise.all(Array.from(this._servers.values()).map((server) => server.close()));
1677
+ await safeAwaitAll2(this._servers.values(), (server) => server.close());
1451
1678
  }
1452
1679
  async restartServer(serverName) {
1453
- log5("restarting server", {
1680
+ log6("restarting server", {
1454
1681
  serverName
1455
1682
  }, {
1456
- F: __dxlog_file5,
1457
- L: 103,
1683
+ F: __dxlog_file6,
1684
+ L: 107,
1458
1685
  S: this,
1459
1686
  C: (f, a) => f(...a)
1460
1687
  });
1461
1688
  invariant5(this._opened, "server already closed", {
1462
- F: __dxlog_file5,
1463
- L: 104,
1689
+ F: __dxlog_file6,
1690
+ L: 108,
1464
1691
  S: this,
1465
1692
  A: [
1466
1693
  "this._opened",
@@ -1469,8 +1696,8 @@ var WebsocketSignalManager = class {
1469
1696
  });
1470
1697
  const server = this._servers.get(serverName);
1471
1698
  invariant5(server, "server not found", {
1472
- F: __dxlog_file5,
1473
- L: 107,
1699
+ F: __dxlog_file6,
1700
+ L: 111,
1474
1701
  S: this,
1475
1702
  A: [
1476
1703
  "server",
@@ -1485,18 +1712,18 @@ var WebsocketSignalManager = class {
1485
1712
  return Array.from(this._servers.values()).map((server) => server.getStatus());
1486
1713
  }
1487
1714
  async join({ topic, peerId }) {
1488
- log5("join", {
1715
+ log6("join", {
1489
1716
  topic,
1490
1717
  peerId
1491
1718
  }, {
1492
- F: __dxlog_file5,
1493
- L: 120,
1719
+ F: __dxlog_file6,
1720
+ L: 124,
1494
1721
  S: this,
1495
1722
  C: (f, a) => f(...a)
1496
1723
  });
1497
1724
  invariant5(this._opened, "Closed", {
1498
- F: __dxlog_file5,
1499
- L: 121,
1725
+ F: __dxlog_file6,
1726
+ L: 125,
1500
1727
  S: this,
1501
1728
  A: [
1502
1729
  "this._opened",
@@ -1509,18 +1736,18 @@ var WebsocketSignalManager = class {
1509
1736
  }));
1510
1737
  }
1511
1738
  async leave({ topic, peerId }) {
1512
- log5("leaving", {
1739
+ log6("leaving", {
1513
1740
  topic,
1514
1741
  peerId
1515
1742
  }, {
1516
- F: __dxlog_file5,
1517
- L: 127,
1743
+ F: __dxlog_file6,
1744
+ L: 131,
1518
1745
  S: this,
1519
1746
  C: (f, a) => f(...a)
1520
1747
  });
1521
1748
  invariant5(this._opened, "Closed", {
1522
- F: __dxlog_file5,
1523
- L: 128,
1749
+ F: __dxlog_file6,
1750
+ L: 132,
1524
1751
  S: this,
1525
1752
  A: [
1526
1753
  "this._opened",
@@ -1533,89 +1760,115 @@ var WebsocketSignalManager = class {
1533
1760
  }));
1534
1761
  }
1535
1762
  async sendMessage({ author, recipient, payload }) {
1536
- log5("signal", {
1763
+ log6("signal", {
1537
1764
  recipient
1538
1765
  }, {
1539
- F: __dxlog_file5,
1540
- L: 142,
1766
+ F: __dxlog_file6,
1767
+ L: 145,
1541
1768
  S: this,
1542
1769
  C: (f, a) => f(...a)
1543
1770
  });
1544
1771
  invariant5(this._opened, "Closed", {
1545
- F: __dxlog_file5,
1546
- L: 143,
1772
+ F: __dxlog_file6,
1773
+ L: 146,
1547
1774
  S: this,
1548
1775
  A: [
1549
1776
  "this._opened",
1550
1777
  "'Closed'"
1551
1778
  ]
1552
1779
  });
1553
- void this._forEachServer(async (server, serverName) => {
1780
+ void this._forEachServer(async (server, serverName, index) => {
1554
1781
  void server.sendMessage({
1555
1782
  author,
1556
1783
  recipient,
1557
1784
  payload
1558
- }).catch((err) => {
1785
+ }).then(() => this._clearServerFailedFlag(serverName, index)).catch((err) => {
1559
1786
  if (err instanceof RateLimitExceededError) {
1560
- log5.info("WSS rate limit exceeded", {
1787
+ log6.info("WSS rate limit exceeded", {
1561
1788
  err
1562
1789
  }, {
1563
- F: __dxlog_file5,
1564
- L: 148,
1790
+ F: __dxlog_file6,
1791
+ L: 154,
1565
1792
  S: this,
1566
1793
  C: (f, a) => f(...a)
1567
1794
  });
1568
- } else if (err instanceof TimeoutError2 || err.constructor.name === "TimeoutError") {
1569
- log5.info("WSS sendMessage timeout", {
1795
+ this._monitor.recordRateLimitExceeded();
1796
+ } else if (err instanceof TimeoutError3 || err.constructor.name === "TimeoutError") {
1797
+ log6.info("WSS sendMessage timeout", {
1570
1798
  err
1571
1799
  }, {
1572
- F: __dxlog_file5,
1573
- L: 150,
1800
+ F: __dxlog_file6,
1801
+ L: 157,
1574
1802
  S: this,
1575
1803
  C: (f, a) => f(...a)
1576
1804
  });
1577
- void this.checkServerFailure(serverName);
1805
+ void this.checkServerFailure(serverName, index);
1578
1806
  } else {
1579
- log5.info(`error sending to ${serverName}`, {
1807
+ log6.warn(`error sending to ${serverName}`, {
1580
1808
  err
1581
1809
  }, {
1582
- F: __dxlog_file5,
1583
- L: 153,
1810
+ F: __dxlog_file6,
1811
+ L: 160,
1584
1812
  S: this,
1585
1813
  C: (f, a) => f(...a)
1586
1814
  });
1587
- void this.checkServerFailure(serverName);
1815
+ void this.checkServerFailure(serverName, index);
1588
1816
  }
1589
1817
  });
1590
1818
  });
1591
1819
  }
1592
- async checkServerFailure(serverName) {
1820
+ async checkServerFailure(serverName, index) {
1593
1821
  const failureCount = this.failureCount.get(serverName) ?? 0;
1594
- if (failureCount > MAX_SERVER_FAILURES) {
1595
- log5.warn(`too many failures sending to ${serverName} (${failureCount} > ${MAX_SERVER_FAILURES}), restarting`, void 0, {
1596
- F: __dxlog_file5,
1597
- L: 164,
1598
- S: this,
1599
- C: (f, a) => f(...a)
1600
- });
1822
+ const isRestartRequired = failureCount > MAX_SERVER_FAILURES;
1823
+ this._monitor.recordServerFailure({
1824
+ serverName,
1825
+ willRestart: isRestartRequired
1826
+ });
1827
+ if (isRestartRequired) {
1828
+ if (!BitField.get(this._failedServersBitfield, index)) {
1829
+ log6.warn("too many failures for ws-server, restarting", {
1830
+ serverName,
1831
+ failureCount
1832
+ }, {
1833
+ F: __dxlog_file6,
1834
+ L: 174,
1835
+ S: this,
1836
+ C: (f, a) => f(...a)
1837
+ });
1838
+ BitField.set(this._failedServersBitfield, index, true);
1839
+ }
1601
1840
  await this.restartServer(serverName);
1602
1841
  this.failureCount.set(serverName, 0);
1603
1842
  return;
1604
1843
  }
1605
1844
  this.failureCount.set(serverName, (this.failureCount.get(serverName) ?? 0) + 1);
1606
1845
  }
1846
+ _clearServerFailedFlag(serverName, index) {
1847
+ if (BitField.get(this._failedServersBitfield, index)) {
1848
+ log6.info("server connection restored", {
1849
+ serverName
1850
+ }, {
1851
+ F: __dxlog_file6,
1852
+ L: 187,
1853
+ S: this,
1854
+ C: (f, a) => f(...a)
1855
+ });
1856
+ BitField.set(this._failedServersBitfield, index, false);
1857
+ this.failureCount.set(serverName, 0);
1858
+ }
1859
+ }
1607
1860
  async subscribeMessages(peerId) {
1608
- log5("subscribed for message stream", {
1861
+ log6("subscribed for message stream", {
1609
1862
  peerId
1610
1863
  }, {
1611
- F: __dxlog_file5,
1612
- L: 174,
1864
+ F: __dxlog_file6,
1865
+ L: 194,
1613
1866
  S: this,
1614
1867
  C: (f, a) => f(...a)
1615
1868
  });
1616
1869
  invariant5(this._opened, "Closed", {
1617
- F: __dxlog_file5,
1618
- L: 175,
1870
+ F: __dxlog_file6,
1871
+ L: 195,
1619
1872
  S: this,
1620
1873
  A: [
1621
1874
  "this._opened",
@@ -1625,17 +1878,17 @@ var WebsocketSignalManager = class {
1625
1878
  await this._forEachServer(async (server) => server.subscribeMessages(peerId));
1626
1879
  }
1627
1880
  async unsubscribeMessages(peerId) {
1628
- log5("subscribed for message stream", {
1881
+ log6("subscribed for message stream", {
1629
1882
  peerId
1630
1883
  }, {
1631
- F: __dxlog_file5,
1632
- L: 181,
1884
+ F: __dxlog_file6,
1885
+ L: 201,
1633
1886
  S: this,
1634
1887
  C: (f, a) => f(...a)
1635
1888
  });
1636
1889
  invariant5(this._opened, "Closed", {
1637
- F: __dxlog_file5,
1638
- L: 182,
1890
+ F: __dxlog_file6,
1891
+ L: 202,
1639
1892
  S: this,
1640
1893
  A: [
1641
1894
  "this._opened",
@@ -1645,17 +1898,17 @@ var WebsocketSignalManager = class {
1645
1898
  await this._forEachServer(async (server) => server.unsubscribeMessages(peerId));
1646
1899
  }
1647
1900
  _initContext() {
1648
- this._ctx = new Context5({
1649
- onError: (err) => log5.catch(err, void 0, {
1650
- F: __dxlog_file5,
1651
- L: 189,
1901
+ this._ctx = new Context4({
1902
+ onError: (err) => log6.catch(err, void 0, {
1903
+ F: __dxlog_file6,
1904
+ L: 209,
1652
1905
  S: this,
1653
1906
  C: (f, a) => f(...a)
1654
1907
  })
1655
1908
  });
1656
1909
  }
1657
1910
  async _forEachServer(fn) {
1658
- return Promise.all(Array.from(this._servers.entries()).map(([serverName, server]) => fn(server, serverName)));
1911
+ return Promise.all(Array.from(this._servers.entries()).map(([serverName, server], idx) => fn(server, serverName, idx)));
1659
1912
  }
1660
1913
  };
1661
1914
  _ts_decorate([
@@ -1676,16 +1929,16 @@ _ts_decorate([
1676
1929
 
1677
1930
  // packages/core/mesh/messaging/src/signal-manager/utils.ts
1678
1931
  import { invariant as invariant6 } from "@dxos/invariant";
1679
- import { log as log6 } from "@dxos/log";
1932
+ import { log as log7 } from "@dxos/log";
1680
1933
  import { DeviceKind } from "@dxos/protocols/proto/dxos/client/services";
1681
- var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/core/mesh/messaging/src/signal-manager/utils.ts";
1934
+ var __dxlog_file7 = "/home/runner/work/dxos/dxos/packages/core/mesh/messaging/src/signal-manager/utils.ts";
1682
1935
  var setIdentityTags = ({ identityService, devicesService, setTag }) => {
1683
1936
  identityService.queryIdentity().subscribe((idqr) => {
1684
1937
  if (!idqr?.identity?.identityKey) {
1685
- log6("empty response from identity service", {
1938
+ log7("empty response from identity service", {
1686
1939
  idqr
1687
1940
  }, {
1688
- F: __dxlog_file6,
1941
+ F: __dxlog_file7,
1689
1942
  L: 21,
1690
1943
  S: void 0,
1691
1944
  C: (f, a) => f(...a)
@@ -1696,10 +1949,10 @@ var setIdentityTags = ({ identityService, devicesService, setTag }) => {
1696
1949
  });
1697
1950
  devicesService.queryDevices().subscribe((dqr) => {
1698
1951
  if (!dqr || !dqr.devices || dqr.devices.length === 0) {
1699
- log6("empty response from device service", {
1952
+ log7("empty response from device service", {
1700
1953
  device: dqr
1701
1954
  }, {
1702
- F: __dxlog_file6,
1955
+ F: __dxlog_file7,
1703
1956
  L: 30,
1704
1957
  S: void 0,
1705
1958
  C: (f, a) => f(...a)
@@ -1707,7 +1960,7 @@ var setIdentityTags = ({ identityService, devicesService, setTag }) => {
1707
1960
  return;
1708
1961
  }
1709
1962
  invariant6(dqr, "empty response from device service", {
1710
- F: __dxlog_file6,
1963
+ F: __dxlog_file7,
1711
1964
  L: 33,
1712
1965
  S: void 0,
1713
1966
  A: [
@@ -1717,10 +1970,10 @@ var setIdentityTags = ({ identityService, devicesService, setTag }) => {
1717
1970
  });
1718
1971
  const thisDevice = dqr.devices.find((device) => device.kind === DeviceKind.CURRENT);
1719
1972
  if (!thisDevice) {
1720
- log6("no current device", {
1973
+ log7("no current device", {
1721
1974
  device: dqr
1722
1975
  }, {
1723
- F: __dxlog_file6,
1976
+ F: __dxlog_file7,
1724
1977
  L: 37,
1725
1978
  S: void 0,
1726
1979
  C: (f, a) => f(...a)