@dxos/messaging 0.5.9-main.bdf733d → 0.5.9-main.bf3bb8f

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