@dxos/network-manager 0.1.19 → 0.1.21

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 (35) hide show
  1. package/dist/lib/browser/index.mjs +279 -116
  2. package/dist/lib/browser/index.mjs.map +3 -3
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/browser/testing/index.mjs +288 -116
  5. package/dist/lib/browser/testing/index.mjs.map +3 -3
  6. package/dist/lib/node/index.cjs +274 -107
  7. package/dist/lib/node/index.cjs.map +3 -3
  8. package/dist/lib/node/meta.json +1 -1
  9. package/dist/lib/node/testing/index.cjs +283 -107
  10. package/dist/lib/node/testing/index.cjs.map +3 -3
  11. package/dist/types/src/network-manager.d.ts +6 -0
  12. package/dist/types/src/network-manager.d.ts.map +1 -1
  13. package/dist/types/src/swarm/peer.d.ts.map +1 -1
  14. package/dist/types/src/swarm/swarm.d.ts +4 -2
  15. package/dist/types/src/swarm/swarm.d.ts.map +1 -1
  16. package/dist/types/src/testing/test-builder.d.ts +2 -0
  17. package/dist/types/src/testing/test-builder.d.ts.map +1 -1
  18. package/dist/types/src/tests/basic-test-suite.d.ts.map +1 -1
  19. package/dist/types/src/transport/webrtc-transport-proxy.d.ts +2 -1
  20. package/dist/types/src/transport/webrtc-transport-proxy.d.ts.map +1 -1
  21. package/dist/types/src/transport/webrtc-transport.d.ts +1 -0
  22. package/dist/types/src/transport/webrtc-transport.d.ts.map +1 -1
  23. package/package.json +16 -16
  24. package/src/network-manager.ts +44 -3
  25. package/src/signal/integration.test.ts +2 -0
  26. package/src/signal/message-router.test.ts +1 -1
  27. package/src/swarm/peer.ts +2 -1
  28. package/src/swarm/swarm.test.ts +1 -1
  29. package/src/swarm/swarm.ts +86 -62
  30. package/src/testing/test-builder.ts +9 -0
  31. package/src/tests/basic-test-suite.ts +31 -4
  32. package/src/tests/webrtc-transport.test.ts +3 -0
  33. package/src/transport/webrtc-transport-proxy.test.ts +2 -1
  34. package/src/transport/webrtc-transport-proxy.ts +9 -3
  35. package/src/transport/webrtc-transport.ts +6 -1
@@ -11,6 +11,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
11
11
  import { PublicKey as PublicKey11 } from "@dxos/keys";
12
12
  import { MemorySignalManager, MemorySignalManagerContext, WebsocketSignalManager } from "@dxos/messaging";
13
13
  import { schema as schema2 } from "@dxos/protocols";
14
+ import { ConnectionState as ConnectionState5 } from "@dxos/protocols/proto/dxos/client/services";
14
15
  import { createLinkedPorts, createProtoRpcPeer } from "@dxos/rpc";
15
16
  import { ComplexMap as ComplexMap9 } from "@dxos/util";
16
17
 
@@ -20,6 +21,7 @@ import { Event as Event5 } from "@dxos/async";
20
21
  import { PublicKey as PublicKey6 } from "@dxos/keys";
21
22
  import { log as log6 } from "@dxos/log";
22
23
  import { Messenger } from "@dxos/messaging";
24
+ import { ConnectionState as ConnectionState2 } from "@dxos/protocols/proto/dxos/client/services";
23
25
  import { ComplexMap as ComplexMap5 } from "@dxos/util";
24
26
 
25
27
  // packages/core/mesh/network-manager/src/connection-log.ts
@@ -44,12 +46,38 @@ var __decorate = function(decorators, target, key, desc) {
44
46
  return c > 3 && r && Object.defineProperty(target, key, r), r;
45
47
  };
46
48
  var ConnectionState;
