@dxos/messaging 0.6.13 → 0.6.14-main.2b6a0f3

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