@dxos/network-manager 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/LICENSE +21 -0
  2. package/dist/src/connection-log.d.ts +23 -0
  3. package/dist/src/connection-log.d.ts.map +1 -0
  4. package/dist/src/connection-log.js +98 -0
  5. package/dist/src/connection-log.js.map +1 -0
  6. package/dist/src/index.d.ts +9 -0
  7. package/dist/src/index.d.ts.map +1 -0
  8. package/dist/src/index.js +28 -0
  9. package/dist/src/index.js.map +1 -0
  10. package/dist/src/network-manager.d.ts +80 -0
  11. package/dist/src/network-manager.d.ts.map +1 -0
  12. package/dist/src/network-manager.js +123 -0
  13. package/dist/src/network-manager.js.map +1 -0
  14. package/dist/src/network-manager.test.d.ts +2 -0
  15. package/dist/src/network-manager.test.d.ts.map +1 -0
  16. package/dist/src/network-manager.test.js +621 -0
  17. package/dist/src/network-manager.test.js.map +1 -0
  18. package/dist/src/presence.test.d.ts +2 -0
  19. package/dist/src/presence.test.d.ts.map +1 -0
  20. package/dist/src/presence.test.js +60 -0
  21. package/dist/src/presence.test.js.map +1 -0
  22. package/dist/src/protocol-factory.d.ts +30 -0
  23. package/dist/src/protocol-factory.d.ts.map +1 -0
  24. package/dist/src/protocol-factory.js +64 -0
  25. package/dist/src/protocol-factory.js.map +1 -0
  26. package/dist/src/signal/index.d.ts +4 -0
  27. package/dist/src/signal/index.d.ts.map +1 -0
  28. package/dist/src/signal/index.js +23 -0
  29. package/dist/src/signal/index.js.map +1 -0
  30. package/dist/src/signal/integration.test.d.ts +2 -0
  31. package/dist/src/signal/integration.test.d.ts.map +1 -0
  32. package/dist/src/signal/integration.test.js +105 -0
  33. package/dist/src/signal/integration.test.js.map +1 -0
  34. package/dist/src/signal/message-router.d.ts +39 -0
  35. package/dist/src/signal/message-router.d.ts.map +1 -0
  36. package/dist/src/signal/message-router.js +135 -0
  37. package/dist/src/signal/message-router.js.map +1 -0
  38. package/dist/src/signal/message-router.test.d.ts +2 -0
  39. package/dist/src/signal/message-router.test.d.ts.map +1 -0
  40. package/dist/src/signal/message-router.test.js +211 -0
  41. package/dist/src/signal/message-router.test.js.map +1 -0
  42. package/dist/src/signal/signal-connection.d.ts +21 -0
  43. package/dist/src/signal/signal-connection.d.ts.map +1 -0
  44. package/dist/src/signal/signal-connection.js +6 -0
  45. package/dist/src/signal/signal-connection.js.map +1 -0
  46. package/dist/src/signal/signal-messaging.d.ts +34 -0
  47. package/dist/src/signal/signal-messaging.d.ts.map +1 -0
  48. package/dist/src/signal/signal-messaging.js +6 -0
  49. package/dist/src/signal/signal-messaging.js.map +1 -0
  50. package/dist/src/swarm/connection.d.ts +69 -0
  51. package/dist/src/swarm/connection.d.ts.map +1 -0
  52. package/dist/src/swarm/connection.js +187 -0
  53. package/dist/src/swarm/connection.js.map +1 -0
  54. package/dist/src/swarm/index.d.ts +4 -0
  55. package/dist/src/swarm/index.d.ts.map +1 -0
  56. package/dist/src/swarm/index.js +23 -0
  57. package/dist/src/swarm/index.js.map +1 -0
  58. package/dist/src/swarm/swarm-mapper.d.ts +30 -0
  59. package/dist/src/swarm/swarm-mapper.d.ts.map +1 -0
  60. package/dist/src/swarm/swarm-mapper.js +90 -0
  61. package/dist/src/swarm/swarm-mapper.js.map +1 -0
  62. package/dist/src/swarm/swarm.d.ts +64 -0
  63. package/dist/src/swarm/swarm.d.ts.map +1 -0
  64. package/dist/src/swarm/swarm.js +276 -0
  65. package/dist/src/swarm/swarm.js.map +1 -0
  66. package/dist/src/swarm/swarm.test.d.ts +2 -0
  67. package/dist/src/swarm/swarm.test.d.ts.map +1 -0
  68. package/dist/src/swarm/swarm.test.js +145 -0
  69. package/dist/src/swarm/swarm.test.js.map +1 -0
  70. package/dist/src/testing/index.d.ts +2 -0
  71. package/dist/src/testing/index.d.ts.map +1 -0
  72. package/dist/src/testing/index.js +21 -0
  73. package/dist/src/testing/index.js.map +1 -0
  74. package/dist/src/testing/test-protocol.d.ts +59 -0
  75. package/dist/src/testing/test-protocol.d.ts.map +1 -0
  76. package/dist/src/testing/test-protocol.js +140 -0
  77. package/dist/src/testing/test-protocol.js.map +1 -0
  78. package/dist/src/topology/fully-connected-topology.d.ts +11 -0
  79. package/dist/src/topology/fully-connected-topology.d.ts.map +1 -0
  80. package/dist/src/topology/fully-connected-topology.js +34 -0
  81. package/dist/src/topology/fully-connected-topology.js.map +1 -0
  82. package/dist/src/topology/index.d.ts +5 -0
  83. package/dist/src/topology/index.d.ts.map +1 -0
  84. package/dist/src/topology/index.js +24 -0
  85. package/dist/src/topology/index.js.map +1 -0
  86. package/dist/src/topology/mmst-topology.d.ts +31 -0
  87. package/dist/src/topology/mmst-topology.d.ts.map +1 -0
  88. package/dist/src/topology/mmst-topology.js +73 -0
  89. package/dist/src/topology/mmst-topology.js.map +1 -0
  90. package/dist/src/topology/star-topology.d.ts +13 -0
  91. package/dist/src/topology/star-topology.d.ts.map +1 -0
  92. package/dist/src/topology/star-topology.js +60 -0
  93. package/dist/src/topology/star-topology.js.map +1 -0
  94. package/dist/src/topology/topology.d.ts +56 -0
  95. package/dist/src/topology/topology.d.ts.map +1 -0
  96. package/dist/src/topology/topology.js +6 -0
  97. package/dist/src/topology/topology.js.map +1 -0
  98. package/dist/src/transport/index.d.ts +6 -0
  99. package/dist/src/transport/index.d.ts.map +1 -0
  100. package/dist/src/transport/index.js +25 -0
  101. package/dist/src/transport/index.js.map +1 -0
  102. package/dist/src/transport/memory-transport.d.ts +32 -0
  103. package/dist/src/transport/memory-transport.d.ts.map +1 -0
  104. package/dist/src/transport/memory-transport.js +98 -0
  105. package/dist/src/transport/memory-transport.js.map +1 -0
  106. package/dist/src/transport/memory-transport.test.d.ts +2 -0
  107. package/dist/src/transport/memory-transport.test.d.ts.map +1 -0
  108. package/dist/src/transport/memory-transport.test.js +84 -0
  109. package/dist/src/transport/memory-transport.test.js.map +1 -0
  110. package/dist/src/transport/transport.d.ts +38 -0
  111. package/dist/src/transport/transport.d.ts.map +1 -0
  112. package/dist/src/transport/transport.js +6 -0
  113. package/dist/src/transport/transport.js.map +1 -0
  114. package/dist/src/transport/webrtc-transport-proxy.d.ts +48 -0
  115. package/dist/src/transport/webrtc-transport-proxy.d.ts.map +1 -0
  116. package/dist/src/transport/webrtc-transport-proxy.js +139 -0
  117. package/dist/src/transport/webrtc-transport-proxy.js.map +1 -0
  118. package/dist/src/transport/webrtc-transport-proxy.test.d.ts +2 -0
  119. package/dist/src/transport/webrtc-transport-proxy.test.d.ts.map +1 -0
  120. package/dist/src/transport/webrtc-transport-proxy.test.js +245 -0
  121. package/dist/src/transport/webrtc-transport-proxy.test.js.map +1 -0
  122. package/dist/src/transport/webrtc-transport-service.d.ts +15 -0
  123. package/dist/src/transport/webrtc-transport-service.d.ts.map +1 -0
  124. package/dist/src/transport/webrtc-transport-service.js +97 -0
  125. package/dist/src/transport/webrtc-transport-service.js.map +1 -0
  126. package/dist/src/transport/webrtc-transport.d.ts +34 -0
  127. package/dist/src/transport/webrtc-transport.d.ts.map +1 -0
  128. package/dist/src/transport/webrtc-transport.js +100 -0
  129. package/dist/src/transport/webrtc-transport.js.map +1 -0
  130. package/dist/src/transport/webrtc-transport.test.d.ts +2 -0
  131. package/dist/src/transport/webrtc-transport.test.d.ts.map +1 -0
  132. package/dist/src/transport/webrtc-transport.test.js +80 -0
  133. package/dist/src/transport/webrtc-transport.test.js.map +1 -0
  134. package/dist/src/transport/webrtc.d.ts +6 -0
  135. package/dist/src/transport/webrtc.d.ts.map +1 -0
  136. package/dist/src/transport/webrtc.js +17 -0
  137. package/dist/src/transport/webrtc.js.map +1 -0
  138. package/dist/src/transport/wrtc-simple-peer.test.d.ts +2 -0
  139. package/dist/src/transport/wrtc-simple-peer.test.d.ts.map +1 -0
  140. package/dist/src/transport/wrtc-simple-peer.test.js +24 -0
  141. package/dist/src/transport/wrtc-simple-peer.test.js.map +1 -0
  142. package/dist/src/types.d.ts +6 -0
  143. package/dist/src/types.d.ts.map +1 -0
  144. package/dist/src/types.js +6 -0
  145. package/dist/src/types.js.map +1 -0
  146. package/package.json +58 -0
  147. package/src/connection-log.ts +102 -0
  148. package/src/globals.d.ts +7 -0
  149. package/src/index.ts +12 -0
  150. package/src/network-manager.test.ts +764 -0
  151. package/src/network-manager.ts +204 -0
  152. package/src/presence.test.ts +78 -0
  153. package/src/protocol-factory.ts +83 -0
  154. package/src/signal/index.ts +7 -0
  155. package/src/signal/integration.test.ts +129 -0
  156. package/src/signal/message-router.test.ts +274 -0
  157. package/src/signal/message-router.ts +200 -0
  158. package/src/signal/signal-connection.ts +20 -0
  159. package/src/signal/signal-messaging.ts +38 -0
  160. package/src/swarm/connection.ts +206 -0
  161. package/src/swarm/index.ts +7 -0
  162. package/src/swarm/swarm-mapper.ts +120 -0
  163. package/src/swarm/swarm.test.ts +192 -0
  164. package/src/swarm/swarm.ts +328 -0
  165. package/src/testing/index.ts +5 -0
  166. package/src/testing/test-protocol.ts +160 -0
  167. package/src/topology/fully-connected-topology.ts +38 -0
  168. package/src/topology/index.ts +8 -0
  169. package/src/topology/mmst-topology.ts +105 -0
  170. package/src/topology/star-topology.ts +67 -0
  171. package/src/topology/topology.ts +69 -0
  172. package/src/transport/index.ts +9 -0
  173. package/src/transport/memory-transport.test.ts +110 -0
  174. package/src/transport/memory-transport.ts +128 -0
  175. package/src/transport/transport.ts +47 -0
  176. package/src/transport/webrtc-transport-proxy.test.ts +290 -0
  177. package/src/transport/webrtc-transport-proxy.ts +167 -0
  178. package/src/transport/webrtc-transport-service.ts +115 -0
  179. package/src/transport/webrtc-transport.test.ts +112 -0
  180. package/src/transport/webrtc-transport.ts +122 -0
  181. package/src/transport/webrtc.ts +15 -0
  182. package/src/transport/wrtc-simple-peer.test.ts +24 -0
  183. package/src/types.ts +10 -0
  184. package/src/typings.d.ts +7 -0
