@dxos/network-manager 0.1.20 → 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
@@ -18,6 +18,10 @@ var __copyProps = (to, from, except, desc) => {
18
18
  return to;
19
19
  };
20
20
  var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
25
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
26
  mod
23
27
  ));
@@ -37,6 +41,7 @@ module.exports = __toCommonJS(testing_exports);
37
41
  var import_keys11 = require("@dxos/keys");
38
42
  var import_messaging2 = require("@dxos/messaging");
39
43
  var import_protocols2 = require("@dxos/protocols");
44
+ var import_services2 = require("@dxos/protocols/proto/dxos/client/services");
40
45
  var import_rpc = require("@dxos/rpc");
41
46
  var import_util9 = require("@dxos/util");
42
47
 
@@ -46,6 +51,7 @@ var import_async6 = require("@dxos/async");
46
51
  var import_keys6 = require("@dxos/keys");
47
52
  var import_log6 = require("@dxos/log");
48
53
  var import_messaging = require("@dxos/messaging");
54
+ var import_services = require("@dxos/protocols/proto/dxos/client/services");
49
55
  var import_util5 = require("@dxos/util");
50
56
 
51
57
  // packages/core/mesh/network-manager/src/connection-log.ts
@@ -70,12 +76,38 @@ var __decorate = function(decorators, target, key, desc) {
70
76
  return c > 3 && r && Object.defineProperty(target, key, r), r;
71
77
  };
72
78
  var ConnectionState;
