@dxos/messaging 0.8.4-main.ae835ea → 0.8.4-main.bc674ce

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.
@@ -1,14 +1,1695 @@
1
1
  import "@dxos/node-std/globals";
2
2
  import {
3
- EdgeSignalManager,
4
3
  MemorySignalManager,
5
4
  MemorySignalManagerContext,
6
5
  Messenger,
7
- PeerInfoHash,
8
- SignalClient,
9
- WebsocketSignalManager,
10
- setIdentityTags
11
- } from "./chunk-L7NDSF6K.mjs";
6
+ PeerInfoHash
7
+ } from "./chunk-5MOQVHHI.mjs";
8
+
9
+ // src/signal-client/signal-client.ts
10
+ import { DeferredTask, Event as Event2, Trigger as Trigger2, scheduleTask, scheduleTaskInterval as scheduleTaskInterval2, sleep } from "@dxos/async";
11
+ import { Resource, cancelWithContext as cancelWithContext2 } from "@dxos/context";
12
+ import { invariant as invariant2 } from "@dxos/invariant";
13
+ import { PublicKey as PublicKey3 } from "@dxos/keys";
14
+ import { log as log3 } from "@dxos/log";
15
+ import { trace as trace4 } from "@dxos/protocols";
16
+ import { SignalState } from "@dxos/protocols/proto/dxos/mesh/signal";
17
+
18
+ // src/signal-client/signal-client-monitor.ts
19
+ import { trace } from "@dxos/tracing";
20
+ var SignalClientMonitor = class {
21
+ _performance = {
22
+ sentMessages: 0,
23
+ receivedMessages: 0,
24
+ reconnectCounter: 0,
25
+ joinCounter: 0,
26
+ leaveCounter: 0
27
+ };
28
+ /**
29
+ * Timestamp of when the connection attempt was began.
30
+ */
31
+ _connectionStarted = /* @__PURE__ */ new Date();
32
+ /**
33
+ * Timestamp of last state change.
34
+ */
35
+ _lastStateChange = /* @__PURE__ */ new Date();
36
+ getRecordedTimestamps() {
37
+ return {
38
+ connectionStarted: this._connectionStarted,
39
+ lastStateChange: this._lastStateChange
40
+ };
41
+ }
42
+ recordStateChangeTime() {
43
+ this._lastStateChange = /* @__PURE__ */ new Date();
44
+ }
45
+ recordConnectionStartTime() {
46
+ this._connectionStarted = /* @__PURE__ */ new Date();
47
+ }
48
+ recordReconnect(params) {
49
+ this._performance.reconnectCounter++;
50
+ trace.metrics.increment("dxos.mesh.signal.signal-client.reconnect", 1, {
51
+ tags: {
52
+ success: params.success
53
+ }
54
+ });
55
+ }
56
+ recordJoin() {
57
+ this._performance.joinCounter++;
58
+ }
59
+ recordLeave() {
60
+ this._performance.leaveCounter++;
61
+ }
62
+ recordMessageReceived(message) {
63
+ this._performance.receivedMessages++;
64
+ trace.metrics.increment("dxos.mesh.signal.signal-client.received-total", 1, {
65
+ tags: createIdentityTags(message)
66
+ });
67
+ trace.metrics.distribution("dxos.mesh.signal.signal-client.bytes-in", getByteCount(message), {
68
+ tags: createIdentityTags(message)
69
+ });
70
+ }
71
+ async recordMessageSending(message, sendMessage) {
72
+ this._performance.sentMessages++;
73
+ const tags = createIdentityTags(message);
74
+ let success = true;
75
+ try {
76
+ const reqStart = Date.now();
77
+ await sendMessage();
78
+ const reqDuration = Date.now() - reqStart;
79
+ trace.metrics.distribution("dxos.mesh.signal.signal-client.send-duration", reqDuration, {
80
+ tags
81
+ });
82
+ trace.metrics.distribution("dxos.mesh.signal.signal-client.bytes-out", getByteCount(message), {
83
+ tags
84
+ });
85
+ } catch (err) {
86
+ success = false;
87
+ }
88
+ trace.metrics.increment("dxos.mesh.signal.signal-client.sent-total", 1, {
89
+ tags: {
90
+ ...tags,
91
+ success
92
+ }
93
+ });
94
+ }
95
+ recordStreamCloseErrors(count) {
96
+ trace.metrics.increment("dxos.mesh.signal.signal-client.stream-close-errors", count);
97
+ }
98
+ recordReconciliation(params) {
99
+ trace.metrics.increment("dxos.mesh.signal.signal-client.reconciliation", 1, {
100
+ tags: {
101
+ success: params.success
102
+ }
103
+ });
104
+ }
105
+ };
106
+ var getByteCount = (message) => {
107
+ return message.author.peerKey.length + message.recipient.peerKey.length + message.payload.type_url.length + message.payload.value.length;
108
+ };
109
+ var createIdentityTags = (message) => {
110
+ return {
111
+ peer: message.author.peerKey
112
+ };
113
+ };
114
+
115
+ // src/signal-client/signal-local-state.ts
116
+ import { Event, asyncTimeout } from "@dxos/async";
117
+ import { cancelWithContext } from "@dxos/context";
118
+ import { PublicKey } from "@dxos/keys";
119
+ import { log } from "@dxos/log";
120
+ import { ComplexMap, ComplexSet, safeAwaitAll } from "@dxos/util";
121
+ var __dxlog_file = "/__w/dxos/dxos/packages/core/mesh/messaging/src/signal-client/signal-local-state.ts";
122
+ var SignalLocalState = class {
123
+ _onMessage;
124
+ _onSwarmEvent;
125
+ /**
126
+ * Swarm events streams. Keys represent actually joined topic and peerId.
127
+ */
128
+ _swarmStreams = new ComplexMap(({ topic, peerId }) => topic.toHex() + peerId.toHex());
129
+ /**
130
+ * Represent desired joined topic and peerId.
131
+ */
132
+ _joinedTopics = new ComplexSet(({ topic, peerId }) => topic.toHex() + peerId.toHex());
133
+ /**
134
+ * Represent desired message subscriptions.
135
+ */
136
+ _subscribedMessages = new ComplexSet(({ peerId }) => peerId.toHex());
137
+ /**
138
+ * Message streams. Keys represents actually subscribed peers.
139
+ * @internal
140
+ */
141
+ messageStreams = new ComplexMap((key) => key.toHex());
142
+ /**
143
+ * Event to use in tests to wait till subscription is successfully established.
144
+ * @internal
145
+ */
146
+ reconciled = new Event();
147
+ constructor(_onMessage, _onSwarmEvent) {
148
+ this._onMessage = _onMessage;
149
+ this._onSwarmEvent = _onSwarmEvent;
150
+ }
151
+ async safeCloseStreams() {
152
+ const streams = [
153
+ ...this._swarmStreams.values()
154
+ ].concat([
155
+ ...this.messageStreams.values()
156
+ ]);
157
+ this._swarmStreams.clear();
158
+ this.messageStreams.clear();
159
+ const failureCount = (await safeAwaitAll(streams, (s) => s.close())).length;
160
+ return {
161
+ failureCount
162
+ };
163
+ }
164
+ join({ topic, peerId }) {
165
+ this._joinedTopics.add({
166
+ topic,
167
+ peerId
168
+ });
169
+ }
170
+ leave({ topic, peerId }) {
171
+ void this._swarmStreams.get({
172
+ topic,
173
+ peerId
174
+ })?.close();
175
+ this._swarmStreams.delete({
176
+ topic,
177
+ peerId
178
+ });
179
+ this._joinedTopics.delete({
180
+ topic,
181
+ peerId
182
+ });
183
+ }
184
+ subscribeMessages(peerId) {
185
+ this._subscribedMessages.add({
186
+ peerId
187
+ });
188
+ }
189
+ unsubscribeMessages(peerId) {
190
+ log("unsubscribing from messages", {
191
+ peerId
192
+ }, {
193
+ F: __dxlog_file,
194
+ L: 80,
195
+ S: this,
196
+ C: (f, a) => f(...a)
197
+ });
198
+ this._subscribedMessages.delete({
199
+ peerId
200
+ });
201
+ void this.messageStreams.get(peerId)?.close();
202
+ this.messageStreams.delete(peerId);
203
+ }
204
+ async reconcile(ctx, client) {
205
+ await this._reconcileSwarmSubscriptions(ctx, client);
206
+ await this._reconcileMessageSubscriptions(ctx, client);
207
+ this.reconciled.emit();
208
+ }
209
+ async _reconcileSwarmSubscriptions(ctx, client) {
210
+ for (const { topic, peerId } of this._swarmStreams.keys()) {
211
+ if (this._joinedTopics.has({
212
+ topic,
213
+ peerId
214
+ })) {
215
+ continue;
216
+ }
217
+ void this._swarmStreams.get({
218
+ topic,
219
+ peerId
220
+ })?.close();
221
+ this._swarmStreams.delete({
222
+ topic,
223
+ peerId
224
+ });
225
+ }
226
+ for (const { topic, peerId } of this._joinedTopics.values()) {
227
+ if (this._swarmStreams.has({
228
+ topic,
229
+ peerId
230
+ })) {
231
+ continue;
232
+ }
233
+ const swarmStream = await asyncTimeout(cancelWithContext(ctx, client.join({
234
+ topic,
235
+ peerId
236
+ })), 5e3);
237
+ swarmStream.subscribe(async (swarmEvent) => {
238
+ if (this._joinedTopics.has({
239
+ topic,
240
+ peerId
241
+ })) {
242
+ log("swarm event", {
243
+ swarmEvent
244
+ }, {
245
+ F: __dxlog_file,
246
+ L: 116,
247
+ S: this,
248
+ C: (f, a) => f(...a)
249
+ });
250
+ const event = swarmEvent.peerAvailable ? {
251
+ topic,
252
+ peerAvailable: {
253
+ ...swarmEvent.peerAvailable,
254
+ peer: {
255
+ peerKey: PublicKey.from(swarmEvent.peerAvailable.peer).toHex()
256
+ }
257
+ }
258
+ } : {
259
+ topic,
260
+ peerLeft: {
261
+ ...swarmEvent.peerLeft,
262
+ peer: {
263
+ peerKey: PublicKey.from(swarmEvent.peerLeft.peer).toHex()
264
+ }
265
+ }
266
+ };
267
+ await this._onSwarmEvent(event);
268
+ }
269
+ });
270
+ this._swarmStreams.set({
271
+ topic,
272
+ peerId
273
+ }, swarmStream);
274
+ }
275
+ }
276
+ async _reconcileMessageSubscriptions(ctx, client) {
277
+ for (const peerId of this.messageStreams.keys()) {
278
+ if (this._subscribedMessages.has({
279
+ peerId
280
+ })) {
281
+ continue;
282
+ }
283
+ void this.messageStreams.get(peerId)?.close();
284
+ this.messageStreams.delete(peerId);
285
+ }
286
+ for (const { peerId } of this._subscribedMessages.values()) {
287
+ if (this.messageStreams.has(peerId)) {
288
+ continue;
289
+ }
290
+ const messageStream = await asyncTimeout(cancelWithContext(ctx, client.receiveMessages(peerId)), 5e3);
291
+ messageStream.subscribe(async (signalMessage) => {
292
+ if (this._subscribedMessages.has({
293
+ peerId
294
+ })) {
295
+ const message = {
296
+ author: {
297
+ peerKey: PublicKey.from(signalMessage.author).toHex()
298
+ },
299
+ recipient: {
300
+ peerKey: PublicKey.from(signalMessage.recipient).toHex()
301
+ },
302
+ payload: signalMessage.payload
303
+ };
304
+ await this._onMessage(message);
305
+ }
306
+ });
307
+ this.messageStreams.set(peerId, messageStream);
308
+ }
309
+ }
310
+ };
311
+
312
+ // src/signal-client/signal-rpc-client.ts
313
+ import WebSocket from "isomorphic-ws";
314
+ import { TimeoutError, Trigger, scheduleTaskInterval } from "@dxos/async";
315
+ import { Context } from "@dxos/context";
316
+ import { invariant } from "@dxos/invariant";
317
+ import { PublicKey as PublicKey2 } from "@dxos/keys";
318
+ import { log as log2 } from "@dxos/log";
319
+ import { trace as trace3 } from "@dxos/protocols";
320
+ import { schema } from "@dxos/protocols/proto";
321
+ import { createProtoRpcPeer } from "@dxos/rpc";
322
+
323
+ // src/signal-client/signal-rpc-client-monitor.ts
324
+ import { trace as trace2 } from "@dxos/tracing";
325
+ var SignalRpcClientMonitor = class {
326
+ recordClientCloseFailure(params) {
327
+ trace2.metrics.increment("dxos.mesh.signal.signal-rpc-client.close-failure", 1, {
328
+ tags: {
329
+ reason: params.failureReason
330
+ }
331
+ });
332
+ }
333
+ };
334
+
335
+ // src/signal-client/signal-rpc-client.ts
336
+ var __dxlog_file2 = "/__w/dxos/dxos/packages/core/mesh/messaging/src/signal-client/signal-rpc-client.ts";
337
+ var SIGNAL_KEEPALIVE_INTERVAL = 1e4;
338
+ var SignalRPCClient = class {
339
+ _socket;
340
+ _rpc;
341
+ _connectTrigger = new Trigger();
342
+ _keepaliveCtx;
343
+ _closed = false;
344
+ _url;
345
+ _callbacks;
346
+ _closeComplete = new Trigger();
347
+ _monitor = new SignalRpcClientMonitor();
348
+ constructor({ url, callbacks = {} }) {
349
+ const traceId = PublicKey2.random().toHex();
350
+ log2.trace("dxos.mesh.signal-rpc-client.constructor", trace3.begin({
351
+ id: traceId
352
+ }), {
353
+ F: __dxlog_file2,
354
+ L: 66,
355
+ S: this,
356
+ C: (f, a) => f(...a)
357
+ });
358
+ this._url = url;
359
+ this._callbacks = callbacks;
360
+ this._socket = new WebSocket(this._url);
361
+ this._rpc = createProtoRpcPeer({
362
+ requested: {
363
+ Signal: schema.getService("dxos.mesh.signal.Signal")
364
+ },
365
+ noHandshake: true,
366
+ port: {
367
+ send: (msg) => {
368
+ if (this._closed) {
369
+ return;
370
+ }
371
+ try {
372
+ this._socket.send(msg);
373
+ } catch (err) {
374
+ log2.warn("send error", err, {
375
+ F: __dxlog_file2,
376
+ L: 85,
377
+ S: this,
378
+ C: (f, a) => f(...a)
379
+ });
380
+ }
381
+ },
382
+ subscribe: (cb) => {
383
+ this._socket.onmessage = async (msg) => {
384
+ if (typeof Blob !== "undefined" && msg.data instanceof Blob) {
385
+ cb(Buffer.from(await msg.data.arrayBuffer()));
386
+ } else {
387
+ cb(msg.data);
388
+ }
389
+ };
390
+ }
391
+ },
392
+ encodingOptions: {
393
+ preserveAny: true
394
+ }
395
+ });
396
+ this._socket.onopen = async () => {
397
+ try {
398
+ await this._rpc.open();
399
+ if (this._closed) {
400
+ await this._safeCloseRpc();
401
+ return;
402
+ }
403
+ log2(`RPC open ${this._url}`, void 0, {
404
+ F: __dxlog_file2,
405
+ L: 110,
406
+ S: this,
407
+ C: (f, a) => f(...a)
408
+ });
409
+ this._callbacks.onConnected?.();
410
+ this._connectTrigger.wake();
411
+ this._keepaliveCtx = new Context(void 0, {
412
+ F: __dxlog_file2,
413
+ L: 113
414
+ });
415
+ scheduleTaskInterval(this._keepaliveCtx, async () => {
416
+ this._socket?.send("__ping__");
417
+ }, SIGNAL_KEEPALIVE_INTERVAL);
418
+ } catch (err) {
419
+ this._callbacks.onError?.(err);
420
+ this._socket.close();
421
+ this._closed = true;
422
+ }
423
+ };
424
+ this._socket.onclose = async () => {
425
+ log2(`Disconnected ${this._url}`, void 0, {
426
+ F: __dxlog_file2,
427
+ L: 133,
428
+ S: this,
429
+ C: (f, a) => f(...a)
430
+ });
431
+ this._callbacks.onDisconnected?.();
432
+ this._closeComplete.wake();
433
+ await this.close();
434
+ };
435
+ this._socket.onerror = async (event) => {
436
+ if (this._closed) {
437
+ this._socket.close();
438
+ return;
439
+ }
440
+ this._closed = true;
441
+ this._callbacks.onError?.(event.error ?? new Error(event.message));
442
+ await this._safeCloseRpc();
443
+ log2.warn(`Socket ${event.type ?? "unknown"} error`, {
444
+ message: event.message,
445
+ url: this._url
446
+ }, {
447
+ F: __dxlog_file2,
448
+ L: 149,
449
+ S: this,
450
+ C: (f, a) => f(...a)
451
+ });
452
+ };
453
+ log2.trace("dxos.mesh.signal-rpc-client.constructor", trace3.end({
454
+ id: traceId
455
+ }), {
456
+ F: __dxlog_file2,
457
+ L: 152,
458
+ S: this,
459
+ C: (f, a) => f(...a)
460
+ });
461
+ }
462
+ async close() {
463
+ if (this._closed) {
464
+ return;
465
+ }
466
+ this._closed = true;
467
+ await this._keepaliveCtx?.dispose();
468
+ try {
469
+ await this._safeCloseRpc();
470
+ if (this._socket.readyState === WebSocket.OPEN || this._socket.readyState === WebSocket.CONNECTING) {
471
+ this._socket.close();
472
+ }
473
+ await this._closeComplete.wait({
474
+ timeout: 1e3
475
+ });
476
+ } catch (err) {
477
+ const failureReason = err instanceof TimeoutError ? "timeout" : err?.constructor?.name ?? "unknown";
478
+ this._monitor.recordClientCloseFailure({
479
+ failureReason
480
+ });
481
+ }
482
+ }
483
+ async join({ topic, peerId }) {
484
+ log2("join", {
485
+ topic,
486
+ peerId,
487
+ metadata: this._callbacks?.getMetadata?.()
488
+ }, {
489
+ F: __dxlog_file2,
490
+ L: 178,
491
+ S: this,
492
+ C: (f, a) => f(...a)
493
+ });
494
+ invariant(!this._closed, "SignalRPCClient is closed", {
495
+ F: __dxlog_file2,
496
+ L: 179,
497
+ S: this,
498
+ A: [
499
+ "!this._closed",
500
+ "'SignalRPCClient is closed'"
501
+ ]
502
+ });
503
+ await this._connectTrigger.wait();
504
+ const swarmStream = this._rpc.rpc.Signal.join({
505
+ swarm: topic.asUint8Array(),
506
+ peer: peerId.asUint8Array(),
507
+ metadata: this._callbacks?.getMetadata?.()
508
+ });
509
+ await swarmStream.waitUntilReady();
510
+ return swarmStream;
511
+ }
512
+ async receiveMessages(peerId) {
513
+ log2("receiveMessages", {
514
+ peerId
515
+ }, {
516
+ F: __dxlog_file2,
517
+ L: 191,
518
+ S: this,
519
+ C: (f, a) => f(...a)
520
+ });
521
+ invariant(!this._closed, "SignalRPCClient is closed", {
522
+ F: __dxlog_file2,
523
+ L: 192,
524
+ S: this,
525
+ A: [
526
+ "!this._closed",
527
+ "'SignalRPCClient is closed'"
528
+ ]
529
+ });
530
+ await this._connectTrigger.wait();
531
+ const messageStream = this._rpc.rpc.Signal.receiveMessages({
532
+ peer: peerId.asUint8Array()
533
+ });
534
+ await messageStream.waitUntilReady();
535
+ return messageStream;
536
+ }
537
+ async sendMessage({ author, recipient, payload }) {
538
+ log2("sendMessage", {
539
+ author,
540
+ recipient,
541
+ payload,
542
+ metadata: this._callbacks?.getMetadata?.()
543
+ }, {
544
+ F: __dxlog_file2,
545
+ L: 210,
546
+ S: this,
547
+ C: (f, a) => f(...a)
548
+ });
549
+ invariant(!this._closed, "SignalRPCClient is closed", {
550
+ F: __dxlog_file2,
551
+ L: 211,
552
+ S: this,
553
+ A: [
554
+ "!this._closed",
555
+ "'SignalRPCClient is closed'"
556
+ ]
557
+ });
558
+ await this._connectTrigger.wait();
559
+ await this._rpc.rpc.Signal.sendMessage({
560
+ author: author.asUint8Array(),
561
+ recipient: recipient.asUint8Array(),
562
+ payload,
563
+ metadata: this._callbacks?.getMetadata?.()
564
+ });
565
+ }
566
+ async _safeCloseRpc() {
567
+ try {
568
+ this._connectTrigger.reset();
569
+ await this._rpc.close();
570
+ } catch (err) {
571
+ log2.catch(err, void 0, {
572
+ F: __dxlog_file2,
573
+ L: 226,
574
+ S: this,
575
+ C: (f, a) => f(...a)
576
+ });
577
+ }
578
+ }
579
+ };
580
+
581
+ // src/signal-client/signal-client.ts
582
+ var __dxlog_file3 = "/__w/dxos/dxos/packages/core/mesh/messaging/src/signal-client/signal-client.ts";
583
+ var DEFAULT_RECONNECT_TIMEOUT = 100;
584
+ var MAX_RECONNECT_TIMEOUT = 5e3;
585
+ var ERROR_RECONCILE_DELAY = 1e3;
586
+ var RECONCILE_INTERVAL = 5e3;
587
+ var SignalClient = class extends Resource {
588
+ _host;
589
+ _getMetadata;
590
+ _monitor = new SignalClientMonitor();
591
+ _state = SignalState.CLOSED;
592
+ _lastError;
593
+ _lastReconciliationFailed = false;
594
+ _clientReady = new Trigger2();
595
+ _connectionCtx;
596
+ _client;
597
+ _reconcileTask;
598
+ _reconnectTask;
599
+ /**
600
+ * Number of milliseconds after which the connection will be attempted again in case of error.
601
+ */
602
+ _reconnectAfter = DEFAULT_RECONNECT_TIMEOUT;
603
+ _instanceId = PublicKey3.random().toHex();
604
+ /**
605
+ * @internal
606
+ */
607
+ localState;
608
+ statusChanged = new Event2();
609
+ onMessage = new Event2();
610
+ swarmEvent = new Event2();
611
+ /**
612
+ * @param _host Signal server websocket URL.
613
+ * @param onMessage called when a new message is received.
614
+ * @param onSwarmEvent called when a new swarm event is received.
615
+ * @param _getMetadata signal-message metadata provider, called for every message.
616
+ */
617
+ constructor(_host, _getMetadata) {
618
+ super(), this._host = _host, this._getMetadata = _getMetadata;
619
+ if (!this._host.startsWith("wss://") && !this._host.startsWith("ws://")) {
620
+ throw new Error(`Signal server requires a websocket URL. Provided: ${this._host}`);
621
+ }
622
+ this.localState = new SignalLocalState(async (message) => {
623
+ this._monitor.recordMessageReceived(message);
624
+ this.onMessage.emit(message);
625
+ }, async (event) => this.swarmEvent.emit(event));
626
+ }
627
+ async _open() {
628
+ log3.trace("dxos.mesh.signal-client.open", trace4.begin({
629
+ id: this._instanceId
630
+ }), {
631
+ F: __dxlog_file3,
632
+ L: 97,
633
+ S: this,
634
+ C: (f, a) => f(...a)
635
+ });
636
+ if ([
637
+ SignalState.CONNECTED,
638
+ SignalState.CONNECTING
639
+ ].includes(this._state)) {
640
+ return;
641
+ }
642
+ this._setState(SignalState.CONNECTING);
643
+ this._reconcileTask = new DeferredTask(this._ctx, async () => {
644
+ try {
645
+ await cancelWithContext2(this._connectionCtx, this._clientReady.wait({
646
+ timeout: 5e3
647
+ }));
648
+ invariant2(this._state === SignalState.CONNECTED, "Not connected to Signal Server", {
649
+ F: __dxlog_file3,
650
+ L: 107,
651
+ S: this,
652
+ A: [
653
+ "this._state === SignalState.CONNECTED",
654
+ "'Not connected to Signal Server'"
655
+ ]
656
+ });
657
+ await this.localState.reconcile(this._connectionCtx, this._client);
658
+ this._monitor.recordReconciliation({
659
+ success: true
660
+ });
661
+ this._lastReconciliationFailed = false;
662
+ } catch (err) {
663
+ this._lastReconciliationFailed = true;
664
+ this._monitor.recordReconciliation({
665
+ success: false
666
+ });
667
+ throw err;
668
+ }
669
+ });
670
+ scheduleTaskInterval2(this._ctx, async () => {
671
+ if (this._state === SignalState.CONNECTED) {
672
+ this._reconcileTask.schedule();
673
+ }
674
+ }, RECONCILE_INTERVAL);
675
+ this._reconnectTask = new DeferredTask(this._ctx, async () => {
676
+ try {
677
+ await this._reconnect();
678
+ this._monitor.recordReconnect({
679
+ success: true
680
+ });
681
+ } catch (err) {
682
+ this._monitor.recordReconnect({
683
+ success: false
684
+ });
685
+ throw err;
686
+ }
687
+ });
688
+ this._createClient();
689
+ log3.trace("dxos.mesh.signal-client.open", trace4.end({
690
+ id: this._instanceId
691
+ }), {
692
+ F: __dxlog_file3,
693
+ L: 140,
694
+ S: this,
695
+ C: (f, a) => f(...a)
696
+ });
697
+ }
698
+ async _catch(err) {
699
+ if (this._state === SignalState.CLOSED || this._ctx.disposed) {
700
+ return;
701
+ }
702
+ if (this._state === SignalState.CONNECTED && !this._lastReconciliationFailed) {
703
+ log3.warn("SignalClient error:", err, {
704
+ F: __dxlog_file3,
705
+ L: 149,
706
+ S: this,
707
+ C: (f, a) => f(...a)
708
+ });
709
+ }
710
+ this._scheduleReconcileAfterError();
711
+ }
712
+ async _close() {
713
+ log3("closing...", void 0, {
714
+ F: __dxlog_file3,
715
+ L: 155,
716
+ S: this,
717
+ C: (f, a) => f(...a)
718
+ });
719
+ if ([
720
+ SignalState.CLOSED
721
+ ].includes(this._state)) {
722
+ return;
723
+ }
724
+ this._setState(SignalState.CLOSED);
725
+ await this._safeResetClient();
726
+ log3("closed", void 0, {
727
+ F: __dxlog_file3,
728
+ L: 163,
729
+ S: this,
730
+ C: (f, a) => f(...a)
731
+ });
732
+ }
733
+ getStatus() {
734
+ return {
735
+ host: this._host,
736
+ state: this._state,
737
+ error: this._lastError?.message,
738
+ reconnectIn: this._reconnectAfter,
739
+ ...this._monitor.getRecordedTimestamps()
740
+ };
741
+ }
742
+ async join(args) {
743
+ log3("joining", {
744
+ topic: args.topic,
745
+ peerId: args.peer.peerKey
746
+ }, {
747
+ F: __dxlog_file3,
748
+ L: 177,
749
+ S: this,
750
+ C: (f, a) => f(...a)
751
+ });
752
+ this._monitor.recordJoin();
753
+ this.localState.join({
754
+ topic: args.topic,
755
+ peerId: PublicKey3.from(args.peer.peerKey)
756
+ });
757
+ this._reconcileTask?.schedule();
758
+ }
759
+ async leave(args) {
760
+ log3("leaving", {
761
+ topic: args.topic,
762
+ peerId: args.peer.peerKey
763
+ }, {
764
+ F: __dxlog_file3,
765
+ L: 184,
766
+ S: this,
767
+ C: (f, a) => f(...a)
768
+ });
769
+ this._monitor.recordLeave();
770
+ this.localState.leave({
771
+ topic: args.topic,
772
+ peerId: PublicKey3.from(args.peer.peerKey)
773
+ });
774
+ }
775
+ async query(params) {
776
+ throw new Error("Not implemented");
777
+ }
778
+ async sendMessage(msg) {
779
+ return this._monitor.recordMessageSending(msg, async () => {
780
+ await this._clientReady.wait();
781
+ invariant2(this._state === SignalState.CONNECTED, "Not connected to Signal Server", {
782
+ F: __dxlog_file3,
783
+ L: 196,
784
+ S: this,
785
+ A: [
786
+ "this._state === SignalState.CONNECTED",
787
+ "'Not connected to Signal Server'"
788
+ ]
789
+ });
790
+ invariant2(msg.author.peerKey, "Author key required", {
791
+ F: __dxlog_file3,
792
+ L: 197,
793
+ S: this,
794
+ A: [
795
+ "msg.author.peerKey",
796
+ "'Author key required'"
797
+ ]
798
+ });
799
+ invariant2(msg.recipient.peerKey, "Recipient key required", {
800
+ F: __dxlog_file3,
801
+ L: 198,
802
+ S: this,
803
+ A: [
804
+ "msg.recipient.peerKey",
805
+ "'Recipient key required'"
806
+ ]
807
+ });
808
+ await this._client.sendMessage({
809
+ author: PublicKey3.from(msg.author.peerKey),
810
+ recipient: PublicKey3.from(msg.recipient.peerKey),
811
+ payload: msg.payload
812
+ });
813
+ });
814
+ }
815
+ async subscribeMessages(peer) {
816
+ invariant2(peer.peerKey, "Peer key required", {
817
+ F: __dxlog_file3,
818
+ L: 208,
819
+ S: this,
820
+ A: [
821
+ "peer.peerKey",
822
+ "'Peer key required'"
823
+ ]
824
+ });
825
+ log3("subscribing to messages", {
826
+ peer
827
+ }, {
828
+ F: __dxlog_file3,
829
+ L: 209,
830
+ S: this,
831
+ C: (f, a) => f(...a)
832
+ });
833
+ this.localState.subscribeMessages(PublicKey3.from(peer.peerKey));
834
+ this._reconcileTask?.schedule();
835
+ }
836
+ async unsubscribeMessages(peer) {
837
+ invariant2(peer.peerKey, "Peer key required", {
838
+ F: __dxlog_file3,
839
+ L: 215,
840
+ S: this,
841
+ A: [
842
+ "peer.peerKey",
843
+ "'Peer key required'"
844
+ ]
845
+ });
846
+ log3("unsubscribing from messages", {
847
+ peer
848
+ }, {
849
+ F: __dxlog_file3,
850
+ L: 216,
851
+ S: this,
852
+ C: (f, a) => f(...a)
853
+ });
854
+ this.localState.unsubscribeMessages(PublicKey3.from(peer.peerKey));
855
+ }
856
+ _scheduleReconcileAfterError() {
857
+ scheduleTask(this._ctx, () => this._reconcileTask.schedule(), ERROR_RECONCILE_DELAY);
858
+ }
859
+ _createClient() {
860
+ log3("creating client", {
861
+ host: this._host,
862
+ state: this._state
863
+ }, {
864
+ F: __dxlog_file3,
865
+ L: 225,
866
+ S: this,
867
+ C: (f, a) => f(...a)
868
+ });
869
+ invariant2(!this._client, "Client already created", {
870
+ F: __dxlog_file3,
871
+ L: 226,
872
+ S: this,
873
+ A: [
874
+ "!this._client",
875
+ "'Client already created'"
876
+ ]
877
+ });
878
+ this._monitor.recordConnectionStartTime();
879
+ this._connectionCtx = this._ctx.derive();
880
+ this._connectionCtx.onDispose(async () => {
881
+ log3("connection context disposed", void 0, {
882
+ F: __dxlog_file3,
883
+ L: 233,
884
+ S: this,
885
+ C: (f, a) => f(...a)
886
+ });
887
+ const { failureCount } = await this.localState.safeCloseStreams();
888
+ this._monitor.recordStreamCloseErrors(failureCount);
889
+ });
890
+ try {
891
+ const client = new SignalRPCClient({
892
+ url: this._host,
893
+ callbacks: {
894
+ onConnected: () => {
895
+ if (client === this._client) {
896
+ log3("socket connected", void 0, {
897
+ F: __dxlog_file3,
898
+ L: 244,
899
+ S: this,
900
+ C: (f, a) => f(...a)
901
+ });
902
+ this._onConnected();
903
+ }
904
+ },
905
+ onDisconnected: () => {
906
+ if (client !== this._client) {
907
+ return;
908
+ }
909
+ log3("socket disconnected", {
910
+ state: this._state
911
+ }, {
912
+ F: __dxlog_file3,
913
+ L: 253,
914
+ S: this,
915
+ C: (f, a) => f(...a)
916
+ });
917
+ if (this._state === SignalState.ERROR) {
918
+ this._setState(SignalState.DISCONNECTED);
919
+ } else {
920
+ this._onDisconnected();
921
+ }
922
+ },
923
+ onError: (error) => {
924
+ if (client === this._client) {
925
+ log3("socket error", {
926
+ error,
927
+ state: this._state
928
+ }, {
929
+ F: __dxlog_file3,
930
+ L: 265,
931
+ S: this,
932
+ C: (f, a) => f(...a)
933
+ });
934
+ this._onDisconnected({
935
+ error
936
+ });
937
+ }
938
+ },
939
+ getMetadata: this._getMetadata
940
+ }
941
+ });
942
+ this._client = client;
943
+ } catch (error) {
944
+ this._client = void 0;
945
+ this._onDisconnected({
946
+ error
947
+ });
948
+ }
949
+ }
950
+ async _reconnect() {
951
+ log3(`reconnecting in ${this._reconnectAfter}ms`, {
952
+ state: this._state
953
+ }, {
954
+ F: __dxlog_file3,
955
+ L: 280,
956
+ S: this,
957
+ C: (f, a) => f(...a)
958
+ });
959
+ if (this._state === SignalState.RECONNECTING) {
960
+ log3.info("Signal api already reconnecting.", void 0, {
961
+ F: __dxlog_file3,
962
+ L: 283,
963
+ S: this,
964
+ C: (f, a) => f(...a)
965
+ });
966
+ return;
967
+ }
968
+ if (this._state === SignalState.CLOSED) {
969
+ return;
970
+ }
971
+ this._setState(SignalState.RECONNECTING);
972
+ await this._safeResetClient();
973
+ await cancelWithContext2(this._ctx, sleep(this._reconnectAfter));
974
+ this._createClient();
975
+ }
976
+ _onConnected() {
977
+ this._lastError = void 0;
978
+ this._lastReconciliationFailed = false;
979
+ this._reconnectAfter = DEFAULT_RECONNECT_TIMEOUT;
980
+ this._setState(SignalState.CONNECTED);
981
+ this._clientReady.wake();
982
+ this._reconcileTask.schedule();
983
+ }
984
+ _onDisconnected(options) {
985
+ this._updateReconnectTimeout();
986
+ if (this._state === SignalState.CLOSED) {
987
+ return;
988
+ }
989
+ if (options?.error) {
990
+ this._lastError = options.error;
991
+ this._setState(SignalState.ERROR);
992
+ } else {
993
+ this._setState(SignalState.DISCONNECTED);
994
+ }
995
+ this._reconnectTask.schedule();
996
+ }
997
+ _setState(newState) {
998
+ this._state = newState;
999
+ this._monitor.recordStateChangeTime();
1000
+ log3("signal state changed", {
1001
+ status: this.getStatus()
1002
+ }, {
1003
+ F: __dxlog_file3,
1004
+ L: 324,
1005
+ S: this,
1006
+ C: (f, a) => f(...a)
1007
+ });
1008
+ this.statusChanged.emit(this.getStatus());
1009
+ }
1010
+ _updateReconnectTimeout() {
1011
+ if (this._state !== SignalState.CONNECTED && this._state !== SignalState.CONNECTING) {
1012
+ this._reconnectAfter *= 2;
1013
+ this._reconnectAfter = Math.min(this._reconnectAfter, MAX_RECONNECT_TIMEOUT);
1014
+ }
1015
+ }
1016
+ async _safeResetClient() {
1017
+ await this._connectionCtx?.dispose();
1018
+ this._connectionCtx = void 0;
1019
+ this._clientReady.reset();
1020
+ await this._client?.close().catch(() => {
1021
+ });
1022
+ this._client = void 0;
1023
+ }
1024
+ };
1025
+
1026
+ // src/signal-manager/websocket-signal-manager.ts
1027
+ import { Event as Event3, sleep as sleep2, synchronized } from "@dxos/async";
1028
+ import { LifecycleState, Resource as Resource2 } from "@dxos/context";
1029
+ import { invariant as invariant3 } from "@dxos/invariant";
1030
+ import { PublicKey as PublicKey4 } from "@dxos/keys";
1031
+ import { log as log4 } from "@dxos/log";
1032
+ import { RateLimitExceededError, TimeoutError as TimeoutError2, trace as trace6 } from "@dxos/protocols";
1033
+ import { BitField, safeAwaitAll as safeAwaitAll2 } from "@dxos/util";
1034
+
1035
+ // src/signal-manager/websocket-signal-manager-monitor.ts
1036
+ import { trace as trace5 } from "@dxos/tracing";
1037
+ var WebsocketSignalManagerMonitor = class {
1038
+ recordRateLimitExceeded() {
1039
+ trace5.metrics.increment("dxos.mesh.signal.signal-manager.rate-limit-hit", 1);
1040
+ }
1041
+ recordServerFailure(params) {
1042
+ trace5.metrics.increment("dxos.mesh.signal.signal-manager.server-failure", 1, {
1043
+ tags: {
1044
+ server: params.serverName,
1045
+ restarted: params.willRestart
1046
+ }
1047
+ });
1048
+ }
1049
+ };
1050
+
1051
+ // src/signal-manager/websocket-signal-manager.ts
1052
+ function _ts_decorate(decorators, target, key, desc) {
1053
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1054
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1055
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1056
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1057
+ }
1058
+ var __dxlog_file4 = "/__w/dxos/dxos/packages/core/mesh/messaging/src/signal-manager/websocket-signal-manager.ts";
1059
+ var MAX_SERVER_FAILURES = 5;
1060
+ var WSS_SIGNAL_SERVER_REBOOT_DELAY = 3e3;
1061
+ var WebsocketSignalManager = class extends Resource2 {
1062
+ _hosts;
1063
+ _getMetadata;
1064
+ _servers = /* @__PURE__ */ new Map();
1065
+ _monitor = new WebsocketSignalManagerMonitor();
1066
+ /**
1067
+ * Used to avoid logging failed server restarts more than once until the server actually recovers.
1068
+ */
1069
+ _failedServersBitfield;
1070
+ failureCount = /* @__PURE__ */ new Map();
1071
+ statusChanged = new Event3();
1072
+ swarmEvent = new Event3();
1073
+ onMessage = new Event3();
1074
+ _instanceId = PublicKey4.random().toHex();
1075
+ constructor(_hosts, _getMetadata) {
1076
+ super(), this._hosts = _hosts, this._getMetadata = _getMetadata;
1077
+ log4("Created WebsocketSignalManager", {
1078
+ hosts: this._hosts
1079
+ }, {
1080
+ F: __dxlog_file4,
1081
+ L: 59,
1082
+ S: this,
1083
+ C: (f, a) => f(...a)
1084
+ });
1085
+ for (const host of this._hosts) {
1086
+ if (this._servers.has(host.server)) {
1087
+ continue;
1088
+ }
1089
+ const server = new SignalClient(host.server, this._getMetadata);
1090
+ server.swarmEvent.on((data) => this.swarmEvent.emit(data));
1091
+ server.onMessage.on((data) => this.onMessage.emit(data));
1092
+ server.statusChanged.on(() => this.statusChanged.emit(this.getStatus()));
1093
+ this._servers.set(host.server, server);
1094
+ this.failureCount.set(host.server, 0);
1095
+ }
1096
+ this._failedServersBitfield = BitField.zeros(this._hosts.length);
1097
+ }
1098
+ async _open() {
1099
+ log4("open signal manager", {
1100
+ hosts: this._hosts
1101
+ }, {
1102
+ F: __dxlog_file4,
1103
+ L: 79,
1104
+ S: this,
1105
+ C: (f, a) => f(...a)
1106
+ });
1107
+ log4.trace("dxos.mesh.websocket-signal-manager.open", trace6.begin({
1108
+ id: this._instanceId
1109
+ }), {
1110
+ F: __dxlog_file4,
1111
+ L: 80,
1112
+ S: this,
1113
+ C: (f, a) => f(...a)
1114
+ });
1115
+ await safeAwaitAll2(this._servers.values(), (server) => server.open());
1116
+ log4.trace("dxos.mesh.websocket-signal-manager.open", trace6.end({
1117
+ id: this._instanceId
1118
+ }), {
1119
+ F: __dxlog_file4,
1120
+ L: 84,
1121
+ S: this,
1122
+ C: (f, a) => f(...a)
1123
+ });
1124
+ }
1125
+ async _close() {
1126
+ await safeAwaitAll2(this._servers.values(), (server) => server.close());
1127
+ }
1128
+ async restartServer(serverName) {
1129
+ log4("restarting server", {
1130
+ serverName
1131
+ }, {
1132
+ F: __dxlog_file4,
1133
+ L: 92,
1134
+ S: this,
1135
+ C: (f, a) => f(...a)
1136
+ });
1137
+ invariant3(this._lifecycleState === LifecycleState.OPEN, void 0, {
1138
+ F: __dxlog_file4,
1139
+ L: 93,
1140
+ S: this,
1141
+ A: [
1142
+ "this._lifecycleState === LifecycleState.OPEN",
1143
+ ""
1144
+ ]
1145
+ });
1146
+ const server = this._servers.get(serverName);
1147
+ invariant3(server, "server not found", {
1148
+ F: __dxlog_file4,
1149
+ L: 96,
1150
+ S: this,
1151
+ A: [
1152
+ "server",
1153
+ "'server not found'"
1154
+ ]
1155
+ });
1156
+ await server.close();
1157
+ await sleep2(WSS_SIGNAL_SERVER_REBOOT_DELAY);
1158
+ await server.open();
1159
+ }
1160
+ getStatus() {
1161
+ return Array.from(this._servers.values()).map((server) => server.getStatus());
1162
+ }
1163
+ async join({ topic, peer }) {
1164
+ log4("join", {
1165
+ topic,
1166
+ peer
1167
+ }, {
1168
+ F: __dxlog_file4,
1169
+ L: 109,
1170
+ S: this,
1171
+ C: (f, a) => f(...a)
1172
+ });
1173
+ invariant3(this._lifecycleState === LifecycleState.OPEN, void 0, {
1174
+ F: __dxlog_file4,
1175
+ L: 110,
1176
+ S: this,
1177
+ A: [
1178
+ "this._lifecycleState === LifecycleState.OPEN",
1179
+ ""
1180
+ ]
1181
+ });
1182
+ await this._forEachServer((server) => server.join({
1183
+ topic,
1184
+ peer
1185
+ }));
1186
+ }
1187
+ async leave({ topic, peer }) {
1188
+ log4("leaving", {
1189
+ topic,
1190
+ peer
1191
+ }, {
1192
+ F: __dxlog_file4,
1193
+ L: 116,
1194
+ S: this,
1195
+ C: (f, a) => f(...a)
1196
+ });
1197
+ invariant3(this._lifecycleState === LifecycleState.OPEN, void 0, {
1198
+ F: __dxlog_file4,
1199
+ L: 117,
1200
+ S: this,
1201
+ A: [
1202
+ "this._lifecycleState === LifecycleState.OPEN",
1203
+ ""
1204
+ ]
1205
+ });
1206
+ await this._forEachServer((server) => server.leave({
1207
+ topic,
1208
+ peer
1209
+ }));
1210
+ }
1211
+ async query({ topic }) {
1212
+ throw new Error("Not implemented");
1213
+ }
1214
+ async sendMessage({ author, recipient, payload }) {
1215
+ log4("signal", {
1216
+ recipient
1217
+ }, {
1218
+ F: __dxlog_file4,
1219
+ L: 126,
1220
+ S: this,
1221
+ C: (f, a) => f(...a)
1222
+ });
1223
+ invariant3(this._lifecycleState === LifecycleState.OPEN, void 0, {
1224
+ F: __dxlog_file4,
1225
+ L: 127,
1226
+ S: this,
1227
+ A: [
1228
+ "this._lifecycleState === LifecycleState.OPEN",
1229
+ ""
1230
+ ]
1231
+ });
1232
+ void this._forEachServer(async (server, serverName, index) => {
1233
+ void server.sendMessage({
1234
+ author,
1235
+ recipient,
1236
+ payload
1237
+ }).then(() => this._clearServerFailedFlag(serverName, index)).catch((err) => {
1238
+ if (err instanceof RateLimitExceededError) {
1239
+ log4.info("WSS rate limit exceeded", {
1240
+ err
1241
+ }, {
1242
+ F: __dxlog_file4,
1243
+ L: 135,
1244
+ S: this,
1245
+ C: (f, a) => f(...a)
1246
+ });
1247
+ this._monitor.recordRateLimitExceeded();
1248
+ } else if (err instanceof TimeoutError2 || err.constructor.name === "TimeoutError") {
1249
+ log4.info("WSS sendMessage timeout", {
1250
+ err
1251
+ }, {
1252
+ F: __dxlog_file4,
1253
+ L: 138,
1254
+ S: this,
1255
+ C: (f, a) => f(...a)
1256
+ });
1257
+ void this.checkServerFailure(serverName, index);
1258
+ } else {
1259
+ log4.warn(`error sending to ${serverName}`, {
1260
+ err
1261
+ }, {
1262
+ F: __dxlog_file4,
1263
+ L: 141,
1264
+ S: this,
1265
+ C: (f, a) => f(...a)
1266
+ });
1267
+ void this.checkServerFailure(serverName, index);
1268
+ }
1269
+ });
1270
+ });
1271
+ }
1272
+ async checkServerFailure(serverName, index) {
1273
+ const failureCount = this.failureCount.get(serverName) ?? 0;
1274
+ const isRestartRequired = failureCount > MAX_SERVER_FAILURES;
1275
+ this._monitor.recordServerFailure({
1276
+ serverName,
1277
+ willRestart: isRestartRequired
1278
+ });
1279
+ if (isRestartRequired) {
1280
+ if (!BitField.get(this._failedServersBitfield, index)) {
1281
+ log4.warn("too many failures for ws-server, restarting", {
1282
+ serverName,
1283
+ failureCount
1284
+ }, {
1285
+ F: __dxlog_file4,
1286
+ L: 155,
1287
+ S: this,
1288
+ C: (f, a) => f(...a)
1289
+ });
1290
+ BitField.set(this._failedServersBitfield, index, true);
1291
+ }
1292
+ await this.restartServer(serverName);
1293
+ this.failureCount.set(serverName, 0);
1294
+ return;
1295
+ }
1296
+ this.failureCount.set(serverName, (this.failureCount.get(serverName) ?? 0) + 1);
1297
+ }
1298
+ _clearServerFailedFlag(serverName, index) {
1299
+ if (BitField.get(this._failedServersBitfield, index)) {
1300
+ log4.info("server connection restored", {
1301
+ serverName
1302
+ }, {
1303
+ F: __dxlog_file4,
1304
+ L: 168,
1305
+ S: this,
1306
+ C: (f, a) => f(...a)
1307
+ });
1308
+ BitField.set(this._failedServersBitfield, index, false);
1309
+ this.failureCount.set(serverName, 0);
1310
+ }
1311
+ }
1312
+ async subscribeMessages(peer) {
1313
+ log4("subscribed for message stream", {
1314
+ peer
1315
+ }, {
1316
+ F: __dxlog_file4,
1317
+ L: 175,
1318
+ S: this,
1319
+ C: (f, a) => f(...a)
1320
+ });
1321
+ invariant3(this._lifecycleState === LifecycleState.OPEN, void 0, {
1322
+ F: __dxlog_file4,
1323
+ L: 176,
1324
+ S: this,
1325
+ A: [
1326
+ "this._lifecycleState === LifecycleState.OPEN",
1327
+ ""
1328
+ ]
1329
+ });
1330
+ await this._forEachServer(async (server) => server.subscribeMessages(peer));
1331
+ }
1332
+ async unsubscribeMessages(peer) {
1333
+ log4("subscribed for message stream", {
1334
+ peer
1335
+ }, {
1336
+ F: __dxlog_file4,
1337
+ L: 182,
1338
+ S: this,
1339
+ C: (f, a) => f(...a)
1340
+ });
1341
+ invariant3(this._lifecycleState === LifecycleState.OPEN, void 0, {
1342
+ F: __dxlog_file4,
1343
+ L: 183,
1344
+ S: this,
1345
+ A: [
1346
+ "this._lifecycleState === LifecycleState.OPEN",
1347
+ ""
1348
+ ]
1349
+ });
1350
+ await this._forEachServer(async (server) => server.unsubscribeMessages(peer));
1351
+ }
1352
+ async _forEachServer(fn) {
1353
+ return Promise.all(Array.from(this._servers.entries()).map(([serverName, server], idx) => fn(server, serverName, idx)));
1354
+ }
1355
+ };
1356
+ _ts_decorate([
1357
+ synchronized
1358
+ ], WebsocketSignalManager.prototype, "join", null);
1359
+ _ts_decorate([
1360
+ synchronized
1361
+ ], WebsocketSignalManager.prototype, "leave", null);
1362
+ _ts_decorate([
1363
+ synchronized
1364
+ ], WebsocketSignalManager.prototype, "checkServerFailure", null);
1365
+
1366
+ // src/signal-manager/edge-signal-manager.ts
1367
+ import { Event as Event4, scheduleMicroTask } from "@dxos/async";
1368
+ import { Resource as Resource3, cancelWithContext as cancelWithContext3 } from "@dxos/context";
1369
+ import { EdgeIdentityChangedError, protocol } from "@dxos/edge-client";
1370
+ import { invariant as invariant4 } from "@dxos/invariant";
1371
+ import { PublicKey as PublicKey5 } from "@dxos/keys";
1372
+ import { log as log5 } from "@dxos/log";
1373
+ import { EdgeService } from "@dxos/protocols";
1374
+ import { bufWkt } from "@dxos/protocols/buf";
1375
+ import { SwarmRequest_Action as SwarmRequestAction, SwarmRequestSchema, SwarmResponseSchema } from "@dxos/protocols/buf/dxos/edge/messenger_pb";
1376
+ import { ComplexMap as ComplexMap2, ComplexSet as ComplexSet2 } from "@dxos/util";
1377
+ var __dxlog_file5 = "/__w/dxos/dxos/packages/core/mesh/messaging/src/signal-manager/edge-signal-manager.ts";
1378
+ var EdgeSignalManager = class extends Resource3 {
1379
+ /**
1380
+ * @deprecated
1381
+ */
1382
+ swarmEvent = new Event4();
1383
+ swarmState = new Event4();
1384
+ onMessage = new Event4();
1385
+ /**
1386
+ * Swarm key -> { peer: <own state payload>, joinedPeers: <state of swarm> }.
1387
+ */
1388
+ // TODO(mykola): This class should not contain swarm state joinedPeers. Temporary before network-manager API changes to accept list of peers.
1389
+ _swarmPeers = new ComplexMap2(PublicKey5.hash);
1390
+ _edgeConnection;
1391
+ constructor({ edgeConnection }) {
1392
+ super();
1393
+ this._edgeConnection = edgeConnection;
1394
+ }
1395
+ async _open() {
1396
+ this._ctx.onDispose(this._edgeConnection.onMessage((message) => this._onMessage(message)));
1397
+ this._ctx.onDispose(this._edgeConnection.onReconnected(() => {
1398
+ scheduleMicroTask(this._ctx, () => this._rejoinAllSwarms());
1399
+ }));
1400
+ }
1401
+ /**
1402
+ * Warning: PeerInfo is inferred from edgeConnection.
1403
+ */
1404
+ async join({ topic, peer }) {
1405
+ if (!this._matchSelfPeerInfo(peer)) {
1406
+ log5.warn("ignoring peer info on join request", {
1407
+ peer,
1408
+ expected: {
1409
+ peerKey: this._edgeConnection.peerKey,
1410
+ identityKey: this._edgeConnection.identityKey
1411
+ }
1412
+ }, {
1413
+ F: __dxlog_file5,
1414
+ L: 66,
1415
+ S: this,
1416
+ C: (f, a) => f(...a)
1417
+ });
1418
+ peer.identityKey = this._edgeConnection.identityKey;
1419
+ peer.peerKey = this._edgeConnection.peerKey;
1420
+ }
1421
+ this._swarmPeers.set(topic, {
1422
+ lastState: peer.state,
1423
+ joinedPeers: new ComplexSet2(PeerInfoHash)
1424
+ });
1425
+ await this._edgeConnection.send(protocol.createMessage(SwarmRequestSchema, {
1426
+ serviceId: EdgeService.SWARM,
1427
+ source: createMessageSource(topic, peer),
1428
+ payload: {
1429
+ action: SwarmRequestAction.JOIN,
1430
+ swarmKeys: [
1431
+ topic.toHex()
1432
+ ]
1433
+ }
1434
+ }));
1435
+ }
1436
+ async leave({ topic, peer }) {
1437
+ this._swarmPeers.delete(topic);
1438
+ try {
1439
+ await this._edgeConnection.send(protocol.createMessage(SwarmRequestSchema, {
1440
+ serviceId: EdgeService.SWARM,
1441
+ source: createMessageSource(topic, peer),
1442
+ payload: {
1443
+ action: SwarmRequestAction.LEAVE,
1444
+ swarmKeys: [
1445
+ topic.toHex()
1446
+ ]
1447
+ }
1448
+ }));
1449
+ } catch (err) {
1450
+ if (err instanceof EdgeIdentityChangedError) {
1451
+ return;
1452
+ }
1453
+ throw err;
1454
+ }
1455
+ }
1456
+ async query({ topic }) {
1457
+ const response = cancelWithContext3(this._ctx, this.swarmState.waitFor((state) => state.swarmKey === topic.toHex()));
1458
+ await this._edgeConnection.send(protocol.createMessage(SwarmRequestSchema, {
1459
+ serviceId: EdgeService.SWARM,
1460
+ source: createMessageSource(topic, {
1461
+ peerKey: this._edgeConnection.peerKey,
1462
+ identityKey: this._edgeConnection.identityKey
1463
+ }),
1464
+ payload: {
1465
+ action: SwarmRequestAction.INFO,
1466
+ swarmKeys: [
1467
+ topic.toHex()
1468
+ ]
1469
+ }
1470
+ }));
1471
+ return response;
1472
+ }
1473
+ async sendMessage(message) {
1474
+ if (!this._matchSelfPeerInfo(message.author)) {
1475
+ log5.warn("ignoring author on send request", {
1476
+ author: message.author,
1477
+ expected: {
1478
+ peerKey: this._edgeConnection.peerKey,
1479
+ identityKey: this._edgeConnection.identityKey
1480
+ }
1481
+ }, {
1482
+ F: __dxlog_file5,
1483
+ L: 131,
1484
+ S: this,
1485
+ C: (f, a) => f(...a)
1486
+ });
1487
+ }
1488
+ await this._edgeConnection.send(protocol.createMessage(bufWkt.AnySchema, {
1489
+ serviceId: EdgeService.SIGNAL,
1490
+ source: message.author,
1491
+ target: [
1492
+ message.recipient
1493
+ ],
1494
+ payload: {
1495
+ typeUrl: message.payload.type_url,
1496
+ value: message.payload.value
1497
+ }
1498
+ }));
1499
+ }
1500
+ async subscribeMessages(peerInfo) {
1501
+ }
1502
+ async unsubscribeMessages(peerInfo) {
1503
+ }
1504
+ _onMessage(message) {
1505
+ switch (message.serviceId) {
1506
+ case EdgeService.SWARM: {
1507
+ this._processSwarmResponse(message);
1508
+ break;
1509
+ }
1510
+ case EdgeService.SIGNAL: {
1511
+ this._processMessage(message);
1512
+ }
1513
+ }
1514
+ }
1515
+ _processSwarmResponse(message) {
1516
+ invariant4(protocol.getPayloadType(message) === SwarmResponseSchema.typeName, "Wrong payload type", {
1517
+ F: __dxlog_file5,
1518
+ L: 168,
1519
+ S: this,
1520
+ A: [
1521
+ "protocol.getPayloadType(message) === SwarmResponseSchema.typeName",
1522
+ "'Wrong payload type'"
1523
+ ]
1524
+ });
1525
+ const payload = protocol.getPayload(message, SwarmResponseSchema);
1526
+ this.swarmState.emit(payload);
1527
+ const topic = PublicKey5.from(payload.swarmKey);
1528
+ if (!this._swarmPeers.has(topic)) {
1529
+ return;
1530
+ }
1531
+ const { joinedPeers: oldPeers } = this._swarmPeers.get(topic);
1532
+ const timestamp = message.timestamp ? new Date(Date.parse(message.timestamp)) : /* @__PURE__ */ new Date();
1533
+ const newPeers = new ComplexSet2(PeerInfoHash, payload.peers);
1534
+ for (const peer of newPeers) {
1535
+ if (oldPeers.has(peer)) {
1536
+ continue;
1537
+ }
1538
+ this.swarmEvent.emit({
1539
+ topic,
1540
+ peerAvailable: {
1541
+ peer,
1542
+ since: timestamp
1543
+ }
1544
+ });
1545
+ }
1546
+ for (const peer of oldPeers) {
1547
+ if (newPeers.has(peer)) {
1548
+ continue;
1549
+ }
1550
+ this.swarmEvent.emit({
1551
+ topic,
1552
+ peerLeft: {
1553
+ peer
1554
+ }
1555
+ });
1556
+ }
1557
+ this._swarmPeers.get(topic).joinedPeers = newPeers;
1558
+ }
1559
+ _processMessage(message) {
1560
+ invariant4(protocol.getPayloadType(message) === bufWkt.AnySchema.typeName, "Wrong payload type", {
1561
+ F: __dxlog_file5,
1562
+ L: 206,
1563
+ S: this,
1564
+ A: [
1565
+ "protocol.getPayloadType(message) === bufWkt.AnySchema.typeName",
1566
+ "'Wrong payload type'"
1567
+ ]
1568
+ });
1569
+ const payload = protocol.getPayload(message, bufWkt.AnySchema);
1570
+ invariant4(message.source, "source is missing", {
1571
+ F: __dxlog_file5,
1572
+ L: 208,
1573
+ S: this,
1574
+ A: [
1575
+ "message.source",
1576
+ "'source is missing'"
1577
+ ]
1578
+ });
1579
+ invariant4(message.target, "target is missing", {
1580
+ F: __dxlog_file5,
1581
+ L: 209,
1582
+ S: this,
1583
+ A: [
1584
+ "message.target",
1585
+ "'target is missing'"
1586
+ ]
1587
+ });
1588
+ invariant4(message.target.length === 1, "target should have exactly one item", {
1589
+ F: __dxlog_file5,
1590
+ L: 210,
1591
+ S: this,
1592
+ A: [
1593
+ "message.target.length === 1",
1594
+ "'target should have exactly one item'"
1595
+ ]
1596
+ });
1597
+ this.onMessage.emit({
1598
+ author: message.source,
1599
+ recipient: message.target[0],
1600
+ payload: {
1601
+ type_url: payload.typeUrl,
1602
+ value: payload.value
1603
+ }
1604
+ });
1605
+ }
1606
+ _matchSelfPeerInfo(peer) {
1607
+ return peer && (peer.peerKey === this._edgeConnection.peerKey || peer.identityKey === this._edgeConnection.identityKey);
1608
+ }
1609
+ async _rejoinAllSwarms() {
1610
+ log5("rejoin swarms", {
1611
+ swarms: Array.from(this._swarmPeers.keys())
1612
+ }, {
1613
+ F: __dxlog_file5,
1614
+ L: 229,
1615
+ S: this,
1616
+ C: (f, a) => f(...a)
1617
+ });
1618
+ for (const [topic, { lastState }] of this._swarmPeers.entries()) {
1619
+ await this.join({
1620
+ topic,
1621
+ peer: {
1622
+ peerKey: this._edgeConnection.peerKey,
1623
+ identityKey: this._edgeConnection.identityKey,
1624
+ state: lastState
1625
+ }
1626
+ });
1627
+ }
1628
+ }
1629
+ };
1630
+ var createMessageSource = (topic, peerInfo) => {
1631
+ return {
1632
+ swarmKey: topic.toHex(),
1633
+ ...peerInfo
1634
+ };
1635
+ };
1636
+
1637
+ // src/signal-manager/utils.ts
1638
+ import { invariant as invariant5 } from "@dxos/invariant";
1639
+ import { log as log6 } from "@dxos/log";
1640
+ import { DeviceKind } from "@dxos/protocols/proto/dxos/client/services";
1641
+ var __dxlog_file6 = "/__w/dxos/dxos/packages/core/mesh/messaging/src/signal-manager/utils.ts";
1642
+ var setIdentityTags = ({ identityService, devicesService, setTag }) => {
1643
+ identityService.queryIdentity().subscribe((idqr) => {
1644
+ if (!idqr?.identity?.identityKey) {
1645
+ log6("empty response from identity service", {
1646
+ idqr
1647
+ }, {
1648
+ F: __dxlog_file6,
1649
+ L: 21,
1650
+ S: void 0,
1651
+ C: (f, a) => f(...a)
1652
+ });
1653
+ return;
1654
+ }
1655
+ setTag("identityKey", idqr.identity.identityKey.truncate());
1656
+ });
1657
+ devicesService.queryDevices().subscribe((dqr) => {
1658
+ if (!dqr || !dqr.devices || dqr.devices.length === 0) {
1659
+ log6("empty response from device service", {
1660
+ device: dqr
1661
+ }, {
1662
+ F: __dxlog_file6,
1663
+ L: 30,
1664
+ S: void 0,
1665
+ C: (f, a) => f(...a)
1666
+ });
1667
+ return;
1668
+ }
1669
+ invariant5(dqr, "empty response from device service", {
1670
+ F: __dxlog_file6,
1671
+ L: 33,
1672
+ S: void 0,
1673
+ A: [
1674
+ "dqr",
1675
+ "'empty response from device service'"
1676
+ ]
1677
+ });
1678
+ const thisDevice = dqr.devices.find((device) => device.kind === DeviceKind.CURRENT);
1679
+ if (!thisDevice) {
1680
+ log6("no current device", {
1681
+ device: dqr
1682
+ }, {
1683
+ F: __dxlog_file6,
1684
+ L: 37,
1685
+ S: void 0,
1686
+ C: (f, a) => f(...a)
1687
+ });
1688
+ return;
1689
+ }
1690
+ setTag("deviceKey", thisDevice.deviceKey.truncate());
1691
+ });
1692
+ };
12
1693
  export {
13
1694
  EdgeSignalManager,
14
1695
  MemorySignalManager,