@dxos/network-manager 0.10.0 → 0.11.1

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 (56) hide show
  1. package/dist/lib/chunk-wire-protocol.mjs +3554 -0
  2. package/dist/lib/chunk-wire-protocol.mjs.map +1 -0
  3. package/dist/lib/index.mjs +294 -0
  4. package/dist/lib/index.mjs.map +1 -0
  5. package/dist/lib/testing.mjs +237 -0
  6. package/dist/lib/testing.mjs.map +1 -0
  7. package/dist/lib/transport/tcp/tcp-transport.browser.mjs +34 -0
  8. package/dist/lib/transport/tcp/tcp-transport.browser.mjs.map +1 -0
  9. package/dist/lib/transport/tcp/tcp-transport.mjs +140 -0
  10. package/dist/lib/transport/tcp/tcp-transport.mjs.map +1 -0
  11. package/dist/lib/transport/tcp.mjs +1 -0
  12. package/dist/types/src/network-manager.d.ts +9 -0
  13. package/dist/types/src/network-manager.d.ts.map +1 -1
  14. package/dist/types/src/signal/swarm-messenger.d.ts +2 -7
  15. package/dist/types/src/signal/swarm-messenger.d.ts.map +1 -1
  16. package/dist/types/src/testing/test-builder.d.ts +2 -6
  17. package/dist/types/src/testing/test-builder.d.ts.map +1 -1
  18. package/dist/types/src/transport/webrtc/rtc-peer-connection.d.ts.map +1 -1
  19. package/dist/types/tsconfig.tsbuildinfo +1 -1
  20. package/package.json +28 -31
  21. package/src/network-manager.ts +17 -4
  22. package/src/signal/integration.node.test.ts +7 -11
  23. package/src/signal/swarm-messenger.node.test.ts +7 -29
  24. package/src/signal/swarm-messenger.ts +3 -13
  25. package/src/swarm/swarm.test.ts +2 -2
  26. package/src/testing/test-builder.ts +48 -75
  27. package/src/tests/webrtc-transport.test.ts +4 -15
  28. package/src/transport/webrtc/rtc-peer-connection.ts +0 -3
  29. package/src/transport/webrtc/rtc-transport-proxy.test.ts +2 -1
  30. package/src/transport/webrtc/rtc-transport.test.ts +2 -1
  31. package/dist/lib/browser/chunk-ERB3AQAF.mjs +0 -3095
  32. package/dist/lib/browser/chunk-ERB3AQAF.mjs.map +0 -7
  33. package/dist/lib/browser/index.mjs +0 -244
  34. package/dist/lib/browser/index.mjs.map +0 -7
  35. package/dist/lib/browser/meta.json +0 -1
  36. package/dist/lib/browser/testing/index.mjs +0 -280
  37. package/dist/lib/browser/testing/index.mjs.map +0 -7
  38. package/dist/lib/browser/transport/tcp/index.mjs +0 -5
  39. package/dist/lib/browser/transport/tcp/index.mjs.map +0 -7
  40. package/dist/lib/browser/transport/tcp/tcp-transport.browser.mjs +0 -36
  41. package/dist/lib/browser/transport/tcp/tcp-transport.browser.mjs.map +0 -7
  42. package/dist/lib/browser/transport/tcp/tcp-transport.mjs +0 -125
  43. package/dist/lib/browser/transport/tcp/tcp-transport.mjs.map +0 -7
  44. package/dist/lib/node-esm/chunk-KYLPLEZQ.mjs +0 -3095
  45. package/dist/lib/node-esm/chunk-KYLPLEZQ.mjs.map +0 -7
  46. package/dist/lib/node-esm/index.mjs +0 -244
  47. package/dist/lib/node-esm/index.mjs.map +0 -7
  48. package/dist/lib/node-esm/meta.json +0 -1
  49. package/dist/lib/node-esm/testing/index.mjs +0 -280
  50. package/dist/lib/node-esm/testing/index.mjs.map +0 -7
  51. package/dist/lib/node-esm/transport/tcp/index.mjs +0 -5
  52. package/dist/lib/node-esm/transport/tcp/index.mjs.map +0 -7
  53. package/dist/lib/node-esm/transport/tcp/tcp-transport.browser.mjs +0 -36
  54. package/dist/lib/node-esm/transport/tcp/tcp-transport.browser.mjs.map +0 -7
  55. package/dist/lib/node-esm/transport/tcp/tcp-transport.mjs +0 -125
  56. package/dist/lib/node-esm/transport/tcp/tcp-transport.mjs.map +0 -7
