@dxos/network-manager 0.6.13 → 0.6.14-main.1366248

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 (164) hide show
  1. package/dist/lib/browser/{chunk-XYSYUN63.mjs → chunk-UEVA7BFW.mjs} +1323 -1102
  2. package/dist/lib/browser/chunk-UEVA7BFW.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +9 -19
  4. package/dist/lib/browser/meta.json +1 -1
  5. package/dist/lib/browser/testing/index.mjs +30 -37
  6. package/dist/lib/browser/testing/index.mjs.map +3 -3
  7. package/dist/lib/browser/transport/tcp/index.mjs +38 -0
  8. package/dist/lib/browser/transport/tcp/index.mjs.map +7 -0
  9. package/dist/lib/node/{chunk-4YAYC7WN.cjs → chunk-LK5D44SA.cjs} +1336 -1239
  10. package/dist/lib/node/chunk-LK5D44SA.cjs.map +7 -0
  11. package/dist/lib/node/index.cjs +27 -37
  12. package/dist/lib/node/index.cjs.map +2 -2
  13. package/dist/lib/node/meta.json +1 -1
  14. package/dist/lib/node/testing/index.cjs +34 -38
  15. package/dist/lib/node/testing/index.cjs.map +3 -3
  16. package/dist/lib/node/transport/tcp/index.cjs +191 -0
  17. package/dist/lib/node/transport/tcp/index.cjs.map +7 -0
  18. package/dist/lib/node-esm/chunk-Y5TD36KR.mjs +4413 -0
  19. package/dist/lib/node-esm/chunk-Y5TD36KR.mjs.map +7 -0
  20. package/dist/lib/node-esm/index.mjs +50 -0
  21. package/dist/lib/node-esm/index.mjs.map +7 -0
  22. package/dist/lib/node-esm/meta.json +1 -0
  23. package/dist/lib/node-esm/testing/index.mjs +285 -0
  24. package/dist/lib/node-esm/testing/index.mjs.map +7 -0
  25. package/dist/lib/node-esm/transport/tcp/index.mjs +159 -0
  26. package/dist/lib/node-esm/transport/tcp/index.mjs.map +7 -0
  27. package/dist/types/src/network-manager.d.ts +2 -1
  28. package/dist/types/src/network-manager.d.ts.map +1 -1
  29. package/dist/types/src/signal/ice.d.ts.map +1 -1
  30. package/dist/types/src/signal/integration.node.test.d.ts +2 -0
  31. package/dist/types/src/signal/integration.node.test.d.ts.map +1 -0
  32. package/dist/types/src/signal/swarm-messenger.node.test.d.ts +2 -0
  33. package/dist/types/src/signal/swarm-messenger.node.test.d.ts.map +1 -0
  34. package/dist/types/src/swarm/connection.d.ts.map +1 -1
  35. package/dist/types/src/swarm/peer.d.ts.map +1 -1
  36. package/dist/types/src/swarm/swarm.d.ts +2 -1
  37. package/dist/types/src/swarm/swarm.d.ts.map +1 -1
  38. package/dist/types/src/testing/test-builder.d.ts +2 -2
  39. package/dist/types/src/testing/test-builder.d.ts.map +1 -1
  40. package/dist/types/src/testing/test-wire-protocol.d.ts +1 -2
  41. package/dist/types/src/testing/test-wire-protocol.d.ts.map +1 -1
  42. package/dist/types/src/tests/basic-test-suite.d.ts.map +1 -1
  43. package/dist/types/src/tests/property-test-suite.d.ts.map +1 -1
  44. package/dist/types/src/tests/tcp-transport.node.test.d.ts +2 -0
  45. package/dist/types/src/tests/tcp-transport.node.test.d.ts.map +1 -0
  46. package/dist/types/src/tests/utils.d.ts.map +1 -1
  47. package/dist/types/src/transport/index.d.ts +1 -5
  48. package/dist/types/src/transport/index.d.ts.map +1 -1
  49. package/dist/types/src/transport/memory-transport.d.ts +2 -2
  50. package/dist/types/src/transport/memory-transport.d.ts.map +1 -1
  51. package/dist/types/src/transport/tcp/index.d.ts +2 -0
  52. package/dist/types/src/transport/tcp/index.d.ts.map +1 -0
  53. package/dist/types/src/transport/{tcp-transport.browser.d.ts → tcp/tcp-transport.browser.d.ts} +3 -3
  54. package/dist/types/src/transport/tcp/tcp-transport.browser.d.ts.map +1 -0
  55. package/dist/types/src/transport/{tcp-transport.d.ts → tcp/tcp-transport.d.ts} +3 -3
  56. package/dist/types/src/transport/tcp/tcp-transport.d.ts.map +1 -0
  57. package/dist/types/src/transport/transport.d.ts +7 -6
  58. package/dist/types/src/transport/transport.d.ts.map +1 -1
  59. package/dist/types/src/transport/webrtc/index.d.ts +4 -0
  60. package/dist/types/src/transport/webrtc/index.d.ts.map +1 -0
  61. package/dist/types/src/transport/webrtc/rtc-connection-factory.d.ts +14 -0
  62. package/dist/types/src/transport/webrtc/rtc-connection-factory.d.ts.map +1 -0
  63. package/dist/types/src/transport/webrtc/rtc-peer-connection.d.ts +68 -0
  64. package/dist/types/src/transport/webrtc/rtc-peer-connection.d.ts.map +1 -0
  65. package/dist/types/src/transport/webrtc/rtc-transport-channel.d.ts +33 -0
  66. package/dist/types/src/transport/webrtc/rtc-transport-channel.d.ts.map +1 -0
  67. package/dist/types/src/transport/webrtc/rtc-transport-channel.test.d.ts +2 -0
  68. package/dist/types/src/transport/webrtc/rtc-transport-channel.test.d.ts.map +1 -0
  69. package/dist/types/src/transport/webrtc/rtc-transport-factory.d.ts +4 -0
  70. package/dist/types/src/transport/webrtc/rtc-transport-factory.d.ts.map +1 -0
  71. package/dist/types/src/transport/{simplepeer-transport-proxy.d.ts → webrtc/rtc-transport-proxy.d.ts} +10 -12
  72. package/dist/types/src/transport/webrtc/rtc-transport-proxy.d.ts.map +1 -0
  73. package/dist/types/src/transport/webrtc/rtc-transport-proxy.test.d.ts +2 -0
  74. package/dist/types/src/transport/webrtc/rtc-transport-proxy.test.d.ts.map +1 -0
  75. package/dist/types/src/transport/{simplepeer-transport-service.d.ts → webrtc/rtc-transport-service.d.ts} +9 -7
  76. package/dist/types/src/transport/webrtc/rtc-transport-service.d.ts.map +1 -0
  77. package/dist/types/src/transport/webrtc/rtc-transport-stats.d.ts +4 -0
  78. package/dist/types/src/transport/webrtc/rtc-transport-stats.d.ts.map +1 -0
  79. package/dist/types/src/transport/webrtc/rtc-transport.test.d.ts +2 -0
  80. package/dist/types/src/transport/webrtc/rtc-transport.test.d.ts.map +1 -0
  81. package/dist/types/src/transport/webrtc/test-utils.d.ts +5 -0
  82. package/dist/types/src/transport/webrtc/test-utils.d.ts.map +1 -0
  83. package/dist/types/src/transport/webrtc/utils.d.ts +3 -0
  84. package/dist/types/src/transport/webrtc/utils.d.ts.map +1 -0
  85. package/package.json +53 -36
  86. package/src/network-manager.ts +5 -13
  87. package/src/signal/ice.test.ts +1 -3
  88. package/src/signal/ice.ts +6 -1
  89. package/src/signal/{integration.test.ts → integration.node.test.ts} +9 -15
  90. package/src/signal/{swarm-messenger.test.ts → swarm-messenger.node.test.ts} +13 -23
  91. package/src/swarm/connection-limiter.test.ts +3 -6
  92. package/src/swarm/connection.test.ts +63 -38
  93. package/src/swarm/connection.ts +7 -7
  94. package/src/swarm/peer.ts +4 -1
  95. package/src/swarm/swarm.test.ts +10 -12
  96. package/src/swarm/swarm.ts +16 -3
  97. package/src/testing/test-builder.ts +14 -29
  98. package/src/testing/test-wire-protocol.ts +7 -8
  99. package/src/tests/basic-test-suite.ts +32 -31
  100. package/src/tests/memory-transport.test.ts +40 -42
  101. package/src/tests/property-test-suite.ts +21 -22
  102. package/src/tests/tcp-transport.node.test.ts +65 -0
  103. package/src/tests/utils.ts +3 -2
  104. package/src/tests/webrtc-transport.test.ts +10 -10
  105. package/src/transport/index.ts +1 -5
  106. package/src/transport/memory-transport.ts +2 -0
  107. package/src/transport/tcp/index.ts +5 -0
  108. package/src/transport/{tcp-transport.browser.ts → tcp/tcp-transport.browser.ts} +7 -3
  109. package/src/transport/{tcp-transport.ts → tcp/tcp-transport.ts} +3 -1
  110. package/src/transport/transport.ts +8 -7
  111. package/src/transport/webrtc/index.ts +7 -0
  112. package/src/transport/webrtc/rtc-connection-factory.ts +82 -0
  113. package/src/transport/webrtc/rtc-peer-connection.ts +472 -0
  114. package/src/transport/webrtc/rtc-transport-channel.test.ts +176 -0
  115. package/src/transport/webrtc/rtc-transport-channel.ts +195 -0
  116. package/src/transport/webrtc/rtc-transport-factory.ts +28 -0
  117. package/src/transport/webrtc/rtc-transport-proxy.test.ts +413 -0
  118. package/src/transport/webrtc/rtc-transport-proxy.ts +264 -0
  119. package/src/transport/webrtc/rtc-transport-service.ts +192 -0
  120. package/src/transport/webrtc/rtc-transport-stats.ts +67 -0
  121. package/src/transport/webrtc/rtc-transport.test.ts +210 -0
  122. package/src/transport/webrtc/test-utils.ts +22 -0
  123. package/src/transport/webrtc/utils.ts +36 -0
  124. package/src/typings.d.ts +8 -2
  125. package/dist/lib/browser/chunk-XYSYUN63.mjs.map +0 -7
  126. package/dist/lib/node/chunk-4YAYC7WN.cjs.map +0 -7
  127. package/dist/types/src/signal/integration.test.d.ts +0 -2
  128. package/dist/types/src/signal/integration.test.d.ts.map +0 -1
  129. package/dist/types/src/signal/swarm-messenger.test.d.ts +0 -2
  130. package/dist/types/src/signal/swarm-messenger.test.d.ts.map +0 -1
  131. package/dist/types/src/tests/tcp-transport.test.d.ts +0 -2
  132. package/dist/types/src/tests/tcp-transport.test.d.ts.map +0 -1
  133. package/dist/types/src/transport/libdatachannel-transport.d.ts +0 -42
  134. package/dist/types/src/transport/libdatachannel-transport.d.ts.map +0 -1
  135. package/dist/types/src/transport/libdatachannel-transport.test.d.ts +0 -2
  136. package/dist/types/src/transport/libdatachannel-transport.test.d.ts.map +0 -1
  137. package/dist/types/src/transport/memory-transport.test.d.ts +0 -2
  138. package/dist/types/src/transport/memory-transport.test.d.ts.map +0 -1
  139. package/dist/types/src/transport/simplepeer-simple-peer.d.ts +0 -2
  140. package/dist/types/src/transport/simplepeer-simple-peer.d.ts.map +0 -1
  141. package/dist/types/src/transport/simplepeer-transport-proxy-test.d.ts +0 -2
  142. package/dist/types/src/transport/simplepeer-transport-proxy-test.d.ts.map +0 -1
  143. package/dist/types/src/transport/simplepeer-transport-proxy.d.ts.map +0 -1
  144. package/dist/types/src/transport/simplepeer-transport-service.d.ts.map +0 -1
  145. package/dist/types/src/transport/simplepeer-transport.d.ts +0 -36
  146. package/dist/types/src/transport/simplepeer-transport.d.ts.map +0 -1
  147. package/dist/types/src/transport/simplepeer-transport.test.d.ts +0 -2
  148. package/dist/types/src/transport/simplepeer-transport.test.d.ts.map +0 -1
  149. package/dist/types/src/transport/tcp-transport.browser.d.ts.map +0 -1
  150. package/dist/types/src/transport/tcp-transport.d.ts.map +0 -1
  151. package/dist/types/src/transport/webrtc.d.ts +0 -6
  152. package/dist/types/src/transport/webrtc.d.ts.map +0 -1
  153. package/src/globals.d.ts +0 -7
  154. package/src/tests/tcp-transport.test.ts +0 -67
  155. package/src/transport/libdatachannel-transport.test.ts +0 -100
  156. package/src/transport/libdatachannel-transport.ts +0 -376
  157. package/src/transport/memory-transport.test.ts +0 -74
  158. package/src/transport/simplepeer-simple-peer.ts +0 -26
  159. package/src/transport/simplepeer-transport-proxy-test.ts +0 -181
  160. package/src/transport/simplepeer-transport-proxy.ts +0 -246
  161. package/src/transport/simplepeer-transport-service.ts +0 -160
  162. package/src/transport/simplepeer-transport.test.ts +0 -61
  163. package/src/transport/simplepeer-transport.ts +0 -250
  164. package/src/transport/webrtc.ts +0 -15