47
- (function(ConnectionState4) {
48
- ConnectionState4["INITIAL"] = "INITIAL";
49
- ConnectionState4["CONNECTING"] = "CONNECTING";
50
- ConnectionState4["CONNECTED"] = "CONNECTED";
51
- ConnectionState4["CLOSING"] = "CLOSING";
52
- ConnectionState4["CLOSED"] = "CLOSED";
49
+ (function(ConnectionState6) {
50
+ ConnectionState6[
51
+ /**
52
+ * Initial state. Connection is registered but no attempt to connect to the remote peer has been performed.
53
+ * Might mean that we are waiting for the answer signal from the remote peer.
54
+ */
55
+ "INITIAL"
56
+ ] = "INITIAL";
57
+ ConnectionState6[
58
+ /**
59
+ * Trying to establish connection.
60
+ */
61
+ "CONNECTING"
62
+ ] = "CONNECTING";
63
+ ConnectionState6[
64
+ /**
65
+ * Connection is established.
66
+ */
67
+ "CONNECTED"
68
+ ] = "CONNECTED";
69
+ ConnectionState6[
70
+ /**
71
+ * Connection is being closed.
72
+ */
73
+ "CLOSING"
74
+ ] = "CLOSING";
75
+ ConnectionState6[
76
+ /**
77
+ * Connection closed.
78
+ */
79
+ "CLOSED"
80
+ ] = "CLOSED";
53
81
  })(ConnectionState || (ConnectionState = {}));
54
82
  var Connection = class {
55
83
  constructor(topic, ownId, remoteId, sessionId, initiator, _signalMessaging, _protocol, _transportFactory) {
@@ -75,6 +103,10 @@ var Connection = class {
75
103
  get protocol() {
76
104
  return this._protocol;
77
105
  }
106
+ /**
107
+ * Create an underlying transport and prepares it for the connection.
108
+ */
109
+ // TODO(burdon): Make async?
78
110
  openConnection() {
79
111
  assert(this._state === ConnectionState.INITIAL, "Invalid state.");
80
112
  this._changeState(ConnectionState.CONNECTING);
@@ -212,7 +244,7 @@ __decorate([
212
244
 
213
245
  // packages/core/mesh/network-manager/src/swarm/swarm.ts
214
246
  import assert4 from "@dxos/node-std/assert";
215
- import { Event as Event2, scheduleTask, sleep } from "@dxos/async";
247
+ import { Event as Event2, scheduleTask, sleep, synchronized as synchronized3 } from "@dxos/async";
216
248
  import { Context } from "@dxos/context";
217
249
  import { ErrorStream as ErrorStream2 } from "@dxos/debug";
218
250
  import { PublicKey as PublicKey3 } from "@dxos/keys";
@@ -298,6 +330,7 @@ var MessageRouter = class {
298
330
  var _a;
299
331
  const networkMessage = {
300
332
  ...message,
333
+ // Setting unique message_id if it not specified yet.
301
334
  messageId: (_a = message.messageId) != null ? _a : PublicKey.random()
302
335
  };
303
336
  log2("sending", {
@@ -410,17 +443,20 @@ var Peer = class {
410
443
  this.advertizing = false;
411
444
  this.initiating = false;
412
445
  }
446
+ /**
447
+ * Respond to remote offer.
448
+ */
413
449
  async onOffer(message) {
414
450
  const remoteId = message.author;
415
451
  if (this.connection || this.initiating) {
416
452
  if (remoteId.toHex() < this.localPeerId.toHex()) {
417
- log3.info("closing local connection and accepting remote peer's offer", {
453
+ log3("closing local connection and accepting remote peer's offer", {
418
454
  id: this.id,
419
455
  topic: this.topic,
420
456
  peerId: this.localPeerId
421
457
  }, {
422
458
  file: "peer.ts",
423
- line: 83,
459
+ line: 84,
424
460
  scope: this,
425
461
  callSite: (f, a) => f(...a)
426
462
  });
@@ -447,7 +483,7 @@ var Peer = class {
447
483
  err
448
484
  }, {
449
485
  file: "peer.ts",
450
- line: 107,
486
+ line: 108,
451
487
  scope: this,
452
488
  callSite: (f, a) => f(...a)
453
489
  });
@@ -462,6 +498,9 @@ var Peer = class {
462
498
  accept: false
463
499
  };
464
500
  }
501
+ /**
502
+ * Initiate a connection to the remote peer.
503
+ */
465
504
  async initiateConnection() {
466
505
  assert3(!this.initiating, "Initiation in progress.");
467
506
  assert3(!this.connection, "Already connected.");
@@ -473,7 +512,7 @@ var Peer = class {
473
512
  sessionId
474
513
  }, {
475
514
  file: "peer.ts",
476
- line: 125,
515
+ line: 126,
477
516
  scope: this,
478
517
  callSite: (f, a) => f(...a)
479
518
  });
@@ -496,14 +535,14 @@ var Peer = class {
496
535
  remoteId: this.id
497
536
  }, {
498
537
  file: "peer.ts",
499
- line: 137,
538
+ line: 138,
500
539
  scope: this,
501
540
  callSite: (f, a) => f(...a)
502
541
  });
503
542
  if (connection.state !== ConnectionState.INITIAL) {
504
543
  log3("ignoring response", {}, {
505
544
  file: "peer.ts",
506
- line: 139,
545
+ line: 140,
507
546
  scope: this,
508
547
  callSite: (f, a) => f(...a)
509
548
  });
@@ -523,7 +562,7 @@ var Peer = class {
523
562
  err
524
563
  }, {
525
564
  file: "peer.ts",
526
- line: 150,
565
+ line: 151,
527
566
  scope: this,
528
567
  callSite: (f, a) => f(...a)
529
568
  });
@@ -533,6 +572,10 @@ var Peer = class {
533
572
  this.initiating = false;
534
573
  }
535
574
  }
575
+ /**
576
+ * Create new connection.
577
+ * Either we're initiating a connection or creating one in response to an offer from the other peer.
578
+ */
536
579
  _createConnection(initiator, sessionId) {
537
580
  log3("creating connection", {
538
581
  topic: this.topic,
@@ -542,7 +585,7 @@ var Peer = class {
542
585
  sessionId
543
586
  }, {
544
587
  file: "peer.ts",
545
- line: 164,
588
+ line: 165,
546
589
  scope: this,
547
590
  callSite: (f, a) => f(...a)
548
591
  });
@@ -554,6 +597,7 @@ var Peer = class {
554
597
  sessionId,
555
598
  initiator,
556
599
  this._signalMessaging,
600
+ // TODO(dmaretskyi): Init only when connection is established.
557
601
  this._protocolProvider({
558
602
  initiator,
559
603
  localPeerId: this.localPeerId,
@@ -577,7 +621,7 @@ var Peer = class {
577
621
  initiator
578
622
  }, {
579
623
  file: "peer.ts",
580
- line: 193,
624
+ line: 194,
581
625
  scope: this,
582
626
  callSite: (f, a) => f(...a)
583
627
  });
@@ -597,7 +641,7 @@ var Peer = class {
597
641
  err
598
642
  }, {
599
643
  file: "peer.ts",
600
- line: 203,
644
+ line: 204,
601
645
  scope: this,
602
646
  callSite: (f, a) => f(...a)
603
647
  });
@@ -616,7 +660,7 @@ var Peer = class {
616
660
  sessionId: connection.sessionId
617
661
  }, {
618
662
  file: "peer.ts",
619
- line: 219,
663
+ line: 220,
620
664
  scope: this,
621
665
  callSite: (f, a) => f(...a)
622
666
  });
@@ -626,7 +670,7 @@ var Peer = class {
626
670
  sessionId: connection.sessionId
627
671
  }, {
628
672
  file: "peer.ts",
629
- line: 225,
673
+ line: 226,
630
674
  scope: this,
631
675
  callSite: (f, a) => f(...a)
632
676
  });
@@ -637,7 +681,7 @@ var Peer = class {
637
681
  message
638
682
  }, {
639
683
  file: "peer.ts",
640
- line: 230,
684
+ line: 231,
641
685
  scope: this,
642
686
  callSite: (f, a) => f(...a)
643
687
  });
@@ -652,7 +696,7 @@ var Peer = class {
652
696
  topic: this.topic
653
697
  }, {
654
698
  file: "peer.ts",
655
- line: 238,
699
+ line: 239,
656
700
  scope: this,
657
701
  callSite: (f, a) => f(...a)
658
702
  });
@@ -677,6 +721,8 @@ var __decorate3 = function(decorators, target, key, desc) {
677
721
  var INITIATION_DELAY = 100;
678
722
  var getClassName = (obj) => Object.getPrototypeOf(obj).constructor.name;
679
723
  var Swarm = class {
724
+ // TODO(burdon): Swarm => Peer.create/destroy =< Connection.open/close
725
+ // TODO(burdon): Split up properties.
680
726
  constructor(_topic, _ownPeerId, _topology, _protocolProvider, _messenger, _transportFactory, _label) {
681
727
  this._topic = _topic;
682
728
  this._ownPeerId = _ownPeerId;
@@ -724,12 +770,16 @@ var Swarm = class {
724
770
  get ownPeerId() {
725
771
  return this._ownPeerId;
726
772
  }
773
+ /**
774
+ * Custom label assigned to this swarm. Used in devtools to display human-readable names for swarms.
775
+ */
727
776
  get label() {
728
777
  return this._label;
729
778
  }
730
779
  get topic() {
731
780
  return this._topic;
732
781
  }
782
+ // TODO(burdon): async open?
733
783
  async destroy() {
734
784
  log4("destroying...", {}, {
735
785
  file: "swarm.ts",
@@ -748,6 +798,7 @@ var Swarm = class {
748
798
  });
749
799
  }
750
800
  async setTopology(topology) {
801
+ assert4(!this._ctx.disposed, "Swarm is offline");
751
802
  if (topology === this._topology) {
752
803
  return;
753
804
  }
@@ -756,7 +807,7 @@ var Swarm = class {
756
807
  topology: getClassName(topology)
757
808
  }, {
758
809
  file: "swarm.ts",
759
- line: 135,
810
+ line: 136,
760
811
  scope: this,
761
812
  callSite: (f, a) => f(...a)
762
813
  });
@@ -765,69 +816,31 @@ var Swarm = class {
765
816
  this._topology.init(this._getSwarmController());
766
817
  this._topology.update();
767
818
  }
768
- _getOrCreatePeer(peerId) {
769
- let peer = this._peers.get(peerId);
770
- if (!peer) {
771
- peer = new Peer(peerId, this._topic, this._ownPeerId, this._swarmMessenger, this._protocolProvider, this._transportFactory, {
772
- onInitiated: (connection) => {
773
- this.connectionAdded.emit(connection);
774
- },
775
- onConnected: () => {
776
- this.connected.emit(peerId);
777
- },
778
- onDisconnected: async () => {
779
- if (!peer.advertizing) {
780
- await this._destroyPeer(peer.id);
781
- }
782
- this.disconnected.emit(peerId);
783
- this._topology.update();
784
- },
785
- onRejected: () => {
786
- if (this._peers.has(peerId)) {
787
- void this._destroyPeer(peerId);
788
- }
789
- },
790
- onAccepted: () => {
791
- this._topology.update();
792
- },
793
- onOffer: (remoteId) => {
794
- return this._topology.onOffer(remoteId);
795
- }
796
- });
797
- this._peers.set(peerId, peer);
798
- }
799
- return peer;
800
- }
801
- async _destroyPeer(peerId) {
802
- assert4(this._peers.has(peerId));
803
- await this._peers.get(peerId).destroy();
804
- this._peers.delete(peerId);
805
- }
806
819
  onSwarmEvent(swarmEvent) {
807
820
  log4("swarm event", {
808
821
  swarmEvent
809
822
  }, {
810
823
  file: "swarm.ts",
811
- line: 199,
824
+ line: 149,
812
825
  scope: this,
813
826
  callSite: (f, a) => f(...a)
814
827
  });
815
- if (this._ctx.disposed) {
816
- log4.warn("ignored for destroyed swarm", {}, {
817
- file: "swarm.ts",
818
- line: 201,
819
- scope: this,
820
- callSite: (f, a) => f(...a)
821
- });
822
- return;
823
- }
824
828
  if (swarmEvent.peerAvailable) {
829
+ if (this._ctx.disposed) {
830
+ log4.warn("ignored for offline swarm", {}, {
831
+ file: "swarm.ts",
832
+ line: 153,
833
+ scope: this,
834
+ callSite: (f, a) => f(...a)
835
+ });
836
+ return;
837
+ }
825
838
  const peerId = PublicKey3.from(swarmEvent.peerAvailable.peer);
826
839
  log4("new peer", {
827
840
  peerId
828
841
  }, {
829
842
  file: "swarm.ts",
830
- line: 207,
843
+ line: 157,
831
844
  scope: this,
832
845
  callSite: (f, a) => f(...a)
833
846
  });
@@ -839,8 +852,13 @@ var Swarm = class {
839
852
  const peer1 = this._peers.get(PublicKey3.from(swarmEvent.peerLeft.peer));
840
853
  if (peer1) {
841
854
  peer1.advertizing = false;
842
- if (!peer1.connection) {
843
- void this._destroyPeer(peer1.id);
855
+ if (!peer1.connection || peer1.connection.state !== ConnectionState.CONNECTED) {
856
+ void this._destroyPeer(peer1.id).catch((err) => log4.catch(err, {}, {
857
+ file: "swarm.ts",
858
+ line: 168,
859
+ scope: this,
860
+ callSite: (f, a) => f(...a)
861
+ }));
844
862
  }
845
863
  }
846
864
  }
@@ -852,14 +870,14 @@ var Swarm = class {
852
870
  message
853
871
  }, {
854
872
  file: "swarm.ts",
855
- line: 226,
873
+ line: 178,
856
874
  scope: this,
857
875
  callSite: (f, a) => f(...a)
858
876
  });
859
877
  if (this._ctx.disposed) {
860
- log4.info("ignored for destroyed swarm", {}, {
878
+ log4.info("ignored for offline swarm", {}, {
861
879
  file: "swarm.ts",
862
- line: 228,
880
+ line: 180,
863
881
  scope: this,
864
882
  callSite: (f, a) => f(...a)
865
883
  });
@@ -873,7 +891,7 @@ var Swarm = class {
873
891
  message
874
892
  }, {
875
893
  file: "swarm.ts",
876
- line: 235,
894
+ line: 187,
877
895
  scope: this,
878
896
  callSite: (f, a) => f(...a)
879
897
  });
@@ -886,7 +904,7 @@ var Swarm = class {
886
904
  message
887
905
  }, {
888
906
  file: "swarm.ts",
889
- line: 239,
907
+ line: 191,
890
908
  scope: this,
891
909
  callSite: (f, a) => f(...a)
892
910
  });
@@ -905,14 +923,14 @@ var Swarm = class {
905
923
  message
906
924
  }, {
907
925
  file: "swarm.ts",
908
- line: 250,
926
+ line: 202,
909
927
  scope: this,
910
928
  callSite: (f, a) => f(...a)
911
929
  });
912
930
  if (this._ctx.disposed) {
913
- log4.info("ignored for destroyed swarm", {}, {
931
+ log4.info("ignored for offline swarm", {}, {
914
932
  file: "swarm.ts",
915
- line: 252,
933
+ line: 204,
916
934
  scope: this,
917
935
  callSite: (f, a) => f(...a)
918
936
  });
@@ -924,6 +942,66 @@ var Swarm = class {
924
942
  const peer = this._getOrCreatePeer(message.author);
925
943
  await peer.onSignal(message);
926
944
  }
945
+ // For debug purposes
946
+ async goOffline() {
947
+ await this._ctx.dispose();
948
+ [
949
+ ...this._peers.values()
950
+ ].forEach((peer) => {
951
+ peer.advertizing = false;
952
+ });
953
+ await Promise.all([
954
+ ...this._peers.keys()
955
+ ].map((peerId) => this._closeConnection(peerId)));
956
+ }
957
+ // For debug purposes
958
+ async goOnline() {
959
+ this._ctx = new Context();
960
+ [
961
+ ...this._peers.values()
962
+ ].forEach((peer) => {
963
+ peer.advertizing = true;
964
+ });
965
+ this._topology.update();
966
+ }
967
+ _getOrCreatePeer(peerId) {
968
+ let peer = this._peers.get(peerId);
969
+ if (!peer) {
970
+ peer = new Peer(peerId, this._topic, this._ownPeerId, this._swarmMessenger, this._protocolProvider, this._transportFactory, {
971
+ onInitiated: (connection) => {
972
+ this.connectionAdded.emit(connection);
973
+ },
974
+ onConnected: () => {
975
+ this.connected.emit(peerId);
976
+ },
977
+ onDisconnected: async () => {
978
+ if (!peer.advertizing) {
979
+ await this._destroyPeer(peer.id);
980
+ }
981
+ this.disconnected.emit(peerId);
982
+ this._topology.update();
983
+ },
984
+ onRejected: () => {
985
+ if (this._peers.has(peerId)) {
986
+ void this._destroyPeer(peerId);
987
+ }
988
+ },
989
+ onAccepted: () => {
990
+ this._topology.update();
991
+ },
992
+ onOffer: (remoteId) => {
993
+ return this._topology.onOffer(remoteId);
994
+ }
995
+ });
996
+ this._peers.set(peerId, peer);
997
+ }
998
+ return peer;
999
+ }
1000
+ async _destroyPeer(peerId) {
1001
+ assert4(this._peers.has(peerId));
1002
+ await this._peers.get(peerId).destroy();
1003
+ this._peers.delete(peerId);
1004
+ }
927
1005
  _getSwarmController() {
928
1006
  return {
929
1007
  getState: () => ({
@@ -941,7 +1019,7 @@ var Swarm = class {
941
1019
  } catch (err) {
942
1020
  log4.warn("initiation error", err, {
943
1021
  file: "swarm.ts",
944
- line: 287,
1022
+ line: 311,
945
1023
  scope: this,
946
1024
  callSite: (f, a) => f(...a)
947
1025
  });
@@ -959,13 +1037,16 @@ var Swarm = class {
959
1037
  }
960
1038
  };
961
1039
  }
1040
+ /**
1041
+ * Creates a connection then sends message over signal network.
1042
+ */
962
1043
  async _initiateConnection(remoteId) {
963
1044
  if (remoteId.toHex() < this._ownPeerId.toHex()) {
964
1045
  log4("initiation delay", {
965
1046
  remoteId
966
1047
  }, {
967
1048
  file: "swarm.ts",
968
- line: 312,
1049
+ line: 336,
969
1050
  scope: this,
970
1051
  callSite: (f, a) => f(...a)
971
1052
  });
@@ -982,7 +1063,7 @@ var Swarm = class {
982
1063
  remoteId
983
1064
  }, {
984
1065
  file: "swarm.ts",
985
- line: 326,
1066
+ line: 350,
986
1067
  scope: this,
987
1068
  callSite: (f, a) => f(...a)
988
1069
  });
@@ -992,7 +1073,7 @@ var Swarm = class {
992
1073
  remoteId
993
1074
  }, {
994
1075
  file: "swarm.ts",
995
- line: 329,
1076
+ line: 353,
996
1077
  scope: this,
997
1078
  callSite: (f, a) => f(...a)
998
1079
  });
@@ -1014,6 +1095,15 @@ __decorate3([
1014
1095
  __decorate3([
1015
1096
  logInfo
1016
1097
  ], Swarm.prototype, "topic", null);
1098
+ __decorate3([
1099
+ synchronized3
1100
+ ], Swarm.prototype, "onSwarmEvent", null);
1101
+ __decorate3([
1102
+ synchronized3
1103
+ ], Swarm.prototype, "onOffer", null);
1104
+ __decorate3([
1105
+ synchronized3
1106
+ ], Swarm.prototype, "goOnline", null);
1017
1107
 
1018
1108
  // packages/core/mesh/network-manager/src/swarm/swarm-mapper.ts
1019
1109
  import { Event as Event3, EventSubscriptions } from "@dxos/async";
@@ -1024,6 +1114,7 @@ var SwarmMapper = class {
1024
1114
  get peers() {
1025
1115
  return Array.from(this._peers.values());
1026
1116
  }
1117
+ // prettier-ignore
1027
1118
  constructor(_swarm) {
1028
1119
  this._swarm = _swarm;
1029
1120
  this._subscriptions = new EventSubscriptions();
@@ -1077,6 +1168,7 @@ var SwarmMapper = class {
1077
1168
  });
1078
1169
  this.mapUpdated.emit(Array.from(this._peers.values()));
1079
1170
  }
1171
+ // TODO(burdon): Async open/close.
1080
1172
  destroy() {
1081
1173
  Array.from(this._connectionSubscriptions.values()).forEach((cb) => cb());
1082
1174
  this._subscriptions.clear();
@@ -1094,6 +1186,9 @@ var EventType;
1094
1186
  })(EventType || (EventType = {}));
1095
1187
  var ConnectionLog = class {
1096
1188
  constructor() {
1189
+ /**
1190
+ * SwarmId => info
1191
+ */
1097
1192
  this._swarms = new ComplexMap4(PublicKey5.hash);
1098
1193
  this.update = new Event4();
1099
1194
  }
@@ -1146,6 +1241,8 @@ var NetworkManager = class {
1146
1241
  constructor({ transportFactory, signalManager, log: log14 }) {
1147
1242
  this._swarms = new ComplexMap5(PublicKey6.hash);
1148
1243
  this._mappers = new ComplexMap5(PublicKey6.hash);
1244
+ this._connectionState = ConnectionState2.ONLINE;
1245
+ this.connectionStateChanged = new Event5();
1149
1246
  this.topicsUpdated = new Event5();
1150
1247
  this._transportFactory = transportFactory;
1151
1248
  this._signalManager = signalManager;
@@ -1164,12 +1261,18 @@ var NetworkManager = class {
1164
1261
  this._connectionLog = new ConnectionLog();
1165
1262
  }
1166
1263
  }
1264
+ // TODO(burdon): Remove access (Devtools only).
1167
1265
  get connectionLog() {
1168
1266
  return this._connectionLog;
1169
1267
  }
1268
+ // TODO(burdon): Remove access (Devtools only).
1170
1269
  get signalManager() {
1171
1270
  return this._signalManager;
1172
1271
  }
1272
+ get connectionState() {
1273
+ return this._connectionState;
1274
+ }
1275
+ // TODO(burdon): Reconcile with "discovery_key".
1173
1276
  get topics() {
1174
1277
  return Array.from(this._swarms.keys());
1175
1278
  }
@@ -1179,19 +1282,27 @@ var NetworkManager = class {
1179
1282
  getSwarm(topic) {
1180
1283
  return this._swarms.get(topic);
1181
1284
  }
1285
+ async open() {
1286
+ await this._messenger.open();
1287
+ await this._signalManager.open();
1288
+ }
1182
1289
  async close() {
1183
1290
  for (const topic of this._swarms.keys()) {
1184
1291
  await this.leaveSwarm(topic).catch((err) => {
1185
1292
  log6(err, {}, {
1186
1293
  file: "network-manager.ts",
1187
- line: 122,
1294
+ line: 136,
1188
1295
  scope: this,
1189
1296
  callSite: (f, a) => f(...a)
1190
1297
  });
1191
1298
  });
1192
1299
  }
1193
- await this._signalManager.destroy();
1300
+ await this._messenger.close();
1301
+ await this._signalManager.close();
1194
1302
  }
1303
+ /**
1304
+ * Join the swarm.
1305
+ */
1195
1306
  async joinSwarm({ topic, peerId, topology, protocolProvider: protocol, label }) {
1196
1307
  var _a;
1197
1308
  assert5(PublicKey6.isPublicKey(topic));
@@ -1207,7 +1318,7 @@ var NetworkManager = class {
1207
1318
  topology: topology.toString()
1208
1319
  }, {
1209
1320
  file: "network-manager.ts",
1210
- line: 148,
1321
+ line: 162,
1211
1322
  scope: this,
1212
1323
  callSite: (f, a) => f(...a)
1213
1324
  });
@@ -1217,7 +1328,7 @@ var NetworkManager = class {
1217
1328
  error
1218
1329
  }, {
1219
1330
  file: "network-manager.ts",
1220
- line: 151,
1331
+ line: 165,
1221
1332
  scope: this,
1222
1333
  callSite: (f, a) => f(...a)
1223
1334
  });
@@ -1228,7 +1339,7 @@ var NetworkManager = class {
1228
1339
  peerId
1229
1340
  }).catch((error) => log6.catch(error, {}, {
1230
1341
  file: "network-manager.ts",
1231
- line: 155,
1342
+ line: 169,
1232
1343
  scope: this,
1233
1344
  callSite: (f, a) => f(...a)
1234
1345
  }));
@@ -1240,7 +1351,7 @@ var NetworkManager = class {
1240
1351
  count: this._swarms.size
1241
1352
  }, {
1242
1353
  file: "network-manager.ts",
1243
- line: 160,
1354
+ line: 174,
1244
1355
  scope: this,
1245
1356
  callSite: (f, a) => f(...a)
1246
1357
  });
@@ -1248,14 +1359,17 @@ var NetworkManager = class {
1248
1359
  close: () => this.leaveSwarm(topic)
1249
1360
  };
1250
1361
  }
1362
+ /**
1363
+ * Close the connection.
1364
+ */
1251
1365
  async leaveSwarm(topic) {
1252
1366
  var _a;
1253
1367
  if (!this._swarms.has(topic)) {
1254
1368
  log6.warn("swarm not open", {
1255
- topic: PublicKey6.from(topic)
1369
+ topic: PublicKey6.from(topic).truncate()
1256
1370
  }, {
1257
1371
  file: "network-manager.ts",
1258
- line: 172,
1372
+ line: 186,
1259
1373
  scope: this,
1260
1374
  callSite: (f, a) => f(...a)
1261
1375
  });
@@ -1265,7 +1379,7 @@ var NetworkManager = class {
1265
1379
  topic: PublicKey6.from(topic)
1266
1380
  }, {
1267
1381
  file: "network-manager.ts",
1268
- line: 176,
1382
+ line: 190,
1269
1383
  scope: this,
1270
1384
  callSite: (f, a) => f(...a)
1271
1385
  });
@@ -1286,11 +1400,37 @@ var NetworkManager = class {
1286
1400
  count: this._swarms.size
1287
1401
  }, {
1288
1402
  file: "network-manager.ts",
1289
- line: 190,
1403
+ line: 204,
1290
1404
  scope: this,
1291
1405
  callSite: (f, a) => f(...a)
1292
1406
  });
1293
1407
  }
1408
+ async setConnectionState(state) {
1409
+ if (state === this._connectionState) {
1410
+ return;
1411
+ }
1412
+ switch (state) {
1413
+ case ConnectionState2.OFFLINE: {
1414
+ this._connectionState = state;
1415
+ await this._messenger.close();
1416
+ await this._signalManager.close();
1417
+ await Promise.all([
1418
+ ...this._swarms.values()
1419
+ ].map((swarm) => swarm.goOffline()));
1420
+ break;
1421
+ }
1422
+ case ConnectionState2.ONLINE: {
1423
+ this._connectionState = state;
1424
+ this._messenger.open();
1425
+ await this._signalManager.open();
1426
+ await Promise.all([
1427
+ ...this._swarms.values()
1428
+ ].map((swarm) => swarm.goOnline()));
1429
+ break;
1430
+ }
1431
+ }
1432
+ this.connectionStateChanged.emit(this._connectionState);
1433
+ }
1294
1434
  };
1295
1435
 
1296
1436
  // packages/core/mesh/network-manager/src/topology/fully-connected-topology.ts
@@ -1482,6 +1622,7 @@ var _MemoryTransport = class {
1482
1622
  }
1483
1623
  };
1484
1624
  var MemoryTransport = _MemoryTransport;
1625
+ // TODO(burdon): Remove static properties (inject context into constructor).
1485
1626
  MemoryTransport._connections = new ComplexMap6(PublicKey7.hash);
1486
1627
  __decorate4([
1487
1628
  logInfo2
@@ -1509,12 +1650,13 @@ var WebRTCTransport = class {
1509
1650
  constructor(params) {
1510
1651
  var _a;
1511
1652
  this.params = params;
1653
+ this._closed = false;
1512
1654
  this.closed = new Event7();
1513
1655
  this.connected = new Event7();
1514
1656
  this.errors = new ErrorStream4();
1515
1657
  log10("created connection", params, {
1516
1658
  file: "webrtc-transport.ts",
1517
- line: 34,
1659
+ line: 35,
1518
1660
  scope: this,
1519
1661
  callSite: (f, a) => f(...a)
1520
1662
  });
@@ -1526,7 +1668,7 @@ var WebRTCTransport = class {
1526
1668
  this._peer.on("signal", async (data) => {
1527
1669
  log10("signal", data, {
1528
1670
  file: "webrtc-transport.ts",
1529
- line: 42,
1671
+ line: 43,
1530
1672
  scope: this,
1531
1673
  callSite: (f, a) => f(...a)
1532
1674
  });
@@ -1539,7 +1681,7 @@ var WebRTCTransport = class {
1539
1681
  this._peer.on("connect", () => {
1540
1682
  log10("connected", {}, {
1541
1683
  file: "webrtc-transport.ts",
1542
- line: 47,
1684
+ line: 48,
1543
1685
  scope: this,
1544
1686
  callSite: (f, a) => f(...a)
1545
1687
  });
@@ -1549,7 +1691,7 @@ var WebRTCTransport = class {
1549
1691
  this._peer.on("close", async () => {
1550
1692
  log10("closed", {}, {
1551
1693
  file: "webrtc-transport.ts",
1552
- line: 53,
1694
+ line: 54,
1553
1695
  scope: this,
1554
1696
  callSite: (f, a) => f(...a)
1555
1697
  });
@@ -1564,22 +1706,25 @@ var WebRTCTransport = class {
1564
1706
  async destroy() {
1565
1707
  log10("closing...", {}, {
1566
1708
  file: "webrtc-transport.ts",
1567
- line: 65,
1709
+ line: 66,
1568
1710
  scope: this,
1569
1711
  callSite: (f, a) => f(...a)
1570
1712
  });
1713
+ this._closed = true;
1571
1714
  await this._disconnectStreams();
1572
1715
  this._peer.destroy();
1573
1716
  this.closed.emit();
1574
1717
  log10("closed", {}, {
1575
1718
  file: "webrtc-transport.ts",
1576
- line: 69,
1719
+ line: 71,
1577
1720
  scope: this,
1578
1721
  callSite: (f, a) => f(...a)
1579
1722
  });
1580
1723
  }
1581
1724
  signal(signal) {
1582
- assert10(this._peer, "Connection not ready to accept signals.");
1725
+ if (this._closed) {
1726
+ return;
1727
+ }
1583
1728
  assert10(signal.payload.data, "Signal message must contain signal data.");
1584
1729
  this._peer.signal(signal.payload.data);
1585
1730
  }
@@ -1595,9 +1740,10 @@ import { Duplex } from "@dxos/node-std/stream";
1595
1740
  import { Stream } from "@dxos/codec-protobuf";
1596
1741
  import { PublicKey as PublicKey8 } from "@dxos/keys";
1597
1742
  import { log as log11 } from "@dxos/log";
1598
- import { ConnectionState as ConnectionState2 } from "@dxos/protocols/proto/dxos/mesh/bridge";
1743
+ import { ConnectionState as ConnectionState3 } from "@dxos/protocols/proto/dxos/mesh/bridge";
1599
1744
  import { ComplexMap as ComplexMap7 } from "@dxos/util";
1600
1745
  var WebRTCTransportService = class {
1746
+ // prettier-ignore
1601
1747
  constructor(_webrtcConfig) {
1602
1748
  this._webrtcConfig = _webrtcConfig;
1603
1749
  this.transports = new ComplexMap7(PublicKey8.hash);
@@ -1629,20 +1775,20 @@ var WebRTCTransportService = class {
1629
1775
  });
1630
1776
  next({
1631
1777
  connection: {
1632
- state: ConnectionState2.CONNECTING
1778
+ state: ConnectionState3.CONNECTING
1633
1779
  }
1634
1780
  });
1635
1781
  transport.connected.on(() => {
1636
1782
  next({
1637
1783
  connection: {
1638
- state: ConnectionState2.CONNECTED
1784
+ state: ConnectionState3.CONNECTED
1639
1785
  }
1640
1786
  });
1641
1787
  });
1642
1788
  transport.errors.handle((err) => {
1643
1789
  next({
1644
1790
  connection: {
1645
- state: ConnectionState2.CLOSED,
1791
+ state: ConnectionState3.CLOSED,
1646
1792
  error: err.toString()
1647
1793
  }
1648
1794
  });
@@ -1651,7 +1797,7 @@ var WebRTCTransportService = class {
1651
1797
  transport.closed.on(() => {
1652
1798
  next({
1653
1799
  connection: {
1654
- state: ConnectionState2.CLOSED
1800
+ state: ConnectionState3.CLOSED
1655
1801
  }
1656
1802
  });
1657
1803
  close();
@@ -1689,18 +1835,21 @@ var WebRTCTransportService = class {
1689
1835
  // packages/core/mesh/network-manager/src/transport/webrtc-transport-proxy.ts
1690
1836
  import assert12 from "@dxos/node-std/assert";
1691
1837
  import { Event as Event8 } from "@dxos/async";
1838
+ import { Context as Context2 } from "@dxos/context";
1692
1839
  import { ErrorStream as ErrorStream5 } from "@dxos/debug";
1693
1840
  import { PublicKey as PublicKey9 } from "@dxos/keys";
1694
1841
  import { log as log12 } from "@dxos/log";
1695
- import { ConnectionState as ConnectionState3 } from "@dxos/protocols/proto/dxos/mesh/bridge";
1842
+ import { ConnectionState as ConnectionState4 } from "@dxos/protocols/proto/dxos/mesh/bridge";
1696
1843
  var WebRTCTransportProxy = class {
1844
+ // prettier-ignore
1697
1845
  constructor(_params) {
1698
1846
  this._params = _params;
1847
+ this._proxyId = PublicKey9.random();
1848
+ this._ctx = new Context2();
1699
1849
  this.closed = new Event8();
1700
1850
  this._closed = false;
1701
1851
  this.connected = new Event8();
1702
1852
  this.errors = new ErrorStream5();
1703
- this._proxyId = PublicKey9.random();
1704
1853
  this._serviceStream = this._params.bridgeService.open({
1705
1854
  proxyId: this._proxyId,
1706
1855
  initiator: this._params.initiator
@@ -1709,7 +1858,7 @@ var WebRTCTransportProxy = class {
1709
1858
  this._serviceStream.subscribe(async (event) => {
1710
1859
  log12("WebRTCTransportProxy: event", event, {
1711
1860
  file: "webrtc-transport-proxy.ts",
1712
- line: 46,
1861
+ line: 49,
1713
1862
  scope: this,
1714
1863
  callSite: (f, a) => f(...a)
1715
1864
  });
@@ -1721,7 +1870,7 @@ var WebRTCTransportProxy = class {
1721
1870
  await this._handleSignal(event.signal);
1722
1871
  }
1723
1872
  });
1724
- this._params.stream.on("data", async (data) => {
1873
+ const dataListener = async (data) => {
1725
1874
  try {
1726
1875
  await this._params.bridgeService.sendData({
1727
1876
  proxyId: this._proxyId,
@@ -1730,15 +1879,19 @@ var WebRTCTransportProxy = class {
1730
1879
  } catch (err) {
1731
1880
  log12.catch(err, {}, {
1732
1881
  file: "webrtc-transport-proxy.ts",
1733
- line: 63,
1882
+ line: 66,
1734
1883
  scope: this,
1735
1884
  callSite: (f, a) => f(...a)
1736
1885
  });
1737
1886
  }
1887
+ };
1888
+ this._params.stream.on("data", dataListener);
1889
+ this._ctx.onDispose(() => {
1890
+ this._params.stream.off("data", dataListener);
1738
1891
  });
1739
1892
  }, (error) => log12.catch(error, {}, {
1740
1893
  file: "webrtc-transport-proxy.ts",
1741
- line: 67,
1894
+ line: 72,
1742
1895
  scope: this,
1743
1896
  callSite: (f, a) => f(...a)
1744
1897
  }));
@@ -1748,11 +1901,11 @@ var WebRTCTransportProxy = class {
1748
1901
  this.errors.raise(new Error(connectionEvent.error));
1749
1902
  }
1750
1903
  switch (connectionEvent.state) {
1751
- case ConnectionState3.CONNECTED: {
1904
+ case ConnectionState4.CONNECTED: {
1752
1905
  this.connected.emit();
1753
1906
  break;
1754
1907
  }
1755
- case ConnectionState3.CLOSED: {
1908
+ case ConnectionState4.CLOSED: {
1756
1909
  await this.destroy();
1757
1910
  break;
1758
1911
  }
@@ -1770,7 +1923,9 @@ var WebRTCTransportProxy = class {
1770
1923
  signal
1771
1924
  }).catch((err) => this.errors.raise(err));
1772
1925
  }
1926
+ // TODO(burdon): Move open from constructor.
1773
1927
  async destroy() {
1928
+ await this._ctx.dispose();
1774
1929
  if (this._closed) {
1775
1930
  return;
1776
1931
  }
@@ -1782,7 +1937,7 @@ var WebRTCTransportProxy = class {
1782
1937
  } catch (err) {
1783
1938
  log12.catch(err, {}, {
1784
1939
  file: "webrtc-transport-proxy.ts",
1785
- line: 117,
1940
+ line: 123,
1786
1941
  scope: this,
1787
1942
  callSite: (f, a) => f(...a)
1788
1943
  });
@@ -1790,6 +1945,10 @@ var WebRTCTransportProxy = class {
1790
1945
  this.closed.emit();
1791
1946
  this._closed = true;
1792
1947
  }
1948
+ /**
1949
+ * Called when underlying proxy service becomes unavailable.
1950
+ */
1951
+ // TODO(burdon): Option on close method.
1793
1952
  forceClose() {
1794
1953
  this._serviceStream.close();
1795
1954
  this.closed.emit();
@@ -1800,6 +1959,10 @@ var WebRTCTransportProxyFactory = class {
1800
1959
  constructor() {
1801
1960
  this._connections = /* @__PURE__ */ new Set();
1802
1961
  }
1962
+ /**
1963
+ * Sets the current BridgeService to be used to open connections.
1964
+ * Calling this method will close any existing connections.
1965
+ */
1803
1966
  setBridgeService(bridgeService) {
1804
1967
  this._bridgeService = bridgeService;
1805
1968
  for (const connection of this._connections) {
@@ -1914,6 +2077,7 @@ var TestPeer = class {
1914
2077
  this._swarms = new ComplexMap9(PublicKey11.hash);
1915
2078
  this._networkManager = this.createNetworkManager();
1916
2079
  }
2080
+ // TODO(burdon): Move to TestBuilder.
1917
2081
  createNetworkManager() {
1918
2082
  let transportFactory = MemoryTransportFactory;
1919
2083
  if (this.testBuilder.options.signalHosts) {
@@ -1979,6 +2143,12 @@ var TestPeer = class {
1979
2143
  this._swarms.set(topic, swarm);
1980
2144
  return swarm;
1981
2145
  }
2146
+ async goOffline() {
2147
+ await this._networkManager.setConnectionState(ConnectionState5.OFFLINE);
2148
+ }
2149
+ async goOnline() {
2150
+ await this._networkManager.setConnectionState(ConnectionState5.ONLINE);
2151
+ }
1982
2152
  };
1983
2153
  var TestSwarmConnection = class {
1984
2154
  constructor(peer, topic) {
@@ -1986,6 +2156,8 @@ var TestSwarmConnection = class {
1986
2156
  this.topic = topic;
1987
2157
  this.protocol = new TestWireProtocol(this.peer.peerId);
1988
2158
  }
2159
+ // TODO(burdon): Need to create new plugin instance per swarm?
2160
+ // If so, then perhaps joinSwarm should return swarm object with access to plugins.
1989
2161
  async join(topology = new FullyConnectedTopology()) {
1990
2162
  await this.peer._networkManager.joinSwarm({
1991
2163
  topic: this.topic,