@dxos/messaging 0.10.0 → 0.11.0

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 (88) hide show
  1. package/dist/lib/chunk-memory-signal-manager.mjs +652 -0
  2. package/dist/lib/chunk-memory-signal-manager.mjs.map +1 -0
  3. package/dist/lib/index.mjs +377 -0
  4. package/dist/lib/index.mjs.map +1 -0
  5. package/dist/lib/testing.mjs +288 -0
  6. package/dist/lib/testing.mjs.map +1 -0
  7. package/dist/types/src/index.d.ts +0 -1
  8. package/dist/types/src/index.d.ts.map +1 -1
  9. package/dist/types/src/messenger.blueprint-test.d.ts.map +1 -1
  10. package/dist/types/src/messenger.d.ts +1 -1
  11. package/dist/types/src/messenger.d.ts.map +1 -1
  12. package/dist/types/src/signal-manager/edge-signal-manager.d.ts +21 -4
  13. package/dist/types/src/signal-manager/edge-signal-manager.d.ts.map +1 -1
  14. package/dist/types/src/signal-manager/edge-signal-manager.test.d.ts +2 -0
  15. package/dist/types/src/signal-manager/edge-signal-manager.test.d.ts.map +1 -0
  16. package/dist/types/src/signal-manager/index.d.ts +1 -2
  17. package/dist/types/src/signal-manager/index.d.ts.map +1 -1
  18. package/dist/types/src/signal-manager/memory-signal-manager.d.ts +12 -10
  19. package/dist/types/src/signal-manager/memory-signal-manager.d.ts.map +1 -1
  20. package/dist/types/src/signal-manager/memory-signal-manager.test.d.ts +2 -0
  21. package/dist/types/src/signal-manager/memory-signal-manager.test.d.ts.map +1 -0
  22. package/dist/types/src/signal-manager/signal-manager.d.ts +5 -0
  23. package/dist/types/src/signal-manager/signal-manager.d.ts.map +1 -1
  24. package/dist/types/src/signal-manager/utils.d.ts +6 -4
  25. package/dist/types/src/signal-manager/utils.d.ts.map +1 -1
  26. package/dist/types/src/signal-methods.d.ts +33 -22
  27. package/dist/types/src/signal-methods.d.ts.map +1 -1
  28. package/dist/types/src/testing/index.d.ts +1 -0
  29. package/dist/types/src/testing/index.d.ts.map +1 -1
  30. package/dist/types/src/testing/test-edge-mesh.d.ts +61 -0
  31. package/dist/types/src/testing/test-edge-mesh.d.ts.map +1 -0
  32. package/dist/types/tsconfig.tsbuildinfo +1 -1
  33. package/package.json +19 -19
  34. package/src/index.ts +0 -1
  35. package/src/messenger.blueprint-test.ts +0 -25
  36. package/src/messenger.node.test.ts +1 -1
  37. package/src/messenger.ts +42 -18
  38. package/src/signal-manager/edge-signal-manager.test.ts +193 -0
  39. package/src/signal-manager/edge-signal-manager.ts +145 -12
  40. package/src/signal-manager/index.ts +1 -2
  41. package/src/signal-manager/memory-signal-manager.test.ts +199 -0
  42. package/src/signal-manager/memory-signal-manager.ts +87 -47
  43. package/src/signal-manager/signal-manager.ts +7 -0
  44. package/src/signal-manager/utils.ts +30 -21
  45. package/src/signal-methods.ts +37 -26
  46. package/src/testing/index.ts +1 -0
  47. package/src/testing/test-edge-mesh.ts +196 -0
  48. package/src/testing/utils.ts +2 -2
  49. package/dist/lib/neutral/chunk-EC4H56J5.mjs +0 -497
  50. package/dist/lib/neutral/chunk-EC4H56J5.mjs.map +0 -7
  51. package/dist/lib/neutral/index.mjs +0 -1249
  52. package/dist/lib/neutral/index.mjs.map +0 -7
  53. package/dist/lib/neutral/meta.json +0 -1
  54. package/dist/lib/neutral/testing/index.mjs +0 -155
  55. package/dist/lib/neutral/testing/index.mjs.map +0 -7
  56. package/dist/types/src/signal-client/index.d.ts +0 -2
  57. package/dist/types/src/signal-client/index.d.ts.map +0 -1
  58. package/dist/types/src/signal-client/signal-client-monitor.d.ts +0 -30
  59. package/dist/types/src/signal-client/signal-client-monitor.d.ts.map +0 -1
  60. package/dist/types/src/signal-client/signal-client.d.ts +0 -59
  61. package/dist/types/src/signal-client/signal-client.d.ts.map +0 -1
  62. package/dist/types/src/signal-client/signal-client.node.test.d.ts +0 -2
  63. package/dist/types/src/signal-client/signal-client.node.test.d.ts.map +0 -1
  64. package/dist/types/src/signal-client/signal-local-state.d.ts +0 -38
  65. package/dist/types/src/signal-client/signal-local-state.d.ts.map +0 -1
  66. package/dist/types/src/signal-client/signal-rpc-client-monitor.d.ts +0 -6
  67. package/dist/types/src/signal-client/signal-rpc-client-monitor.d.ts.map +0 -1
  68. package/dist/types/src/signal-client/signal-rpc-client.d.ts +0 -47
  69. package/dist/types/src/signal-client/signal-rpc-client.d.ts.map +0 -1
  70. package/dist/types/src/signal-client/signal-rpc-client.node.test.d.ts +0 -2
  71. package/dist/types/src/signal-client/signal-rpc-client.node.test.d.ts.map +0 -1
  72. package/dist/types/src/signal-manager/websocket-signal-manager-monitor.d.ts +0 -8
  73. package/dist/types/src/signal-manager/websocket-signal-manager-monitor.d.ts.map +0 -1
  74. package/dist/types/src/signal-manager/websocket-signal-manager.d.ts +0 -41
  75. package/dist/types/src/signal-manager/websocket-signal-manager.d.ts.map +0 -1
  76. package/dist/types/src/signal-manager/websocket-signal-manager.node.test.d.ts +0 -2
  77. package/dist/types/src/signal-manager/websocket-signal-manager.node.test.d.ts.map +0 -1
  78. package/src/signal-client/index.ts +0 -5
  79. package/src/signal-client/signal-client-monitor.ts +0 -111
  80. package/src/signal-client/signal-client.node.test.ts +0 -185
  81. package/src/signal-client/signal-client.ts +0 -339
  82. package/src/signal-client/signal-local-state.ts +0 -174
  83. package/src/signal-client/signal-rpc-client-monitor.ts +0 -15
  84. package/src/signal-client/signal-rpc-client.node.test.ts +0 -102
  85. package/src/signal-client/signal-rpc-client.ts +0 -227
  86. package/src/signal-manager/websocket-signal-manager-monitor.ts +0 -20
  87. package/src/signal-manager/websocket-signal-manager.node.test.ts +0 -112
  88. package/src/signal-manager/websocket-signal-manager.ts +0 -188