@@ -0,0 +1,3554 @@
1
+ import { DeferredTask, Event, Mutex, SubscriptionList, Trigger, scheduleTask, scheduleTaskInterval, sleep, synchronized } from "@dxos/async";
2
+ import { ErrorStream, raise } from "@dxos/debug";
3
+ import { PublicKey } from "@dxos/keys";
4
+ import { ComplexMap, arrayToBuffer, isNonNullable } from "@dxos/util";
5
+ import { Context, ContextDisposedError, Resource, cancelWithContext } from "@dxos/context";
6
+ import { assertArgument, invariant } from "@dxos/invariant";
7
+ import { log, logInfo } from "@dxos/log";
8
+ import { CancelledError, ConnectionResetError, ConnectivityError, ProtocolError, SystemError, TimeoutError } from "@dxos/protocols";
9
+ import { Messenger, PeerInfoHash } from "@dxos/messaging";
10
+ import { schema } from "@dxos/protocols/proto";
11
+ import * as EffectContext from "effect/Context";
12
+ import { ConnectionState } from "@dxos/protocols/proto/dxos/client/services";
13
+ import { Duplex, Transform, Writable } from "@dxos/node-std/stream";
14
+ import { ConnectionState as ConnectionState$1 } from "@dxos/protocols/proto/dxos/mesh/bridge";
15
+ import { Stream } from "@dxos/codec-protobuf/stream";
16
+ import { Teleport } from "@dxos/teleport";
17
+ //#region \0@oxc-project+runtime@0.139.0/helpers/esm/decorate.js
18
+ function __decorate(decorators, target, key, desc) {
19
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
20
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
21
+ 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;
22
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
23
+ }
24
+ //#endregion
25
+ //#region src/swarm/connection.ts
26
+ var __dxlog_file$12 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/swarm/connection.ts";
27
+ /**
28
+ * How long to wait before sending the signal in case we receive another signal.
29
+ * This value is increased exponentially.
30
+ */
31
+ var STARTING_SIGNALLING_DELAY = 10;
32
+ /**
33
+ * How long to wait for the transport to establish connectivity, i.e. for the connection to move between CONNECTING and CONNECTED.
34
+ */
35
+ var TRANSPORT_CONNECTION_TIMEOUT = 1e4;
36
+ var TRANSPORT_STATS_INTERVAL = 5e3;
37
+ /**
38
+ * Maximum delay between signal batches.
39
+ */
40
+ var MAX_SIGNALLING_DELAY = 300;
41
+ /**
42
+ * State machine for each connection.
43
+ */
44
+ var ConnectionState$2 = /* @__PURE__ */ function(ConnectionState) {
45
+ /**
46
+ * Connection is created, but not yet passed through the connection limiter.
47
+ */
48
+ ConnectionState["CREATED"] = "CREATED";
49
+ /**
50
+ * Initial state. Connection is registered but no attempt to connect to the remote peer has been performed.
51
+ * Might mean that we are waiting for the answer signal from the remote peer.
52
+ */
53
+ ConnectionState["INITIAL"] = "INITIAL";
54
+ /**
55
+ * Trying to establish connection.
56
+ */
57
+ ConnectionState["CONNECTING"] = "CONNECTING";
58
+ /**
59
+ * Connection is established.
60
+ */
61
+ ConnectionState["CONNECTED"] = "CONNECTED";
62
+ /**
63
+ * Connection is being closed.
64
+ */
65
+ ConnectionState["CLOSING"] = "CLOSING";
66
+ /**
67
+ * Connection closed.
68
+ */
69
+ ConnectionState["CLOSED"] = "CLOSED";
70
+ ConnectionState["ABORTING"] = "ABORTING";
71
+ ConnectionState["ABORTED"] = "ABORTED";
72
+ return ConnectionState;
73
+ }({});
74
+ /**
75
+ * Represents a connection to a remote peer.
76
+ * Owns a transport paired together with a wire-protocol.
77
+ */
78
+ var Connection = class {
79
+ topic;
80
+ localInfo;
81
+ remoteInfo;
82
+ sessionId;
83
+ initiator;
84
+ _signalMessaging;
85
+ _protocol;
86
+ _transportFactory;
87
+ _callbacks;
88
+ _ctx = new Context(void 0, {
89
+ "~LogMeta": "~LogMeta",
90
+ F: __dxlog_file$12,
91
+ L: 93
92
+ });
93
+ connectedTimeoutContext = new Context(void 0, {
94
+ "~LogMeta": "~LogMeta",
95
+ F: __dxlog_file$12,
96
+ L: 94
97
+ });
98
+ _protocolClosed = new Trigger();
99
+ _transportClosed = new Trigger();
100
+ _state = "CREATED";
101
+ _transport;
102
+ closeReason;
103
+ _incomingSignalBuffer = [];
104
+ _outgoingSignalBuffer = [];
105
+ stateChanged = new Event();
106
+ errors = new ErrorStream();
107
+ transportStats = new Event();
108
+ _signalSendTask = new DeferredTask(this._ctx, async () => {
109
+ await this._flushSignalBuffer(this._ctx);
110
+ });
111
+ _signallingDelay = STARTING_SIGNALLING_DELAY;
112
+ constructor(topic, localInfo, remoteInfo, sessionId, initiator, _signalMessaging, _protocol, _transportFactory, _callbacks) {
113
+ this.topic = topic;
114
+ this.localInfo = localInfo;
115
+ this.remoteInfo = remoteInfo;
116
+ this.sessionId = sessionId;
117
+ this.initiator = initiator;
118
+ this._signalMessaging = _signalMessaging;
119
+ this._protocol = _protocol;
120
+ this._transportFactory = _transportFactory;
121
+ this._callbacks = _callbacks;
122
+ log.trace("dxos.mesh.connection.construct", {
123
+ sessionId: this.sessionId,
124
+ topic: this.topic,
125
+ localPeer: this.localInfo,
126
+ remotePeer: this.remoteInfo,
127
+ initiator: this.initiator
128
+ }, {
129
+ "~LogMeta": "~LogMeta",
130
+ F: __dxlog_file$12,
131
+ L: 128,
132
+ S: this
133
+ });
134
+ }
135
+ get sessionIdString() {
136
+ return this.sessionId.truncate();
137
+ }
138
+ get state() {
139
+ return this._state;
140
+ }
141
+ get transport() {
142
+ return this._transport;
143
+ }
144
+ get protocol() {
145
+ return this._protocol;
146
+ }
147
+ /**
148
+ * Create an underlying transport and prepares it for the connection.
149
+ */
150
+ async openConnection() {
151
+ invariant(this._state === "INITIAL", "Invalid state.", {
152
+ "~LogMeta": "~LogMeta",
153
+ F: __dxlog_file$12,
154
+ L: 158,
155
+ S: this,
156
+ A: ["this._state === ConnectionState.INITIAL", "'Invalid state.'"]
157
+ });
158
+ log("opening connection", void 0, {
159
+ "~LogMeta": "~LogMeta",
160
+ F: __dxlog_file$12,
161
+ L: 159,
162
+ S: this
163
+ });
164
+ log.trace("dxos.mesh.connection.open", {
165
+ sessionId: this.sessionId,
166
+ topic: this.topic,
167
+ localPeerId: this.localInfo,
168
+ remotePeerId: this.remoteInfo,
169
+ initiator: this.initiator
170
+ }, {
171
+ "~LogMeta": "~LogMeta",
172
+ F: __dxlog_file$12,
173
+ L: 160,
174
+ S: this
175
+ });
176
+ this._changeState("CONNECTING");
177
+ this._protocol.open(this.sessionId).catch((err) => {
178
+ this.errors.raise(err);
179
+ });
180
+ this._protocol.stream.on("close", () => {
181
+ log("protocol stream closed", void 0, {
182
+ "~LogMeta": "~LogMeta",
183
+ F: __dxlog_file$12,
184
+ L: 177,
185
+ S: this
186
+ });
187
+ this._protocolClosed.wake();
188
+ this.close({ error: new ProtocolError({ message: "protocol stream closed" }) }).catch((err) => this.errors.raise(err));
189
+ });
190
+ scheduleTask(this.connectedTimeoutContext, async () => {
191
+ log.info(`timeout waiting ${TRANSPORT_CONNECTION_TIMEOUT / 1e3}s for transport to connect, aborting`, void 0, {
192
+ "~LogMeta": "~LogMeta",
193
+ F: __dxlog_file$12,
194
+ L: 187,
195
+ S: this
196
+ });
197
+ await this.abort(new TimeoutError({ message: `${TRANSPORT_CONNECTION_TIMEOUT / 1e3}s for transport to connect` })).catch((err) => this.errors.raise(err));
198
+ }, TRANSPORT_CONNECTION_TIMEOUT);
199
+ invariant(!this._transport, void 0, {
200
+ "~LogMeta": "~LogMeta",
201
+ F: __dxlog_file$12,
202
+ L: 195,
203
+ S: this,
204
+ A: ["!this._transport", ""]
205
+ });
206
+ this._transport = this._transportFactory.createTransport({
207
+ ownPeerKey: this.localInfo.peerKey,
208
+ remotePeerKey: this.remoteInfo.peerKey,
209
+ topic: this.topic.toHex(),
210
+ initiator: this.initiator,
211
+ stream: this._protocol.stream,
212
+ sendSignal: async (signal) => this._sendSignal(signal),
213
+ sessionId: this.sessionId
214
+ });
215
+ this._transport.connected.once(async () => {
216
+ this._changeState("CONNECTED");
217
+ await this.connectedTimeoutContext.dispose();
218
+ this._callbacks?.onConnected?.();
219
+ scheduleTaskInterval(this._ctx, async () => this._emitTransportStats(), TRANSPORT_STATS_INTERVAL);
220
+ });
221
+ this._transport.closed.once(() => {
222
+ this._transport = void 0;
223
+ this._transportClosed.wake();
224
+ log("abort triggered by transport close", void 0, {
225
+ "~LogMeta": "~LogMeta",
226
+ F: __dxlog_file$12,
227
+ L: 217,
228
+ S: this
229
+ });
230
+ this.abort().catch((err) => this.errors.raise(err));
231
+ });
232
+ this._transport.errors.handle(async (err) => {
233
+ log("transport error:", { err }, {
234
+ "~LogMeta": "~LogMeta",
235
+ F: __dxlog_file$12,
236
+ L: 222,
237
+ S: this
238
+ });
239
+ if (!this.closeReason) this.closeReason = err?.message;
240
+ if (err instanceof ConnectionResetError) {
241
+ log.info("aborting due to transport ConnectionResetError", void 0, {
242
+ "~LogMeta": "~LogMeta",
243
+ F: __dxlog_file$12,
244
+ L: 229,
245
+ S: this
246
+ });
247
+ this.abort(err).catch((err) => this.errors.raise(err));
248
+ } else if (err instanceof ConnectivityError) {
249
+ log.info("aborting due to transport ConnectivityError", void 0, {
250
+ "~LogMeta": "~LogMeta",
251
+ F: __dxlog_file$12,
252
+ L: 232,
253
+ S: this
254
+ });
255
+ this.abort(err).catch((err) => this.errors.raise(err));
256
+ }
257
+ if (this._state !== "CLOSED" && this._state !== "CLOSING") {
258
+ await this.connectedTimeoutContext.dispose();
259
+ this.errors.raise(err);
260
+ }
261
+ });
262
+ await this._transport.open();
263
+ for (const signal of this._incomingSignalBuffer) this._transport.onSignal(signal);
264
+ this._incomingSignalBuffer = [];
265
+ log("opened connection", void 0, {
266
+ "~LogMeta": "~LogMeta",
267
+ F: __dxlog_file$12,
268
+ L: 251,
269
+ S: this
270
+ });
271
+ }
272
+ async abort(err) {
273
+ log("abort", { err }, {
274
+ "~LogMeta": "~LogMeta",
275
+ F: __dxlog_file$12,
276
+ L: 258,
277
+ S: this
278
+ });
279
+ if (this._state === "CLOSED" || this._state === "ABORTED") {
280
+ log(`abort ignored: already ${this._state}`, this.closeReason, {
281
+ "~LogMeta": "~LogMeta",
282
+ F: __dxlog_file$12,
283
+ L: 260,
284
+ S: this
285
+ });
286
+ return;
287
+ }
288
+ await this.connectedTimeoutContext.dispose();
289
+ this._changeState("ABORTING");
290
+ if (!this.closeReason) this.closeReason = err?.message;
291
+ await this._ctx.dispose();
292
+ log("aborting...", {
293
+ peerId: this.localInfo,
294
+ err
295
+ }, {
296
+ "~LogMeta": "~LogMeta",
297
+ F: __dxlog_file$12,
298
+ L: 272,
299
+ S: this
300
+ });
301
+ try {
302
+ await this._closeProtocol({ abort: true });
303
+ } catch (err) {
304
+ log.catch(err, void 0, {
305
+ "~LogMeta": "~LogMeta",
306
+ F: __dxlog_file$12,
307
+ L: 278,
308
+ S: this
309
+ });
310
+ }
311
+ try {
312
+ await this._closeTransport();
313
+ } catch (err) {
314
+ log.catch(err, void 0, {
315
+ "~LogMeta": "~LogMeta",
316
+ F: __dxlog_file$12,
317
+ L: 285,
318
+ S: this
319
+ });
320
+ }
321
+ try {
322
+ this._callbacks?.onClosed?.(err);
323
+ } catch (err) {
324
+ log.catch(err, void 0, {
325
+ "~LogMeta": "~LogMeta",
326
+ F: __dxlog_file$12,
327
+ L: 291,
328
+ S: this
329
+ });
330
+ }
331
+ this._changeState("ABORTED");
332
+ }
333
+ async close({ error, reason } = {}) {
334
+ log("close", { error }, {
335
+ "~LogMeta": "~LogMeta",
336
+ F: __dxlog_file$12,
337
+ L: 298,
338
+ S: this
339
+ });
340
+ if (!this.closeReason) this.closeReason = reason ?? error?.message;
341
+ else this.closeReason += `; ${reason ?? error?.message}`;
342
+ if (this._state === "CLOSED" || this._state === "ABORTING" || this._state === "ABORTED") {
343
+ log("close ignored: already in progress", {
344
+ state: this._state,
345
+ error
346
+ }, {
347
+ "~LogMeta": "~LogMeta",
348
+ F: __dxlog_file$12,
349
+ L: 309,
350
+ S: this
351
+ });
352
+ return;
353
+ }
354
+ const lastState = this._state;
355
+ this._changeState("CLOSING");
356
+ await this.connectedTimeoutContext.dispose();
357
+ await this._ctx.dispose();
358
+ let abortProtocol = false;
359
+ if (lastState !== "CONNECTED" || error != null) {
360
+ log(`graceful close requested when we were in ${lastState} state? aborting`, void 0, {
361
+ "~LogMeta": "~LogMeta",
362
+ F: __dxlog_file$12,
363
+ L: 320,
364
+ S: this
365
+ });
366
+ abortProtocol = true;
367
+ }
368
+ log("closing...", {
369
+ peerId: this.localInfo,
370
+ abortProtocol,
371
+ error
372
+ }, {
373
+ "~LogMeta": "~LogMeta",
374
+ F: __dxlog_file$12,
375
+ L: 324,
376
+ S: this
377
+ });
378
+ try {
379
+ await this._closeProtocol({ abort: abortProtocol });
380
+ } catch (err) {
381
+ log.catch(err, void 0, {
382
+ "~LogMeta": "~LogMeta",
383
+ F: __dxlog_file$12,
384
+ L: 329,
385
+ S: this
386
+ });
387
+ }
388
+ try {
389
+ await this._closeTransport();
390
+ } catch (err) {
391
+ log.catch(err, void 0, {
392
+ "~LogMeta": "~LogMeta",
393
+ F: __dxlog_file$12,
394
+ L: 335,
395
+ S: this
396
+ });
397
+ }
398
+ log("closed", { peerId: this.localInfo }, {
399
+ "~LogMeta": "~LogMeta",
400
+ F: __dxlog_file$12,
401
+ L: 338,
402
+ S: this
403
+ });
404
+ this._changeState("CLOSED");
405
+ this._callbacks?.onClosed?.(error);
406
+ }
407
+ async _closeProtocol(options) {
408
+ log("closing protocol", options, {
409
+ "~LogMeta": "~LogMeta",
410
+ F: __dxlog_file$12,
411
+ L: 344,
412
+ S: this
413
+ });
414
+ await Promise.race([options?.abort ? this._protocol.abort() : this._protocol.close(), this._protocolClosed.wait()]);
415
+ log("protocol closed", options, {
416
+ "~LogMeta": "~LogMeta",
417
+ F: __dxlog_file$12,
418
+ L: 346,
419
+ S: this
420
+ });
421
+ }
422
+ async _closeTransport() {
423
+ log("closing transport", void 0, {
424
+ "~LogMeta": "~LogMeta",
425
+ F: __dxlog_file$12,
426
+ L: 350,
427
+ S: this
428
+ });
429
+ await Promise.race([this._transport?.close(), this._transportClosed.wait()]);
430
+ log("transport closed", void 0, {
431
+ "~LogMeta": "~LogMeta",
432
+ F: __dxlog_file$12,
433
+ L: 352,
434
+ S: this
435
+ });
436
+ }
437
+ _sendSignal(signal) {
438
+ this._outgoingSignalBuffer.push(signal);
439
+ this._signalSendTask.schedule();
440
+ }
441
+ async _flushSignalBuffer(ctx) {
442
+ if (this._outgoingSignalBuffer.length === 0) return;
443
+ try {
444
+ if (process.env.NODE_ENV !== "test") {
445
+ await cancelWithContext(ctx, sleep(this._signallingDelay));
446
+ this._signallingDelay = Math.min(this._signallingDelay * 2, MAX_SIGNALLING_DELAY);
447
+ }
448
+ const signals = [...this._outgoingSignalBuffer];
449
+ this._outgoingSignalBuffer.length = 0;
450
+ await this._signalMessaging.signal(ctx, {
451
+ author: this.localInfo,
452
+ recipient: this.remoteInfo,
453
+ sessionId: this.sessionId,
454
+ topic: this.topic,
455
+ data: { signalBatch: { signals } }
456
+ });
457
+ } catch (err) {
458
+ if (err instanceof CancelledError || err instanceof ContextDisposedError || err instanceof Error && err.message?.includes("CANCELLED")) return;
459
+ log.info("signal message failed to deliver", { err }, {
460
+ "~LogMeta": "~LogMeta",
461
+ F: __dxlog_file$12,
462
+ L: 392,
463
+ S: this
464
+ });
465
+ await this.close({ error: new ConnectivityError({
466
+ message: "signal message failed to deliver",
467
+ cause: err
468
+ }) });
469
+ }
470
+ }
471
+ /**
472
+ * Receive a signal from the remote peer.
473
+ */
474
+ async signal(_ctx, msg) {
475
+ invariant(msg.sessionId, void 0, {
476
+ "~LogMeta": "~LogMeta",
477
+ F: __dxlog_file$12,
478
+ L: 401,
479
+ S: this,
480
+ A: ["msg.sessionId", ""]
481
+ });
482
+ if (!msg.sessionId.equals(this.sessionId)) {
483
+ log("dropping signal for incorrect session id", void 0, {
484
+ "~LogMeta": "~LogMeta",
485
+ F: __dxlog_file$12,
486
+ L: 403,
487
+ S: this
488
+ });
489
+ return;
490
+ }
491
+ invariant(msg.data.signal || msg.data.signalBatch, void 0, {
492
+ "~LogMeta": "~LogMeta",
493
+ F: __dxlog_file$12,
494
+ L: 406,
495
+ S: this,
496
+ A: ["msg.data.signal || msg.data.signalBatch", ""]
497
+ });
498
+ invariant(msg.author.peerKey === this.remoteInfo.peerKey, void 0, {
499
+ "~LogMeta": "~LogMeta",
500
+ F: __dxlog_file$12,
501
+ L: 407,
502
+ S: this,
503
+ A: ["msg.author.peerKey === this.remoteInfo.peerKey", ""]
504
+ });
505
+ invariant(msg.recipient.peerKey === this.localInfo.peerKey, void 0, {
506
+ "~LogMeta": "~LogMeta",
507
+ F: __dxlog_file$12,
508
+ L: 408,
509
+ S: this,
510
+ A: ["msg.recipient.peerKey === this.localInfo.peerKey", ""]
511
+ });
512
+ const signals = msg.data.signalBatch ? msg.data.signalBatch.signals ?? [] : [msg.data.signal];
513
+ for (const signal of signals) {
514
+ if (!signal) continue;
515
+ if (["CREATED", "INITIAL"].includes(this.state)) {
516
+ log("buffered signal", {
517
+ peerId: this.localInfo,
518
+ remoteId: this.remoteInfo,
519
+ msg: msg.data
520
+ }, {
521
+ "~LogMeta": "~LogMeta",
522
+ F: __dxlog_file$12,
523
+ L: 417,
524
+ S: this
525
+ });
526
+ this._incomingSignalBuffer.push(signal);
527
+ } else {
528
+ invariant(this._transport, "Connection not ready to accept signals.", {
529
+ "~LogMeta": "~LogMeta",
530
+ F: __dxlog_file$12,
531
+ L: 420,
532
+ S: this,
533
+ A: ["this._transport", "'Connection not ready to accept signals.'"]
534
+ });
535
+ log("received signal", {
536
+ peerId: this.localInfo,
537
+ remoteId: this.remoteInfo,
538
+ msg: msg.data
539
+ }, {
540
+ "~LogMeta": "~LogMeta",
541
+ F: __dxlog_file$12,
542
+ L: 421,
543
+ S: this
544
+ });
545
+ await this._transport.onSignal(signal);
546
+ }
547
+ }
548
+ }
549
+ initiate() {
550
+ this._changeState("INITIAL");
551
+ }
552
+ _changeState(state) {
553
+ log("stateChanged", {
554
+ from: this._state,
555
+ to: state,
556
+ peerId: this.localInfo
557
+ }, {
558
+ "~LogMeta": "~LogMeta",
559
+ F: __dxlog_file$12,
560
+ L: 432,
561
+ S: this
562
+ });
563
+ invariant(state !== this._state, "Already in this state.", {
564
+ "~LogMeta": "~LogMeta",
565
+ F: __dxlog_file$12,
566
+ L: 433,
567
+ S: this,
568
+ A: ["state !== this._state", "'Already in this state.'"]
569
+ });
570
+ this._state = state;
571
+ this.stateChanged.emit(state);
572
+ }
573
+ async _emitTransportStats() {
574
+ const stats = await this.transport?.getStats();
575
+ if (stats) this.transportStats.emit(stats);
576
+ }
577
+ };
578
+ __decorate([logInfo], Connection.prototype, "sessionIdString", null);
579
+ __decorate([synchronized], Connection.prototype, "abort", null);
580
+ __decorate([synchronized], Connection.prototype, "close", null);
581
+ //#endregion
582
+ //#region src/signal/swarm-messenger.ts
583
+ var __dxlog_file$11 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/signal/swarm-messenger.ts";
584
+ var SwarmMessage = schema.getCodecForType("dxos.mesh.swarm.SwarmMessage");
585
+ /**
586
+ * Adds offer/answer and signal interfaces.
587
+ */
588
+ var SwarmMessenger = class {
589
+ _sendMessage;
590
+ _onSignal;
591
+ _onOffer;
592
+ _topic;
593
+ _offerRecords = new ComplexMap((key) => key.toHex());
594
+ constructor({ sendMessage, onSignal, onOffer, topic }) {
595
+ this._sendMessage = sendMessage;
596
+ this._onSignal = onSignal;
597
+ this._onOffer = onOffer;
598
+ this._topic = topic;
599
+ }
600
+ async receiveMessage(ctx, { author, recipient, payload }) {
601
+ if (payload.type_url !== "dxos.mesh.swarm.SwarmMessage") return;
602
+ invariant(recipient, "Recipient is required", {
603
+ "~LogMeta": "~LogMeta",
604
+ F: __dxlog_file$11,
605
+ L: 54,
606
+ S: this,
607
+ A: ["recipient", "'Recipient is required'"]
608
+ });
609
+ const message = SwarmMessage.decode(payload.value);
610
+ if (!this._topic.equals(message.topic)) return;
611
+ log("received", {
612
+ from: author,
613
+ to: recipient,
614
+ msg: message
615
+ }, {
616
+ "~LogMeta": "~LogMeta",
617
+ F: __dxlog_file$11,
618
+ L: 62,
619
+ S: this
620
+ });
621
+ if (message.data?.offer) await this._handleOffer(ctx, {
622
+ author,
623
+ recipient,
624
+ message
625
+ });
626
+ else if (message.data?.answer) await this._resolveAnswers(message);
627
+ else if (message.data?.signal) await this._handleSignal(ctx, {
628
+ author,
629
+ recipient,
630
+ message
631
+ });
632
+ else if (message.data?.signalBatch) await this._handleSignal(ctx, {
633
+ author,
634
+ recipient,
635
+ message
636
+ });
637
+ else log.warn("unknown message", { message }, {
638
+ "~LogMeta": "~LogMeta",
639
+ F: __dxlog_file$11,
640
+ L: 73,
641
+ S: this
642
+ });
643
+ }
644
+ async signal(ctx, message) {
645
+ invariant(message.data?.signal || message.data?.signalBatch, "Invalid message", {
646
+ "~LogMeta": "~LogMeta",
647
+ F: __dxlog_file$11,
648
+ L: 78,
649
+ S: this,
650
+ A: ["message.data?.signal || message.data?.signalBatch", "'Invalid message'"]
651
+ });
652
+ await this._sendReliableMessage(ctx, {
653
+ author: message.author,
654
+ recipient: message.recipient,
655
+ message
656
+ });
657
+ }
658
+ async offer(ctx, message) {
659
+ const networkMessage = {
660
+ ...message,
661
+ messageId: PublicKey.random()
662
+ };
663
+ return new Promise((resolve, reject) => {
664
+ this._offerRecords.set(networkMessage.messageId, { resolve });
665
+ this._sendReliableMessage(ctx, {
666
+ author: message.author,
667
+ recipient: message.recipient,
668
+ message: networkMessage
669
+ }).catch((err) => reject(err));
670
+ });
671
+ }
672
+ async _sendReliableMessage(ctx, { author, recipient, message }) {
673
+ const networkMessage = {
674
+ ...message,
675
+ messageId: message.messageId ?? PublicKey.random()
676
+ };
677
+ log("sending", {
678
+ from: author,
679
+ to: recipient,
680
+ msg: networkMessage
681
+ }, {
682
+ "~LogMeta": "~LogMeta",
683
+ F: __dxlog_file$11,
684
+ L: 119,
685
+ S: this
686
+ });
687
+ await this._sendMessage(ctx, {
688
+ author,
689
+ recipient,
690
+ payload: {
691
+ type_url: "dxos.mesh.swarm.SwarmMessage",
692
+ value: SwarmMessage.encode(networkMessage)
693
+ }
694
+ });
695
+ }
696
+ async _resolveAnswers(message) {
697
+ invariant(message.data?.answer?.offerMessageId, "No offerMessageId", {
698
+ "~LogMeta": "~LogMeta",
699
+ F: __dxlog_file$11,
700
+ L: 131,
701
+ S: this,
702
+ A: ["message.data?.answer?.offerMessageId", "'No offerMessageId'"]
703
+ });
704
+ const offerRecord = this._offerRecords.get(message.data.answer.offerMessageId);
705
+ if (offerRecord) {
706
+ this._offerRecords.delete(message.data.answer.offerMessageId);
707
+ invariant(message.data?.answer, "No answer", {
708
+ "~LogMeta": "~LogMeta",
709
+ F: __dxlog_file$11,
710
+ L: 135,
711
+ S: this,
712
+ A: ["message.data?.answer", "'No answer'"]
713
+ });
714
+ log("resolving", { answer: message.data.answer }, {
715
+ "~LogMeta": "~LogMeta",
716
+ F: __dxlog_file$11,
717
+ L: 136,
718
+ S: this
719
+ });
720
+ offerRecord.resolve(message.data.answer);
721
+ }
722
+ }
723
+ async _handleOffer(ctx, { author, recipient, message }) {
724
+ invariant(message.data.offer, "No offer", {
725
+ "~LogMeta": "~LogMeta",
726
+ F: __dxlog_file$11,
727
+ L: 153,
728
+ S: this,
729
+ A: ["message.data.offer", "'No offer'"]
730
+ });
731
+ const offerMessage = {
732
+ author,
733
+ recipient,
734
+ ...message,
735
+ data: { offer: message.data.offer }
736
+ };
737
+ const answer = await this._onOffer(ctx, offerMessage);
738
+ answer.offerMessageId = message.messageId;
739
+ try {
740
+ await this._sendReliableMessage(ctx, {
741
+ author: recipient,
742
+ recipient: author,
743
+ message: {
744
+ topic: message.topic,
745
+ sessionId: message.sessionId,
746
+ data: { answer }
747
+ }
748
+ });
749
+ } catch (err) {
750
+ if (err instanceof TimeoutError) log.info("timeout sending answer to offer", { err }, {
751
+ "~LogMeta": "~LogMeta",
752
+ F: __dxlog_file$11,
753
+ L: 174,
754
+ S: this
755
+ });
756
+ else log.info("error sending answer to offer", { err }, {
757
+ "~LogMeta": "~LogMeta",
758
+ F: __dxlog_file$11,
759
+ L: 176,
760
+ S: this
761
+ });
762
+ }
763
+ }
764
+ async _handleSignal(ctx, { author, recipient, message }) {
765
+ invariant(message.messageId, void 0, {
766
+ "~LogMeta": "~LogMeta",
767
+ F: __dxlog_file$11,
768
+ L: 193,
769
+ S: this,
770
+ A: ["message.messageId", ""]
771
+ });
772
+ invariant(message.data.signal || message.data.signalBatch, "Invalid message", {
773
+ "~LogMeta": "~LogMeta",
774
+ F: __dxlog_file$11,
775
+ L: 194,
776
+ S: this,
777
+ A: ["message.data.signal || message.data.signalBatch", "'Invalid message'"]
778
+ });
779
+ const signalMessage = {
780
+ author,
781
+ recipient,
782
+ ...message,
783
+ data: {
784
+ signal: message.data.signal,
785
+ signalBatch: message.data.signalBatch
786
+ }
787
+ };
788
+ await this._onSignal(ctx, signalMessage);
789
+ }
790
+ };
791
+ //#endregion
792
+ //#region src/swarm/peer.ts
793
+ var __dxlog_file$10 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/swarm/peer.ts";
794
+ var ConnectionDisplacedError = class extends SystemError {
795
+ constructor() {
796
+ super({ message: "Connection displaced by remote initiator." });
797
+ }
798
+ };
799
+ /**
800
+ * Minimum time the connection needs to be open to not incur a cool-down timer for this peer after the connection closes.
801
+ */
802
+ var CONNECTION_COUNTS_STABLE_AFTER = 5e3;
803
+ /**
804
+ * State of remote peer during the lifetime of a swarm connection.
805
+ * Can open and close multiple connections to the remote peer.
806
+ */
807
+ var Peer = class {
808
+ remoteInfo;
809
+ topic;
810
+ localInfo;
811
+ _signalMessaging;
812
+ _protocolProvider;
813
+ _transportFactory;
814
+ _connectionLimiter;
815
+ _callbacks;
816
+ /**
817
+ * Will be available to connect after this time.
818
+ */
819
+ _availableAfter = 0;
820
+ availableToConnect = true;
821
+ _lastConnectionTime;
822
+ _ctx = new Context(void 0, {
823
+ "~LogMeta": "~LogMeta",
824
+ F: __dxlog_file$10,
825
+ L: 80
826
+ });
827
+ _connectionCtx;
828
+ connection;
829
+ /**
830
+ * Whether the peer is currently advertizing itself on the signal-network.
831
+ */
832
+ advertizing = false;
833
+ initiating = false;
834
+ connectionDisplaced = new Event();
835
+ constructor(remoteInfo, topic, localInfo, _signalMessaging, _protocolProvider, _transportFactory, _connectionLimiter, _callbacks) {
836
+ this.remoteInfo = remoteInfo;
837
+ this.topic = topic;
838
+ this.localInfo = localInfo;
839
+ this._signalMessaging = _signalMessaging;
840
+ this._protocolProvider = _protocolProvider;
841
+ this._transportFactory = _transportFactory;
842
+ this._connectionLimiter = _connectionLimiter;
843
+ this._callbacks = _callbacks;
844
+ }
845
+ /**
846
+ * Respond to remote offer.
847
+ */
848
+ async onOffer(_ctx, message) {
849
+ const remote = message.author;
850
+ if (this.connection && ![
851
+ ConnectionState$2.CREATED,
852
+ ConnectionState$2.INITIAL,
853
+ ConnectionState$2.CONNECTING
854
+ ].includes(this.connection.state)) {
855
+ log.info(`received offer when connection already in ${this.connection.state} state`, void 0, {
856
+ "~LogMeta": "~LogMeta",
857
+ F: __dxlog_file$10,
858
+ L: 115,
859
+ S: this
860
+ });
861
+ return { accept: false };
862
+ }
863
+ if (this.connection || this.initiating) if (remote.peerKey < this.localInfo.peerKey) {
864
+ log("close local connection", {
865
+ localPeer: this.localInfo,
866
+ topic: this.topic,
867
+ remotePeer: this.remoteInfo,
868
+ sessionId: this.connection?.sessionId
869
+ }, {
870
+ "~LogMeta": "~LogMeta",
871
+ F: __dxlog_file$10,
872
+ L: 124,
873
+ S: this
874
+ });
875
+ if (this.connection) await this.closeConnection(new ConnectionDisplacedError());
876
+ } else return { accept: false };
877
+ if (await this._callbacks.onOffer(remote)) {
878
+ if (!this.connection) {
879
+ invariant(message.sessionId, void 0, {
880
+ "~LogMeta": "~LogMeta",
881
+ F: __dxlog_file$10,
882
+ L: 144,
883
+ S: this,
884
+ A: ["message.sessionId", ""]
885
+ });
886
+ const connection = this._createConnection(false, message.sessionId);
887
+ try {
888
+ await this._connectionLimiter.connecting(message.sessionId);
889
+ connection.initiate();
890
+ await connection.openConnection();
891
+ } catch (err) {
892
+ if (!(err instanceof CancelledError)) log.info("connection error", {
893
+ topic: this.topic,
894
+ peerId: this.localInfo,
895
+ remoteId: this.remoteInfo,
896
+ err
897
+ }, {
898
+ "~LogMeta": "~LogMeta",
899
+ F: __dxlog_file$10,
900
+ L: 154,
901
+ S: this
902
+ });
903
+ await this.closeConnection(err);
904
+ }
905
+ return { accept: true };
906
+ }
907
+ }
908
+ return { accept: false };
909
+ }
910
+ /**
911
+ * Initiate a connection to the remote peer.
912
+ */
913
+ async initiateConnection(ctx) {
914
+ invariant(!this.initiating, "Initiation in progress.", {
915
+ "~LogMeta": "~LogMeta",
916
+ F: __dxlog_file$10,
917
+ L: 172,
918
+ S: this,
919
+ A: ["!this.initiating", "'Initiation in progress.'"]
920
+ });
921
+ invariant(!this.connection, "Already connected.", {
922
+ "~LogMeta": "~LogMeta",
923
+ F: __dxlog_file$10,
924
+ L: 173,
925
+ S: this,
926
+ A: ["!this.connection", "'Already connected.'"]
927
+ });
928
+ const sessionId = PublicKey.random();
929
+ log("initiating...", {
930
+ local: this.localInfo,
931
+ topic: this.topic,
932
+ remote: this.remoteInfo,
933
+ sessionId
934
+ }, {
935
+ "~LogMeta": "~LogMeta",
936
+ F: __dxlog_file$10,
937
+ L: 175,
938
+ S: this
939
+ });
940
+ const connection = this._createConnection(true, sessionId);
941
+ this.initiating = true;
942
+ let answer;
943
+ try {
944
+ await this._connectionLimiter.connecting(sessionId);
945
+ connection.initiate();
946
+ answer = await this._signalMessaging.offer(ctx, {
947
+ author: this.localInfo,
948
+ recipient: this.remoteInfo,
949
+ sessionId,
950
+ topic: this.topic,
951
+ data: { offer: {} }
952
+ });
953
+ log("received", {
954
+ answer,
955
+ topic: this.topic,
956
+ local: this.localInfo,
957
+ remote: this.remoteInfo
958
+ }, {
959
+ "~LogMeta": "~LogMeta",
960
+ F: __dxlog_file$10,
961
+ L: 192,
962
+ S: this
963
+ });
964
+ if (connection.state !== ConnectionState$2.INITIAL) {
965
+ log("ignoring response", void 0, {
966
+ "~LogMeta": "~LogMeta",
967
+ F: __dxlog_file$10,
968
+ L: 194,
969
+ S: this
970
+ });
971
+ return;
972
+ }
973
+ } catch (err) {
974
+ log("initiation error: send offer", {
975
+ err,
976
+ topic: this.topic,
977
+ local: this.localInfo,
978
+ remote: this.remoteInfo
979
+ }, {
980
+ "~LogMeta": "~LogMeta",
981
+ F: __dxlog_file$10,
982
+ L: 198,
983
+ S: this
984
+ });
985
+ await connection.abort(err);
986
+ throw err;
987
+ } finally {
988
+ this.initiating = false;
989
+ }
990
+ try {
991
+ if (!answer.accept) {
992
+ this._callbacks.onRejected();
993
+ return;
994
+ }
995
+ } catch (err) {
996
+ log("initiation error: accept answer", {
997
+ err,
998
+ topic: this.topic,
999
+ local: this.localInfo,
1000
+ remote: this.remoteInfo
1001
+ }, {
1002
+ "~LogMeta": "~LogMeta",
1003
+ F: __dxlog_file$10,
1004
+ L: 211,
1005
+ S: this
1006
+ });
1007
+ await connection.abort(err);
1008
+ throw err;
1009
+ } finally {
1010
+ this.initiating = false;
1011
+ }
1012
+ try {
1013
+ log("opening connection as initiator", void 0, {
1014
+ "~LogMeta": "~LogMeta",
1015
+ F: __dxlog_file$10,
1016
+ L: 224,
1017
+ S: this
1018
+ });
1019
+ await connection.openConnection();
1020
+ this._callbacks.onAccepted();
1021
+ } catch (err) {
1022
+ log("initiation error: open connection", {
1023
+ err,
1024
+ topic: this.topic,
1025
+ local: this.localInfo,
1026
+ remote: this.remoteInfo
1027
+ }, {
1028
+ "~LogMeta": "~LogMeta",
1029
+ F: __dxlog_file$10,
1030
+ L: 228,
1031
+ S: this
1032
+ });
1033
+ log.warn("closing connection due to unhandled error on openConnection", { err }, {
1034
+ "~LogMeta": "~LogMeta",
1035
+ F: __dxlog_file$10,
1036
+ L: 235,
1037
+ S: this
1038
+ });
1039
+ await this.closeConnection(err);
1040
+ throw err;
1041
+ } finally {
1042
+ this.initiating = false;
1043
+ }
1044
+ }
1045
+ /**
1046
+ * Create new connection.
1047
+ * Either we're initiating a connection or creating one in response to an offer from the other peer.
1048
+ */
1049
+ _createConnection(initiator, sessionId) {
1050
+ log("creating connection", {
1051
+ topic: this.topic,
1052
+ peerId: this.localInfo,
1053
+ remoteId: this.remoteInfo,
1054
+ initiator,
1055
+ sessionId
1056
+ }, {
1057
+ "~LogMeta": "~LogMeta",
1058
+ F: __dxlog_file$10,
1059
+ L: 249,
1060
+ S: this
1061
+ });
1062
+ invariant(!this.connection, "Already connected.", {
1063
+ "~LogMeta": "~LogMeta",
1064
+ F: __dxlog_file$10,
1065
+ L: 256,
1066
+ S: this,
1067
+ A: ["!this.connection", "'Already connected.'"]
1068
+ });
1069
+ const connection = new Connection(this.topic, this.localInfo, this.remoteInfo, sessionId, initiator, this._signalMessaging, this._protocolProvider({
1070
+ initiator,
1071
+ localPeerId: PublicKey.from(this.localInfo.peerKey),
1072
+ remotePeerId: PublicKey.from(this.remoteInfo.peerKey),
1073
+ topic: this.topic
1074
+ }), this._transportFactory, {
1075
+ onConnected: () => {
1076
+ this.availableToConnect = true;
1077
+ this._lastConnectionTime = Date.now();
1078
+ this._callbacks.onConnected();
1079
+ this._connectionLimiter.doneConnecting(sessionId);
1080
+ log.trace("dxos.mesh.connection.connected", {
1081
+ topic: this.topic,
1082
+ localPeerId: this.localInfo,
1083
+ remotePeerId: this.remoteInfo,
1084
+ sessionId,
1085
+ initiator
1086
+ }, {
1087
+ "~LogMeta": "~LogMeta",
1088
+ F: __dxlog_file$10,
1089
+ L: 280,
1090
+ S: this
1091
+ });
1092
+ },
1093
+ onClosed: (err) => {
1094
+ const logMeta = {
1095
+ topic: this.topic,
1096
+ peerId: this.localInfo,
1097
+ remoteId: this.remoteInfo,
1098
+ initiator
1099
+ };
1100
+ log("connection closed", logMeta, {
1101
+ "~LogMeta": "~LogMeta",
1102
+ F: __dxlog_file$10,
1103
+ L: 290,
1104
+ S: this
1105
+ });
1106
+ this._connectionLimiter.doneConnecting(sessionId);
1107
+ invariant(this.connection === connection, "Connection mismatch (race condition).", {
1108
+ "~LogMeta": "~LogMeta",
1109
+ F: __dxlog_file$10,
1110
+ L: 295,
1111
+ S: this,
1112
+ A: ["this.connection === connection", "'Connection mismatch (race condition).'"]
1113
+ });
1114
+ log.trace("dxos.mesh.connection.closed", {
1115
+ topic: this.topic,
1116
+ localPeerId: this.localInfo,
1117
+ remotePeerId: this.remoteInfo,
1118
+ sessionId,
1119
+ initiator
1120
+ }, {
1121
+ "~LogMeta": "~LogMeta",
1122
+ F: __dxlog_file$10,
1123
+ L: 297,
1124
+ S: this
1125
+ });
1126
+ if (err instanceof ConnectionDisplacedError) this.connectionDisplaced.emit(this.connection);
1127
+ else {
1128
+ if (this._lastConnectionTime && this._lastConnectionTime + CONNECTION_COUNTS_STABLE_AFTER < Date.now()) this._availableAfter = 0;
1129
+ else {
1130
+ this.availableToConnect = false;
1131
+ this._availableAfter = increaseInterval(this._availableAfter);
1132
+ }
1133
+ this._callbacks.onDisconnected();
1134
+ scheduleTask(this._connectionCtx, () => {
1135
+ log("peer became available", logMeta, {
1136
+ "~LogMeta": "~LogMeta",
1137
+ F: __dxlog_file$10,
1138
+ L: 321,
1139
+ S: this
1140
+ });
1141
+ this.availableToConnect = true;
1142
+ this._callbacks.onPeerAvailable();
1143
+ }, this._availableAfter);
1144
+ }
1145
+ this.connection = void 0;
1146
+ }
1147
+ });
1148
+ this._callbacks.onInitiated(connection);
1149
+ this._connectionCtx?.dispose();
1150
+ this._connectionCtx = this._ctx.derive();
1151
+ connection.errors.handle((err) => {
1152
+ log.info("connection error, closing", {
1153
+ topic: this.topic,
1154
+ peerId: this.localInfo,
1155
+ remoteId: this.remoteInfo,
1156
+ initiator,
1157
+ err
1158
+ }, {
1159
+ "~LogMeta": "~LogMeta",
1160
+ F: __dxlog_file$10,
1161
+ L: 339,
1162
+ S: this
1163
+ });
1164
+ log.trace("dxos.mesh.connection.error", {
1165
+ topic: this.topic,
1166
+ localPeerId: this.localInfo,
1167
+ remotePeerId: this.remoteInfo,
1168
+ sessionId,
1169
+ initiator,
1170
+ err
1171
+ }, {
1172
+ "~LogMeta": "~LogMeta",
1173
+ F: __dxlog_file$10,
1174
+ L: 346,
1175
+ S: this
1176
+ });
1177
+ this.closeConnection(err);
1178
+ });
1179
+ this.connection = connection;
1180
+ return connection;
1181
+ }
1182
+ async closeConnection(err) {
1183
+ if (!this.connection) return;
1184
+ const connection = this.connection;
1185
+ log("closing...", {
1186
+ peerId: this.remoteInfo,
1187
+ sessionId: connection.sessionId
1188
+ }, {
1189
+ "~LogMeta": "~LogMeta",
1190
+ F: __dxlog_file$10,
1191
+ L: 371,
1192
+ S: this
1193
+ });
1194
+ await connection.close({ error: err });
1195
+ log("closed", {
1196
+ peerId: this.remoteInfo,
1197
+ sessionId: connection.sessionId
1198
+ }, {
1199
+ "~LogMeta": "~LogMeta",
1200
+ F: __dxlog_file$10,
1201
+ L: 377,
1202
+ S: this
1203
+ });
1204
+ }
1205
+ async onSignal(ctx, message) {
1206
+ if (!this.connection) {
1207
+ log("dropping signal message for non-existent connection", { message }, {
1208
+ "~LogMeta": "~LogMeta",
1209
+ F: __dxlog_file$10,
1210
+ L: 382,
1211
+ S: this
1212
+ });
1213
+ return;
1214
+ }
1215
+ await this.connection.signal(ctx, message);
1216
+ }
1217
+ async safeDestroy(reason) {
1218
+ await this._ctx.dispose();
1219
+ log("Destroying peer", {
1220
+ peerId: this.remoteInfo,
1221
+ topic: this.topic
1222
+ }, {
1223
+ "~LogMeta": "~LogMeta",
1224
+ F: __dxlog_file$10,
1225
+ L: 392,
1226
+ S: this
1227
+ });
1228
+ await this?.connection?.close({ reason });
1229
+ }
1230
+ };
1231
+ __decorate([synchronized], Peer.prototype, "safeDestroy", null);
1232
+ var increaseInterval = (interval) => {
1233
+ if (interval === 0) return 50;
1234
+ else if (interval < 500) return 500;
1235
+ else if (interval < 1e3) return 1e3;
1236
+ else if (interval < 5e3) return 5e3;
1237
+ return 1e4;
1238
+ };
1239
+ //#endregion
1240
+ //#region src/swarm/swarm.ts
1241
+ var __dxlog_file$9 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/swarm/swarm.ts";
1242
+ var INITIATION_DELAY = 100;
1243
+ var getClassName = (obj) => Object.getPrototypeOf(obj).constructor.name;
1244
+ /**
1245
+ * A single peer's view of the swarm.
1246
+ * Manages a set of peers subscribed on the same topic.
1247
+ * Routes signal events and maintains swarm topology.
1248
+ */
1249
+ var Swarm = class {
1250
+ _topic;
1251
+ _ownPeer;
1252
+ _topology;
1253
+ _protocolProvider;
1254
+ _messenger;
1255
+ _transportFactory;
1256
+ _label;
1257
+ _connectionLimiter;
1258
+ _initiationDelay;
1259
+ _swarmMessenger;
1260
+ _ctx = new Context(void 0, {
1261
+ "~LogMeta": "~LogMeta",
1262
+ F: __dxlog_file$9,
1263
+ L: 37
1264
+ });
1265
+ _listeningHandle = void 0;
1266
+ /**
1267
+ * PeerInfo -> Peer.
1268
+ * @internal
1269
+ */
1270
+ _peers = new ComplexMap(PeerInfoHash);
1271
+ /**
1272
+ * Unique id of the swarm, local to the current peer, generated when swarm is joined.
1273
+ */
1274
+ _instanceId = PublicKey.random().toHex();
1275
+ /**
1276
+ * New connection to a peer is started.
1277
+ * @internal
1278
+ */
1279
+ connectionAdded = new Event();
1280
+ /**
1281
+ * Connection to a peer is dropped.
1282
+ * @internal
1283
+ */
1284
+ disconnected = new Event();
1285
+ /**
1286
+ * Connection is established to a new peer.
1287
+ * @internal
1288
+ */
1289
+ connected = new Event();
1290
+ errors = new ErrorStream();
1291
+ constructor(_topic, _ownPeer, _topology, _protocolProvider, _messenger, _transportFactory, _label, _connectionLimiter, _initiationDelay = INITIATION_DELAY) {
1292
+ this._topic = _topic;
1293
+ this._ownPeer = _ownPeer;
1294
+ this._topology = _topology;
1295
+ this._protocolProvider = _protocolProvider;
1296
+ this._messenger = _messenger;
1297
+ this._transportFactory = _transportFactory;
1298
+ this._label = _label;
1299
+ this._connectionLimiter = _connectionLimiter;
1300
+ this._initiationDelay = _initiationDelay;
1301
+ log("creating swarm", {
1302
+ topic: this._topic.toHex(),
1303
+ peer: this._ownPeer
1304
+ }, {
1305
+ "~LogMeta": "~LogMeta",
1306
+ F: __dxlog_file$9,
1307
+ L: 87,
1308
+ S: this
1309
+ });
1310
+ _topology.init(this._getSwarmController());
1311
+ this._swarmMessenger = new SwarmMessenger({
1312
+ sendMessage: async (ctx, msg) => await this._messenger.sendMessage(ctx, msg),
1313
+ onSignal: async (ctx, msg) => await this.onSignal(ctx, msg),
1314
+ onOffer: async (ctx, msg) => await this.onOffer(ctx, msg),
1315
+ topic: this._topic
1316
+ });
1317
+ }
1318
+ get connections() {
1319
+ return Array.from(this._peers.values()).map((peer) => peer.connection).filter(isNonNullable);
1320
+ }
1321
+ get ownPeerId() {
1322
+ return PublicKey.from(this._ownPeer.peerKey);
1323
+ }
1324
+ get ownPeer() {
1325
+ return this._ownPeer;
1326
+ }
1327
+ /**
1328
+ * Custom label assigned to this swarm. Used in devtools to display human-readable names for swarms.
1329
+ */
1330
+ get label() {
1331
+ return this._label;
1332
+ }
1333
+ get topic() {
1334
+ return this._topic;
1335
+ }
1336
+ async open() {
1337
+ invariant(!this._listeningHandle, void 0, {
1338
+ "~LogMeta": "~LogMeta",
1339
+ F: __dxlog_file$9,
1340
+ L: 126,
1341
+ S: this,
1342
+ A: ["!this._listeningHandle", ""]
1343
+ });
1344
+ this._listeningHandle = await this._messenger.listen({
1345
+ peer: this._ownPeer,
1346
+ payloadType: "dxos.mesh.swarm.SwarmMessage",
1347
+ onMessage: async (message) => {
1348
+ await this._swarmMessenger.receiveMessage(this._ctx, message).catch((err) => log.info("Error while receiving message", { err }, {
1349
+ "~LogMeta": "~LogMeta",
1350
+ F: __dxlog_file$9,
1351
+ L: 134,
1352
+ S: this
1353
+ }));
1354
+ }
1355
+ });
1356
+ }
1357
+ async destroy() {
1358
+ log("destroying...", void 0, {
1359
+ "~LogMeta": "~LogMeta",
1360
+ F: __dxlog_file$9,
1361
+ L: 140,
1362
+ S: this
1363
+ });
1364
+ await this._listeningHandle?.unsubscribe();
1365
+ this._listeningHandle = void 0;
1366
+ await this._ctx.dispose();
1367
+ await this._topology.destroy();
1368
+ await Promise.all(Array.from(this._peers.keys()).map((key) => this._destroyPeer(key, "swarm destroyed")));
1369
+ log("destroyed", void 0, {
1370
+ "~LogMeta": "~LogMeta",
1371
+ F: __dxlog_file$9,
1372
+ L: 147,
1373
+ S: this
1374
+ });
1375
+ }
1376
+ async setTopology(topology) {
1377
+ invariant(!this._ctx.disposed, "Swarm is offline", {
1378
+ "~LogMeta": "~LogMeta",
1379
+ F: __dxlog_file$9,
1380
+ L: 151,
1381
+ S: this,
1382
+ A: ["!this._ctx.disposed", "'Swarm is offline'"]
1383
+ });
1384
+ if (topology === this._topology) return;
1385
+ log("setting topology", {
1386
+ previous: getClassName(this._topology),
1387
+ topology: getClassName(topology)
1388
+ }, {
1389
+ "~LogMeta": "~LogMeta",
1390
+ F: __dxlog_file$9,
1391
+ L: 155,
1392
+ S: this
1393
+ });
1394
+ await this._topology.destroy();
1395
+ this._topology = topology;
1396
+ this._topology.init(this._getSwarmController());
1397
+ this._topology.update();
1398
+ }
1399
+ async onSwarmEvent(swarmEvent) {
1400
+ log("swarm event", { swarmEvent }, {
1401
+ "~LogMeta": "~LogMeta",
1402
+ F: __dxlog_file$9,
1403
+ L: 168,
1404
+ S: this
1405
+ });
1406
+ if (this._ctx.disposed) {
1407
+ log("swarm event ignored for disposed swarm", void 0, {
1408
+ "~LogMeta": "~LogMeta",
1409
+ F: __dxlog_file$9,
1410
+ L: 171,
1411
+ S: this
1412
+ });
1413
+ return;
1414
+ }
1415
+ if (swarmEvent.peerAvailable) {
1416
+ const peerId = swarmEvent.peerAvailable.peer.peerKey;
1417
+ if (peerId !== this._ownPeer.peerKey) {
1418
+ log("new peer", { peerId }, {
1419
+ "~LogMeta": "~LogMeta",
1420
+ F: __dxlog_file$9,
1421
+ L: 178,
1422
+ S: this
1423
+ });
1424
+ const peer = this._getOrCreatePeer(swarmEvent.peerAvailable.peer);
1425
+ peer.advertizing = true;
1426
+ }
1427
+ } else if (swarmEvent.peerLeft) {
1428
+ const peer = this._peers.get(swarmEvent.peerLeft.peer);
1429
+ if (peer) {
1430
+ peer.advertizing = false;
1431
+ if (this._isConnectionEstablishmentInProgress(peer)) {
1432
+ log(`destroying peer, state: ${peer.connection?.state}`, void 0, {
1433
+ "~LogMeta": "~LogMeta",
1434
+ F: __dxlog_file$9,
1435
+ L: 189,
1436
+ S: this
1437
+ });
1438
+ this._destroyPeer(swarmEvent.peerLeft.peer, "peer left").catch((err) => log.catch(err, void 0, {
1439
+ "~LogMeta": "~LogMeta",
1440
+ F: __dxlog_file$9,
1441
+ L: 190,
1442
+ S: this
1443
+ }));
1444
+ }
1445
+ } else log("received peerLeft but no peer found", { peer: swarmEvent.peerLeft.peer.peerKey }, {
1446
+ "~LogMeta": "~LogMeta",
1447
+ F: __dxlog_file$9,
1448
+ L: 193,
1449
+ S: this
1450
+ });
1451
+ }
1452
+ this._topology.update();
1453
+ }
1454
+ async onOffer(ctx, message) {
1455
+ log("offer", { message }, {
1456
+ "~LogMeta": "~LogMeta",
1457
+ F: __dxlog_file$9,
1458
+ L: 202,
1459
+ S: this
1460
+ });
1461
+ if (this._ctx.disposed) {
1462
+ log("ignored for disposed swarm", void 0, {
1463
+ "~LogMeta": "~LogMeta",
1464
+ F: __dxlog_file$9,
1465
+ L: 204,
1466
+ S: this
1467
+ });
1468
+ return { accept: false };
1469
+ }
1470
+ invariant(message.author, void 0, {
1471
+ "~LogMeta": "~LogMeta",
1472
+ F: __dxlog_file$9,
1473
+ L: 209,
1474
+ S: this,
1475
+ A: ["message.author", ""]
1476
+ });
1477
+ if (message.recipient.peerKey !== this._ownPeer.peerKey) {
1478
+ log("rejecting offer with incorrect peerId", { message }, {
1479
+ "~LogMeta": "~LogMeta",
1480
+ F: __dxlog_file$9,
1481
+ L: 211,
1482
+ S: this
1483
+ });
1484
+ return { accept: false };
1485
+ }
1486
+ if (!message.topic?.equals(this._topic)) {
1487
+ log("rejecting offer with incorrect topic", { message }, {
1488
+ "~LogMeta": "~LogMeta",
1489
+ F: __dxlog_file$9,
1490
+ L: 215,
1491
+ S: this
1492
+ });
1493
+ return { accept: false };
1494
+ }
1495
+ const answer = await this._getOfferSenderPeer(message.author).onOffer(ctx, message);
1496
+ this._topology.update();
1497
+ return answer;
1498
+ }
1499
+ _getOfferSenderPeer(senderInfo) {
1500
+ const peer = this._getOrCreatePeer(senderInfo);
1501
+ const connectionState = peer.connection?.state;
1502
+ if (connectionState === ConnectionState$2.CLOSING || connectionState === ConnectionState$2.ABORTING) {
1503
+ this._peers.delete(peer.remoteInfo);
1504
+ this.disconnected.emit(peer.remoteInfo);
1505
+ return this._getOrCreatePeer(peer.remoteInfo);
1506
+ }
1507
+ return peer;
1508
+ }
1509
+ async onSignal(ctx, message) {
1510
+ log("signal", { message }, {
1511
+ "~LogMeta": "~LogMeta",
1512
+ F: __dxlog_file$9,
1513
+ L: 240,
1514
+ S: this
1515
+ });
1516
+ if (this._ctx.disposed) {
1517
+ log.info("ignored for offline swarm", void 0, {
1518
+ "~LogMeta": "~LogMeta",
1519
+ F: __dxlog_file$9,
1520
+ L: 242,
1521
+ S: this
1522
+ });
1523
+ return;
1524
+ }
1525
+ invariant(message.recipient.peerKey === this._ownPeer.peerKey, `Invalid signal peer id expected=${this.ownPeerId}, actual=${message.recipient}`, {
1526
+ "~LogMeta": "~LogMeta",
1527
+ F: __dxlog_file$9,
1528
+ L: 245,
1529
+ S: this,
1530
+ A: ["message.recipient.peerKey === this._ownPeer.peerKey", "`Invalid signal peer id expected=${this.ownPeerId}, actual=${message.recipient}`"]
1531
+ });
1532
+ invariant(message.topic?.equals(this._topic), void 0, {
1533
+ "~LogMeta": "~LogMeta",
1534
+ F: __dxlog_file$9,
1535
+ L: 249,
1536
+ S: this,
1537
+ A: ["message.topic?.equals(this._topic)", ""]
1538
+ });
1539
+ invariant(message.author, void 0, {
1540
+ "~LogMeta": "~LogMeta",
1541
+ F: __dxlog_file$9,
1542
+ L: 250,
1543
+ S: this,
1544
+ A: ["message.author", ""]
1545
+ });
1546
+ await this._getOrCreatePeer(message.author).onSignal(ctx, message);
1547
+ }
1548
+ async goOffline() {
1549
+ await this._ctx.dispose();
1550
+ await Promise.all([...this._peers.keys()].map((peerId) => this._destroyPeer(peerId, "goOffline")));
1551
+ }
1552
+ async goOnline() {
1553
+ this._ctx = new Context(void 0, {
1554
+ "~LogMeta": "~LogMeta",
1555
+ F: __dxlog_file$9,
1556
+ L: 266
1557
+ });
1558
+ }
1559
+ _getOrCreatePeer(peerInfo) {
1560
+ invariant(peerInfo.peerKey, "PeerInfo.peerKey is required", {
1561
+ "~LogMeta": "~LogMeta",
1562
+ F: __dxlog_file$9,
1563
+ L: 270,
1564
+ S: this,
1565
+ A: ["peerInfo.peerKey", "'PeerInfo.peerKey is required'"]
1566
+ });
1567
+ let peer = this._peers.get(peerInfo);
1568
+ if (!peer) {
1569
+ peer = new Peer(peerInfo, this._topic, this._ownPeer, this._swarmMessenger, this._protocolProvider, this._transportFactory, this._connectionLimiter, {
1570
+ onInitiated: (connection) => {
1571
+ this.connectionAdded.emit(connection);
1572
+ },
1573
+ onConnected: () => {
1574
+ this.connected.emit(peerInfo);
1575
+ },
1576
+ onDisconnected: async () => {
1577
+ if (this._isUnregistered(peer)) {
1578
+ log.verbose("ignored onDisconnected for unregistered peer", void 0, {
1579
+ "~LogMeta": "~LogMeta",
1580
+ F: __dxlog_file$9,
1581
+ L: 290,
1582
+ S: this
1583
+ });
1584
+ return;
1585
+ }
1586
+ if (!peer.advertizing) await this._destroyPeer(peerInfo, "peer disconnected");
1587
+ this.disconnected.emit(peerInfo);
1588
+ this._topology.update();
1589
+ },
1590
+ onRejected: () => {
1591
+ if (!this._isUnregistered(peer)) {
1592
+ log("peer rejected connection", { peerInfo }, {
1593
+ "~LogMeta": "~LogMeta",
1594
+ F: __dxlog_file$9,
1595
+ L: 304,
1596
+ S: this
1597
+ });
1598
+ this._destroyPeer(peerInfo, "peer rejected connection");
1599
+ }
1600
+ },
1601
+ onAccepted: () => {
1602
+ this._topology.update();
1603
+ },
1604
+ onOffer: (remoteId) => {
1605
+ return this._topology.onOffer(PublicKey.from(remoteId.peerKey));
1606
+ },
1607
+ onPeerAvailable: () => {
1608
+ this._topology.update();
1609
+ }
1610
+ });
1611
+ this._peers.set(peerInfo, peer);
1612
+ }
1613
+ return peer;
1614
+ }
1615
+ async _destroyPeer(peerInfo, reason) {
1616
+ log("destroy peer", {
1617
+ peerKey: peerInfo.peerKey,
1618
+ reason
1619
+ }, {
1620
+ "~LogMeta": "~LogMeta",
1621
+ F: __dxlog_file$9,
1622
+ L: 326,
1623
+ S: this
1624
+ });
1625
+ const peer = this._peers.get(peerInfo);
1626
+ invariant(peer, void 0, {
1627
+ "~LogMeta": "~LogMeta",
1628
+ F: __dxlog_file$9,
1629
+ L: 328,
1630
+ S: this,
1631
+ A: ["peer", ""]
1632
+ });
1633
+ this._peers.delete(peerInfo);
1634
+ await peer.safeDestroy(reason);
1635
+ }
1636
+ _getSwarmController() {
1637
+ return {
1638
+ getState: () => ({
1639
+ ownPeerId: PublicKey.from(this._ownPeer.peerKey),
1640
+ connected: Array.from(this._peers.entries()).filter(([_, peer]) => peer.connection).map(([info]) => PublicKey.from(info.peerKey)),
1641
+ candidates: Array.from(this._peers.entries()).filter(([_, peer]) => !peer.connection && peer.advertizing && peer.availableToConnect).map(([info]) => PublicKey.from(info.peerKey)),
1642
+ allPeers: Array.from(this._peers.keys()).map((info) => PublicKey.from(info.peerKey))
1643
+ }),
1644
+ connect: (peer) => {
1645
+ if (this._ctx.disposed) return;
1646
+ scheduleTask(this._ctx, async () => {
1647
+ try {
1648
+ await this._initiateConnection({ peerKey: peer.toHex() });
1649
+ } catch (err) {
1650
+ log("initiation error", err, {
1651
+ "~LogMeta": "~LogMeta",
1652
+ F: __dxlog_file$9,
1653
+ L: 355,
1654
+ S: this
1655
+ });
1656
+ }
1657
+ });
1658
+ },
1659
+ disconnect: async (peer) => {
1660
+ if (this._ctx.disposed) return;
1661
+ scheduleTask(this._ctx, async () => {
1662
+ await this._closeConnection({ peerKey: peer.toHex() });
1663
+ this._topology.update();
1664
+ });
1665
+ }
1666
+ };
1667
+ }
1668
+ /**
1669
+ * Creates a connection then sends message over signal network.
1670
+ */
1671
+ async _initiateConnection(remotePeer) {
1672
+ const ctx = this._ctx;
1673
+ const peer = this._getOrCreatePeer(remotePeer);
1674
+ if (remotePeer.peerKey < this._ownPeer.peerKey) {
1675
+ log("initiation delay", { remotePeer }, {
1676
+ "~LogMeta": "~LogMeta",
1677
+ F: __dxlog_file$9,
1678
+ L: 383,
1679
+ S: this
1680
+ });
1681
+ await sleep(this._initiationDelay);
1682
+ }
1683
+ if (ctx.disposed) return;
1684
+ if (this._isUnregistered(peer)) throw new Error("Peer left during initiation delay");
1685
+ if (peer.connection) return;
1686
+ log("initiating connection...", { remotePeer }, {
1687
+ "~LogMeta": "~LogMeta",
1688
+ F: __dxlog_file$9,
1689
+ L: 399,
1690
+ S: this
1691
+ });
1692
+ await peer.initiateConnection(ctx);
1693
+ this._topology.update();
1694
+ log("initiated", { remotePeer }, {
1695
+ "~LogMeta": "~LogMeta",
1696
+ F: __dxlog_file$9,
1697
+ L: 402,
1698
+ S: this
1699
+ });
1700
+ }
1701
+ async _closeConnection(peerInfo) {
1702
+ const peer = this._peers.get(peerInfo);
1703
+ if (!peer) return;
1704
+ await peer.closeConnection();
1705
+ }
1706
+ _isConnectionEstablishmentInProgress(peer) {
1707
+ if (!peer.connection) return true;
1708
+ return [
1709
+ ConnectionState$2.INITIAL,
1710
+ ConnectionState$2.CREATED,
1711
+ ConnectionState$2.CONNECTING
1712
+ ].includes(peer.connection.state);
1713
+ }
1714
+ _isUnregistered(peer) {
1715
+ return !peer || this._peers.get(peer.remoteInfo) !== peer;
1716
+ }
1717
+ };
1718
+ __decorate([logInfo], Swarm.prototype, "_instanceId", void 0);
1719
+ __decorate([logInfo], Swarm.prototype, "ownPeer", null);
1720
+ __decorate([logInfo], Swarm.prototype, "topic", null);
1721
+ __decorate([synchronized], Swarm.prototype, "onSwarmEvent", null);
1722
+ __decorate([synchronized], Swarm.prototype, "onOffer", null);
1723
+ __decorate([synchronized], Swarm.prototype, "goOffline", null);
1724
+ __decorate([synchronized], Swarm.prototype, "goOnline", null);
1725
+ //#endregion
1726
+ //#region src/swarm/swarm-mapper.ts
1727
+ var __dxlog_file$8 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/swarm/swarm-mapper.ts";
1728
+ var SwarmMapper = class {
1729
+ _swarm;
1730
+ _subscriptions = new SubscriptionList();
1731
+ _connectionSubscriptions = new ComplexMap(PeerInfoHash);
1732
+ _peers = new ComplexMap(PeerInfoHash);
1733
+ mapUpdated = new Event();
1734
+ get peers() {
1735
+ return Array.from(this._peers.values());
1736
+ }
1737
+ constructor(_swarm) {
1738
+ this._swarm = _swarm;
1739
+ this._subscriptions.add(_swarm.connectionAdded.on((connection) => {
1740
+ this._update();
1741
+ this._connectionSubscriptions.set(connection.remoteInfo, connection.stateChanged.on(() => {
1742
+ this._update();
1743
+ }));
1744
+ }));
1745
+ this._subscriptions.add(_swarm.disconnected.on((peerId) => {
1746
+ this._connectionSubscriptions.get(peerId)?.();
1747
+ this._connectionSubscriptions.delete(peerId);
1748
+ this._update();
1749
+ }));
1750
+ this._update();
1751
+ }
1752
+ _update() {
1753
+ log("updating swarm", void 0, {
1754
+ "~LogMeta": "~LogMeta",
1755
+ F: __dxlog_file$8,
1756
+ L: 71,
1757
+ S: this
1758
+ });
1759
+ this._peers.clear();
1760
+ this._peers.set(this._swarm.ownPeer, {
1761
+ id: this._swarm.ownPeerId,
1762
+ state: "ME",
1763
+ connections: []
1764
+ });
1765
+ for (const connection of this._swarm.connections) this._peers.set(connection.remoteInfo, {
1766
+ id: PublicKey.from(connection.remoteInfo.peerKey),
1767
+ state: connection.state,
1768
+ connections: [this._swarm.ownPeerId]
1769
+ });
1770
+ log("graph changed", {
1771
+ directConnections: this._swarm.connections.length,
1772
+ totalPeersInSwarm: this._peers.size
1773
+ }, {
1774
+ "~LogMeta": "~LogMeta",
1775
+ F: __dxlog_file$8,
1776
+ L: 112,
1777
+ S: this
1778
+ });
1779
+ this.mapUpdated.emit(Array.from(this._peers.values()));
1780
+ }
1781
+ destroy() {
1782
+ Array.from(this._connectionSubscriptions.values()).forEach((cb) => cb());
1783
+ this._connectionSubscriptions.clear();
1784
+ this._subscriptions.clear();
1785
+ }
1786
+ };
1787
+ //#endregion
1788
+ //#region src/swarm/connection-limiter.ts
1789
+ var __dxlog_file$7 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/swarm/connection-limiter.ts";
1790
+ var MAX_CONCURRENT_INITIATING_CONNECTIONS = 50;
1791
+ /**
1792
+ * Limits the amount of concurrent connections with 'CONNECTING' state.
1793
+ */
1794
+ var ConnectionLimiter = class {
1795
+ _ctx = new Context(void 0, {
1796
+ "~LogMeta": "~LogMeta",
1797
+ F: __dxlog_file$7,
1798
+ L: 23
1799
+ });
1800
+ _maxConcurrentInitConnections;
1801
+ /**
1802
+ * Queue of promises to resolve when initiating connections amount is below the limit.
1803
+ */
1804
+ _waitingPromises = new ComplexMap(PublicKey.hash);
1805
+ resolveWaitingPromises = new DeferredTask(this._ctx, async () => {
1806
+ Array.from(this._waitingPromises.values()).slice(0, this._maxConcurrentInitConnections).forEach(({ resolve }) => {
1807
+ resolve();
1808
+ });
1809
+ });
1810
+ constructor({ maxConcurrentInitConnections = 50 } = {}) {
1811
+ this._maxConcurrentInitConnections = maxConcurrentInitConnections;
1812
+ }
1813
+ /**
1814
+ * @returns Promise that resolves in queue when connections amount with 'CONNECTING' state is below the limit.
1815
+ */
1816
+ async connecting(sessionId) {
1817
+ invariant(!this._waitingPromises.has(sessionId), "Peer is already waiting for connection", {
1818
+ "~LogMeta": "~LogMeta",
1819
+ F: __dxlog_file$7,
1820
+ L: 48,
1821
+ S: this,
1822
+ A: ["!this._waitingPromises.has(sessionId)", "'Peer is already waiting for connection'"]
1823
+ });
1824
+ log("waiting", { sessionId }, {
1825
+ "~LogMeta": "~LogMeta",
1826
+ F: __dxlog_file$7,
1827
+ L: 49,
1828
+ S: this
1829
+ });
1830
+ await new Promise((resolve, reject) => {
1831
+ this._waitingPromises.set(sessionId, {
1832
+ resolve,
1833
+ reject
1834
+ });
1835
+ this.resolveWaitingPromises.schedule();
1836
+ });
1837
+ log("allow", { sessionId }, {
1838
+ "~LogMeta": "~LogMeta",
1839
+ F: __dxlog_file$7,
1840
+ L: 57,
1841
+ S: this
1842
+ });
1843
+ }
1844
+ /**
1845
+ * Rejects promise returned by `connecting` method.
1846
+ */
1847
+ doneConnecting(sessionId) {
1848
+ log("done", { sessionId }, {
1849
+ "~LogMeta": "~LogMeta",
1850
+ F: __dxlog_file$7,
1851
+ L: 64,
1852
+ S: this
1853
+ });
1854
+ if (!this._waitingPromises.has(sessionId)) return;
1855
+ this._waitingPromises.get(sessionId).reject(new CancelledError());
1856
+ this._waitingPromises.delete(sessionId);
1857
+ this.resolveWaitingPromises.schedule();
1858
+ }
1859
+ };
1860
+ //#endregion
1861
+ //#region src/connection-log.ts
1862
+ var CONNECTION_GC_THRESHOLD = 1e3 * 60 * 15;
1863
+ var EventType = /* @__PURE__ */ function(EventType) {
1864
+ EventType["CONNECTION_STATE_CHANGED"] = "CONNECTION_STATE_CHANGED";
1865
+ EventType["PROTOCOL_ERROR"] = "PROTOCOL_ERROR";
1866
+ EventType["PROTOCOL_EXTENSIONS_INITIALIZED"] = "PROTOCOL_EXTENSIONS_INITIALIZED";
1867
+ EventType["PROTOCOL_EXTENSIONS_HANDSHAKE"] = "PROTOCOL_EXTENSIONS_HANDSHAKE";
1868
+ EventType["PROTOCOL_HANDSHAKE"] = "PROTOCOL_HANDSHAKE";
1869
+ return EventType;
1870
+ }({});
1871
+ var ConnectionLog = class {
1872
+ /**
1873
+ * SwarmId => info
1874
+ */
1875
+ _swarms = new ComplexMap(PublicKey.hash);
1876
+ update = new Event();
1877
+ getSwarmInfo(swarmId) {
1878
+ return this._swarms.get(swarmId) ?? raise(/* @__PURE__ */ new Error(`Swarm not found: ${swarmId}`));
1879
+ }
1880
+ get swarms() {
1881
+ return Array.from(this._swarms.values());
1882
+ }
1883
+ joinedSwarm(swarm) {
1884
+ const info = {
1885
+ id: PublicKey.from(swarm._instanceId),
1886
+ topic: swarm.topic,
1887
+ isActive: true,
1888
+ label: swarm.label,
1889
+ connections: []
1890
+ };
1891
+ this._swarms.set(PublicKey.from(swarm._instanceId), info);
1892
+ this.update.emit();
1893
+ swarm.connectionAdded.on((connection) => {
1894
+ const connectionInfo = {
1895
+ state: ConnectionState$2.CREATED,
1896
+ closeReason: connection.closeReason,
1897
+ remotePeerId: PublicKey.from(connection.remoteInfo.peerKey),
1898
+ sessionId: connection.sessionId,
1899
+ transport: connection.transport && Object.getPrototypeOf(connection.transport).constructor.name,
1900
+ protocolExtensions: [],
1901
+ events: [],
1902
+ lastUpdate: /* @__PURE__ */ new Date()
1903
+ };
1904
+ info.connections.push(connectionInfo);
1905
+ this.update.emit();
1906
+ connection.stateChanged.on(async (state) => {
1907
+ connectionInfo.state = state;
1908
+ connectionInfo.closeReason = connection.closeReason;
1909
+ connectionInfo.lastUpdate = /* @__PURE__ */ new Date();
1910
+ connectionInfo.events.push({
1911
+ type: "CONNECTION_STATE_CHANGED",
1912
+ newState: state
1913
+ });
1914
+ if (state === ConnectionState$2.CONNECTED) {
1915
+ const details = await connection.transport?.getDetails();
1916
+ connectionInfo.transportDetails = details;
1917
+ }
1918
+ this.update.emit();
1919
+ });
1920
+ connection.protocol?.stats?.on((stats) => {
1921
+ connectionInfo.readBufferSize = stats.readBufferSize;
1922
+ connectionInfo.writeBufferSize = stats.writeBufferSize;
1923
+ connectionInfo.streams = stats.channels;
1924
+ connectionInfo.lastUpdate = /* @__PURE__ */ new Date();
1925
+ this.update.emit();
1926
+ });
1927
+ connection.transportStats?.on((stats) => {
1928
+ connectionInfo.transportBytesSent = stats.bytesSent;
1929
+ connectionInfo.transportBytesReceived = stats.bytesReceived;
1930
+ connectionInfo.transportPacketsSent = stats.packetsSent;
1931
+ connectionInfo.transportPacketsReceived = stats.packetsReceived;
1932
+ });
1933
+ gcSwarm(info);
1934
+ });
1935
+ }
1936
+ leftSwarm(swarm) {
1937
+ this.getSwarmInfo(PublicKey.from(swarm._instanceId)).isActive = false;
1938
+ this.update.emit();
1939
+ }
1940
+ };
1941
+ var gcSwarm = (swarm) => {
1942
+ swarm.connections = swarm.connections?.filter((connection) => {
1943
+ return connection.lastUpdate ? Date.now() - connection.lastUpdate.getTime() < CONNECTION_GC_THRESHOLD : true;
1944
+ });
1945
+ };
1946
+ //#endregion
1947
+ //#region src/network-manager.ts
1948
+ var __dxlog_file$6 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/network-manager.ts";
1949
+ /**
1950
+ * Effect service tag for {@link SwarmNetworkManager}.
1951
+ */
1952
+ var SwarmNetworkManagerService = class extends EffectContext.Tag("@dxos/network-manager/SwarmNetworkManager")() {};
1953
+ /**
1954
+ * Manages p2p connection to the swarm.
1955
+ */
1956
+ var SwarmNetworkManager = class {
1957
+ /**
1958
+ * @internal
1959
+ */
1960
+ _swarms = new ComplexMap(PublicKey.hash);
1961
+ _mappers = new ComplexMap(PublicKey.hash);
1962
+ _transportFactory;
1963
+ _signalManager;
1964
+ _messenger;
1965
+ _signalConnection;
1966
+ _connectionLimiter;
1967
+ _connectionLog;
1968
+ _peerInfo = void 0;
1969
+ _connectionState = ConnectionState.ONLINE;
1970
+ connectionStateChanged = new Event();
1971
+ topicsUpdated = new Event();
1972
+ constructor({ transportFactory, signalManager, enableDevtoolsLogging, peerInfo }) {
1973
+ this._transportFactory = transportFactory;
1974
+ this._signalManager = signalManager;
1975
+ this._signalManager.swarmEvent.on((event) => this._swarms.get(event.topic)?.onSwarmEvent(event));
1976
+ this._messenger = new Messenger({ signalManager: this._signalManager });
1977
+ this._signalConnection = {
1978
+ join: (ctx, opts) => this._signalManager.join(ctx, opts),
1979
+ leave: (ctx, opts) => this._signalManager.leave(ctx, opts)
1980
+ };
1981
+ this._peerInfo = peerInfo;
1982
+ this._connectionLimiter = new ConnectionLimiter();
1983
+ if (enableDevtoolsLogging) this._connectionLog = new ConnectionLog();
1984
+ }
1985
+ get connectionLog() {
1986
+ return this._connectionLog;
1987
+ }
1988
+ get connectionState() {
1989
+ return this._connectionState;
1990
+ }
1991
+ get topics() {
1992
+ return Array.from(this._swarms.keys());
1993
+ }
1994
+ getSwarmMap(topic) {
1995
+ return this._mappers.get(topic);
1996
+ }
1997
+ getSwarm(topic) {
1998
+ return this._swarms.get(topic);
1999
+ }
2000
+ setPeerInfo(peerInfo) {
2001
+ this._peerInfo = peerInfo;
2002
+ }
2003
+ getPeerInfo() {
2004
+ return this._peerInfo;
2005
+ }
2006
+ async open() {
2007
+ log("opening network manager", void 0, {
2008
+ "~LogMeta": "~LogMeta",
2009
+ F: __dxlog_file$6,
2010
+ L: 146,
2011
+ S: this
2012
+ });
2013
+ await this._messenger.open();
2014
+ await this._signalManager.open();
2015
+ log("opened network manager", void 0, {
2016
+ "~LogMeta": "~LogMeta",
2017
+ F: __dxlog_file$6,
2018
+ L: 149,
2019
+ S: this
2020
+ });
2021
+ }
2022
+ async close(ctx) {
2023
+ for (const topic of this._swarms.keys()) await this.leaveSwarm(ctx, topic).catch((err) => {
2024
+ log(err, void 0, {
2025
+ "~LogMeta": "~LogMeta",
2026
+ F: __dxlog_file$6,
2027
+ L: 155,
2028
+ S: this
2029
+ });
2030
+ });
2031
+ await this._messenger.close();
2032
+ await this._signalManager.close();
2033
+ }
2034
+ /**
2035
+ * Join the swarm.
2036
+ */
2037
+ async joinSwarm(ctx, { topic, topology, protocolProvider: protocol, label }) {
2038
+ assertArgument(PublicKey.isPublicKey(topic), "topic");
2039
+ invariant(topology, void 0, {
2040
+ "~LogMeta": "~LogMeta",
2041
+ F: __dxlog_file$6,
2042
+ L: 177,
2043
+ S: this,
2044
+ A: ["topology", ""]
2045
+ });
2046
+ invariant(this._peerInfo, void 0, {
2047
+ "~LogMeta": "~LogMeta",
2048
+ F: __dxlog_file$6,
2049
+ L: 178,
2050
+ S: this,
2051
+ A: ["this._peerInfo", ""]
2052
+ });
2053
+ assertArgument(typeof protocol === "function", "protocolProvider");
2054
+ if (this._swarms.has(topic)) throw new Error(`Already connected to swarm: ${PublicKey.from(topic)}`);
2055
+ log("joining", {
2056
+ topic: PublicKey.from(topic),
2057
+ peerInfo: this._peerInfo,
2058
+ topology: topology.toString()
2059
+ }, {
2060
+ "~LogMeta": "~LogMeta",
2061
+ F: __dxlog_file$6,
2062
+ L: 184,
2063
+ S: this
2064
+ });
2065
+ const swarm = new Swarm(topic, this._peerInfo, topology, protocol, this._messenger, this._transportFactory, label, this._connectionLimiter);
2066
+ swarm.errors.handle((error) => {
2067
+ log("swarm error", { error }, {
2068
+ "~LogMeta": "~LogMeta",
2069
+ F: __dxlog_file$6,
2070
+ L: 197,
2071
+ S: this
2072
+ });
2073
+ });
2074
+ this._swarms.set(topic, swarm);
2075
+ this._mappers.set(topic, new SwarmMapper(swarm));
2076
+ await swarm.open();
2077
+ this._signalConnection.join(ctx, {
2078
+ topic,
2079
+ peer: this._peerInfo
2080
+ }).catch((error) => log.catch(error, void 0, {
2081
+ "~LogMeta": "~LogMeta",
2082
+ F: __dxlog_file$6,
2083
+ L: 206,
2084
+ S: this
2085
+ }));
2086
+ this.topicsUpdated.emit();
2087
+ this._connectionLog?.joinedSwarm(swarm);
2088
+ log("joined", {
2089
+ topic: PublicKey.from(topic),
2090
+ count: this._swarms.size
2091
+ }, {
2092
+ "~LogMeta": "~LogMeta",
2093
+ F: __dxlog_file$6,
2094
+ L: 210,
2095
+ S: this
2096
+ });
2097
+ return { close: (ctx) => this.leaveSwarm(ctx, topic) };
2098
+ }
2099
+ /**
2100
+ * Close the connection.
2101
+ */
2102
+ async leaveSwarm(ctx, topic) {
2103
+ if (!this._swarms.has(topic)) return;
2104
+ log("leaving", { topic: PublicKey.from(topic) }, {
2105
+ "~LogMeta": "~LogMeta",
2106
+ F: __dxlog_file$6,
2107
+ L: 227,
2108
+ S: this
2109
+ });
2110
+ const swarm = this._swarms.get(topic);
2111
+ await this._signalConnection.leave(ctx, {
2112
+ topic,
2113
+ peer: swarm.ownPeer
2114
+ });
2115
+ this._mappers.get(topic).destroy();
2116
+ this._mappers.delete(topic);
2117
+ this._connectionLog?.leftSwarm(swarm);
2118
+ await swarm.destroy();
2119
+ this._swarms.delete(topic);
2120
+ this.topicsUpdated.emit();
2121
+ log("left", {
2122
+ topic: PublicKey.from(topic),
2123
+ count: this._swarms.size
2124
+ }, {
2125
+ "~LogMeta": "~LogMeta",
2126
+ F: __dxlog_file$6,
2127
+ L: 241,
2128
+ S: this
2129
+ });
2130
+ }
2131
+ async setConnectionState(state) {
2132
+ if (state === this._connectionState) return;
2133
+ switch (state) {
2134
+ case ConnectionState.OFFLINE:
2135
+ this._connectionState = state;
2136
+ await Promise.all([...this._swarms.values()].map((swarm) => swarm.goOffline()));
2137
+ await this._signalManager.close();
2138
+ break;
2139
+ case ConnectionState.ONLINE:
2140
+ this._connectionState = state;
2141
+ await Promise.all([...this._swarms.values()].map((swarm) => swarm.goOnline()));
2142
+ await this._signalManager.open();
2143
+ break;
2144
+ }
2145
+ this.connectionStateChanged.emit(this._connectionState);
2146
+ }
2147
+ };
2148
+ __decorate([synchronized], SwarmNetworkManager.prototype, "joinSwarm", null);
2149
+ __decorate([synchronized], SwarmNetworkManager.prototype, "leaveSwarm", null);
2150
+ //#endregion
2151
+ //#region src/topology/fully-connected-topology.ts
2152
+ var __dxlog_file$5 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/topology/fully-connected-topology.ts";
2153
+ var FullyConnectedTopology = class {
2154
+ _controller;
2155
+ toString() {
2156
+ return "FullyConnectedTopology";
2157
+ }
2158
+ init(controller) {
2159
+ invariant(!this._controller, "Already initialized", {
2160
+ "~LogMeta": "~LogMeta",
2161
+ F: __dxlog_file$5,
2162
+ L: 18,
2163
+ S: this,
2164
+ A: ["!this._controller", "'Already initialized'"]
2165
+ });
2166
+ this._controller = controller;
2167
+ }
2168
+ update() {
2169
+ invariant(this._controller, "Not initialized", {
2170
+ "~LogMeta": "~LogMeta",
2171
+ F: __dxlog_file$5,
2172
+ L: 23,
2173
+ S: this,
2174
+ A: ["this._controller", "'Not initialized'"]
2175
+ });
2176
+ const { candidates: discovered } = this._controller.getState();
2177
+ for (const peer of discovered) this._controller.connect(peer);
2178
+ }
2179
+ async onOffer(peer) {
2180
+ return true;
2181
+ }
2182
+ async destroy() {}
2183
+ };
2184
+ //#endregion
2185
+ //#region src/transport/memory-transport.ts
2186
+ var __dxlog_file$4 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/transport/memory-transport.ts";
2187
+ var MEMORY_TRANSPORT_DELAY = 1;
2188
+ /**
2189
+ * Creates a binary stream that delays data being sent through the stream by the specified amount of time.
2190
+ */
2191
+ var createStreamDelay = (delay) => {
2192
+ return new Transform({
2193
+ objectMode: true,
2194
+ transform: (chunk, _, cb) => {
2195
+ setTimeout(() => cb(null, chunk), delay);
2196
+ }
2197
+ });
2198
+ };
2199
+ var MemoryTransportFactory = { createTransport: (options) => new MemoryTransport(options) };
2200
+ /**
2201
+ * Fake transport.
2202
+ */
2203
+ var MemoryTransport = class MemoryTransport {
2204
+ _options;
2205
+ static _connections = new ComplexMap(PublicKey.hash);
2206
+ _instanceId = PublicKey.random();
2207
+ _remote = new Trigger();
2208
+ _outgoingDelay = createStreamDelay(MEMORY_TRANSPORT_DELAY);
2209
+ _incomingDelay = createStreamDelay(MEMORY_TRANSPORT_DELAY);
2210
+ _closed = false;
2211
+ _remoteInstanceId;
2212
+ _remoteConnection;
2213
+ closed = new Event();
2214
+ connected = new Event();
2215
+ errors = new ErrorStream();
2216
+ constructor(_options) {
2217
+ this._options = _options;
2218
+ invariant(!MemoryTransport._connections.has(this._instanceId), "Duplicate memory connection", {
2219
+ "~LogMeta": "~LogMeta",
2220
+ F: __dxlog_file$4,
2221
+ L: 64,
2222
+ S: this,
2223
+ A: ["!MemoryTransport._connections.has(this._instanceId)", "'Duplicate memory connection'"]
2224
+ });
2225
+ MemoryTransport._connections.set(this._instanceId, this);
2226
+ }
2227
+ get isOpen() {
2228
+ return !this._closed;
2229
+ }
2230
+ async open() {
2231
+ log("opening...", void 0, {
2232
+ "~LogMeta": "~LogMeta",
2233
+ F: __dxlog_file$4,
2234
+ L: 74,
2235
+ S: this
2236
+ });
2237
+ if (this._options.initiator) {
2238
+ log("sending signal", void 0, {
2239
+ "~LogMeta": "~LogMeta",
2240
+ F: __dxlog_file$4,
2241
+ L: 78,
2242
+ S: this
2243
+ });
2244
+ try {
2245
+ await this._options.sendSignal({ payload: { transportId: this._instanceId.toHex() } });
2246
+ } catch (err) {
2247
+ if (!this._closed) this.errors.raise(toError(err));
2248
+ }
2249
+ } else this._remote.wait({ timeout: this._options.timeout ?? 1e3 }).then((remoteId) => {
2250
+ if (this._closed) return;
2251
+ this._remoteInstanceId = remoteId;
2252
+ this._remoteConnection = MemoryTransport._connections.get(this._remoteInstanceId);
2253
+ if (!this._remoteConnection) {
2254
+ this._closed = true;
2255
+ this.closed.emit();
2256
+ return;
2257
+ }
2258
+ invariant(!this._remoteConnection._remoteConnection, `Remote already connected: ${this._remoteInstanceId}`, {
2259
+ "~LogMeta": "~LogMeta",
2260
+ F: __dxlog_file$4,
2261
+ L: 104,
2262
+ S: this,
2263
+ A: ["!this._remoteConnection._remoteConnection", "`Remote already connected: ${this._remoteInstanceId}`"]
2264
+ });
2265
+ this._remoteConnection._remoteConnection = this;
2266
+ this._remoteConnection._remoteInstanceId = this._instanceId;
2267
+ log("connected", void 0, {
2268
+ "~LogMeta": "~LogMeta",
2269
+ F: __dxlog_file$4,
2270
+ L: 108,
2271
+ S: this
2272
+ });
2273
+ this._options.stream.pipe(this._outgoingDelay).pipe(this._remoteConnection._options.stream).pipe(this._incomingDelay).pipe(this._options.stream);
2274
+ this.connected.emit();
2275
+ this._remoteConnection.connected.emit();
2276
+ }).catch((err) => {
2277
+ if (this._closed) return;
2278
+ this.errors.raise(err);
2279
+ });
2280
+ return this;
2281
+ }
2282
+ async close() {
2283
+ log("closing...", void 0, {
2284
+ "~LogMeta": "~LogMeta",
2285
+ F: __dxlog_file$4,
2286
+ L: 130,
2287
+ S: this
2288
+ });
2289
+ this._closed = true;
2290
+ MemoryTransport._connections.delete(this._instanceId);
2291
+ if (this._remoteConnection) {
2292
+ this._remoteConnection._closed = true;
2293
+ MemoryTransport._connections.delete(this._remoteInstanceId);
2294
+ this._options.stream.unpipe(this._incomingDelay);
2295
+ this._incomingDelay.unpipe(this._remoteConnection._options.stream);
2296
+ this._remoteConnection._options.stream.unpipe(this._outgoingDelay);
2297
+ this._outgoingDelay.unpipe(this._options.stream);
2298
+ this._options.stream.unpipe(this._outgoingDelay);
2299
+ this._remoteConnection.closed.emit();
2300
+ this._remoteConnection._remoteConnection = void 0;
2301
+ this._remoteConnection = void 0;
2302
+ }
2303
+ this.closed.emit();
2304
+ log("closed", void 0, {
2305
+ "~LogMeta": "~LogMeta",
2306
+ F: __dxlog_file$4,
2307
+ L: 158,
2308
+ S: this
2309
+ });
2310
+ return this;
2311
+ }
2312
+ async onSignal({ payload }) {
2313
+ log("received signal", { payload }, {
2314
+ "~LogMeta": "~LogMeta",
2315
+ F: __dxlog_file$4,
2316
+ L: 163,
2317
+ S: this
2318
+ });
2319
+ if (!payload?.transportId) return;
2320
+ const transportId = payload.transportId;
2321
+ if (transportId) {
2322
+ const remoteId = PublicKey.fromHex(transportId);
2323
+ this._remote.wake(remoteId);
2324
+ }
2325
+ }
2326
+ async getDetails() {
2327
+ return this._instanceId.toHex();
2328
+ }
2329
+ async getStats() {
2330
+ return {
2331
+ bytesSent: 0,
2332
+ bytesReceived: 0,
2333
+ packetsSent: 0,
2334
+ packetsReceived: 0
2335
+ };
2336
+ }
2337
+ };
2338
+ __decorate([logInfo], MemoryTransport.prototype, "_instanceId", void 0);
2339
+ __decorate([logInfo], MemoryTransport.prototype, "_remoteInstanceId", void 0);
2340
+ var toError = (err) => err instanceof Error ? err : new Error(String(err));
2341
+ //#endregion
2342
+ //#region src/transport/transport.ts
2343
+ var TransportKind = /* @__PURE__ */ function(TransportKind) {
2344
+ TransportKind["WEB_RTC"] = "WEB-RTC";
2345
+ TransportKind["WEB_RTC_PROXY"] = "WEB-RTC_PROXY";
2346
+ TransportKind["MEMORY"] = "MEMORY";
2347
+ TransportKind["TCP"] = "TCP";
2348
+ return TransportKind;
2349
+ }({});
2350
+ //#endregion
2351
+ //#region src/transport/webrtc/rtc-connection-factory.ts
2352
+ /**
2353
+ * Use built-in browser RTCPeerConnection.
2354
+ */
2355
+ var BrowserRtcConnectionFactory = class {
2356
+ async initialize() {}
2357
+ async onConnectionDestroyed() {}
2358
+ async createConnection(config) {
2359
+ return new RTCPeerConnection(config);
2360
+ }
2361
+ async initConnection(connection, info) {}
2362
+ };
2363
+ /**
2364
+ * Use `node-datachannel` polyfill.
2365
+ * https://github.com/paullouisageneau/libdatachannel
2366
+ */
2367
+ var NodeRtcConnectionFactory = class NodeRtcConnectionFactory {
2368
+ static _createdConnections = 0;
2369
+ static _cleanupMutex = new Mutex();
2370
+ async initialize() {}
2371
+ async onConnectionDestroyed() {
2372
+ return NodeRtcConnectionFactory._cleanupMutex.executeSynchronized(async () => {
2373
+ if (--NodeRtcConnectionFactory._createdConnections === 0) (await import("#node-datachannel")).cleanup();
2374
+ });
2375
+ }
2376
+ async createConnection(config) {
2377
+ return NodeRtcConnectionFactory._cleanupMutex.executeSynchronized(async () => {
2378
+ const { RTCPeerConnection } = await import("#node-datachannel/polyfill");
2379
+ NodeRtcConnectionFactory._createdConnections++;
2380
+ return new RTCPeerConnection(config);
2381
+ });
2382
+ }
2383
+ async initConnection(connection, info) {
2384
+ if (info.initiator) connection.onnegotiationneeded?.(null);
2385
+ }
2386
+ };
2387
+ /**
2388
+ * Create platform-specific connection factory.
2389
+ */
2390
+ var getRtcConnectionFactory = () => {
2391
+ return typeof globalThis.RTCPeerConnection === "undefined" ? new NodeRtcConnectionFactory() : new BrowserRtcConnectionFactory();
2392
+ };
2393
+ //#endregion
2394
+ //#region src/transport/webrtc/rtc-transport-stats.ts
2395
+ var describeSelectedRemoteCandidate = async (connection) => {
2396
+ const rc = (connection && await getRtcConnectionStats(connection))?.remoteCandidate;
2397
+ if (!rc) return "unavailable";
2398
+ if (rc.candidateType === "relay") return `${rc.ip}:${rc.port} relay for ${rc.relatedAddress}:${rc.relatedPort}`;
2399
+ return `${rc.ip}:${rc.port} ${rc.candidateType}`;
2400
+ };
2401
+ var createRtcTransportStats = async (connection, topic) => {
2402
+ const stats = connection && await getRtcConnectionStats(connection, topic);
2403
+ if (!stats) return {
2404
+ bytesSent: 0,
2405
+ bytesReceived: 0,
2406
+ packetsSent: 0,
2407
+ packetsReceived: 0,
2408
+ rawStats: {}
2409
+ };
2410
+ return {
2411
+ bytesSent: stats.dataChannel?.bytesSent,
2412
+ bytesReceived: stats.dataChannel?.bytesReceived,
2413
+ packetsSent: 0,
2414
+ packetsReceived: 0,
2415
+ rawStats: stats.raw
2416
+ };
2417
+ };
2418
+ var getRtcConnectionStats = async (connection, channelTopic) => {
2419
+ const stats = await connection.getStats();
2420
+ const statsEntries = Array.from(stats.entries());
2421
+ const transport = statsEntries.find(([_, entry]) => entry.type === "transport")?.[1];
2422
+ const selectedCandidatePair = transport && statsEntries.find(([entryId]) => entryId === transport.selectedCandidatePairId)?.[1];
2423
+ const remoteCandidate = selectedCandidatePair && statsEntries.find(([entryId]) => entryId === selectedCandidatePair.remoteCandidateId)?.[1];
2424
+ return {
2425
+ transport,
2426
+ selectedCandidatePair,
2427
+ dataChannel: channelTopic && statsEntries.find(([_, entry]) => entry.type === "data-channel" && entry.label === channelTopic)?.[1],
2428
+ remoteCandidate,
2429
+ raw: Object.fromEntries(stats)
2430
+ };
2431
+ };
2432
+ //#endregion
2433
+ //#region src/transport/webrtc/rtc-transport-channel.ts
2434
+ var __dxlog_file$3 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/transport/webrtc/rtc-transport-channel.ts";
2435
+ var MAX_MESSAGE_SIZE = 64 * 1024;
2436
+ var MAX_BUFFERED_AMOUNT = 64 * 1024;
2437
+ /**
2438
+ * A WebRTC connection data channel.
2439
+ * Manages a WebRTC connection to a remote peer using an abstract signalling mechanism.
2440
+ */
2441
+ var RtcTransportChannel = class extends Resource {
2442
+ _connection;
2443
+ _options;
2444
+ closed = new Event();
2445
+ connected = new Event();
2446
+ errors = new ErrorStream();
2447
+ _channel;
2448
+ _stream;
2449
+ _streamDataFlushedCallback = null;
2450
+ _isChannelCreationInProgress = false;
2451
+ constructor(_connection, _options) {
2452
+ super();
2453
+ this._connection = _connection;
2454
+ this._options = _options;
2455
+ }
2456
+ get isRtcChannelCreationInProgress() {
2457
+ return this._isChannelCreationInProgress;
2458
+ }
2459
+ onConnectionError(error) {
2460
+ if (this.isOpen) this.errors.raise(error);
2461
+ }
2462
+ async _open() {
2463
+ invariant(!this._isChannelCreationInProgress, void 0, {
2464
+ "~LogMeta": "~LogMeta",
2465
+ F: __dxlog_file$3,
2466
+ L: 56,
2467
+ S: this,
2468
+ A: ["!this._isChannelCreationInProgress", ""]
2469
+ });
2470
+ this._isChannelCreationInProgress = true;
2471
+ this._connection.createDataChannel(this._options.topic).then((channel) => {
2472
+ if (this.isOpen) {
2473
+ this._channel = channel;
2474
+ this._initChannel(this._channel);
2475
+ } else this._safeCloseChannel(channel);
2476
+ }).catch((err) => {
2477
+ if (this.isOpen) {
2478
+ const error = err instanceof Error ? err : new ConnectivityError({ message: `Failed to create a channel: ${JSON.stringify(err?.message)}` });
2479
+ this.errors.raise(error);
2480
+ } else log.verbose("connection establishment failed after transport was closed", { err }, {
2481
+ "~LogMeta": "~LogMeta",
2482
+ F: __dxlog_file$3,
2483
+ L: 76,
2484
+ S: this
2485
+ });
2486
+ }).finally(() => {
2487
+ this._isChannelCreationInProgress = false;
2488
+ });
2489
+ }
2490
+ async _close() {
2491
+ if (this._channel) {
2492
+ this._safeCloseChannel(this._channel);
2493
+ this._channel = void 0;
2494
+ this._stream = void 0;
2495
+ }
2496
+ this.closed.emit();
2497
+ log("closed", void 0, {
2498
+ "~LogMeta": "~LogMeta",
2499
+ F: __dxlog_file$3,
2500
+ L: 92,
2501
+ S: this
2502
+ });
2503
+ }
2504
+ _initChannel(channel) {
2505
+ Object.assign(channel, {
2506
+ onopen: () => {
2507
+ if (!this.isOpen) {
2508
+ log.warn("channel opened in a closed transport", { topic: this._options.topic }, {
2509
+ "~LogMeta": "~LogMeta",
2510
+ F: __dxlog_file$3,
2511
+ L: 99,
2512
+ S: this
2513
+ });
2514
+ this._safeCloseChannel(channel);
2515
+ return;
2516
+ }
2517
+ log("onopen", void 0, {
2518
+ "~LogMeta": "~LogMeta",
2519
+ F: __dxlog_file$3,
2520
+ L: 104,
2521
+ S: this
2522
+ });
2523
+ const duplex = new Duplex({
2524
+ read: () => {},
2525
+ write: (chunk, encoding, callback) => {
2526
+ return this._handleChannelWrite(chunk, callback);
2527
+ }
2528
+ });
2529
+ duplex.pipe(this._options.stream).pipe(duplex);
2530
+ this._stream = duplex;
2531
+ this.connected.emit();
2532
+ },
2533
+ onclose: async () => {
2534
+ log("onclose", void 0, {
2535
+ "~LogMeta": "~LogMeta",
2536
+ F: __dxlog_file$3,
2537
+ L: 117,
2538
+ S: this
2539
+ });
2540
+ await this.close();
2541
+ },
2542
+ onmessage: async (event) => {
2543
+ if (!this._stream) {
2544
+ log.warn("ignoring message on a closed channel", void 0, {
2545
+ "~LogMeta": "~LogMeta",
2546
+ F: __dxlog_file$3,
2547
+ L: 123,
2548
+ S: this
2549
+ });
2550
+ return;
2551
+ }
2552
+ let data = event.data;
2553
+ if (data instanceof ArrayBuffer) data = Buffer.from(data);
2554
+ else if (data instanceof Blob) data = Buffer.from(await data.arrayBuffer());
2555
+ this._stream.push(data);
2556
+ },
2557
+ onerror: (event) => {
2558
+ if (this.isOpen) {
2559
+ const err = event.error instanceof Error ? event.error : /* @__PURE__ */ new Error(`Datachannel error: ${event.type}.`);
2560
+ this.errors.raise(err);
2561
+ }
2562
+ },
2563
+ onbufferedamountlow: () => {
2564
+ const cb = this._streamDataFlushedCallback;
2565
+ this._streamDataFlushedCallback = null;
2566
+ cb?.();
2567
+ }
2568
+ });
2569
+ }
2570
+ async _handleChannelWrite(chunk, callback) {
2571
+ if (!this._channel) {
2572
+ log.warn("writing to a channel after a connection was closed", void 0, {
2573
+ "~LogMeta": "~LogMeta",
2574
+ F: __dxlog_file$3,
2575
+ L: 153,
2576
+ S: this
2577
+ });
2578
+ return;
2579
+ }
2580
+ if (chunk.length > MAX_MESSAGE_SIZE) {
2581
+ const error = /* @__PURE__ */ new Error(`Message too large: ${chunk.length} > ${MAX_MESSAGE_SIZE}.`);
2582
+ this.errors.raise(error);
2583
+ callback();
2584
+ return;
2585
+ }
2586
+ try {
2587
+ this._channel.send(chunk);
2588
+ } catch (err) {
2589
+ this.errors.raise(err);
2590
+ callback();
2591
+ return;
2592
+ }
2593
+ if (this._channel.bufferedAmount > MAX_BUFFERED_AMOUNT) {
2594
+ if (this._streamDataFlushedCallback !== null) log.error("consumer trying to write before we are ready for more data", void 0, {
2595
+ "~LogMeta": "~LogMeta",
2596
+ F: __dxlog_file$3,
2597
+ L: 174,
2598
+ S: this
2599
+ });
2600
+ this._streamDataFlushedCallback = callback;
2601
+ } else callback();
2602
+ }
2603
+ _safeCloseChannel(channel) {
2604
+ try {
2605
+ channel.close();
2606
+ } catch (error) {
2607
+ log.catch(error, void 0, {
2608
+ "~LogMeta": "~LogMeta",
2609
+ F: __dxlog_file$3,
2610
+ L: 186,
2611
+ S: this
2612
+ });
2613
+ }
2614
+ }
2615
+ onSignal(signal) {
2616
+ return this._connection.onSignal(signal);
2617
+ }
2618
+ async getDetails() {
2619
+ return describeSelectedRemoteCandidate(this._connection.currentConnection);
2620
+ }
2621
+ async getStats() {
2622
+ return createRtcTransportStats(this._connection.currentConnection, this._options.topic);
2623
+ }
2624
+ };
2625
+ //#endregion
2626
+ //#region src/transport/webrtc/utils.ts
2627
+ var chooseInitiatorPeer = (peer1Key, peer2Key) => peer1Key < peer2Key ? peer1Key : peer2Key;
2628
+ var areSdpEqual = (sdp1, sdp2) => {
2629
+ const sdp1Lines = deduplicatedSdpLines(sdp1);
2630
+ const sdp2Lines = deduplicatedSdpLines(sdp2);
2631
+ if (sdp1Lines.length !== sdp2Lines.length) return false;
2632
+ return sdp1Lines.every((line, idx) => line === sdp2Lines[idx]);
2633
+ };
2634
+ /**
2635
+ * For some reason libdatachannel duplicates some attributes after an sdp is set.
2636
+ * So the following test might fail:
2637
+ * conn.setRemoteDescription(sdp);
2638
+ * expect(conn.remoteDescription.sdp).toEqual(sdp);
2639
+ */
2640
+ var deduplicatedSdpLines = (sdp) => {
2641
+ const deduplicatedLines = [];
2642
+ const seenLines = [];
2643
+ for (const line of sdp.split("\r\n")) {
2644
+ if (line.startsWith("m")) seenLines.length = 0;
2645
+ if (seenLines.includes(line)) continue;
2646
+ seenLines.push(line);
2647
+ deduplicatedLines.push(line);
2648
+ }
2649
+ return deduplicatedLines;
2650
+ };
2651
+ //#endregion
2652
+ //#region src/transport/webrtc/rtc-peer-connection.ts
2653
+ var __dxlog_file$2 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/transport/webrtc/rtc-peer-connection.ts";
2654
+ /**
2655
+ * A factory for rtc Transport implementations for a particular peer.
2656
+ * Contains WebRTC connection establishment logic.
2657
+ * When the first Transport is opened a connection is established and kept until all the transports are closed.
2658
+ */
2659
+ var RtcPeerConnection = class {
2660
+ _factory;
2661
+ _options;
2662
+ _channelCreatedCallbacks = /* @__PURE__ */ new Map();
2663
+ _transportChannels = /* @__PURE__ */ new Map();
2664
+ _dataChannels = /* @__PURE__ */ new Map();
2665
+ _readyForCandidates = new Trigger();
2666
+ _offerProcessingMutex = new Mutex();
2667
+ /**
2668
+ * Can't use peer.connection.initiator, because if two connections to the same peer are created in
2669
+ * different swarms, we might be the initiator of the first one, but not of the other one.
2670
+ * Use a stable peer keypair property (key ordering) to decide who's acting as the initiator of
2671
+ * transport connection establishment and data channel creation.
2672
+ */
2673
+ _initiator;
2674
+ _connection;
2675
+ constructor(_factory, _options) {
2676
+ this._factory = _factory;
2677
+ this._options = _options;
2678
+ this._initiator = chooseInitiatorPeer(_options.ownPeerKey, _options.remotePeerKey) === _options.ownPeerKey;
2679
+ }
2680
+ get transportChannelCount() {
2681
+ return this._transportChannels.size;
2682
+ }
2683
+ get currentConnection() {
2684
+ return this._connection;
2685
+ }
2686
+ async createDataChannel(topic) {
2687
+ const connection = await this._openConnection();
2688
+ if (!this._transportChannels.has(topic)) {
2689
+ if (!this._transportChannels.size) this._lockAndCloseConnection();
2690
+ throw new Error("Transport closed while connection was being open");
2691
+ }
2692
+ if (this._initiator) {
2693
+ const channel = connection.createDataChannel(topic);
2694
+ this._dataChannels.set(topic, channel);
2695
+ return channel;
2696
+ } else {
2697
+ const existingChannel = this._dataChannels.get(topic);
2698
+ if (existingChannel) return existingChannel;
2699
+ log("waiting for initiator-peer to open a data channel", void 0, {
2700
+ "~LogMeta": "~LogMeta",
2701
+ F: __dxlog_file$2,
2702
+ L: 93,
2703
+ S: this
2704
+ });
2705
+ return new Promise((resolve, reject) => {
2706
+ this._channelCreatedCallbacks.set(topic, {
2707
+ resolve,
2708
+ reject
2709
+ });
2710
+ });
2711
+ }
2712
+ }
2713
+ createTransportChannel(options) {
2714
+ const channel = new RtcTransportChannel(this, options);
2715
+ this._transportChannels.set(options.topic, channel);
2716
+ channel.closed.on(() => {
2717
+ this._transportChannels.delete(options.topic);
2718
+ if (this._transportChannels.size === 0) this._lockAndCloseConnection();
2719
+ });
2720
+ return channel;
2721
+ }
2722
+ async _openConnection() {
2723
+ if (this._connection) return this._connection;
2724
+ log("initializing connection...", () => ({ remotePeer: this._options.remotePeerKey }), {
2725
+ "~LogMeta": "~LogMeta",
2726
+ F: __dxlog_file$2,
2727
+ L: 118,
2728
+ S: this
2729
+ });
2730
+ const config = await this._loadConnectionConfig();
2731
+ const connection = await this._factory.createConnection(config);
2732
+ const iceCandidateErrors = [];
2733
+ Object.assign(connection, {
2734
+ onnegotiationneeded: async () => {
2735
+ invariant(this._initiator, void 0, {
2736
+ "~LogMeta": "~LogMeta",
2737
+ F: __dxlog_file$2,
2738
+ L: 133,
2739
+ S: this,
2740
+ A: ["this._initiator", ""]
2741
+ });
2742
+ if (connection !== this._connection) {
2743
+ this._onConnectionCallbackAfterClose("onnegotiationneeded", connection);
2744
+ return;
2745
+ }
2746
+ log("onnegotiationneeded", void 0, {
2747
+ "~LogMeta": "~LogMeta",
2748
+ F: __dxlog_file$2,
2749
+ L: 140,
2750
+ S: this
2751
+ });
2752
+ try {
2753
+ const offer = await connection.createOffer();
2754
+ await connection.setLocalDescription(offer);
2755
+ await this._sendDescription(connection, offer);
2756
+ } catch (err) {
2757
+ this._lockAndAbort(connection, err);
2758
+ }
2759
+ },
2760
+ onicecandidate: async (event) => {
2761
+ if (connection !== this._connection) {
2762
+ this._onConnectionCallbackAfterClose("onicecandidate", connection);
2763
+ return;
2764
+ }
2765
+ if (event.candidate) {
2766
+ log("onicecandidate", { candidate: event.candidate.candidate }, {
2767
+ "~LogMeta": "~LogMeta",
2768
+ F: __dxlog_file$2,
2769
+ L: 159,
2770
+ S: this
2771
+ });
2772
+ await this._sendIceCandidate(event.candidate);
2773
+ } else log("onicecandidate gathering complete", void 0, {
2774
+ "~LogMeta": "~LogMeta",
2775
+ F: __dxlog_file$2,
2776
+ L: 162,
2777
+ S: this
2778
+ });
2779
+ },
2780
+ onicecandidateerror: (event) => {
2781
+ const { url, errorCode, errorText } = event;
2782
+ iceCandidateErrors.push({
2783
+ url,
2784
+ errorCode,
2785
+ errorText
2786
+ });
2787
+ },
2788
+ oniceconnectionstatechange: () => {
2789
+ if (connection !== this._connection) {
2790
+ this._onConnectionCallbackAfterClose("oniceconnectionstatechange", connection);
2791
+ return;
2792
+ }
2793
+ log("oniceconnectionstatechange", { state: connection.iceConnectionState }, {
2794
+ "~LogMeta": "~LogMeta",
2795
+ F: __dxlog_file$2,
2796
+ L: 182,
2797
+ S: this
2798
+ });
2799
+ if (connection.iceConnectionState === "failed") this._lockAndAbort(connection, createIceFailureError(iceCandidateErrors));
2800
+ },
2801
+ onconnectionstatechange: () => {
2802
+ if (connection !== this._connection) {
2803
+ if (connection.connectionState !== "closed" && connection.connectionState !== "failed") this._onConnectionCallbackAfterClose("onconnectionstatechange", connection);
2804
+ return;
2805
+ }
2806
+ log("onconnectionstatechange", { state: connection.connectionState }, {
2807
+ "~LogMeta": "~LogMeta",
2808
+ F: __dxlog_file$2,
2809
+ L: 199,
2810
+ S: this
2811
+ });
2812
+ if (connection.connectionState === "failed") this._lockAndAbort(connection, /* @__PURE__ */ new Error("Connection failed."));
2813
+ },
2814
+ onsignalingstatechange: () => {
2815
+ log("onsignalingstatechange", { state: connection.signalingState }, {
2816
+ "~LogMeta": "~LogMeta",
2817
+ F: __dxlog_file$2,
2818
+ L: 206,
2819
+ S: this
2820
+ });
2821
+ },
2822
+ ondatachannel: (event) => {
2823
+ invariant(!this._initiator, "Initiator is expected to create data channels.", {
2824
+ "~LogMeta": "~LogMeta",
2825
+ F: __dxlog_file$2,
2826
+ L: 212,
2827
+ S: this,
2828
+ A: ["!this._initiator", "'Initiator is expected to create data channels.'"]
2829
+ });
2830
+ if (connection !== this._connection) {
2831
+ this._onConnectionCallbackAfterClose("ondatachannel", connection);
2832
+ return;
2833
+ }
2834
+ log("ondatachannel", { label: event.channel.label }, {
2835
+ "~LogMeta": "~LogMeta",
2836
+ F: __dxlog_file$2,
2837
+ L: 219,
2838
+ S: this
2839
+ });
2840
+ this._dataChannels.set(event.channel.label, event.channel);
2841
+ const pendingCallback = this._channelCreatedCallbacks.get(event.channel.label);
2842
+ if (pendingCallback) {
2843
+ this._channelCreatedCallbacks.delete(event.channel.label);
2844
+ pendingCallback.resolve(event.channel);
2845
+ }
2846
+ }
2847
+ });
2848
+ this._connection = connection;
2849
+ this._readyForCandidates.reset();
2850
+ await this._factory.initConnection(connection, { initiator: this._initiator });
2851
+ return this._connection;
2852
+ }
2853
+ async _lockAndAbort(connection, error) {
2854
+ this._abortConnection(connection, error);
2855
+ }
2856
+ _abortConnection(connection, error) {
2857
+ if (connection !== this._connection) {
2858
+ log.error("attempted to abort an inactive connection", { error }, {
2859
+ "~LogMeta": "~LogMeta",
2860
+ F: __dxlog_file$2,
2861
+ L: 244,
2862
+ S: this
2863
+ });
2864
+ this._safeCloseConnection(connection);
2865
+ return;
2866
+ }
2867
+ for (const [topic, pendingCallback] of this._channelCreatedCallbacks.entries()) {
2868
+ pendingCallback.reject(error);
2869
+ this._transportChannels.delete(topic);
2870
+ }
2871
+ this._channelCreatedCallbacks.clear();
2872
+ for (const channel of this._transportChannels.values()) channel.onConnectionError(error);
2873
+ this._transportChannels.clear();
2874
+ this._safeCloseConnection();
2875
+ log("connection aborted", { reason: error.message }, {
2876
+ "~LogMeta": "~LogMeta",
2877
+ F: __dxlog_file$2,
2878
+ L: 258,
2879
+ S: this
2880
+ });
2881
+ }
2882
+ async _lockAndCloseConnection() {
2883
+ invariant(this._transportChannels.size === 0, void 0, {
2884
+ "~LogMeta": "~LogMeta",
2885
+ F: __dxlog_file$2,
2886
+ L: 263,
2887
+ S: this,
2888
+ A: ["this._transportChannels.size === 0", ""]
2889
+ });
2890
+ if (this._connection) {
2891
+ this._safeCloseConnection();
2892
+ log("connection closed", void 0, {
2893
+ "~LogMeta": "~LogMeta",
2894
+ F: __dxlog_file$2,
2895
+ L: 266,
2896
+ S: this
2897
+ });
2898
+ }
2899
+ }
2900
+ async onSignal(signal) {
2901
+ const connection = this._connection;
2902
+ if (!connection) {
2903
+ log.warn("a signal ignored because the connection was closed", { type: signal.payload.data.type }, {
2904
+ "~LogMeta": "~LogMeta",
2905
+ F: __dxlog_file$2,
2906
+ L: 274,
2907
+ S: this
2908
+ });
2909
+ return;
2910
+ }
2911
+ const data = signal.payload.data;
2912
+ switch (data.type) {
2913
+ case "offer":
2914
+ await this._offerProcessingMutex.executeSynchronized(async () => {
2915
+ if (isRemoteDescriptionSet(connection, data)) return;
2916
+ if (connection.connectionState !== "new") {
2917
+ this._abortConnection(connection, /* @__PURE__ */ new Error(`Received an offer in ${connection.connectionState}.`));
2918
+ return;
2919
+ }
2920
+ try {
2921
+ await connection.setRemoteDescription({
2922
+ type: data.type,
2923
+ sdp: data.sdp
2924
+ });
2925
+ const answer = await connection.createAnswer();
2926
+ await connection.setLocalDescription(answer);
2927
+ await this._sendDescription(connection, answer);
2928
+ this._onSessionNegotiated(connection);
2929
+ } catch (err) {
2930
+ this._abortConnection(connection, new Error("Error handling a remote offer.", { cause: err }));
2931
+ }
2932
+ });
2933
+ break;
2934
+ case "answer":
2935
+ await this._offerProcessingMutex.executeSynchronized(async () => {
2936
+ try {
2937
+ if (isRemoteDescriptionSet(connection, data)) return;
2938
+ if (connection.signalingState !== "have-local-offer") {
2939
+ this._abortConnection(connection, /* @__PURE__ */ new Error(`Unexpected answer from remote peer, signalingState was ${connection.signalingState}.`));
2940
+ return;
2941
+ }
2942
+ await connection.setRemoteDescription({
2943
+ type: data.type,
2944
+ sdp: data.sdp
2945
+ });
2946
+ this._onSessionNegotiated(connection);
2947
+ } catch (err) {
2948
+ this._abortConnection(connection, new Error("Error handling a remote answer.", { cause: err }));
2949
+ }
2950
+ });
2951
+ break;
2952
+ case "candidate":
2953
+ this._processIceCandidate(connection, data.candidate);
2954
+ break;
2955
+ default:
2956
+ this._abortConnection(connection, /* @__PURE__ */ new Error(`Unknown signal type ${data.type}.`));
2957
+ break;
2958
+ }
2959
+ log("signal processed", { type: data.type }, {
2960
+ "~LogMeta": "~LogMeta",
2961
+ F: __dxlog_file$2,
2962
+ L: 333,
2963
+ S: this
2964
+ });
2965
+ }
2966
+ async _processIceCandidate(connection, candidate) {
2967
+ try {
2968
+ await this._readyForCandidates.wait();
2969
+ if (connection === this._connection) {
2970
+ log("adding ice candidate", { candidate }, {
2971
+ "~LogMeta": "~LogMeta",
2972
+ F: __dxlog_file$2,
2973
+ L: 341,
2974
+ S: this
2975
+ });
2976
+ await connection.addIceCandidate(candidate);
2977
+ }
2978
+ } catch (err) {
2979
+ log.catch(err, void 0, {
2980
+ "~LogMeta": "~LogMeta",
2981
+ F: __dxlog_file$2,
2982
+ L: 345,
2983
+ S: this
2984
+ });
2985
+ }
2986
+ }
2987
+ _onSessionNegotiated(connection) {
2988
+ if (connection === this._connection) {
2989
+ log("ready to process ice candidates", void 0, {
2990
+ "~LogMeta": "~LogMeta",
2991
+ F: __dxlog_file$2,
2992
+ L: 351,
2993
+ S: this
2994
+ });
2995
+ this._readyForCandidates.wake();
2996
+ } else log.warn("session was negotiated after connection became inactive", void 0, {
2997
+ "~LogMeta": "~LogMeta",
2998
+ F: __dxlog_file$2,
2999
+ L: 354,
3000
+ S: this
3001
+ });
3002
+ }
3003
+ _onConnectionCallbackAfterClose(callback, connection) {
3004
+ log.warn("callback invoked after a connection was destroyed, this is probably a bug", {
3005
+ callback,
3006
+ state: connection.connectionState
3007
+ }, {
3008
+ "~LogMeta": "~LogMeta",
3009
+ F: __dxlog_file$2,
3010
+ L: 359,
3011
+ S: this
3012
+ });
3013
+ this._safeCloseConnection(connection);
3014
+ }
3015
+ _safeCloseConnection(connection = this._connection) {
3016
+ const resetFields = this._connection && connection === this._connection;
3017
+ try {
3018
+ connection?.close();
3019
+ } catch (err) {
3020
+ log.catch(err, void 0, {
3021
+ "~LogMeta": "~LogMeta",
3022
+ F: __dxlog_file$2,
3023
+ L: 371,
3024
+ S: this
3025
+ });
3026
+ }
3027
+ if (resetFields) {
3028
+ this._connection = void 0;
3029
+ this._dataChannels.clear();
3030
+ this._readyForCandidates.wake();
3031
+ this._factory.onConnectionDestroyed().catch((err) => log.catch(err, void 0, {
3032
+ "~LogMeta": "~LogMeta",
3033
+ F: __dxlog_file$2,
3034
+ L: 377,
3035
+ S: this
3036
+ }));
3037
+ for (const [_, pendingCallback] of this._channelCreatedCallbacks.entries()) pendingCallback.reject("Connection closed.");
3038
+ this._channelCreatedCallbacks.clear();
3039
+ }
3040
+ }
3041
+ async _loadConnectionConfig() {
3042
+ const config = { ...this._options.webrtcConfig };
3043
+ try {
3044
+ const providedIceServers = await this._options.iceProvider?.getIceServers() ?? [];
3045
+ if (providedIceServers.length > 0) config.iceServers = [...config.iceServers ?? [], ...providedIceServers];
3046
+ } catch (error) {
3047
+ log.catch(error, void 0, {
3048
+ "~LogMeta": "~LogMeta",
3049
+ F: __dxlog_file$2,
3050
+ L: 393,
3051
+ S: this
3052
+ });
3053
+ }
3054
+ return config;
3055
+ }
3056
+ async _sendIceCandidate(candidate) {
3057
+ try {
3058
+ await this._options.sendSignal({ payload: { data: {
3059
+ type: "candidate",
3060
+ candidate: {
3061
+ candidate: candidate.candidate,
3062
+ sdpMLineIndex: candidate.sdpMLineIndex ?? "0",
3063
+ sdpMid: candidate.sdpMid ?? "0"
3064
+ }
3065
+ } } });
3066
+ } catch (err) {
3067
+ log.warn("signaling error", { err }, {
3068
+ "~LogMeta": "~LogMeta",
3069
+ F: __dxlog_file$2,
3070
+ L: 414,
3071
+ S: this
3072
+ });
3073
+ }
3074
+ }
3075
+ async _sendDescription(connection, description) {
3076
+ if (connection !== this._connection) return;
3077
+ const data = {
3078
+ type: description.type,
3079
+ sdp: description.sdp
3080
+ };
3081
+ await this._options.sendSignal({ payload: { data } });
3082
+ }
3083
+ get _connectionInfo() {
3084
+ return {
3085
+ ...this._connection && {
3086
+ connectionState: this._connection.connectionState,
3087
+ iceConnectionState: this._connection.iceConnectionState,
3088
+ iceGatheringState: this._connection.iceGatheringState,
3089
+ signalingState: this._connection.signalingState,
3090
+ remoteDescription: this._connection.remoteDescription,
3091
+ localDescription: this._connection.localDescription
3092
+ },
3093
+ ts: Date.now(),
3094
+ remotePeerKey: this._options.remotePeerKey,
3095
+ channels: [...this._transportChannels.keys()].map((topic) => topic),
3096
+ config: this._connection?.getConfiguration()
3097
+ };
3098
+ }
3099
+ get _loggerContext() {
3100
+ return {
3101
+ ownPeerKey: this._options.ownPeerKey,
3102
+ remotePeerKey: this._options.remotePeerKey,
3103
+ initiator: this._initiator,
3104
+ channels: this._transportChannels.size
3105
+ };
3106
+ }
3107
+ };
3108
+ __decorate([synchronized], RtcPeerConnection.prototype, "_openConnection", null);
3109
+ __decorate([synchronized], RtcPeerConnection.prototype, "_lockAndAbort", null);
3110
+ __decorate([synchronized], RtcPeerConnection.prototype, "_lockAndCloseConnection", null);
3111
+ __decorate([synchronized], RtcPeerConnection.prototype, "onSignal", null);
3112
+ __decorate([logInfo], RtcPeerConnection.prototype, "_loggerContext", null);
3113
+ var isRemoteDescriptionSet = (connection, data) => {
3114
+ if (!connection.remoteDescription?.type || connection.remoteDescription?.type !== data.type) return false;
3115
+ return areSdpEqual(connection.remoteDescription.sdp, data.sdp);
3116
+ };
3117
+ var createIceFailureError = (details) => {
3118
+ return new ConnectivityError({ message: `ICE failed:\n${details.map(({ url, errorCode, errorText }) => `${errorCode} ${url}: ${errorText}`).join("\n")}` });
3119
+ };
3120
+ //#endregion
3121
+ //#region src/transport/webrtc/rtc-transport-factory.ts
3122
+ var createRtcTransportFactory = (webrtcConfig, iceProvider) => {
3123
+ const connectionFactory = getRtcConnectionFactory();
3124
+ return { createTransport: (options) => {
3125
+ return new RtcPeerConnection(connectionFactory, {
3126
+ ownPeerKey: options.ownPeerKey,
3127
+ remotePeerKey: options.remotePeerKey,
3128
+ sendSignal: options.sendSignal,
3129
+ legacyInitiator: options.initiator,
3130
+ webrtcConfig,
3131
+ iceProvider
3132
+ }).createTransportChannel(options);
3133
+ } };
3134
+ };
3135
+ //#endregion
3136
+ //#region src/transport/webrtc/rtc-transport-proxy.ts
3137
+ var __dxlog_file$1 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/transport/webrtc/rtc-transport-proxy.ts";
3138
+ var RPC_TIMEOUT = 1e4;
3139
+ var CLOSE_RPC_TIMEOUT = 3e3;
3140
+ var RESP_MIN_THRESHOLD = 500;
3141
+ var RtcTransportProxy = class extends Resource {
3142
+ _options;
3143
+ _proxyId = PublicKey.random();
3144
+ closed = new Event();
3145
+ connected = new Event();
3146
+ errors = new ErrorStream();
3147
+ _serviceStream;
3148
+ constructor(_options) {
3149
+ super();
3150
+ this._options = _options;
3151
+ }
3152
+ async _open() {
3153
+ let stream;
3154
+ try {
3155
+ stream = this._options.bridgeService.open({
3156
+ proxyId: this._proxyId,
3157
+ remotePeerKey: this._options.remotePeerKey,
3158
+ ownPeerKey: this._options.ownPeerKey,
3159
+ topic: this._options.topic,
3160
+ initiator: this._options.initiator ?? false
3161
+ }, { timeout: RPC_TIMEOUT });
3162
+ } catch (error) {
3163
+ this.errors.raise(error);
3164
+ return;
3165
+ }
3166
+ this._serviceStream = stream;
3167
+ stream.waitUntilReady().then(() => {
3168
+ stream.subscribe(async (event) => {
3169
+ log("rtc transport proxy event", event, {
3170
+ "~LogMeta": "~LogMeta",
3171
+ F: __dxlog_file$1,
3172
+ L: 66,
3173
+ S: this
3174
+ });
3175
+ if (event.connection) await this._handleConnection(event.connection);
3176
+ else if (event.data) this._handleData(event.data);
3177
+ else if (event.signal) await this._handleSignal(event.signal);
3178
+ }, (err) => {
3179
+ log("rtc bridge stream closed", { err }, {
3180
+ "~LogMeta": "~LogMeta",
3181
+ F: __dxlog_file$1,
3182
+ L: 76,
3183
+ S: this
3184
+ });
3185
+ if (err) this._raiseIfOpen(err);
3186
+ else this.close();
3187
+ });
3188
+ const connectorStream = new Writable({ write: (chunk, _, callback) => {
3189
+ const sendStartMs = Date.now();
3190
+ this._options.bridgeService.sendData({
3191
+ proxyId: this._proxyId,
3192
+ payload: chunk
3193
+ }, { timeout: RPC_TIMEOUT }).then(() => {
3194
+ if (Date.now() - sendStartMs > RESP_MIN_THRESHOLD) {
3195
+ log("slow response, delaying callback", void 0, {
3196
+ "~LogMeta": "~LogMeta",
3197
+ F: __dxlog_file$1,
3198
+ L: 93,
3199
+ S: this
3200
+ });
3201
+ scheduleTask(this._ctx, () => callback(), RESP_MIN_THRESHOLD);
3202
+ } else callback();
3203
+ }, (err) => {
3204
+ callback();
3205
+ this._raiseIfOpen(err);
3206
+ });
3207
+ } });
3208
+ connectorStream.on("error", (err) => {
3209
+ this._raiseIfOpen(err);
3210
+ });
3211
+ this._options.stream.pipe(connectorStream);
3212
+ }, (error) => {
3213
+ if (error) this._raiseIfOpen(error);
3214
+ else this.close();
3215
+ });
3216
+ }
3217
+ async _close() {
3218
+ try {
3219
+ await this._serviceStream?.close();
3220
+ this._serviceStream = void 0;
3221
+ } catch (err) {
3222
+ log.catch(err, void 0, {
3223
+ "~LogMeta": "~LogMeta",
3224
+ F: __dxlog_file$1,
3225
+ L: 128,
3226
+ S: this
3227
+ });
3228
+ }
3229
+ try {
3230
+ await this._options.bridgeService.close({ proxyId: this._proxyId }, { timeout: CLOSE_RPC_TIMEOUT });
3231
+ } catch (err) {
3232
+ log.catch(err, void 0, {
3233
+ "~LogMeta": "~LogMeta",
3234
+ F: __dxlog_file$1,
3235
+ L: 134,
3236
+ S: this
3237
+ });
3238
+ }
3239
+ this.closed.emit();
3240
+ }
3241
+ async onSignal(signal) {
3242
+ this._options.bridgeService.sendSignal({
3243
+ proxyId: this._proxyId,
3244
+ signal
3245
+ }, { timeout: RPC_TIMEOUT }).catch((err) => this._raiseIfOpen(decodeError(err)));
3246
+ }
3247
+ async _handleConnection(connectionEvent) {
3248
+ if (connectionEvent.error) {
3249
+ this.errors.raise(decodeError(connectionEvent.error));
3250
+ return;
3251
+ }
3252
+ switch (connectionEvent.state) {
3253
+ case ConnectionState$1.CONNECTED:
3254
+ this.connected.emit();
3255
+ break;
3256
+ case ConnectionState$1.CLOSED:
3257
+ await this.close();
3258
+ break;
3259
+ }
3260
+ }
3261
+ _handleData(dataEvent) {
3262
+ try {
3263
+ this._options.stream.write(arrayToBuffer(dataEvent.payload));
3264
+ } catch (error) {
3265
+ this._raiseIfOpen(error);
3266
+ }
3267
+ }
3268
+ async _handleSignal(signalEvent) {
3269
+ try {
3270
+ await this._options.sendSignal(signalEvent.payload);
3271
+ } catch (error) {
3272
+ const type = signalEvent.payload.payload.data?.type;
3273
+ if (type === "offer" || type === "answer") this._raiseIfOpen(new ConnectivityError({ message: `Session establishment failed: ${type} couldn't be sent.` }));
3274
+ }
3275
+ }
3276
+ async getDetails() {
3277
+ try {
3278
+ return (await this._options.bridgeService.getDetails({ proxyId: this._proxyId }, { timeout: RPC_TIMEOUT })).details;
3279
+ } catch (err) {
3280
+ return "bridge-svc unreachable";
3281
+ }
3282
+ }
3283
+ async getStats() {
3284
+ try {
3285
+ return (await this._options.bridgeService.getStats({ proxyId: this._proxyId }, { timeout: RPC_TIMEOUT })).stats;
3286
+ } catch (err) {
3287
+ return {
3288
+ bytesSent: 0,
3289
+ bytesReceived: 0,
3290
+ packetsSent: 0,
3291
+ packetsReceived: 0,
3292
+ rawStats: "bridge-svc unreachable"
3293
+ };
3294
+ }
3295
+ }
3296
+ _raiseIfOpen(error) {
3297
+ if (this.isOpen) this.errors.raise(error);
3298
+ else log.info("error swallowed because transport was closed", { message: error.message }, {
3299
+ "~LogMeta": "~LogMeta",
3300
+ F: __dxlog_file$1,
3301
+ L: 217,
3302
+ S: this
3303
+ });
3304
+ }
3305
+ /**
3306
+ * Called when underlying proxy service becomes unavailable.
3307
+ */
3308
+ forceClose() {
3309
+ this._serviceStream?.close();
3310
+ this.closed.emit();
3311
+ }
3312
+ };
3313
+ var RtcTransportProxyFactory = class {
3314
+ _bridgeService;
3315
+ _connections = /* @__PURE__ */ new Set();
3316
+ /**
3317
+ * Sets the current BridgeService to be used to open connections.
3318
+ * Calling this method will close any existing connections.
3319
+ */
3320
+ setBridgeService(bridgeService) {
3321
+ this._bridgeService = bridgeService;
3322
+ for (const connection of this._connections) connection.forceClose();
3323
+ return this;
3324
+ }
3325
+ createTransport(options) {
3326
+ invariant(this._bridgeService, "RtcTransportProxyFactory is not ready to open connections", {
3327
+ "~LogMeta": "~LogMeta",
3328
+ F: __dxlog_file$1,
3329
+ L: 247,
3330
+ S: this,
3331
+ A: ["this._bridgeService", "'RtcTransportProxyFactory is not ready to open connections'"]
3332
+ });
3333
+ const transport = new RtcTransportProxy({
3334
+ ...options,
3335
+ bridgeService: this._bridgeService
3336
+ });
3337
+ this._connections.add(transport);
3338
+ transport.closed.on(() => {
3339
+ this._connections.delete(transport);
3340
+ });
3341
+ return transport;
3342
+ }
3343
+ };
3344
+ var decodeError = (err) => {
3345
+ const message = typeof err === "string" ? err : err.message;
3346
+ if (message.includes("CONNECTION_RESET")) return new ConnectionResetError({ message });
3347
+ else if (message.includes("TIMEOUT")) return new TimeoutError({ message });
3348
+ else if (message.includes("CONNECTIVITY_ERROR")) return new ConnectivityError({ message });
3349
+ else return typeof err === "string" ? new Error(err) : err;
3350
+ };
3351
+ //#endregion
3352
+ //#region src/transport/webrtc/rtc-transport-service.ts
3353
+ var __dxlog_file = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/transport/webrtc/rtc-transport-service.ts";
3354
+ var RtcTransportService = class {
3355
+ _transportFactory;
3356
+ _openTransports = new ComplexMap(PublicKey.hash);
3357
+ constructor(webrtcConfig, iceProvider, _transportFactory = createRtcTransportFactory(webrtcConfig, iceProvider)) {
3358
+ this._transportFactory = _transportFactory;
3359
+ }
3360
+ hasOpenTransports() {
3361
+ return this._openTransports.size > 0;
3362
+ }
3363
+ open(request) {
3364
+ const existingTransport = this._openTransports.get(request.proxyId);
3365
+ if (existingTransport) {
3366
+ log.error("requesting a new transport bridge for an existing proxy", void 0, {
3367
+ "~LogMeta": "~LogMeta",
3368
+ F: __dxlog_file,
3369
+ L: 53,
3370
+ S: this
3371
+ });
3372
+ this._safeCloseTransport(existingTransport);
3373
+ this._openTransports.delete(request.proxyId);
3374
+ }
3375
+ return new Stream(({ ready, next, close }) => {
3376
+ const pushNewState = createStateUpdater(next);
3377
+ const transportStream = new Duplex({
3378
+ read: () => {
3379
+ const callbacks = [...transportState.writeProcessedCallbacks];
3380
+ transportState.writeProcessedCallbacks.length = 0;
3381
+ callbacks.forEach((cb) => cb());
3382
+ },
3383
+ write: function(chunk, _, callback) {
3384
+ next({ data: { payload: chunk } });
3385
+ callback();
3386
+ }
3387
+ });
3388
+ const transport = this._transportFactory.createTransport({
3389
+ initiator: request.initiator,
3390
+ topic: request.topic,
3391
+ ownPeerKey: request.ownPeerKey,
3392
+ remotePeerKey: request.remotePeerKey,
3393
+ stream: transportStream,
3394
+ sendSignal: async (signal) => {
3395
+ next({ signal: { payload: signal } });
3396
+ }
3397
+ });
3398
+ const transportState = {
3399
+ proxyId: request.proxyId,
3400
+ transport,
3401
+ connectorStream: transportStream,
3402
+ writeProcessedCallbacks: []
3403
+ };
3404
+ transport.connected.on(() => pushNewState(ConnectionState$1.CONNECTED));
3405
+ transport.errors.handle(async (err) => {
3406
+ pushNewState(ConnectionState$1.CLOSED, err);
3407
+ this._safeCloseTransport(transportState);
3408
+ close(err);
3409
+ });
3410
+ transport.closed.on(async () => {
3411
+ pushNewState(ConnectionState$1.CLOSED);
3412
+ this._safeCloseTransport(transportState);
3413
+ close();
3414
+ });
3415
+ this._openTransports.set(request.proxyId, transportState);
3416
+ transport.open().catch(async (err) => {
3417
+ pushNewState(ConnectionState$1.CLOSED, err);
3418
+ this._safeCloseTransport(transportState);
3419
+ close(err);
3420
+ });
3421
+ ready();
3422
+ log("stream ready", void 0, {
3423
+ "~LogMeta": "~LogMeta",
3424
+ F: __dxlog_file,
3425
+ L: 115,
3426
+ S: this
3427
+ });
3428
+ pushNewState(ConnectionState$1.CONNECTING);
3429
+ });
3430
+ }
3431
+ async sendSignal({ proxyId, signal }) {
3432
+ const transport = this._openTransports.get(proxyId);
3433
+ invariant(transport, void 0, {
3434
+ "~LogMeta": "~LogMeta",
3435
+ F: __dxlog_file,
3436
+ L: 123,
3437
+ S: this,
3438
+ A: ["transport", ""]
3439
+ });
3440
+ await transport.transport.onSignal(signal);
3441
+ }
3442
+ async getDetails({ proxyId }) {
3443
+ const transport = this._openTransports.get(proxyId);
3444
+ invariant(transport, void 0, {
3445
+ "~LogMeta": "~LogMeta",
3446
+ F: __dxlog_file,
3447
+ L: 130,
3448
+ S: this,
3449
+ A: ["transport", ""]
3450
+ });
3451
+ return { details: await transport.transport.getDetails() };
3452
+ }
3453
+ async getStats({ proxyId }) {
3454
+ const transport = this._openTransports.get(proxyId);
3455
+ invariant(transport, void 0, {
3456
+ "~LogMeta": "~LogMeta",
3457
+ F: __dxlog_file,
3458
+ L: 137,
3459
+ S: this,
3460
+ A: ["transport", ""]
3461
+ });
3462
+ return { stats: await transport.transport.getStats() };
3463
+ }
3464
+ async sendData({ proxyId, payload }) {
3465
+ const transport = this._openTransports.get(proxyId);
3466
+ invariant(transport, void 0, {
3467
+ "~LogMeta": "~LogMeta",
3468
+ F: __dxlog_file,
3469
+ L: 144,
3470
+ S: this,
3471
+ A: ["transport", ""]
3472
+ });
3473
+ if (!transport.connectorStream.push(payload)) await new Promise((resolve) => {
3474
+ transport.writeProcessedCallbacks.push(resolve);
3475
+ });
3476
+ }
3477
+ async close({ proxyId }) {
3478
+ const transport = this._openTransports.get(proxyId);
3479
+ if (!transport) return;
3480
+ this._openTransports.delete(proxyId);
3481
+ await this._safeCloseTransport(transport);
3482
+ }
3483
+ async _safeCloseTransport(transport) {
3484
+ if (this._openTransports.get(transport.proxyId) === transport) this._openTransports.delete(transport.proxyId);
3485
+ transport.writeProcessedCallbacks.forEach((cb) => cb());
3486
+ try {
3487
+ await transport.transport.close();
3488
+ } catch (error) {
3489
+ log.warn("transport close error", { message: error?.message }, {
3490
+ "~LogMeta": "~LogMeta",
3491
+ F: __dxlog_file,
3492
+ L: 174,
3493
+ S: this
3494
+ });
3495
+ }
3496
+ try {
3497
+ transport.connectorStream.end();
3498
+ } catch (error) {
3499
+ log.warn("connectorStream close error", { message: error?.message }, {
3500
+ "~LogMeta": "~LogMeta",
3501
+ F: __dxlog_file,
3502
+ L: 179,
3503
+ S: this
3504
+ });
3505
+ }
3506
+ log("closed", void 0, {
3507
+ "~LogMeta": "~LogMeta",
3508
+ F: __dxlog_file,
3509
+ L: 181,
3510
+ S: this
3511
+ });
3512
+ }
3513
+ };
3514
+ var createStateUpdater = (next) => {
3515
+ return (state, err) => {
3516
+ next({ connection: {
3517
+ state,
3518
+ ...err ? { error: err.message } : void 0
3519
+ } });
3520
+ };
3521
+ };
3522
+ //#endregion
3523
+ //#region src/wire-protocol.ts
3524
+ /**
3525
+ * Create a wire-protocol provider backed by a teleport instance.
3526
+ * @param onConnection Called after teleport is initialized for the session. Protocol extensions could be attached here.
3527
+ * @param defaultProps Optionally provide default Teleport params that might be overridden by factory callers.
3528
+ * @returns
3529
+ */
3530
+ var createTeleportProtocolFactory = (onConnection, defaultProps) => {
3531
+ return (params) => {
3532
+ const teleport = new Teleport({
3533
+ ...defaultProps,
3534
+ ...params
3535
+ });
3536
+ return {
3537
+ stream: teleport.stream,
3538
+ open: async (sessionId) => {
3539
+ await teleport.open(sessionId);
3540
+ await onConnection(teleport);
3541
+ },
3542
+ close: async () => {
3543
+ await teleport.close();
3544
+ },
3545
+ abort: async () => {
3546
+ await teleport.abort();
3547
+ }
3548
+ };
3549
+ };
3550
+ };
3551
+ //#endregion
3552
+ export { Swarm as _, createRtcTransportFactory as a, ConnectionState$2 as b, MemoryTransportFactory as c, SwarmNetworkManagerService as d, ConnectionLog as f, SwarmMapper as g, MAX_CONCURRENT_INITIATING_CONNECTIONS as h, RtcTransportProxyFactory as i, FullyConnectedTopology as l, ConnectionLimiter as m, RtcTransportService as n, TransportKind as o, EventType as p, RtcTransportProxy as r, MemoryTransport as s, createTeleportProtocolFactory as t, SwarmNetworkManager as u, SwarmMessenger as v, Connection as y };
3553
+
3554
+ //# sourceMappingURL=chunk-wire-protocol.mjs.map