@@ -0,0 +1,206 @@
1
+ //
2
+ // Copyright 2021 DXOS.org
3
+ //
4
+
5
+ import assert from 'node:assert';
6
+
7
+ import { Event, synchronized } from '@dxos/async';
8
+ import { ErrorStream } from '@dxos/debug';
9
+ import { PublicKey } from '@dxos/keys';
10
+ import { log } from '@dxos/log';
11
+ import { Protocol } from '@dxos/mesh-protocol';
12
+ import { Signal } from '@dxos/protocols/proto/dxos/mesh/swarm';
13
+
14
+ import { SignalMessage, SignalMessaging } from '../signal';
15
+ import { Transport, TransportFactory } from '../transport';
16
+
17
+ /**
18
+ * State machine for each connection.
19
+ */
20
+ export enum ConnectionState {
21
+ /**
22
+ * Initial state. Connection is registered but no attempt to connect to the remote peer has been performed.
23
+ * Might mean that we are waiting for the answer signal from the remote peer.
24
+ */
25
+ INITIAL = 'INITIAL',
26
+
27
+ /**
28
+ * Originating a connection.
29
+ */
30
+ INITIATING_CONNECTION = 'INITIATING_CONNECTION',
31
+
32
+ /**
33
+ * Waiting for a connection to be originated from the remote peer.
34
+ */
35
+ WAITING_FOR_CONNECTION = 'WAITING_FOR_CONNECTION',
36
+
37
+ /**
38
+ * Peer rejected offer.
39
+ */
40
+ ACCEPTED = 'ACCEPTED',
41
+
42
+ /**
43
+ * Peer rejected offer.
44
+ */
45
+ REJECTED = 'REJECTED',
46
+
47
+ /**
48
+ * Connection is established.
49
+ */
50
+ CONNECTED = 'CONNECTED',
51
+
52
+ /**
53
+ * Connection closed.
54
+ */
55
+ CLOSED = 'CLOSED'
56
+ }
57
+
58
+ /**
59
+ * Represents a connection to a remote peer.
60
+ */
61
+ export class Connection {
62
+ private _state: ConnectionState = ConnectionState.INITIAL;
63
+ private _transport: Transport | undefined;
64
+ private _bufferedSignals: Signal[] = [];
65
+
66
+ readonly stateChanged = new Event<ConnectionState>();
67
+ readonly errors = new ErrorStream();
68
+
69
+ constructor(
70
+ public readonly topic: PublicKey,
71
+ public readonly ownId: PublicKey, // TODO(burdon): peerID?
72
+ public readonly remoteId: PublicKey,
73
+ public readonly sessionId: PublicKey,
74
+ public readonly initiator: boolean,
75
+ private readonly _signalMessaging: SignalMessaging,
76
+ private readonly _protocol: Protocol,
77
+ private readonly _transportFactory: TransportFactory
78
+ ) {}
79
+
80
+ get state() {
81
+ return this._state;
82
+ }
83
+
84
+ get transport() {
85
+ return this._transport;
86
+ }
87
+
88
+ get protocol() {
89
+ return this._protocol;
90
+ }
91
+
92
+ // TODO(burdon): Make async.
93
+ initiate() {
94
+ this._signalMessaging
95
+ .offer({
96
+ author: this.ownId,
97
+ recipient: this.remoteId,
98
+ sessionId: this.sessionId,
99
+ topic: this.topic,
100
+ data: { offer: {} }
101
+ })
102
+ .then((answer) => {
103
+ log('received', { answer, topic: this.topic, ownId: this.ownId, remoteId: this.remoteId });
104
+ if (this.state !== ConnectionState.INITIAL) {
105
+ log('ignoring response');
106
+ return;
107
+ }
108
+
109
+ if (answer.accept) {
110
+ try {
111
+ this.open();
112
+ } catch (err: any) {
113
+ this.errors.raise(err);
114
+ }
115
+ } else {
116
+ // If the peer rejected our connection remove it from the set of candidates.
117
+ this._changeState(ConnectionState.REJECTED);
118
+ }
119
+
120
+ this._changeState(ConnectionState.ACCEPTED);
121
+ })
122
+ .catch((err) => {
123
+ this.errors.raise(err);
124
+ });
125
+ }
126
+
127
+ // TODO(burdon): Make async?
128
+ open() {
129
+ assert(this._state === ConnectionState.INITIAL, 'Invalid state.');
130
+ this._changeState(this.initiator ? ConnectionState.INITIATING_CONNECTION : ConnectionState.WAITING_FOR_CONNECTION);
131
+
132
+ assert(!this._transport);
133
+ this._transport = this._transportFactory.create({
134
+ topic: this.topic,
135
+ ownId: this.ownId,
136
+ remoteId: this.remoteId,
137
+ sessionId: this.sessionId,
138
+ initiator: this.initiator,
139
+ stream: this._protocol.stream,
140
+ sendSignal: this._signalMessaging.signal.bind(this._signalMessaging)
141
+ });
142
+
143
+ this._transport.connected.once(() => {
144
+ this._changeState(ConnectionState.CONNECTED);
145
+ });
146
+
147
+ this._transport.closed.once(() => {
148
+ this._transport = undefined;
149
+ this.close().catch((err) => this.errors.raise(err));
150
+ });
151
+
152
+ this._transport.errors.pipeTo(this.errors);
153
+
154
+ // Replay signals that were received before transport was created.
155
+ for (const signal of this._bufferedSignals) {
156
+ void this._transport.signal(signal); // TODO(burdon): Remove async?
157
+ }
158
+
159
+ this._bufferedSignals = [];
160
+ }
161
+
162
+ @synchronized
163
+ async close() {
164
+ if (this._state === ConnectionState.CLOSED) {
165
+ return;
166
+ }
167
+
168
+ // TODO(dmaretskyi): CLOSING state.
169
+ log('closing', { peerId: this.ownId });
170
+
171
+ // This will try to gracefull close the stream flushing any unsent data packets.
172
+ await this._protocol.close();
173
+
174
+ // After the transport is closed streams are disconnected.
175
+ await this._transport?.close();
176
+
177
+ this._changeState(ConnectionState.CLOSED);
178
+ log('closed', { peerId: this.ownId });
179
+ }
180
+
181
+ async signal(msg: SignalMessage) {
182
+ assert(msg.sessionId);
183
+ if (!msg.sessionId.equals(this.sessionId)) {
184
+ log('dropping signal for incorrect session id');
185
+ return;
186
+ }
187
+ assert(msg.data.signal);
188
+ assert(msg.author?.equals(this.remoteId));
189
+ assert(msg.recipient?.equals(this.ownId));
190
+
191
+ if (this._state === ConnectionState.INITIAL) {
192
+ log('buffered signal', { peerId: this.ownId, remoteId: this.remoteId, msg: msg.data });
193
+ this._bufferedSignals.push(msg.data.signal);
194
+ return;
195
+ }
196
+
197
+ assert(this._transport, 'Connection not ready to accept signals.');
198
+ log('received signal', { peerId: this.ownId, remoteId: this.remoteId, msg: msg.data });
199
+ await this._transport.signal(msg.data.signal);
200
+ }
201
+
202
+ private _changeState(state: ConnectionState): void {
203
+ this._state = state;
204
+ this.stateChanged.emit(state);
205
+ }
206
+ }
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2020 DXOS.org
3
+ //
4
+
5
+ export * from './connection';
6
+ export * from './swarm';
7
+ export * from './swarm-mapper';
@@ -0,0 +1,120 @@
1
+ //
2
+ // Copyright 2020 DXOS.org
3
+ //
4
+
5
+ import { Event, EventSubscriptions } from '@dxos/async';
6
+ import { PublicKey } from '@dxos/keys';
7
+ import { log } from '@dxos/log';
8
+ import { PresencePlugin } from '@dxos/protocol-plugin-presence';
9
+ import { ComplexMap } from '@dxos/util';
10
+
11
+ import { ConnectionState } from './connection';
12
+ import { Swarm } from './swarm';
13
+
14
+ /**
15
+ * State of the connection to the remote peer with additional info derived from network mapping.
16
+ */
17
+ export type PeerState = ConnectionState | 'INDIRECTLY_CONNECTED' | 'ME';
18
+
19
+ /**
20
+ * Information about remote peer, directly or indirectly connected.
21
+ */
22
+ export interface PeerInfo {
23
+ id: PublicKey;
24
+ state: PeerState;
25
+ connections: PublicKey[];
26
+ }
27
+
28
+ type Unsubscribe = () => void;
29
+
30
+ export class SwarmMapper {
31
+ private readonly _subscriptions = new EventSubscriptions();
32
+ private readonly _connectionSubscriptions = new ComplexMap<PublicKey, Unsubscribe>(PublicKey.hash);
33
+
34
+ private readonly _peers = new ComplexMap<PublicKey, PeerInfo>(PublicKey.hash);
35
+
36
+ get peers(): PeerInfo[] {
37
+ return Array.from(this._peers.values());
38
+ }
39
+
40
+ readonly mapUpdated = new Event<PeerInfo[]>();
41
+
42
+ // prettier-ignore
43
+ constructor(
44
+ private readonly _swarm: Swarm,
45
+ private readonly _presence: PresencePlugin | undefined
46
+ ) {
47
+ this._subscriptions.add(
48
+ _swarm.connectionAdded.on((connection) => {
49
+ this._update();
50
+ this._connectionSubscriptions.set(
51
+ connection.remoteId,
52
+ connection.stateChanged.on(() => {
53
+ this._update();
54
+ })
55
+ );
56
+ })
57
+ );
58
+ this._subscriptions.add(
59
+ _swarm.connectionRemoved.on((connection) => {
60
+ this._connectionSubscriptions.get(connection.remoteId)?.();
61
+ this._connectionSubscriptions.delete(connection.remoteId);
62
+ this._update();
63
+ })
64
+ );
65
+ if (_presence) {
66
+ const cb = () => {
67
+ this._update();
68
+ };
69
+ this._subscriptions.add(_presence.graphUpdated.on(cb));
70
+ }
71
+ this._update();
72
+ }
73
+
74
+ private _update() {
75
+ this._peers.clear();
76
+ this._peers.set(this._swarm.ownPeerId, {
77
+ id: this._swarm.ownPeerId,
78
+ state: 'ME',
79
+ connections: []
80
+ });
81
+ for (const connection of this._swarm.connections) {
82
+ this._peers.set(connection.remoteId, {
83
+ id: connection.remoteId,
84
+ state: connection.state,
85
+ connections: [this._swarm.ownPeerId]
86
+ });
87
+ }
88
+ if (this._presence) {
89
+ this._presence.graph.forEachNode((node: any) => {
90
+ const id = PublicKey.fromHex(node.id);
91
+ if (this._peers.has(id)) {
92
+ return;
93
+ }
94
+ this._peers.set(id, {
95
+ id,
96
+ state: 'INDIRECTLY_CONNECTED',
97
+ connections: []
98
+ });
99
+ });
100
+ this._presence.graph.forEachLink((link: any) => {
101
+ const from = PublicKey.from(link.fromId);
102
+ const to = PublicKey.from(link.toId);
103
+ // Ignore connections to self, they are already handled.
104
+ if (!from.equals(this._swarm.ownPeerId) && !to.equals(this._swarm.ownPeerId)) {
105
+ this._peers.get(from)!.connections.push(to);
106
+ }
107
+ });
108
+ }
109
+ log('graph changed', {
110
+ directConnections: this._swarm.connections.length,
111
+ totalPeersInSwarm: this._peers.size
112
+ });
113
+ this.mapUpdated.emit(Array.from(this._peers.values()));
114
+ }
115
+
116
+ destroy() {
117
+ Array.from(this._connectionSubscriptions.values()).forEach((cb) => cb());
118
+ this._subscriptions.clear();
119
+ }
120
+ }
@@ -0,0 +1,192 @@
1
+ //
2
+ // Copyright 2020 DXOS.org
3
+ //
4
+
5
+ // @dxos/mocha platform=nodejs
6
+
7
+ import { expect, mockFn } from 'earljs';
8
+ import waitForExpect from 'wait-for-expect';
9
+
10
+ import { sleep, asyncTimeout } from '@dxos/async';
11
+ import { PublicKey } from '@dxos/keys';
12
+ import { log } from '@dxos/log';
13
+ import { Protocol } from '@dxos/mesh-protocol';
14
+ import { MemorySignalManager, MemorySignalManagerContext, Messenger } from '@dxos/messaging';
15
+ import { afterTest } from '@dxos/testutils';
16
+
17
+ import { FullyConnectedTopology } from '../topology';
18
+ import { createWebRTCTransportFactory, WebRTCTransport } from '../transport';
19
+ import { Swarm } from './swarm';
20
+
21
+ describe('Swarm', function () {
22
+ const context = new MemorySignalManagerContext();
23
+
24
+ const setupSwarm = () => {
25
+ const topic = PublicKey.random();
26
+ const peerId1 = PublicKey.random();
27
+ const peerId2 = PublicKey.random();
28
+
29
+ const swarm1 = new Swarm(
30
+ topic,
31
+ peerId1,
32
+ new FullyConnectedTopology(),
33
+ () => new Protocol(),
34
+ new Messenger({ signalManager: new MemorySignalManager(context) }),
35
+ createWebRTCTransportFactory(),
36
+ undefined
37
+ );
38
+
39
+ const swarm2 = new Swarm(
40
+ topic,
41
+ peerId2,
42
+ new FullyConnectedTopology(),
43
+ () => new Protocol(),
44
+ new Messenger({ signalManager: new MemorySignalManager(context) }),
45
+ createWebRTCTransportFactory(),
46
+ undefined
47
+ );
48
+
49
+ afterTest(async () => {
50
+ await swarm1.destroy();
51
+ await swarm2.destroy();
52
+ });
53
+
54
+ return { swarm1, swarm2, peerId1, peerId2 };
55
+ };
56
+
57
+ it('connects two peers in a swarm', async function () {
58
+ const { swarm1, swarm2, peerId1, peerId2 } = setupSwarm();
59
+
60
+ expect(swarm1.connections.length).toEqual(0);
61
+ expect(swarm2.connections.length).toEqual(0);
62
+
63
+ const promise = Promise.all([
64
+ asyncTimeout(swarm1.connected.waitForCount(1), 3000, new Error('Swarm1 connect timeout.')),
65
+ asyncTimeout(swarm2.connected.waitForCount(1), 3000, new Error('Swarm2 connect timeout.'))
66
+ ]);
67
+
68
+ // Behavior of the Signal Server.
69
+ swarm1.onSwarmEvent({
70
+ peerAvailable: {
71
+ peer: peerId1.asUint8Array(),
72
+ since: new Date()
73
+ }
74
+ });
75
+
76
+ swarm1.onSwarmEvent({
77
+ peerAvailable: {
78
+ peer: peerId2.asUint8Array(),
79
+ since: new Date()
80
+ }
81
+ });
82
+
83
+ log('Candidates changed');
84
+ await promise;
85
+ log('Swarms connected');
86
+
87
+ const swarm1Connection = swarm1.connections[0];
88
+ const swarm2Connection = swarm2.connections[0];
89
+ const onData = mockFn<(data: Buffer) => void>().returns(undefined);
90
+ (swarm2Connection.transport as WebRTCTransport).peer!.on('data', onData);
91
+
92
+ const data = Buffer.from('1234');
93
+ (swarm1Connection.transport as WebRTCTransport).peer!.send(data);
94
+ await waitForExpect(() => {
95
+ expect(onData).toHaveBeenCalledWith([data]);
96
+ });
97
+ }).timeout(5_000);
98
+
99
+ it('two peers try to originate connections to each other simultaneously', async function () {
100
+ const { swarm1, swarm2, peerId1, peerId2 } = setupSwarm();
101
+
102
+ expect(swarm1.connections.length).toEqual(0);
103
+ expect(swarm2.connections.length).toEqual(0);
104
+
105
+ const connectPromises = Promise.all([swarm1.connected.waitForCount(1), swarm2.connected.waitForCount(1)]);
106
+
107
+ swarm1.onSwarmEvent({
108
+ peerAvailable: {
109
+ peer: peerId2.asUint8Array(),
110
+ since: new Date()
111
+ }
112
+ });
113
+
114
+ swarm2.onSwarmEvent({
115
+ peerAvailable: {
116
+ peer: peerId1.asUint8Array(),
117
+ since: new Date()
118
+ }
119
+ });
120
+
121
+ await connectPromises;
122
+ }).timeout(5_000);
123
+
124
+ it('second peer discovered after delay', async function () {
125
+ const { swarm1, swarm2, peerId1, peerId2 } = setupSwarm();
126
+
127
+ expect(swarm1.connections.length).toEqual(0);
128
+ expect(swarm2.connections.length).toEqual(0);
129
+
130
+ const connectPromises = Promise.all([swarm1.connected.waitForCount(1), swarm2.connected.waitForCount(1)]);
131
+
132
+ swarm1.onSwarmEvent({
133
+ peerAvailable: {
134
+ peer: peerId2.asUint8Array(),
135
+ since: new Date()
136
+ }
137
+ });
138
+ await sleep(15);
139
+ swarm2.onSwarmEvent({
140
+ peerAvailable: {
141
+ peer: peerId1.asUint8Array(),
142
+ since: new Date()
143
+ }
144
+ });
145
+
146
+ await connectPromises;
147
+
148
+ const swarm1Connection = swarm1.connections[0];
149
+ const swarm2Connection = swarm2.connections[0];
150
+ const onData = mockFn<(data: Buffer) => void>().returns(undefined);
151
+ (swarm2Connection.transport as WebRTCTransport).peer!.on('data', onData);
152
+
153
+ const data = Buffer.from('1234');
154
+ (swarm1Connection.transport as WebRTCTransport).peer!.send(data);
155
+ await waitForExpect(() => {
156
+ expect(onData).toHaveBeenCalledWith([data]);
157
+ });
158
+ }).timeout(10_000);
159
+
160
+ it('swarming with message router', async function () {
161
+ const { swarm1, swarm2, peerId2 } = setupSwarm();
162
+
163
+ const promise = Promise.all([
164
+ asyncTimeout(swarm1.connected.waitForCount(1), 3000, new Error('Swarm1 connect timeout.')),
165
+ asyncTimeout(swarm2.connected.waitForCount(1), 3000, new Error('Swarm2 connect timeout.'))
166
+ ]);
167
+
168
+ swarm1.onSwarmEvent({
169
+ peerAvailable: {
170
+ peer: peerId2.asUint8Array(),
171
+ since: new Date()
172
+ }
173
+ });
174
+
175
+ log('Candidates changed');
176
+ await promise;
177
+ log('Swarms connected');
178
+
179
+ const swarm1Connection = swarm1.connections[0];
180
+ const swarm2Connection = swarm2.connections[0];
181
+ const onData = mockFn<(data: Buffer) => void>().returns(undefined);
182
+ (swarm2Connection.transport as WebRTCTransport).peer!.on('data', onData);
183
+
184
+ const data = Buffer.from('1234');
185
+ (swarm1Connection.transport as WebRTCTransport).peer!.send(data);
186
+ await waitForExpect(() => {
187
+ expect(onData).toHaveBeenCalledWith([data]);
188
+ });
189
+ await swarm1.destroy();
190
+ await swarm2.destroy();
191
+ }).timeout(5_000);
192
+ });