73
- (function(ConnectionState4) {
74
- ConnectionState4["INITIAL"] = "INITIAL";
75
- ConnectionState4["CONNECTING"] = "CONNECTING";
76
- ConnectionState4["CONNECTED"] = "CONNECTED";
77
- ConnectionState4["CLOSING"] = "CLOSING";
78
- ConnectionState4["CLOSED"] = "CLOSED";
79
+ (function(ConnectionState6) {
80
+ ConnectionState6[
81
+ /**
82
+ * Initial state. Connection is registered but no attempt to connect to the remote peer has been performed.
83
+ * Might mean that we are waiting for the answer signal from the remote peer.
84
+ */
85
+ "INITIAL"
86
+ ] = "INITIAL";
87
+ ConnectionState6[
88
+ /**
89
+ * Trying to establish connection.
90
+ */
91
+ "CONNECTING"
92
+ ] = "CONNECTING";
93
+ ConnectionState6[
94
+ /**
95
+ * Connection is established.
96
+ */
97
+ "CONNECTED"
98
+ ] = "CONNECTED";
99
+ ConnectionState6[
100
+ /**
101
+ * Connection is being closed.
102
+ */
103
+ "CLOSING"
104
+ ] = "CLOSING";
105
+ ConnectionState6[
106
+ /**
107
+ * Connection closed.
108
+ */
109
+ "CLOSED"
110
+ ] = "CLOSED";
79
111
  })(ConnectionState || (ConnectionState = {}));
80
112
  var Connection = class {
81
113
  constructor(topic, ownId, remoteId, sessionId, initiator, _signalMessaging, _protocol, _transportFactory) {
@@ -101,6 +133,10 @@ var Connection = class {
101
133
  get protocol() {
102
134
  return this._protocol;
103
135
  }
136
+ /**
137
+ * Create an underlying transport and prepares it for the connection.
138
+ */
139
+ // TODO(burdon): Make async?
104
140
  openConnection() {
105
141
  (0, import_node_assert.default)(this._state === ConnectionState.INITIAL, "Invalid state.");
106
142
  this._changeState(ConnectionState.CONNECTING);
@@ -324,6 +360,7 @@ var MessageRouter = class {
324
360
  var _a;
325
361
  const networkMessage = {
326
362
  ...message,
363
+ // Setting unique message_id if it not specified yet.
327
364
  messageId: (_a = message.messageId) != null ? _a : import_keys.PublicKey.random()
328
365
  };
329
366
  (0, import_log2.log)("sending", {
@@ -436,17 +473,20 @@ var Peer = class {
436
473
  this.advertizing = false;
437
474
  this.initiating = false;
438
475
  }
476
+ /**
477
+ * Respond to remote offer.
478
+ */
439
479
  async onOffer(message) {
440
480
  const remoteId = message.author;
441
481
  if (this.connection || this.initiating) {
442
482
  if (remoteId.toHex() < this.localPeerId.toHex()) {
443
- import_log3.log.info("closing local connection and accepting remote peer's offer", {
483
+ (0, import_log3.log)("closing local connection and accepting remote peer's offer", {
444
484
  id: this.id,
445
485
  topic: this.topic,
446
486
  peerId: this.localPeerId
447
487
  }, {
448
488
  file: "peer.ts",
449
- line: 83,
489
+ line: 84,
450
490
  scope: this,
451
491
  callSite: (f, a) => f(...a)
452
492
  });
@@ -473,7 +513,7 @@ var Peer = class {
473
513
  err
474
514
  }, {
475
515
  file: "peer.ts",
476
- line: 107,
516
+ line: 108,
477
517
  scope: this,
478
518
  callSite: (f, a) => f(...a)
479
519
  });
@@ -488,6 +528,9 @@ var Peer = class {
488
528
  accept: false
489
529
  };
490
530
  }
531
+ /**
532
+ * Initiate a connection to the remote peer.
533
+ */
491
534
  async initiateConnection() {
492
535
  (0, import_node_assert3.default)(!this.initiating, "Initiation in progress.");
493
536
  (0, import_node_assert3.default)(!this.connection, "Already connected.");
@@ -499,7 +542,7 @@ var Peer = class {
499
542
  sessionId
500
543
  }, {
501
544
  file: "peer.ts",
502
- line: 125,
545
+ line: 126,
503
546
  scope: this,
504
547
  callSite: (f, a) => f(...a)
505
548
  });
@@ -522,14 +565,14 @@ var Peer = class {
522
565
  remoteId: this.id
523
566
  }, {
524
567
  file: "peer.ts",
525
- line: 137,
568
+ line: 138,
526
569
  scope: this,
527
570
  callSite: (f, a) => f(...a)
528
571
  });
529
572
  if (connection.state !== ConnectionState.INITIAL) {
530
573
  (0, import_log3.log)("ignoring response", {}, {
531
574
  file: "peer.ts",
532
- line: 139,
575
+ line: 140,
533
576
  scope: this,
534
577
  callSite: (f, a) => f(...a)
535
578
  });
@@ -549,7 +592,7 @@ var Peer = class {
549
592
  err
550
593
  }, {
551
594
  file: "peer.ts",
552
- line: 150,
595
+ line: 151,
553
596
  scope: this,
554
597
  callSite: (f, a) => f(...a)
555
598
  });
@@ -559,6 +602,10 @@ var Peer = class {
559
602
  this.initiating = false;
560
603
  }
561
604
  }
605
+ /**
606
+ * Create new connection.
607
+ * Either we're initiating a connection or creating one in response to an offer from the other peer.
608
+ */
562
609
  _createConnection(initiator, sessionId) {
563
610
  (0, import_log3.log)("creating connection", {
564
611
  topic: this.topic,
@@ -568,7 +615,7 @@ var Peer = class {
568
615
  sessionId
569
616
  }, {
570
617
  file: "peer.ts",
571
- line: 164,
618
+ line: 165,
572
619
  scope: this,
573
620
  callSite: (f, a) => f(...a)
574
621
  });
@@ -580,6 +627,7 @@ var Peer = class {
580
627
  sessionId,
581
628
  initiator,
582
629
  this._signalMessaging,
630
+ // TODO(dmaretskyi): Init only when connection is established.
583
631
  this._protocolProvider({
584
632
  initiator,
585
633
  localPeerId: this.localPeerId,
@@ -603,7 +651,7 @@ var Peer = class {
603
651
  initiator
604
652
  }, {
605
653
  file: "peer.ts",
606
- line: 193,
654
+ line: 194,
607
655
  scope: this,
608
656
  callSite: (f, a) => f(...a)
609
657
  });
@@ -623,7 +671,7 @@ var Peer = class {
623
671
  err
624
672
  }, {
625
673
  file: "peer.ts",
626
- line: 203,
674
+ line: 204,
627
675
  scope: this,
628
676
  callSite: (f, a) => f(...a)
629
677
  });
@@ -642,7 +690,7 @@ var Peer = class {
642
690
  sessionId: connection.sessionId
643
691
  }, {
644
692
  file: "peer.ts",
645
- line: 219,
693
+ line: 220,
646
694
  scope: this,
647
695
  callSite: (f, a) => f(...a)
648
696
  });
@@ -652,7 +700,7 @@ var Peer = class {
652
700
  sessionId: connection.sessionId
653
701
  }, {
654
702
  file: "peer.ts",
655
- line: 225,
703
+ line: 226,
656
704
  scope: this,
657
705
  callSite: (f, a) => f(...a)
658
706
  });
@@ -663,7 +711,7 @@ var Peer = class {
663
711
  message
664
712
  }, {
665
713
  file: "peer.ts",
666
- line: 230,
714
+ line: 231,
667
715
  scope: this,
668
716
  callSite: (f, a) => f(...a)
669
717
  });
@@ -678,7 +726,7 @@ var Peer = class {
678
726
  topic: this.topic
679
727
  }, {
680
728
  file: "peer.ts",
681
- line: 238,
729
+ line: 239,
682
730
  scope: this,
683
731
  callSite: (f, a) => f(...a)
684
732
  });
@@ -703,6 +751,8 @@ var __decorate3 = function(decorators, target, key, desc) {
703
751
  var INITIATION_DELAY = 100;
704
752
  var getClassName = (obj) => Object.getPrototypeOf(obj).constructor.name;
705
753
  var Swarm = class {
754
+ // TODO(burdon): Swarm => Peer.create/destroy =< Connection.open/close
755
+ // TODO(burdon): Split up properties.
706
756
  constructor(_topic, _ownPeerId, _topology, _protocolProvider, _messenger, _transportFactory, _label) {
707
757
  this._topic = _topic;
708
758
  this._ownPeerId = _ownPeerId;
@@ -750,12 +800,16 @@ var Swarm = class {
750
800
  get ownPeerId() {
751
801
  return this._ownPeerId;
752
802
  }
803
+ /**
804
+ * Custom label assigned to this swarm. Used in devtools to display human-readable names for swarms.
805
+ */
753
806
  get label() {
754
807
  return this._label;
755
808
  }
756
809
  get topic() {
757
810
  return this._topic;
758
811
  }
812
+ // TODO(burdon): async open?
759
813
  async destroy() {
760
814
  (0, import_log4.log)("destroying...", {}, {
761
815
  file: "swarm.ts",
@@ -774,6 +828,7 @@ var Swarm = class {
774
828
  });
775
829
  }
776
830
  async setTopology(topology) {
831
+ (0, import_node_assert4.default)(!this._ctx.disposed, "Swarm is offline");
777
832
  if (topology === this._topology) {
778
833
  return;
779
834
  }
@@ -782,7 +837,7 @@ var Swarm = class {
782
837
  topology: getClassName(topology)
783
838
  }, {
784
839
  file: "swarm.ts",
785
- line: 135,
840
+ line: 136,
786
841
  scope: this,
787
842
  callSite: (f, a) => f(...a)
788
843
  });
@@ -791,69 +846,31 @@ var Swarm = class {
791
846
  this._topology.init(this._getSwarmController());
792
847
  this._topology.update();
793
848
  }
794
- _getOrCreatePeer(peerId) {
795
- let peer = this._peers.get(peerId);
796
- if (!peer) {
797
- peer = new Peer(peerId, this._topic, this._ownPeerId, this._swarmMessenger, this._protocolProvider, this._transportFactory, {
798
- onInitiated: (connection) => {
799
- this.connectionAdded.emit(connection);
800
- },
801
- onConnected: () => {
802
- this.connected.emit(peerId);
803
- },
804
- onDisconnected: async () => {
805
- if (!peer.advertizing) {
806
- await this._destroyPeer(peer.id);
807
- }
808
- this.disconnected.emit(peerId);
809
- this._topology.update();
810
- },
811
- onRejected: () => {
812
- if (this._peers.has(peerId)) {
813
- void this._destroyPeer(peerId);
814
- }
815
- },
816
- onAccepted: () => {
817
- this._topology.update();
818
- },
819
- onOffer: (remoteId) => {
820
- return this._topology.onOffer(remoteId);
821
- }
822
- });
823
- this._peers.set(peerId, peer);
824
- }
825
- return peer;
826
- }
827
- async _destroyPeer(peerId) {
828
- (0, import_node_assert4.default)(this._peers.has(peerId));
829
- await this._peers.get(peerId).destroy();
830
- this._peers.delete(peerId);
831
- }
832
849
  onSwarmEvent(swarmEvent) {
833
850
  (0, import_log4.log)("swarm event", {
834
851
  swarmEvent
835
852
  }, {
836
853
  file: "swarm.ts",
837
- line: 199,
854
+ line: 149,
838
855
  scope: this,
839
856
  callSite: (f, a) => f(...a)
840
857
  });
841
- if (this._ctx.disposed) {
842
- import_log4.log.warn("ignored for destroyed swarm", {}, {
843
- file: "swarm.ts",
844
- line: 201,
845
- scope: this,
846
- callSite: (f, a) => f(...a)
847
- });
848
- return;
849
- }
850
858
  if (swarmEvent.peerAvailable) {
859
+ if (this._ctx.disposed) {
860
+ import_log4.log.warn("ignored for offline swarm", {}, {
861
+ file: "swarm.ts",
862
+ line: 153,
863
+ scope: this,
864
+ callSite: (f, a) => f(...a)
865
+ });
866
+ return;
867
+ }
851
868
  const peerId = import_keys3.PublicKey.from(swarmEvent.peerAvailable.peer);
852
869
  (0, import_log4.log)("new peer", {
853
870
  peerId
854
871
  }, {
855
872
  file: "swarm.ts",
856
- line: 207,
873
+ line: 157,
857
874
  scope: this,
858
875
  callSite: (f, a) => f(...a)
859
876
  });
@@ -865,8 +882,13 @@ var Swarm = class {
865
882
  const peer1 = this._peers.get(import_keys3.PublicKey.from(swarmEvent.peerLeft.peer));
866
883
  if (peer1) {
867
884
  peer1.advertizing = false;
868
- if (!peer1.connection) {
869
- void this._destroyPeer(peer1.id);
885
+ if (!peer1.connection || peer1.connection.state !== ConnectionState.CONNECTED) {
886
+ void this._destroyPeer(peer1.id).catch((err) => import_log4.log.catch(err, {}, {
887
+ file: "swarm.ts",
888
+ line: 168,
889
+ scope: this,
890
+ callSite: (f, a) => f(...a)
891
+ }));
870
892
  }
871
893
  }
872
894
  }
@@ -878,14 +900,14 @@ var Swarm = class {
878
900
  message
879
901
  }, {
880
902
  file: "swarm.ts",
881
- line: 226,
903
+ line: 178,
882
904
  scope: this,
883
905
  callSite: (f, a) => f(...a)
884
906
  });
885
907
  if (this._ctx.disposed) {
886
- import_log4.log.info("ignored for destroyed swarm", {}, {
908
+ import_log4.log.info("ignored for offline swarm", {}, {
887
909
  file: "swarm.ts",
888
- line: 228,
910
+ line: 180,
889
911
  scope: this,
890
912
  callSite: (f, a) => f(...a)
891
913
  });
@@ -899,7 +921,7 @@ var Swarm = class {
899
921
  message
900
922
  }, {
901
923
  file: "swarm.ts",
902
- line: 235,
924
+ line: 187,
903
925
  scope: this,
904
926
  callSite: (f, a) => f(...a)
905
927
  });
@@ -912,7 +934,7 @@ var Swarm = class {
912
934
  message
913
935
  }, {
914
936
  file: "swarm.ts",
915
- line: 239,
937
+ line: 191,
916
938
  scope: this,
917
939
  callSite: (f, a) => f(...a)
918
940
  });
@@ -931,14 +953,14 @@ var Swarm = class {
931
953
  message
932
954
  }, {
933
955
  file: "swarm.ts",
934
- line: 250,
956
+ line: 202,
935
957
  scope: this,
936
958
  callSite: (f, a) => f(...a)
937
959
  });
938
960
  if (this._ctx.disposed) {
939
- import_log4.log.info("ignored for destroyed swarm", {}, {
961
+ import_log4.log.info("ignored for offline swarm", {}, {
940
962
  file: "swarm.ts",
941
- line: 252,
963
+ line: 204,
942
964
  scope: this,
943
965
  callSite: (f, a) => f(...a)
944
966
  });
@@ -950,6 +972,66 @@ var Swarm = class {
950
972
  const peer = this._getOrCreatePeer(message.author);
951
973
  await peer.onSignal(message);
952
974
  }
975
+ // For debug purposes
976
+ async goOffline() {
977
+ await this._ctx.dispose();
978
+ [
979
+ ...this._peers.values()
980
+ ].forEach((peer) => {
981
+ peer.advertizing = false;
982
+ });
983
+ await Promise.all([
984
+ ...this._peers.keys()
985
+ ].map((peerId) => this._closeConnection(peerId)));
986
+ }
987
+ // For debug purposes
988
+ async goOnline() {
989
+ this._ctx = new import_context.Context();
990
+ [
991
+ ...this._peers.values()
992
+ ].forEach((peer) => {
993
+ peer.advertizing = true;
994
+ });
995
+ this._topology.update();
996
+ }
997
+ _getOrCreatePeer(peerId) {
998
+ let peer = this._peers.get(peerId);
999
+ if (!peer) {
1000
+ peer = new Peer(peerId, this._topic, this._ownPeerId, this._swarmMessenger, this._protocolProvider, this._transportFactory, {
1001
+ onInitiated: (connection) => {
1002
+ this.connectionAdded.emit(connection);
1003
+ },
1004
+ onConnected: () => {
1005
+ this.connected.emit(peerId);
1006
+ },
1007
+ onDisconnected: async () => {
1008
+ if (!peer.advertizing) {
1009
+ await this._destroyPeer(peer.id);
1010
+ }
1011
+ this.disconnected.emit(peerId);
1012
+ this._topology.update();
1013
+ },
1014
+ onRejected: () => {
1015
+ if (this._peers.has(peerId)) {
1016
+ void this._destroyPeer(peerId);
1017
+ }
1018
+ },
1019
+ onAccepted: () => {
1020
+ this._topology.update();
1021
+ },
1022
+ onOffer: (remoteId) => {
1023
+ return this._topology.onOffer(remoteId);
1024
+ }
1025
+ });
1026
+ this._peers.set(peerId, peer);
1027
+ }
1028
+ return peer;
1029
+ }
1030
+ async _destroyPeer(peerId) {
1031
+ (0, import_node_assert4.default)(this._peers.has(peerId));
1032
+ await this._peers.get(peerId).destroy();
1033
+ this._peers.delete(peerId);
1034
+ }
953
1035
  _getSwarmController() {
954
1036
  return {
955
1037
  getState: () => ({
@@ -967,7 +1049,7 @@ var Swarm = class {
967
1049
  } catch (err) {
968
1050
  import_log4.log.warn("initiation error", err, {
969
1051
  file: "swarm.ts",
970
- line: 287,
1052
+ line: 311,
971
1053
  scope: this,
972
1054
  callSite: (f, a) => f(...a)
973
1055
  });
@@ -985,13 +1067,16 @@ var Swarm = class {
985
1067
  }
986
1068
  };
987
1069
  }
1070
+ /**
1071
+ * Creates a connection then sends message over signal network.
1072
+ */
988
1073
  async _initiateConnection(remoteId) {
989
1074
  if (remoteId.toHex() < this._ownPeerId.toHex()) {
990
1075
  (0, import_log4.log)("initiation delay", {
991
1076
  remoteId
992
1077
  }, {
993
1078
  file: "swarm.ts",
994
- line: 312,
1079
+ line: 336,
995
1080
  scope: this,
996
1081
  callSite: (f, a) => f(...a)
997
1082
  });
@@ -1008,7 +1093,7 @@ var Swarm = class {
1008
1093
  remoteId
1009
1094
  }, {
1010
1095
  file: "swarm.ts",
1011
- line: 326,
1096
+ line: 350,
1012
1097
  scope: this,
1013
1098
  callSite: (f, a) => f(...a)
1014
1099
  });
@@ -1018,7 +1103,7 @@ var Swarm = class {
1018
1103
  remoteId
1019
1104
  }, {
1020
1105
  file: "swarm.ts",
1021
- line: 329,
1106
+ line: 353,
1022
1107
  scope: this,
1023
1108
  callSite: (f, a) => f(...a)
1024
1109
  });
@@ -1040,6 +1125,15 @@ __decorate3([
1040
1125
  __decorate3([
1041
1126
  import_log4.logInfo
1042
1127
  ], Swarm.prototype, "topic", null);
1128
+ __decorate3([
1129
+ import_async3.synchronized
1130
+ ], Swarm.prototype, "onSwarmEvent", null);
1131
+ __decorate3([
1132
+ import_async3.synchronized
1133
+ ], Swarm.prototype, "onOffer", null);
1134
+ __decorate3([
1135
+ import_async3.synchronized
1136
+ ], Swarm.prototype, "goOnline", null);
1043
1137
 
1044
1138
  // packages/core/mesh/network-manager/src/swarm/swarm-mapper.ts
1045
1139
  var import_async4 = require("@dxos/async");
@@ -1050,6 +1144,7 @@ var SwarmMapper = class {
1050
1144
  get peers() {
1051
1145
  return Array.from(this._peers.values());
1052
1146
  }
1147
+ // prettier-ignore
1053
1148
  constructor(_swarm) {
1054
1149
  this._swarm = _swarm;
1055
1150
  this._subscriptions = new import_async4.EventSubscriptions();
@@ -1103,6 +1198,7 @@ var SwarmMapper = class {
1103
1198
  });
1104
1199
  this.mapUpdated.emit(Array.from(this._peers.values()));
1105
1200
  }
1201
+ // TODO(burdon): Async open/close.
1106
1202
  destroy() {
1107
1203
  Array.from(this._connectionSubscriptions.values()).forEach((cb) => cb());
1108
1204
  this._subscriptions.clear();
@@ -1120,6 +1216,9 @@ var EventType;
1120
1216
  })(EventType || (EventType = {}));
1121
1217
  var ConnectionLog = class {
1122
1218
  constructor() {
1219
+ /**
1220
+ * SwarmId => info
1221
+ */
1123
1222
  this._swarms = new import_util4.ComplexMap(import_keys5.PublicKey.hash);
1124
1223
  this.update = new import_async5.Event();
1125
1224
  }
@@ -1172,6 +1271,8 @@ var NetworkManager = class {
1172
1271
  constructor({ transportFactory, signalManager, log: log14 }) {
1173
1272
  this._swarms = new import_util5.ComplexMap(import_keys6.PublicKey.hash);
1174
1273
  this._mappers = new import_util5.ComplexMap(import_keys6.PublicKey.hash);
1274
+ this._connectionState = import_services.ConnectionState.ONLINE;
1275
+ this.connectionStateChanged = new import_async6.Event();
1175
1276
  this.topicsUpdated = new import_async6.Event();
1176
1277
  this._transportFactory = transportFactory;
1177
1278
  this._signalManager = signalManager;
@@ -1190,12 +1291,18 @@ var NetworkManager = class {
1190
1291
  this._connectionLog = new ConnectionLog();
1191
1292
  }
1192
1293
  }
1294
+ // TODO(burdon): Remove access (Devtools only).
1193
1295
  get connectionLog() {
1194
1296
  return this._connectionLog;
1195
1297
  }
1298
+ // TODO(burdon): Remove access (Devtools only).
1196
1299
  get signalManager() {
1197
1300
  return this._signalManager;
1198
1301
  }
1302
+ get connectionState() {
1303
+ return this._connectionState;
1304
+ }
1305
+ // TODO(burdon): Reconcile with "discovery_key".
1199
1306
  get topics() {
1200
1307
  return Array.from(this._swarms.keys());
1201
1308
  }
@@ -1205,19 +1312,27 @@ var NetworkManager = class {
1205
1312
  getSwarm(topic) {
1206
1313
  return this._swarms.get(topic);
1207
1314
  }
1315
+ async open() {
1316
+ await this._messenger.open();
1317
+ await this._signalManager.open();
1318
+ }
1208
1319
  async close() {
1209
1320
  for (const topic of this._swarms.keys()) {
1210
1321
  await this.leaveSwarm(topic).catch((err) => {
1211
1322
  (0, import_log6.log)(err, {}, {
1212
1323
  file: "network-manager.ts",
1213
- line: 122,
1324
+ line: 136,
1214
1325
  scope: this,
1215
1326
  callSite: (f, a) => f(...a)
1216
1327
  });
1217
1328
  });
1218
1329
  }
1219
- await this._signalManager.destroy();
1330
+ await this._messenger.close();
1331
+ await this._signalManager.close();
1220
1332
  }
1333
+ /**
1334
+ * Join the swarm.
1335
+ */
1221
1336
  async joinSwarm({ topic, peerId, topology, protocolProvider: protocol, label }) {
1222
1337
  var _a;
1223
1338
  (0, import_node_assert5.default)(import_keys6.PublicKey.isPublicKey(topic));
@@ -1233,7 +1348,7 @@ var NetworkManager = class {
1233
1348
  topology: topology.toString()
1234
1349
  }, {
1235
1350
  file: "network-manager.ts",
1236
- line: 148,
1351
+ line: 162,
1237
1352
  scope: this,
1238
1353
  callSite: (f, a) => f(...a)
1239
1354
  });
@@ -1243,7 +1358,7 @@ var NetworkManager = class {
1243
1358
  error
1244
1359
  }, {
1245
1360
  file: "network-manager.ts",
1246
- line: 151,
1361
+ line: 165,
1247
1362
  scope: this,
1248
1363
  callSite: (f, a) => f(...a)
1249
1364
  });
@@ -1254,7 +1369,7 @@ var NetworkManager = class {
1254
1369
  peerId
1255
1370
  }).catch((error) => import_log6.log.catch(error, {}, {
1256
1371
  file: "network-manager.ts",
1257
- line: 155,
1372
+ line: 169,
1258
1373
  scope: this,
1259
1374
  callSite: (f, a) => f(...a)
1260
1375
  }));
@@ -1266,7 +1381,7 @@ var NetworkManager = class {
1266
1381
  count: this._swarms.size
1267
1382
  }, {
1268
1383
  file: "network-manager.ts",
1269
- line: 160,
1384
+ line: 174,
1270
1385
  scope: this,
1271
1386
  callSite: (f, a) => f(...a)
1272
1387
  });
@@ -1274,14 +1389,17 @@ var NetworkManager = class {
1274
1389
  close: () => this.leaveSwarm(topic)
1275
1390
  };
1276
1391
  }
1392
+ /**
1393
+ * Close the connection.
1394
+ */
1277
1395
  async leaveSwarm(topic) {
1278
1396
  var _a;
1279
1397
  if (!this._swarms.has(topic)) {
1280
1398
  import_log6.log.warn("swarm not open", {
1281
- topic: import_keys6.PublicKey.from(topic)
1399
+ topic: import_keys6.PublicKey.from(topic).truncate()
1282
1400
  }, {
1283
1401
  file: "network-manager.ts",
1284
- line: 172,
1402
+ line: 186,
1285
1403
  scope: this,
1286
1404
  callSite: (f, a) => f(...a)
1287
1405
  });
@@ -1291,7 +1409,7 @@ var NetworkManager = class {
1291
1409
  topic: import_keys6.PublicKey.from(topic)
1292
1410
  }, {
1293
1411
  file: "network-manager.ts",
1294
- line: 176,
1412
+ line: 190,
1295
1413
  scope: this,
1296
1414
  callSite: (f, a) => f(...a)
1297
1415
  });
@@ -1312,11 +1430,37 @@ var NetworkManager = class {
1312
1430
  count: this._swarms.size
1313
1431
  }, {
1314
1432
  file: "network-manager.ts",
1315
- line: 190,
1433
+ line: 204,
1316
1434
  scope: this,
1317
1435
  callSite: (f, a) => f(...a)
1318
1436
  });
1319
1437
  }
1438
+ async setConnectionState(state) {
1439
+ if (state === this._connectionState) {
1440
+ return;
1441
+ }
1442
+ switch (state) {
1443
+ case import_services.ConnectionState.OFFLINE: {
1444
+ this._connectionState = state;
1445
+ await this._messenger.close();
1446
+ await this._signalManager.close();
1447
+ await Promise.all([
1448
+ ...this._swarms.values()
1449
+ ].map((swarm) => swarm.goOffline()));
1450
+ break;
1451
+ }
1452
+ case import_services.ConnectionState.ONLINE: {
1453
+ this._connectionState = state;
1454
+ this._messenger.open();
1455
+ await this._signalManager.open();
1456
+ await Promise.all([
1457
+ ...this._swarms.values()
1458
+ ].map((swarm) => swarm.goOnline()));
1459
+ break;
1460
+ }
1461
+ }
1462
+ this.connectionStateChanged.emit(this._connectionState);
1463
+ }
1320
1464
  };
1321
1465
 
1322
1466
  // packages/core/mesh/network-manager/src/topology/fully-connected-topology.ts
@@ -1506,6 +1650,7 @@ var _MemoryTransport = class {
1506
1650
  }
1507
1651
  };
1508
1652
  var MemoryTransport = _MemoryTransport;
1653
+ // TODO(burdon): Remove static properties (inject context into constructor).
1509
1654
  MemoryTransport._connections = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
1510
1655
  __decorate4([
1511
1656
  import_log9.logInfo
@@ -1533,12 +1678,13 @@ var WebRTCTransport = class {
1533
1678
  constructor(params) {
1534
1679
  var _a;
1535
1680
  this.params = params;
1681
+ this._closed = false;
1536
1682
  this.closed = new import_async8.Event();
1537
1683
  this.connected = new import_async8.Event();
1538
1684
  this.errors = new import_debug5.ErrorStream();
1539
1685
  (0, import_log10.log)("created connection", params, {
1540
1686
  file: "webrtc-transport.ts",
1541
- line: 34,
1687
+ line: 35,
1542
1688
  scope: this,
1543
1689
  callSite: (f, a) => f(...a)
1544
1690
  });
@@ -1550,7 +1696,7 @@ var WebRTCTransport = class {
1550
1696
  this._peer.on("signal", async (data) => {
1551
1697
  (0, import_log10.log)("signal", data, {
1552
1698
  file: "webrtc-transport.ts",
1553
- line: 42,
1699
+ line: 43,
1554
1700
  scope: this,
1555
1701
  callSite: (f, a) => f(...a)
1556
1702
  });
@@ -1563,7 +1709,7 @@ var WebRTCTransport = class {
1563
1709
  this._peer.on("connect", () => {
1564
1710
  (0, import_log10.log)("connected", {}, {
1565
1711
  file: "webrtc-transport.ts",
1566
- line: 47,
1712
+ line: 48,
1567
1713
  scope: this,
1568
1714
  callSite: (f, a) => f(...a)
1569
1715
  });
@@ -1573,7 +1719,7 @@ var WebRTCTransport = class {
1573
1719
  this._peer.on("close", async () => {
1574
1720
  (0, import_log10.log)("closed", {}, {
1575
1721
  file: "webrtc-transport.ts",
1576
- line: 53,
1722
+ line: 54,
1577
1723
  scope: this,
1578
1724
  callSite: (f, a) => f(...a)
1579
1725
  });
@@ -1588,22 +1734,25 @@ var WebRTCTransport = class {
1588
1734
  async destroy() {
1589
1735
  (0, import_log10.log)("closing...", {}, {
1590
1736
  file: "webrtc-transport.ts",
1591
- line: 65,
1737
+ line: 66,
1592
1738
  scope: this,
1593
1739
  callSite: (f, a) => f(...a)
1594
1740
  });
1741
+ this._closed = true;
1595
1742
  await this._disconnectStreams();
1596
1743
  this._peer.destroy();
1597
1744
  this.closed.emit();
1598
1745
  (0, import_log10.log)("closed", {}, {
1599
1746
  file: "webrtc-transport.ts",
1600
- line: 69,
1747
+ line: 71,
1601
1748
  scope: this,
1602
1749
  callSite: (f, a) => f(...a)
1603
1750
  });
1604
1751
  }
1605
1752
  signal(signal) {
1606
- (0, import_node_assert8.default)(this._peer, "Connection not ready to accept signals.");
1753
+ if (this._closed) {
1754
+ return;
1755
+ }
1607
1756
  (0, import_node_assert8.default)(signal.payload.data, "Signal message must contain signal data.");
1608
1757
  this._peer.signal(signal.payload.data);
1609
1758
  }
@@ -1622,6 +1771,7 @@ var import_log11 = require("@dxos/log");
1622
1771
  var import_bridge = require("@dxos/protocols/proto/dxos/mesh/bridge");
1623
1772
  var import_util7 = require("@dxos/util");
1624
1773
  var WebRTCTransportService = class {
1774
+ // prettier-ignore
1625
1775
  constructor(_webrtcConfig) {
1626
1776
  this._webrtcConfig = _webrtcConfig;
1627
1777
  this.transports = new import_util7.ComplexMap(import_keys8.PublicKey.hash);
@@ -1713,18 +1863,21 @@ var WebRTCTransportService = class {
1713
1863
  // packages/core/mesh/network-manager/src/transport/webrtc-transport-proxy.ts
1714
1864
  var import_node_assert10 = __toESM(require("node:assert"));
1715
1865
  var import_async9 = require("@dxos/async");
1866
+ var import_context2 = require("@dxos/context");
1716
1867
  var import_debug6 = require("@dxos/debug");
1717
1868
  var import_keys9 = require("@dxos/keys");
1718
1869
  var import_log12 = require("@dxos/log");
1719
1870
  var import_bridge2 = require("@dxos/protocols/proto/dxos/mesh/bridge");
1720
1871
  var WebRTCTransportProxy = class {
1872
+ // prettier-ignore
1721
1873
  constructor(_params) {
1722
1874
  this._params = _params;
1875
+ this._proxyId = import_keys9.PublicKey.random();
1876
+ this._ctx = new import_context2.Context();
1723
1877
  this.closed = new import_async9.Event();
1724
1878
  this._closed = false;
1725
1879
  this.connected = new import_async9.Event();
1726
1880
  this.errors = new import_debug6.ErrorStream();
1727
- this._proxyId = import_keys9.PublicKey.random();
1728
1881
  this._serviceStream = this._params.bridgeService.open({
1729
1882
  proxyId: this._proxyId,
1730
1883
  initiator: this._params.initiator
@@ -1733,7 +1886,7 @@ var WebRTCTransportProxy = class {
1733
1886
  this._serviceStream.subscribe(async (event) => {
1734
1887
  (0, import_log12.log)("WebRTCTransportProxy: event", event, {
1735
1888
  file: "webrtc-transport-proxy.ts",
1736
- line: 46,
1889
+ line: 49,
1737
1890
  scope: this,
1738
1891
  callSite: (f, a) => f(...a)
1739
1892
  });
@@ -1745,7 +1898,7 @@ var WebRTCTransportProxy = class {
1745
1898
  await this._handleSignal(event.signal);
1746
1899
  }
1747
1900
  });
1748
- this._params.stream.on("data", async (data) => {
1901
+ const dataListener = async (data) => {
1749
1902
  try {
1750
1903
  await this._params.bridgeService.sendData({
1751
1904
  proxyId: this._proxyId,
@@ -1754,15 +1907,19 @@ var WebRTCTransportProxy = class {
1754
1907
  } catch (err) {
1755
1908
  import_log12.log.catch(err, {}, {
1756
1909
  file: "webrtc-transport-proxy.ts",
1757
- line: 63,
1910
+ line: 66,
1758
1911
  scope: this,
1759
1912
  callSite: (f, a) => f(...a)
1760
1913
  });
1761
1914
  }
1915
+ };
1916
+ this._params.stream.on("data", dataListener);
1917
+ this._ctx.onDispose(() => {
1918
+ this._params.stream.off("data", dataListener);
1762
1919
  });
1763
1920
  }, (error) => import_log12.log.catch(error, {}, {
1764
1921
  file: "webrtc-transport-proxy.ts",
1765
- line: 67,
1922
+ line: 72,
1766
1923
  scope: this,
1767
1924
  callSite: (f, a) => f(...a)
1768
1925
  }));
@@ -1794,7 +1951,9 @@ var WebRTCTransportProxy = class {
1794
1951
  signal
1795
1952
  }).catch((err) => this.errors.raise(err));
1796
1953
  }
1954
+ // TODO(burdon): Move open from constructor.
1797
1955
  async destroy() {
1956
+ await this._ctx.dispose();
1798
1957
  if (this._closed) {
1799
1958
  return;
1800
1959
  }
@@ -1806,7 +1965,7 @@ var WebRTCTransportProxy = class {
1806
1965
  } catch (err) {
1807
1966
  import_log12.log.catch(err, {}, {
1808
1967
  file: "webrtc-transport-proxy.ts",
1809
- line: 117,
1968
+ line: 123,
1810
1969
  scope: this,
1811
1970
  callSite: (f, a) => f(...a)
1812
1971
  });
@@ -1814,6 +1973,10 @@ var WebRTCTransportProxy = class {
1814
1973
  this.closed.emit();
1815
1974
  this._closed = true;
1816
1975
  }
1976
+ /**
1977
+ * Called when underlying proxy service becomes unavailable.
1978
+ */
1979
+ // TODO(burdon): Option on close method.
1817
1980
  forceClose() {
1818
1981
  this._serviceStream.close();
1819
1982
  this.closed.emit();
@@ -1824,6 +1987,10 @@ var WebRTCTransportProxyFactory = class {
1824
1987
  constructor() {
1825
1988
  this._connections = /* @__PURE__ */ new Set();
1826
1989
  }
1990
+ /**
1991
+ * Sets the current BridgeService to be used to open connections.
1992
+ * Calling this method will close any existing connections.
1993
+ */
1827
1994
  setBridgeService(bridgeService) {
1828
1995
  this._bridgeService = bridgeService;
1829
1996
  for (const connection of this._connections) {
@@ -1938,6 +2105,7 @@ var TestPeer = class {
1938
2105
  this._swarms = new import_util9.ComplexMap(import_keys11.PublicKey.hash);
1939
2106
  this._networkManager = this.createNetworkManager();
1940
2107
  }
2108
+ // TODO(burdon): Move to TestBuilder.
1941
2109
  createNetworkManager() {
1942
2110
  let transportFactory = MemoryTransportFactory;
1943
2111
  if (this.testBuilder.options.signalHosts) {
@@ -2003,6 +2171,12 @@ var TestPeer = class {
2003
2171
  this._swarms.set(topic, swarm);
2004
2172
  return swarm;
2005
2173
  }
2174
+ async goOffline() {
2175
+ await this._networkManager.setConnectionState(import_services2.ConnectionState.OFFLINE);
2176
+ }
2177
+ async goOnline() {
2178
+ await this._networkManager.setConnectionState(import_services2.ConnectionState.ONLINE);
2179
+ }
2006
2180
  };
2007
2181
  var TestSwarmConnection = class {
2008
2182
  constructor(peer, topic) {
@@ -2010,6 +2184,8 @@ var TestSwarmConnection = class {
2010
2184
  this.topic = topic;
2011
2185
  this.protocol = new TestWireProtocol(this.peer.peerId);
2012
2186
  }
2187
+ // TODO(burdon): Need to create new plugin instance per swarm?
2188
+ // If so, then perhaps joinSwarm should return swarm object with access to plugins.
2013
2189
  async join(topology = new FullyConnectedTopology()) {
2014
2190
  await this.peer._networkManager.joinSwarm({
2015
2191
  topic: this.topic,