@@ -2,78 +2,103 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ import { describe, test } from 'vitest';
6
+
5
7
  import { sleep } from '@dxos/async';
6
8
  import { PublicKey } from '@dxos/keys';
7
- import { describe, test } from '@dxos/test';
8
9
 
9
10
  import { Connection } from './connection';
10
11
  import { TestWireProtocol } from '../testing/test-wire-protocol';
11
- import { createLibDataChannelTransportFactory, createSimplePeerTransportFactory } from '../transport';
12
+ import { createRtcTransportFactory } from '../transport';
13
+ import { chooseInitiatorPeer } from '../transport/webrtc/utils';
12
14
 
13
15
  describe('Connection', () => {
16
+ test('responder opens after initiator', async () => {
17
+ const { initiator, responder } = createPeerKeys();
18
+ await connectionTest({
19
+ fastConnectionKey: initiator,
20
+ slowConnectionKey: responder,
21
+ });
22
+ });
23
+
14
24
  test('initiator opens after responder', async () => {
25
+ const { initiator, responder } = createPeerKeys();
26
+ await connectionTest({
27
+ fastConnectionKey: responder,
28
+ slowConnectionKey: initiator,
29
+ });
30
+ });
31
+
32
+ const connectionTest = async (setup: { fastConnectionKey: PublicKey; slowConnectionKey: PublicKey }) => {
15
33
  const [topic, sessionId] = PublicKey.randomSequence();
16
- const peer1 = { peerKey: PublicKey.random().toHex() };
17
- const peer2 = { peerKey: PublicKey.random().toHex() };
18
- const protocol1 = new TestWireProtocol(PublicKey.from(peer1.peerKey));
19
- const connection1 = new Connection(
34
+ const slowPeer = { peerKey: setup.slowConnectionKey.toHex() };
35
+ const fastPeer = { peerKey: setup.fastConnectionKey.toHex() };
36
+
37
+ const slowPeerProtocol = new TestWireProtocol();
38
+ const slowConnection = new Connection(
20
39
  topic,
21
- peer1,
22
- peer2,
40
+ slowPeer,
41
+ fastPeer,
23
42
  sessionId,
24
43
  true,
25
44
  {
26
- offer: async (msg) => {
27
- return { accept: true };
28
- },
45
+ offer: async (msg) => ({ accept: true }),
29
46
  signal: async (msg) => {
30
- await connection2.signal(msg);
47
+ await fastConnection.signal(msg);
31
48
  },
32
49
  },
33
- protocol1.factory({
50
+ slowPeerProtocol.factory({
34
51
  initiator: true,
35
- localPeerId: PublicKey.from(peer1.peerKey),
36
- remotePeerId: PublicKey.from(peer2.peerKey),
52
+ localPeerId: PublicKey.from(slowPeer.peerKey),
53
+ remotePeerId: PublicKey.from(fastPeer.peerKey),
37
54
  topic,
38
55
  }),
39
- // TODO(nf): configure better
40
- process.env.MOCHA_ENV === 'nodejs' ? createLibDataChannelTransportFactory() : createSimplePeerTransportFactory(),
56
+ createRtcTransportFactory(),
41
57
  );
42
58
 
43
- const protocol2 = new TestWireProtocol(PublicKey.from(peer2.peerKey));
44
- const connection2 = new Connection(
59
+ const fastPeerProtocol = new TestWireProtocol();
60
+ const fastConnection = new Connection(
45
61
  topic,
46
- peer2,
47
- peer1,
62
+ fastPeer,
63
+ slowPeer,
48
64
  sessionId,
49
65
  false,
50
66
  {
51
- offer: async (msg) => {
52
- return { accept: true };
53
- },
67
+ offer: async (msg) => ({ accept: true }),
54
68
  signal: async (msg) => {
55
- await connection1.signal(msg);
69
+ await slowConnection.signal(msg);
56
70
  },
57
71
  },
58
- protocol2.factory({
72
+ fastPeerProtocol.factory({
59
73
  initiator: false,
60
- localPeerId: PublicKey.from(peer2.peerKey),
61
- remotePeerId: PublicKey.from(peer1.peerKey),
74
+ localPeerId: PublicKey.from(fastPeer.peerKey),
75
+ remotePeerId: PublicKey.from(slowPeer.peerKey),
62
76
  topic,
63
77
  }),
64
- // TODO(nf): configure better
65
- process.env.MOCHA_ENV === 'nodejs' ? createLibDataChannelTransportFactory() : createSimplePeerTransportFactory(),
78
+ createRtcTransportFactory(),
66
79
  );
67
80
 
68
- connection2.initiate();
69
- await connection2.openConnection();
70
- await sleep(100);
81
+ fastConnection.initiate();
82
+ await fastConnection.openConnection();
83
+ await sleep(200);
84
+
85
+ slowConnection.initiate();
86
+ await slowConnection.openConnection();
71
87
 
72
- connection1.initiate();
73
- await connection1.openConnection();
74
88
  await Promise.all([
75
- protocol1.testConnection(PublicKey.from(peer2.peerKey), 'test message 1'),
76
- protocol2.testConnection(PublicKey.from(peer1.peerKey), 'test message 2'),
89
+ slowPeerProtocol.testConnection(PublicKey.from(fastPeer.peerKey), 'test message 1'),
90
+ fastPeerProtocol.testConnection(PublicKey.from(slowPeer.peerKey), 'test message 2'),
77
91
  ]);
78
- });
92
+ };
93
+
94
+ const createPeerKeys = () => {
95
+ const peer1 = PublicKey.random();
96
+ const peer2 = PublicKey.random();
97
+ const initiator = PublicKey.fromHex(chooseInitiatorPeer(peer1.toHex(), peer2.toHex()));
98
+ if (initiator.equals(peer1)) {
99
+ return { initiator: peer1, responder: peer2 };
100
+ } else {
101
+ return { initiator: peer2, responder: peer1 };
102
+ }
103
+ };
79
104
  });
@@ -15,7 +15,6 @@ import {
15
15
  ConnectionResetError,
16
16
  ConnectivityError,
17
17
  TimeoutError,
18
- UnknownProtocolError,
19
18
  trace,
20
19
  } from '@dxos/protocols';
21
20
  import { type Signal } from '@dxos/protocols/proto/dxos/mesh/swarm';
@@ -202,14 +201,15 @@ export class Connection {
202
201
 
203
202
  invariant(!this._transport);
204
203
  this._transport = this._transportFactory.createTransport({
204
+ ownPeerKey: this.localInfo.peerKey,
205
+ remotePeerKey: this.remoteInfo.peerKey,
206
+ topic: this.topic.toHex(),
205
207
  initiator: this.initiator,
206
208
  stream: this._protocol.stream,
207
209
  sendSignal: async (signal) => this._sendSignal(signal),
208
210
  sessionId: this.sessionId,
209
211
  });
210
212
 
211
- await this._transport.open();
212
-
213
213
  this._transport.connected.once(async () => {
214
214
  this._changeState(ConnectionState.CONNECTED);
215
215
  await this.connectedTimeoutContext.dispose();
@@ -234,12 +234,10 @@ export class Connection {
234
234
  // TODO(nf): fix ErrorStream so instanceof works here
235
235
  if (err instanceof ConnectionResetError) {
236
236
  log.info('aborting due to transport ConnectionResetError');
237
- this.abort().catch((err) => this.errors.raise(err));
237
+ this.abort(err).catch((err) => this.errors.raise(err));
238
238
  } else if (err instanceof ConnectivityError) {
239
239
  log.info('aborting due to transport ConnectivityError');
240
- this.abort().catch((err) => this.errors.raise(err));
241
- } else if (err instanceof UnknownProtocolError) {
242
- log.warn('unsure what to do with UnknownProtocolError, will keep on truckin', { err });
240
+ this.abort(err).catch((err) => this.errors.raise(err));
243
241
  }
244
242
 
245
243
  if (this._state !== ConnectionState.CLOSED && this._state !== ConnectionState.CLOSING) {
@@ -248,6 +246,8 @@ export class Connection {
248
246
  }
249
247
  });
250
248
 
249
+ await this._transport.open();
250
+
251
251
  // Replay signals that were received before transport was created.
252
252
  for (const signal of this._incomingSignalBuffer) {
253
253
  void this._transport.onSignal(signal); // TODO(burdon): Remove async?
package/src/swarm/peer.ts CHANGED
@@ -285,7 +285,8 @@ export class Peer {
285
285
  });
286
286
  },
287
287
  onClosed: (err) => {
288
- log('connection closed', { topic: this.topic, peerId: this.localInfo, remoteId: this.remoteInfo, initiator });
288
+ const logMeta = { topic: this.topic, peerId: this.localInfo, remoteId: this.remoteInfo, initiator };
289
+ log('connection closed', logMeta);
289
290
 
290
291
  // Make sure none of the connections are stuck in the limiter.
291
292
  this._connectionLimiter.doneConnecting(sessionId);
@@ -310,11 +311,13 @@ export class Peer {
310
311
  this.availableToConnect = false;
311
312
  this._availableAfter = increaseInterval(this._availableAfter);
312
313
  }
314
+
313
315
  this._callbacks.onDisconnected();
314
316
 
315
317
  scheduleTask(
316
318
  this._connectionCtx!,
317
319
  () => {
320
+ log('peer became available', logMeta);
318
321
  this.availableToConnect = true;
319
322
  this._callbacks.onPeerAvailable();
320
323
  },
@@ -2,7 +2,7 @@
2
2
  // Copyright 2020 DXOS.org
3
3
  //
4
4
 
5
- import { expect } from 'chai';
5
+ import { onTestFinished, describe, expect, test } from 'vitest';
6
6
 
7
7
  import { asyncTimeout, sleep } from '@dxos/async';
8
8
  import { PublicKey } from '@dxos/keys';
@@ -13,7 +13,6 @@ import {
13
13
  type PeerInfo,
14
14
  type SignalManager,
15
15
  } from '@dxos/messaging';
16
- import { afterTest, describe, test } from '@dxos/test';
17
16
  import { ComplexSet } from '@dxos/util';
18
17
 
19
18
  import { ConnectionState } from './connection';
@@ -21,7 +20,7 @@ import { ConnectionLimiter } from './connection-limiter';
21
20
  import { Swarm } from './swarm';
22
21
  import { TestWireProtocol } from '../testing/test-wire-protocol';
23
22
  import { FullyConnectedTopology } from '../topology';
24
- import { createLibDataChannelTransportFactory, createSimplePeerTransportFactory } from '../transport';
23
+ import { createRtcTransportFactory } from '../transport';
25
24
 
26
25
  type TestPeer = {
27
26
  swarm: Swarm;
@@ -47,21 +46,20 @@ describe('Swarm', () => {
47
46
  signalManager?: SignalManager;
48
47
  initiationDelay?: number;
49
48
  }): Promise<TestPeer> => {
50
- const protocol = new TestWireProtocol(PublicKey.from(peer.peerKey));
49
+ const protocol = new TestWireProtocol();
51
50
  const swarm = new Swarm(
52
51
  topic,
53
52
  peer,
54
53
  new FullyConnectedTopology(),
55
54
  protocol.factory,
56
55
  new Messenger({ signalManager }),
57
- // TODO(nf): configure better
58
- process.env.MOCHA_ENV === 'nodejs' ? createLibDataChannelTransportFactory() : createSimplePeerTransportFactory(),
56
+ createRtcTransportFactory(),
59
57
  undefined,
60
58
  connectionLimiter,
61
59
  initiationDelay,
62
60
  );
63
61
 
64
- afterTest(async () => {
62
+ onTestFinished(async () => {
65
63
  await swarm.destroy();
66
64
  await signalManager.close();
67
65
  });
@@ -81,7 +79,7 @@ describe('Swarm', () => {
81
79
  expect(peer2.swarm.connections.length).to.equal(0);
82
80
 
83
81
  await connectSwarms(peer1, peer2);
84
- }).timeout(5_000);
82
+ });
85
83
 
86
84
  test('two peers try to originate connections to each other simultaneously', async () => {
87
85
  const topic = PublicKey.random();
@@ -93,7 +91,7 @@ describe('Swarm', () => {
93
91
  expect(peer2.swarm.connections.length).to.equal(0);
94
92
 
95
93
  await connectSwarms(peer1, peer2);
96
- }).timeout(5_000);
94
+ });
97
95
 
98
96
  test('with simultaneous connections one of the peers drops initiated connection', async () => {
99
97
  const topic = PublicKey.random();
@@ -111,9 +109,9 @@ describe('Swarm', () => {
111
109
 
112
110
  await connectSwarms(peer1, peer2);
113
111
  await asyncTimeout(connectionDisplaced!, 1000);
114
- }).timeout(5_000);
112
+ });
115
113
 
116
- test('second peer discovered after delay', async () => {
114
+ test('second peer discovered after delay', { timeout: 10_000 }, async () => {
117
115
  const topic = PublicKey.random();
118
116
 
119
117
  const peer1 = await setupSwarm({ topic });
@@ -126,7 +124,7 @@ describe('Swarm', () => {
126
124
  expect(peer2.swarm.connections.length).to.equal(0);
127
125
 
128
126
  await connectSwarms(peer1, peer2, () => sleep(15));
129
- }).timeout(10_000);
127
+ });
130
128
 
131
129
  test('connection limiter', async () => {
132
130
  // remotePeer1 <--> peer (connectionLimiter: max = 1) <--> remotePeer2
@@ -29,7 +29,7 @@ const getClassName = (obj: any) => Object.getPrototypeOf(obj).constructor.name;
29
29
 
30
30
  /**
31
31
  * A single peer's view of the swarm.
32
- * Manages a set of connections implemented by simple-peer instances.
32
+ * Manages a set of peers subscribed on the same topic.
33
33
  * Routes signal events and maintains swarm topology.
34
34
  */
35
35
  export class Swarm {
@@ -189,8 +189,10 @@ export class Swarm {
189
189
  const peer = this._peers.get(swarmEvent.peerLeft.peer);
190
190
  if (peer) {
191
191
  peer.advertizing = false;
192
- if (peer.connection?.state !== ConnectionState.CONNECTED) {
193
- // Destroy peer only if there is no p2p-connection established
192
+ // Destroy peer only if there is no p2p-connection established. Otherwise, let peers go through
193
+ // the graceful shutdown protocol.
194
+ if (this._isConnectionEstablishmentInProgress(peer)) {
195
+ log(`destroying peer, state: ${peer.connection?.state}`);
194
196
  void this._destroyPeer(swarmEvent.peerLeft.peer, 'peer left').catch((err) => log.catch(err));
195
197
  }
196
198
  } else {
@@ -277,6 +279,7 @@ export class Swarm {
277
279
  },
278
280
  onDisconnected: async () => {
279
281
  if (this._isUnregistered(peer)) {
282
+ log.verbose('ignored onDisconnected for unregistered peer');
280
283
  return;
281
284
  }
282
285
  if (!peer!.advertizing) {
@@ -312,6 +315,7 @@ export class Swarm {
312
315
  }
313
316
 
314
317
  private async _destroyPeer(peerInfo: PeerInfo, reason?: string) {
318
+ log('destroy peer', { peerKey: peerInfo.peerKey, reason });
315
319
  const peer = this._peers.get(peerInfo);
316
320
  invariant(peer);
317
321
  this._peers.delete(peerInfo);
@@ -399,6 +403,15 @@ export class Swarm {
399
403
  await peer.closeConnection();
400
404
  }
401
405
 
406
+ private _isConnectionEstablishmentInProgress(peer: Peer) {
407
+ if (!peer.connection) {
408
+ return true;
409
+ }
410
+ return [ConnectionState.INITIAL, ConnectionState.CREATED, ConnectionState.CONNECTING].includes(
411
+ peer.connection.state,
412
+ );
413
+ }
414
+
402
415
  private _isUnregistered(peer?: Peer): boolean {
403
416
  return !peer || this._peers.get(peer.remoteInfo) !== peer;
404
417
  }
@@ -16,19 +16,12 @@ import { type Runtime } from '@dxos/protocols/proto/dxos/config';
16
16
  import { createLinkedPorts, createProtoRpcPeer, type ProtoRpcPeer } from '@dxos/rpc';
17
17
  import { ComplexMap } from '@dxos/util';
18
18
 
19
+ import { TcpTransportFactory } from '#tcp-transport';
19
20
  import { type TestTeleportExtensionFactory, TestWireProtocol } from './test-wire-protocol';
20
21
  import { SwarmNetworkManager } from '../network-manager';
21
22
  import { FullyConnectedTopology } from '../topology';
22
- import {
23
- createLibDataChannelTransportFactory,
24
- createSimplePeerTransportFactory,
25
- MemoryTransportFactory,
26
- SimplePeerTransportProxyFactory,
27
- SimplePeerTransportService,
28
- type TransportFactory,
29
- TransportKind,
30
- } from '../transport';
31
- import { TcpTransportFactory } from '../transport/tcp-transport';
23
+ import { MemoryTransportFactory, type TransportFactory, TransportKind } from '../transport';
24
+ import { createRtcTransportFactory, RtcTransportProxyFactory, RtcTransportService } from '../transport';
32
25
 
33
26
  // Signal server will be started by the setup script.
34
27
  const port = process.env.SIGNAL_PORT ?? 4000;
@@ -85,18 +78,13 @@ export class TestPeer {
85
78
  constructor(
86
79
  private readonly testBuilder: TestBuilder,
87
80
  public readonly peerId: PublicKey,
88
- public readonly transport?: TransportKind,
81
+ public readonly transport: TransportKind = testBuilder.options.signalHosts
82
+ ? TransportKind.WEB_RTC
83
+ : TransportKind.MEMORY,
89
84
  ) {
90
85
  this._signalManager = this.testBuilder.createSignalManager();
91
- if (!transport) {
92
- if (this.testBuilder.options.signalHosts) {
93
- // TODO(nf): configure better
94
- transport = process.env.MOCHA_ENV === 'nodejs' ? TransportKind.LIBDATACHANNEL : TransportKind.SIMPLE_PEER;
95
- } else {
96
- transport = TransportKind.MEMORY;
97
- }
98
- }
99
- this._networkManager = this.createNetworkManager(transport);
86
+ this._networkManager = this.createNetworkManager(this.transport);
87
+ this._networkManager.setPeerInfo({ identityKey: peerId.toHex(), peerKey: peerId.toHex() });
100
88
  }
101
89
 
102
90
  // TODO(burdon): Move to TestBuilder.
@@ -110,13 +98,10 @@ export class TestPeer {
110
98
  case TransportKind.TCP:
111
99
  transportFactory = TcpTransportFactory;
112
100
  break;
113
- case TransportKind.SIMPLE_PEER:
114
- transportFactory = createSimplePeerTransportFactory();
115
- break;
116
- case TransportKind.LIBDATACHANNEL:
117
- transportFactory = createLibDataChannelTransportFactory();
101
+ case TransportKind.WEB_RTC:
102
+ transportFactory = createRtcTransportFactory();
118
103
  break;
119
- case TransportKind.SIMPLE_PEER_PROXY:
104
+ case TransportKind.WEB_RTC_PROXY:
120
105
  {
121
106
  // Simulates bridge to shared worker.
122
107
  const [proxyPort, servicePort] = createLinkedPorts();
@@ -137,14 +122,14 @@ export class TestPeer {
137
122
  exposed: {
138
123
  BridgeService: schema.getService('dxos.mesh.bridge.BridgeService'),
139
124
  },
140
- handlers: { BridgeService: new SimplePeerTransportService() },
125
+ handlers: { BridgeService: new RtcTransportService() },
141
126
  noHandshake: true,
142
127
  encodingOptions: {
143
128
  preserveAny: true,
144
129
  },
145
130
  });
146
131
 
147
- transportFactory = new SimplePeerTransportProxyFactory().setBridgeService(this._proxy.rpc.BridgeService);
132
+ transportFactory = new RtcTransportProxyFactory().setBridgeService(this._proxy.rpc.BridgeService);
148
133
  }
149
134
  break;
150
135
  default:
@@ -219,7 +204,7 @@ export class TestSwarmConnection {
219
204
  ) {
220
205
  // TODO(burdon): Configure plugins.
221
206
  // TODO(burdon): Prevent reuse?
222
- this.protocol = new TestWireProtocol(this.peer.peerId, this.extensionFactory);
207
+ this.protocol = new TestWireProtocol(this.extensionFactory);
223
208
  }
224
209
 
225
210
  // TODO(burdon): Need to create new plugin instance per swarm?
@@ -29,18 +29,17 @@ export class TestWireProtocol {
29
29
  ({ remotePeerId, extension }) => remotePeerId.toHex() + extension,
30
30
  );
31
31
 
32
- constructor(
33
- public readonly peerId: PublicKey,
34
- private readonly _extensionFactory: TestTeleportExtensionFactory = () => [],
35
- ) {}
32
+ constructor(private readonly _extensionFactory: TestTeleportExtensionFactory = () => []) {}
36
33
 
37
34
  readonly factory = createTeleportProtocolFactory(async (teleport) => {
38
35
  log('create', { remotePeerId: teleport.remotePeerId });
36
+ const handleDisconnect = () => {
37
+ this.connections.delete(teleport.remotePeerId);
38
+ this.disconnected.emit(teleport.remotePeerId);
39
+ };
39
40
  const extension = new TestExtension({
40
- onClose: async () => {
41
- this.connections.delete(teleport.remotePeerId);
42
- this.disconnected.emit(teleport.remotePeerId);
43
- },
41
+ onClose: async () => handleDisconnect(),
42
+ onAbort: async () => handleDisconnect(),
44
43
  });
45
44
  this.connections.set(teleport.remotePeerId, extension);
46
45
  teleport.addExtension('test', extension);
@@ -2,13 +2,11 @@
2
2
  // Copyright 2021 DXOS.org
3
3
  //
4
4
 
5
- import { expect } from 'chai';
6
- import waitForExpect from 'wait-for-expect';
5
+ import { onTestFinished, expect, test } from 'vitest';
7
6
 
8
7
  import { asyncTimeout } from '@dxos/async';
9
8
  import { PublicKey } from '@dxos/keys';
10
9
  import { log } from '@dxos/log';
11
- import { afterTest, test } from '@dxos/test';
12
10
  import { range } from '@dxos/util';
13
11
 
14
12
  import { exchangeMessages, joinSwarm, leaveSwarm, openAndCloseAfterTest } from './utils';
@@ -34,21 +32,21 @@ export const basicTestSuite = (testBuilder: TestBuilder, runTests = true) => {
34
32
  const [swarm1, swarm2] = await joinSwarm([peer1, peer2], topic, () => new FullyConnectedTopology());
35
33
  await exchangeMessages(swarm1, swarm2);
36
34
  await leaveSwarm([peer1, peer2], topic);
37
- }).tag('flaky');
35
+ });
38
36
 
39
37
  // TODO(burdon): Test with more peers (configure and test messaging).
40
38
  test('joins swarm with star topology', async () => {
41
39
  const peer1 = testBuilder.createPeer();
42
- afterTest(() => peer1.close());
40
+ onTestFinished(() => peer1.close());
43
41
  const peer2 = testBuilder.createPeer();
44
- afterTest(() => peer2.close());
42
+ onTestFinished(() => peer2.close());
45
43
  await openAndCloseAfterTest([peer1, peer2]);
46
44
 
47
45
  const topic = PublicKey.random();
48
46
  const [swarm1, swarm2] = await joinSwarm([peer1, peer2], topic, () => new StarTopology(peer1.peerId)); // NOTE: Same peer.
49
47
  await exchangeMessages(swarm1, swarm2);
50
48
  await leaveSwarm([peer1, peer2], topic);
51
- }).tag('flaky');
49
+ });
52
50
 
53
51
  // TODO(burdon): Fails when trying to reconnect to same topic.
54
52
  test('joins swarm multiple times', async () => {
@@ -75,7 +73,7 @@ export const basicTestSuite = (testBuilder: TestBuilder, runTests = true) => {
75
73
  await exchangeMessages(swarm1, swarm2);
76
74
  await leaveSwarm([peer1, peer2], topic2);
77
75
  }
78
- }).tag('flaky');
76
+ });
79
77
 
80
78
  test('joins multiple swarms', async () => {
81
79
  // TODO(burdon): N peers.
@@ -87,7 +85,7 @@ export const basicTestSuite = (testBuilder: TestBuilder, runTests = true) => {
87
85
  const numSwarms = 5;
88
86
  const topics = Array.from(Array(numSwarms)).map(() => PublicKey.random());
89
87
  expect(topics).to.have.length(numSwarms);
90
- }).tag('flaky');
88
+ });
91
89
 
92
90
  test('joins multiple swarms concurrently', async () => {
93
91
  const createSwarm = async () => {
@@ -109,7 +107,7 @@ export const basicTestSuite = (testBuilder: TestBuilder, runTests = true) => {
109
107
  test1.swarm1a.protocol.testConnection(test1.peer2a.peerId),
110
108
  test2.swarm1a.protocol.testConnection(test2.peer2a.peerId),
111
109
  ]);
112
- }).tag('flaky');
110
+ });
113
111
 
114
112
  test('peers reconnect after and error in connection', async () => {
115
113
  const peer1 = testBuilder.createPeer();
@@ -120,20 +118,20 @@ export const basicTestSuite = (testBuilder: TestBuilder, runTests = true) => {
120
118
  const [swarm1, swarm2] = await joinSwarm([peer1, peer2], topic, () => new FullyConnectedTopology());
121
119
  await exchangeMessages(swarm1, swarm2);
122
120
 
123
- void swarm1.protocol.connections.get(swarm2.peer.peerId)!.closeConnection(new Error('test error'));
121
+ const disconnectedKeys = new Set();
122
+ swarm1.protocol.disconnected.on((peerInfo) => disconnectedKeys.add(peerInfo.toHex()));
123
+ swarm2.protocol.disconnected.on((peerInfo) => disconnectedKeys.add(peerInfo.toHex()));
124
124
 
125
+ void swarm1.protocol.connections.get(swarm2.peer.peerId)!.closeConnection(new Error('test error'));
125
126
  // Wait until both peers are disconnected.
126
- await Promise.all([
127
- swarm1.protocol.disconnected.waitForCondition(() => swarm1.protocol.connections.size === 0),
128
- swarm2.protocol.disconnected.waitForCondition(() => swarm2.protocol.connections.size === 0),
129
- ]);
127
+ await expect.poll(() => disconnectedKeys.size).toEqual(2);
130
128
 
131
129
  await exchangeMessages(swarm1, swarm2);
132
130
 
133
131
  await leaveSwarm([peer1, peer2], topic);
134
- }).tag('flaky');
132
+ });
135
133
 
136
- test('going offline and back online', async () => {
134
+ test('going offline and back online', { timeout: 2_000 }, async () => {
137
135
  const peer1 = testBuilder.createPeer();
138
136
  const peer2 = testBuilder.createPeer();
139
137
  await openAndCloseAfterTest([peer1, peer2]);
@@ -158,25 +156,28 @@ export const basicTestSuite = (testBuilder: TestBuilder, runTests = true) => {
158
156
  await peerLeft;
159
157
 
160
158
  // Wait for peer to be removed from the swarm.
161
- await waitForExpect(() => {
162
- expect(!!peer2._networkManager.getSwarm(topic)!._peers.get({ peerKey: peer1.peerId.toHex() })?.advertizing).to.be
163
- .false;
164
- }, 1_000);
159
+ await expect
160
+ .poll(() => !!peer2._networkManager.getSwarm(topic)!._peers.get({ peerKey: peer1.peerId.toHex() })?.advertizing, {
161
+ timeout: 1_000,
162
+ })
163
+ .toBe(false);
165
164
 
166
165
  await peer1.goOnline();
167
166
 
168
- await waitForExpect(() => {
169
- expect(peer1._networkManager.getSwarm(topic)?._peers.get({ peerKey: peer2.peerId.toHex() })?.advertizing).to.be
170
- .true;
171
- expect(peer2._networkManager.getSwarm(topic)?._peers.get({ peerKey: peer1.peerId.toHex() })?.advertizing).to.be
172
- .true;
173
- }, 2_000);
167
+ await expect
168
+ .poll(() => peer1._networkManager.getSwarm(topic)?._peers.get({ peerKey: peer2.peerId.toHex() })?.advertizing, {
169
+ timeout: 2_000,
170
+ })
171
+ .toBe(true);
172
+ await expect
173
+ .poll(() => peer2._networkManager.getSwarm(topic)?._peers.get({ peerKey: peer1.peerId.toHex() })?.advertizing, {
174
+ timeout: 2_000,
175
+ })
176
+ .toBe(true);
174
177
 
175
178
  await exchangeMessages(swarm1, swarm2);
176
179
  await leaveSwarm([peer1, peer2], topic);
177
- })
178
- .tag('flaky')
179
- .timeout(2_000);
180
+ });
180
181
 
181
182
  // TODO(mykola): Fails with large amount of peers ~10.
182
183
  test('many peers and connections', async () => {
@@ -207,5 +208,5 @@ export const basicTestSuite = (testBuilder: TestBuilder, runTests = true) => {
207
208
  );
208
209
 
209
210
  await asyncTimeout(Promise.all(swarmsAllPeersConnected), 2_000);
210
- }).tag('stress');
211
+ });
211
212
  };