@@ -0,0 +1,652 @@
1
+ import { Event, TimeoutError, Trigger, scheduleExponentialBackoffTaskInterval, scheduleTask, scheduleTaskInterval } from "@dxos/async";
2
+ import { Context } from "@dxos/context";
3
+ import { invariant } from "@dxos/invariant";
4
+ import { PublicKey } from "@dxos/keys";
5
+ import { log } from "@dxos/log";
6
+ import { TimeoutError as TimeoutError$1 } from "@dxos/protocols";
7
+ import { schema } from "@dxos/protocols/proto";
8
+ import { ComplexMap, ComplexSet } from "@dxos/util";
9
+ import { trace } from "@dxos/tracing";
10
+ //#region src/messenger-monitor.ts
11
+ var MessengerMonitor = class {
12
+ recordMessageAckFailed() {
13
+ trace.metrics.increment("dxos.mesh.signal.messenger.failed-ack", 1);
14
+ }
15
+ recordReliableMessage(params) {
16
+ trace.metrics.increment("dxos.mesh.signal.messenger.reliable-send", 1, { tags: {
17
+ success: params.sent,
18
+ attempts: params.sendAttempts
19
+ } });
20
+ }
21
+ };
22
+ //#endregion
23
+ //#region src/timeouts.ts
24
+ /**
25
+ * Timeout for retrying messages.
26
+ */
27
+ var MESSAGE_TIMEOUT = 1e4;
28
+ //#endregion
29
+ //#region src/messenger.ts
30
+ var __dxlog_file$1 = "/__w/dxos/dxos/packages/core/mesh/messaging/src/messenger.ts";
31
+ var ReliablePayload = schema.getCodecForType("dxos.mesh.messaging.ReliablePayload");
32
+ var Acknowledgement = schema.getCodecForType("dxos.mesh.messaging.Acknowledgement");
33
+ var RECEIVED_MESSAGES_GC_INTERVAL = 12e4;
34
+ /**
35
+ * Reliable messenger that works trough signal network.
36
+ */
37
+ var Messenger = class {
38
+ _monitor = new MessengerMonitor();
39
+ _signalManager;
40
+ _listeners = new ComplexMap(({ peerId, payloadType }) => peerId + payloadType);
41
+ _defaultListeners = /* @__PURE__ */ new Map();
42
+ _onAckCallbacks = new ComplexMap(PublicKey.hash);
43
+ _receivedMessages = new ComplexSet(PublicKey.hash);
44
+ /**
45
+ * Keys scheduled to be cleared from _receivedMessages on the next iteration.
46
+ */
47
+ _toClear = new ComplexSet(PublicKey.hash);
48
+ _ctx;
49
+ _closed = true;
50
+ _retryDelay;
51
+ constructor({ signalManager, retryDelay = 1e3 }) {
52
+ this._signalManager = signalManager;
53
+ this._retryDelay = retryDelay;
54
+ this.open();
55
+ }
56
+ open() {
57
+ if (!this._closed) return;
58
+ log("opening messenger", void 0, {
59
+ "~LogMeta": "~LogMeta",
60
+ F: __dxlog_file$1,
61
+ L: 71,
62
+ S: this
63
+ });
64
+ this._ctx = new Context({ onError: (err) => log.catch(err, void 0, {
65
+ "~LogMeta": "~LogMeta",
66
+ F: __dxlog_file$1,
67
+ L: 73,
68
+ S: this
69
+ }) }, {
70
+ "~LogMeta": "~LogMeta",
71
+ F: __dxlog_file$1,
72
+ L: 72
73
+ });
74
+ scheduleTaskInterval(this._ctx, async () => {
75
+ this._performGc();
76
+ }, RECEIVED_MESSAGES_GC_INTERVAL);
77
+ this._closed = false;
78
+ log("opened messenger", void 0, {
79
+ "~LogMeta": "~LogMeta",
80
+ F: __dxlog_file$1,
81
+ L: 86,
82
+ S: this
83
+ });
84
+ }
85
+ async close() {
86
+ if (this._closed) return;
87
+ this._closed = true;
88
+ await this._ctx.dispose();
89
+ }
90
+ async sendMessage(ctx, message) {
91
+ invariant(!this._closed, "Closed", {
92
+ "~LogMeta": "~LogMeta",
93
+ F: __dxlog_file$1,
94
+ L: 102,
95
+ S: this,
96
+ A: ["!this._closed", "'Closed'"]
97
+ });
98
+ const { author, recipient, payload } = message;
99
+ invariant(recipient, "Recipient is required", {
100
+ "~LogMeta": "~LogMeta",
101
+ F: __dxlog_file$1,
102
+ L: 105,
103
+ S: this,
104
+ A: ["recipient", "'Recipient is required'"]
105
+ });
106
+ const messageContext = this._ctx.derive();
107
+ const reliablePayload = {
108
+ messageId: PublicKey.random(),
109
+ payload
110
+ };
111
+ invariant(!this._onAckCallbacks.has(reliablePayload.messageId), void 0, {
112
+ "~LogMeta": "~LogMeta",
113
+ F: __dxlog_file$1,
114
+ L: 112,
115
+ S: this,
116
+ A: ["!this._onAckCallbacks.has(reliablePayload.messageId!)", ""]
117
+ });
118
+ log("send message", {
119
+ messageId: reliablePayload.messageId,
120
+ author,
121
+ recipient
122
+ }, {
123
+ "~LogMeta": "~LogMeta",
124
+ F: __dxlog_file$1,
125
+ L: 113,
126
+ S: this
127
+ });
128
+ let messageReceived;
129
+ let timeoutHit;
130
+ let sendAttempts = 0;
131
+ const promise = new Promise((resolve, reject) => {
132
+ messageReceived = resolve;
133
+ timeoutHit = reject;
134
+ });
135
+ scheduleExponentialBackoffTaskInterval(messageContext, async () => {
136
+ log("retrying message", { messageId: reliablePayload.messageId }, {
137
+ "~LogMeta": "~LogMeta",
138
+ F: __dxlog_file$1,
139
+ L: 128,
140
+ S: this
141
+ });
142
+ sendAttempts++;
143
+ await this._encodeAndSend(ctx, {
144
+ author,
145
+ recipient,
146
+ reliablePayload
147
+ }).catch((err) => log("failed to send message", { err }, {
148
+ "~LogMeta": "~LogMeta",
149
+ F: __dxlog_file$1,
150
+ L: 131,
151
+ S: this
152
+ }));
153
+ }, this._retryDelay);
154
+ scheduleTask(messageContext, () => {
155
+ log("message not delivered", { messageId: reliablePayload.messageId }, {
156
+ "~LogMeta": "~LogMeta",
157
+ F: __dxlog_file$1,
158
+ L: 140,
159
+ S: this
160
+ });
161
+ this._onAckCallbacks.delete(reliablePayload.messageId);
162
+ timeoutHit(new TimeoutError$1({
163
+ message: "signaling message not delivered",
164
+ cause: new TimeoutError(MESSAGE_TIMEOUT, "Message not delivered")
165
+ }));
166
+ messageContext.dispose();
167
+ this._monitor.recordReliableMessage({
168
+ sendAttempts,
169
+ sent: false
170
+ });
171
+ }, MESSAGE_TIMEOUT);
172
+ this._onAckCallbacks.set(reliablePayload.messageId, () => {
173
+ messageReceived();
174
+ this._onAckCallbacks.delete(reliablePayload.messageId);
175
+ messageContext.dispose();
176
+ this._monitor.recordReliableMessage({
177
+ sendAttempts,
178
+ sent: true
179
+ });
180
+ });
181
+ await this._encodeAndSend(ctx, {
182
+ author,
183
+ recipient,
184
+ reliablePayload
185
+ });
186
+ return promise;
187
+ }
188
+ /**
189
+ * Subscribes onMessage function to messages that contains payload with payloadType.
190
+ * @param payloadType if not specified, onMessage will be subscribed to all types of messages.
191
+ */
192
+ async listen({ peer, payloadType, onMessage }) {
193
+ invariant(!this._closed, "Closed", {
194
+ "~LogMeta": "~LogMeta",
195
+ F: __dxlog_file$1,
196
+ L: 178,
197
+ S: this,
198
+ A: ["!this._closed", "'Closed'"]
199
+ });
200
+ invariant(peer.peerKey, "Peer key is required", {
201
+ "~LogMeta": "~LogMeta",
202
+ F: __dxlog_file$1,
203
+ L: 179,
204
+ S: this,
205
+ A: ["peer.peerKey", "'Peer key is required'"]
206
+ });
207
+ const peerKey = peer.peerKey;
208
+ const unsubscribe = await this._signalManager.subscribeMessages({
209
+ peer,
210
+ onMessage: (message) => {
211
+ if (this._closed) return;
212
+ log("received message", { from: message.author }, {
213
+ "~LogMeta": "~LogMeta",
214
+ F: __dxlog_file$1,
215
+ L: 194,
216
+ S: this
217
+ });
218
+ this._handleMessage(message).catch((err) => log.catch(err, void 0, {
219
+ "~LogMeta": "~LogMeta",
220
+ F: __dxlog_file$1,
221
+ L: 195,
222
+ S: this
223
+ }));
224
+ }
225
+ });
226
+ const clearDispose = this._ctx.onDispose(unsubscribe);
227
+ let listeners;
228
+ if (!payloadType) {
229
+ listeners = this._defaultListeners.get(peerKey);
230
+ if (!listeners) {
231
+ listeners = /* @__PURE__ */ new Set();
232
+ this._defaultListeners.set(peerKey, listeners);
233
+ }
234
+ } else {
235
+ listeners = this._listeners.get({
236
+ peerId: peerKey,
237
+ payloadType
238
+ });
239
+ if (!listeners) {
240
+ listeners = /* @__PURE__ */ new Set();
241
+ this._listeners.set({
242
+ peerId: peerKey,
243
+ payloadType
244
+ }, listeners);
245
+ }
246
+ }
247
+ listeners.add(onMessage);
248
+ return { unsubscribe: async () => {
249
+ clearDispose();
250
+ listeners.delete(onMessage);
251
+ await unsubscribe();
252
+ } };
253
+ }
254
+ async _encodeAndSend(ctx, { author, recipient, reliablePayload }) {
255
+ await this._signalManager.sendMessage(ctx, {
256
+ author,
257
+ recipient,
258
+ payload: {
259
+ type_url: "dxos.mesh.messaging.ReliablePayload",
260
+ value: ReliablePayload.encode(reliablePayload, { preserveAny: true })
261
+ }
262
+ });
263
+ }
264
+ async _handleMessage(message) {
265
+ switch (message.payload.type_url) {
266
+ case "dxos.mesh.messaging.ReliablePayload":
267
+ await this._handleReliablePayload(message);
268
+ break;
269
+ case "dxos.mesh.messaging.Acknowledgement":
270
+ await this._handleAcknowledgement({ payload: message.payload });
271
+ break;
272
+ }
273
+ }
274
+ async _handleReliablePayload(message) {
275
+ const { author, recipient, payload } = message;
276
+ invariant(payload.type_url === "dxos.mesh.messaging.ReliablePayload", void 0, {
277
+ "~LogMeta": "~LogMeta",
278
+ F: __dxlog_file$1,
279
+ L: 263,
280
+ S: this,
281
+ A: ["payload.type_url === 'dxos.mesh.messaging.ReliablePayload'", ""]
282
+ });
283
+ invariant(recipient, "Recipient is required", {
284
+ "~LogMeta": "~LogMeta",
285
+ F: __dxlog_file$1,
286
+ L: 264,
287
+ S: this,
288
+ A: ["recipient", "'Recipient is required'"]
289
+ });
290
+ const reliablePayload = ReliablePayload.decode(payload.value, { preserveAny: true });
291
+ log("handling message", { messageId: reliablePayload.messageId }, {
292
+ "~LogMeta": "~LogMeta",
293
+ F: __dxlog_file$1,
294
+ L: 267,
295
+ S: this
296
+ });
297
+ try {
298
+ await this._sendAcknowledgement(this._ctx, {
299
+ author,
300
+ recipient,
301
+ messageId: reliablePayload.messageId
302
+ });
303
+ } catch (err) {
304
+ this._monitor.recordMessageAckFailed();
305
+ throw err;
306
+ }
307
+ if (this._receivedMessages.has(reliablePayload.messageId)) return;
308
+ this._receivedMessages.add(reliablePayload.messageId);
309
+ await this._callListeners({
310
+ author,
311
+ recipient,
312
+ payload: reliablePayload.payload
313
+ });
314
+ }
315
+ async _handleAcknowledgement({ payload }) {
316
+ invariant(payload.type_url === "dxos.mesh.messaging.Acknowledgement", void 0, {
317
+ "~LogMeta": "~LogMeta",
318
+ F: __dxlog_file$1,
319
+ L: 295,
320
+ S: this,
321
+ A: ["payload.type_url === 'dxos.mesh.messaging.Acknowledgement'", ""]
322
+ });
323
+ this._onAckCallbacks.get(Acknowledgement.decode(payload.value).messageId)?.();
324
+ }
325
+ async _sendAcknowledgement(ctx, { author, recipient, messageId }) {
326
+ log("sending ACK", {
327
+ messageId,
328
+ from: recipient,
329
+ to: author
330
+ }, {
331
+ "~LogMeta": "~LogMeta",
332
+ F: __dxlog_file$1,
333
+ L: 311,
334
+ S: this
335
+ });
336
+ await this._signalManager.sendMessage(ctx, {
337
+ author: recipient,
338
+ recipient: author,
339
+ payload: {
340
+ type_url: "dxos.mesh.messaging.Acknowledgement",
341
+ value: Acknowledgement.encode({ messageId })
342
+ }
343
+ });
344
+ }
345
+ async _callListeners(message) {
346
+ const { recipient } = message;
347
+ invariant(recipient?.peerKey, "Peer key is required", {
348
+ "~LogMeta": "~LogMeta",
349
+ F: __dxlog_file$1,
350
+ L: 325,
351
+ S: this,
352
+ A: ["recipient?.peerKey", "'Peer key is required'"]
353
+ });
354
+ const peerKey = recipient.peerKey;
355
+ {
356
+ const defaultListenerMap = this._defaultListeners.get(peerKey);
357
+ if (defaultListenerMap) for (const listener of defaultListenerMap) await listener(message);
358
+ }
359
+ {
360
+ const listenerMap = this._listeners.get({
361
+ peerId: peerKey,
362
+ payloadType: message.payload.type_url
363
+ });
364
+ if (listenerMap) for (const listener of listenerMap) await listener(message);
365
+ }
366
+ }
367
+ _performGc() {
368
+ const start = performance.now();
369
+ for (const key of this._toClear.keys()) this._receivedMessages.delete(key);
370
+ this._toClear.clear();
371
+ for (const key of this._receivedMessages.keys()) this._toClear.add(key);
372
+ const elapsed = performance.now() - start;
373
+ if (elapsed > 100) log.warn("GC took too long", { elapsed }, {
374
+ "~LogMeta": "~LogMeta",
375
+ F: __dxlog_file$1,
376
+ L: 362,
377
+ S: this
378
+ });
379
+ }
380
+ };
381
+ //#endregion
382
+ //#region src/signal-methods.ts
383
+ var PeerInfoHash = ({ peerKey }) => peerKey;
384
+ //#endregion
385
+ //#region src/signal-manager/memory-signal-manager.ts
386
+ var __dxlog_file = "/__w/dxos/dxos/packages/core/mesh/messaging/src/signal-manager/memory-signal-manager.ts";
387
+ /**
388
+ * Common signaling context that connects multiple MemorySignalManager instances.
389
+ */
390
+ var MemorySignalManagerContext = class {
391
+ swarmEvent = new Event();
392
+ swarms = new ComplexMap(PublicKey.hash);
393
+ connections = new ComplexMap(PeerInfoHash);
394
+ };
395
+ /**
396
+ * In memory signal manager for testing.
397
+ */
398
+ var MemorySignalManager = class {
399
+ _context;
400
+ statusChanged = new Event();
401
+ swarmEvent = new Event();
402
+ /**
403
+ * Active message subscriptions on this manager. Routing is encapsulated here (DX-1125): a delivered
404
+ * message is dispatched to every subscription it matches.
405
+ */
406
+ _subscriptions = /* @__PURE__ */ new Set();
407
+ /** Will be used to emit SwarmEvents on .open() and .close() */
408
+ _joinedSwarms = new ComplexSet(({ topic, peer }) => topic.toHex() + peer.peerKey);
409
+ _ctx;
410
+ _freezeTrigger = new Trigger().wake();
411
+ constructor(_context) {
412
+ this._context = _context;
413
+ this._ctx = new Context(void 0, {
414
+ "~LogMeta": "~LogMeta",
415
+ F: __dxlog_file,
416
+ L: 75
417
+ });
418
+ this._ctx.onDispose(this._context.swarmEvent.on((data) => this.swarmEvent.emit(data)));
419
+ }
420
+ async open() {
421
+ if (!this._ctx.disposed) return;
422
+ this._ctx = new Context(void 0, {
423
+ "~LogMeta": "~LogMeta",
424
+ F: __dxlog_file,
425
+ L: 84
426
+ });
427
+ this._ctx.onDispose(this._context.swarmEvent.on((data) => this.swarmEvent.emit(data)));
428
+ await Promise.all([...this._joinedSwarms.values()].map((value) => this.join(this._ctx, value)));
429
+ }
430
+ async close() {
431
+ if (this._ctx.disposed) return;
432
+ const joinedSwarmsCopy = new ComplexSet(({ topic, peer }) => topic.toHex() + peer.peerKey, [...this._joinedSwarms.values()]);
433
+ await Promise.all([...this._joinedSwarms.values()].map((value) => this.leave(this._ctx, value)));
434
+ this._joinedSwarms = joinedSwarmsCopy;
435
+ await this._ctx.dispose();
436
+ }
437
+ getStatus() {
438
+ return [];
439
+ }
440
+ async join(_ctx, { topic, peer }) {
441
+ invariant(!this._ctx.disposed, "Closed", {
442
+ "~LogMeta": "~LogMeta",
443
+ F: __dxlog_file,
444
+ L: 113,
445
+ S: this,
446
+ A: ["!this._ctx.disposed", "'Closed'"]
447
+ });
448
+ this._joinedSwarms.add({
449
+ topic,
450
+ peer
451
+ });
452
+ if (!this._context.swarms.has(topic)) this._context.swarms.set(topic, new ComplexSet(PeerInfoHash));
453
+ this._context.swarms.get(topic).add(peer);
454
+ this._context.swarmEvent.emit({
455
+ topic,
456
+ peerAvailable: {
457
+ peer,
458
+ since: /* @__PURE__ */ new Date()
459
+ }
460
+ });
461
+ for (const [topic, peers] of this._context.swarms) Array.from(peers).forEach((peer) => {
462
+ this.swarmEvent.emit({
463
+ topic,
464
+ peerAvailable: {
465
+ peer,
466
+ since: /* @__PURE__ */ new Date()
467
+ }
468
+ });
469
+ });
470
+ }
471
+ async leave(_ctx, { topic, peer }) {
472
+ invariant(!this._ctx.disposed, "Closed", {
473
+ "~LogMeta": "~LogMeta",
474
+ F: __dxlog_file,
475
+ L: 145,
476
+ S: this,
477
+ A: ["!this._ctx.disposed", "'Closed'"]
478
+ });
479
+ this._joinedSwarms.delete({
480
+ topic,
481
+ peer
482
+ });
483
+ if (!this._context.swarms.has(topic)) this._context.swarms.set(topic, new ComplexSet(PeerInfoHash));
484
+ this._context.swarms.get(topic).delete(peer);
485
+ const swarmEvent = {
486
+ topic,
487
+ peerLeft: { peer }
488
+ };
489
+ this._context.swarmEvent.emit(swarmEvent);
490
+ }
491
+ async query(_ctx, request) {
492
+ throw new Error("Not implemented");
493
+ }
494
+ async sendMessage(_ctx, message) {
495
+ invariant(!this._ctx.disposed, "Closed", {
496
+ "~LogMeta": "~LogMeta",
497
+ F: __dxlog_file,
498
+ L: 170,
499
+ S: this,
500
+ A: ["!this._ctx.disposed", "'Closed'"]
501
+ });
502
+ const { author, recipient, tags, payload } = message;
503
+ invariant(recipient == null !== !tags?.length, "Exactly one of `recipient` or `tags` must be set", {
504
+ "~LogMeta": "~LogMeta",
505
+ F: __dxlog_file,
506
+ L: 173,
507
+ S: this,
508
+ A: ["(recipient == null) !== !tags?.length", "'Exactly one of `recipient` or `tags` must be set'"]
509
+ });
510
+ await this._freezeTrigger.wait();
511
+ if (recipient != null) {
512
+ log("send message", {
513
+ author,
514
+ recipient,
515
+ ...dec(payload)
516
+ }, {
517
+ "~LogMeta": "~LogMeta",
518
+ F: __dxlog_file,
519
+ L: 178,
520
+ S: this
521
+ });
522
+ const remote = this._context.connections.get(recipient);
523
+ if (!remote) {
524
+ log.warn("recipient is not subscribed for messages", {
525
+ author,
526
+ recipient
527
+ }, {
528
+ "~LogMeta": "~LogMeta",
529
+ F: __dxlog_file,
530
+ L: 181,
531
+ S: this
532
+ });
533
+ return;
534
+ }
535
+ remote._deliver(message);
536
+ } else {
537
+ log("broadcast message", {
538
+ author,
539
+ tags,
540
+ ...dec(payload)
541
+ }, {
542
+ "~LogMeta": "~LogMeta",
543
+ F: __dxlog_file,
544
+ L: 187,
545
+ S: this
546
+ });
547
+ for (const manager of new Set(this._context.connections.values())) manager._deliver(message);
548
+ }
549
+ }
550
+ async subscribeMessages({ peer, tags = [], onMessage }) {
551
+ invariant(!this._ctx.disposed, "Closed", {
552
+ "~LogMeta": "~LogMeta",
553
+ F: __dxlog_file,
554
+ L: 195,
555
+ S: this,
556
+ A: ["!this._ctx.disposed", "'Closed'"]
557
+ });
558
+ log("subscribing", {
559
+ peer,
560
+ tags
561
+ }, {
562
+ "~LogMeta": "~LogMeta",
563
+ F: __dxlog_file,
564
+ L: 196,
565
+ S: this
566
+ });
567
+ const subscription = {
568
+ peerKey: peer.peerKey,
569
+ tags: new Set(tags),
570
+ onMessage
571
+ };
572
+ this._subscriptions.add(subscription);
573
+ this._context.connections.set(peer, this);
574
+ return async () => {
575
+ log("unsubscribing", {
576
+ peer,
577
+ tags
578
+ }, {
579
+ "~LogMeta": "~LogMeta",
580
+ F: __dxlog_file,
581
+ L: 202,
582
+ S: this
583
+ });
584
+ this._subscriptions.delete(subscription);
585
+ if (![...this._subscriptions].some((sub) => sub.peerKey === peer.peerKey)) this._context.connections.delete(peer);
586
+ };
587
+ }
588
+ freeze() {
589
+ this._freezeTrigger.reset();
590
+ }
591
+ unfreeze() {
592
+ this._freezeTrigger.wake();
593
+ }
594
+ /**
595
+ * Route a delivered message to this manager's matching subscriptions once it is unfrozen and open.
596
+ */
597
+ _deliver(message) {
598
+ if (this._ctx.disposed) {
599
+ log.warn("recipient is disposed", { message }, {
600
+ "~LogMeta": "~LogMeta",
601
+ F: __dxlog_file,
602
+ L: 224,
603
+ S: this
604
+ });
605
+ return;
606
+ }
607
+ this._freezeTrigger.wait().then(() => {
608
+ if (this._ctx.disposed) {
609
+ log.warn("recipient is disposed", { message }, {
610
+ "~LogMeta": "~LogMeta",
611
+ F: __dxlog_file,
612
+ L: 232,
613
+ S: this
614
+ });
615
+ return;
616
+ }
617
+ log("receive message", {
618
+ author: message.author,
619
+ recipient: message.recipient,
620
+ ...dec(message.payload)
621
+ }, {
622
+ "~LogMeta": "~LogMeta",
623
+ F: __dxlog_file,
624
+ L: 236,
625
+ S: this
626
+ });
627
+ for (const subscription of this._subscriptions) if (message.recipient != null) {
628
+ if (subscription.peerKey === message.recipient.peerKey) subscription.onMessage(message);
629
+ } else if (message.tags?.some((tag) => subscription.tags.has(tag))) subscription.onMessage(message);
630
+ }).catch((err) => {
631
+ log.error("error while waiting for freeze", { err }, {
632
+ "~LogMeta": "~LogMeta",
633
+ F: __dxlog_file,
634
+ L: 248,
635
+ S: this
636
+ });
637
+ });
638
+ }
639
+ };
640
+ var dec = (payload) => {
641
+ if (!payload.type_url.endsWith("ReliablePayload")) return {};
642
+ const relPayload = schema.getCodecForType("dxos.mesh.messaging.ReliablePayload").decode(payload.value);
643
+ if (typeof relPayload?.payload?.data === "object") return {
644
+ payload: Object.keys(relPayload?.payload?.data)[0],
645
+ sessionId: relPayload?.payload?.sessionId
646
+ };
647
+ return {};
648
+ };
649
+ //#endregion
650
+ export { Messenger as i, MemorySignalManagerContext as n, PeerInfoHash as r, MemorySignalManager as t };
651
+
652
+ //# sourceMappingURL=chunk-memory-signal-manager.mjs.map