@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,764 @@
1
+ //
2
+ // Copyright 2021 DXOS.org
3
+ //
4
+
5
+ import expect from 'expect';
6
+ import * as fc from 'fast-check';
7
+ import { ModelRunSetup } from 'fast-check';
8
+ import waitForExpect from 'wait-for-expect';
9
+
10
+ import { Event, latch, sleep } from '@dxos/async';
11
+ import { PublicKey } from '@dxos/keys';
12
+ import { log } from '@dxos/log';
13
+ import { Protocol } from '@dxos/mesh-protocol';
14
+ import { MemorySignalManagerContext, MemorySignalManager, WebsocketSignalManager } from '@dxos/messaging';
15
+ import { PresencePlugin } from '@dxos/protocol-plugin-presence';
16
+ import { schema } from '@dxos/protocols';
17
+ import { BridgeService } from '@dxos/protocols/proto/dxos/mesh/bridge';
18
+ import { createLinkedPorts, createProtoRpcPeer } from '@dxos/rpc';
19
+ import { afterTest } from '@dxos/testutils';
20
+ import { range, ComplexMap, ComplexSet } from '@dxos/util';
21
+
22
+ import { NetworkManager } from './network-manager';
23
+ import { createProtocolFactory } from './protocol-factory';
24
+ import { TestProtocolPlugin, testProtocolProvider } from './testing';
25
+ import { FullyConnectedTopology, StarTopology, Topology } from './topology';
26
+ import {
27
+ createWebRTCTransportFactory,
28
+ MemoryTransportFactory,
29
+ TransportFactory,
30
+ WebRTCTransportProxyFactory,
31
+ WebRTCTransportService
32
+ } from './transport';
33
+
34
+ // Signal server will be started by the setup script.
35
+ const SIGNAL_URL = 'ws://localhost:4000/.well-known/dx/signal';
36
+
37
+ // TODO(burdon): Remove global and replace with factories.
38
+ const signalContext = new MemorySignalManagerContext();
39
+
40
+ // const createMemoryNetworkManager = (signalContext: MemorySignalManagerContext) => {
41
+ // return new NetworkManager({
42
+ // signalManager: new MemorySignalManager(signalContext),
43
+ // transportFactory: MemoryTransportFactory
44
+ // });
45
+ // };
46
+
47
+ // const createWebRTCNetworkManager = async (config: any, peerId: PublicKey) => {
48
+ // const signalManager = new WebsocketSignalManager(signalHosts!);
49
+ // await signalManager.subscribeMessages(peerId);
50
+ //
51
+ // return new NetworkManager({
52
+ // signalManager,
53
+ // transportFactory: createWebRTCTransportFactory(config)
54
+ // });
55
+ // };
56
+
57
+ interface CreatePeerOptions {
58
+ topic: PublicKey;
59
+ peerId: PublicKey;
60
+ topology?: Topology;
61
+ signalHosts?: string[];
62
+ transportFactory: TransportFactory;
63
+ }
64
+
65
+ /**
66
+ * @deprecated
67
+ */
68
+ // TODO(burdon): Builder pattern.
69
+ const createPeer = async ({
70
+ topic,
71
+ peerId,
72
+ topology = new FullyConnectedTopology(),
73
+ signalHosts,
74
+ transportFactory
75
+ }: CreatePeerOptions) => {
76
+ const signalManager = signalHosts ? new WebsocketSignalManager(signalHosts!) : new MemorySignalManager(signalContext);
77
+ await signalManager.subscribeMessages(peerId);
78
+ const networkManager = new NetworkManager({
79
+ signalManager,
80
+ transportFactory
81
+ });
82
+
83
+ afterTest(() => networkManager.destroy());
84
+
85
+ const plugin = new TestProtocolPlugin(peerId.asBuffer());
86
+ const protocolProvider = testProtocolProvider(topic.asBuffer(), peerId.asBuffer(), plugin);
87
+ await networkManager.openSwarmConnection({ topic, peerId, protocol: protocolProvider, topology });
88
+
89
+ return {
90
+ networkManager,
91
+ plugin
92
+ };
93
+ };
94
+
95
+ describe('NetworkManager', function () {
96
+ describe('WebRTC transport', function () {
97
+ let topic: PublicKey;
98
+ let peer1Id: PublicKey;
99
+ let peer2Id: PublicKey;
100
+
101
+ beforeEach(function () {
102
+ topic = PublicKey.random();
103
+ peer1Id = PublicKey.random();
104
+ peer2Id = PublicKey.random();
105
+ });
106
+
107
+ sharedTests({
108
+ inMemory: false,
109
+ signalUrl: SIGNAL_URL,
110
+ getTransportFactory: async () => createWebRTCTransportFactory()
111
+ });
112
+
113
+ // TODO(burdon): Factor out config and remove consts.
114
+ it.skip('two peers with different signal & turn servers', async function () {
115
+ const { networkManager: networkManager1, plugin: plugin1 } = await createPeer({
116
+ topic,
117
+ peerId: peer1Id,
118
+ signalHosts: ['wss://apollo3.kube.moon.dxos.network/dxos/signal'],
119
+ transportFactory: createWebRTCTransportFactory({
120
+ iceServers: {
121
+ urls: 'turn:apollo3.kube.moon.dxos.network:3478',
122
+ username: 'dxos',
123
+ credential: 'dxos'
124
+ }
125
+ })
126
+ });
127
+
128
+ await sleep(3000);
129
+ const { networkManager: networkManager2, plugin: plugin2 } = await createPeer({
130
+ topic,
131
+ peerId: peer2Id,
132
+ signalHosts: ['wss://apollo2.kube.moon.dxos.network/dxos/signal'],
133
+ transportFactory: createWebRTCTransportFactory({
134
+ iceServers: {
135
+ urls: 'turn:apollo2.kube.moon.dxos.network:3478',
136
+ username: 'dxos',
137
+ credential: 'dxos'
138
+ }
139
+ })
140
+ });
141
+
142
+ const received: any[] = [];
143
+ const mockReceive = (p: Protocol, s: string) => {
144
+ received.push(p, s);
145
+ return undefined;
146
+ };
147
+
148
+ plugin1.on('receive', mockReceive);
149
+ plugin2.on('connect', async () => {
150
+ await plugin2.send(peer1Id.asBuffer(), '{"message": "Hello"}');
151
+ });
152
+
153
+ await waitForExpect(() => {
154
+ expect(received.length).toBe(2);
155
+ expect(received[0]).toBeInstanceOf(Protocol);
156
+ expect(received[1]).toBe('{"message": "Hello"}');
157
+ });
158
+
159
+ await networkManager1.destroy();
160
+ await networkManager2.destroy();
161
+ }).timeout(10_000);
162
+
163
+ describe('StarTopology', function () {
164
+ it('two peers connect to each other', async function () {
165
+ const { plugin: plugin1 } = await createPeer({
166
+ topic,
167
+ peerId: peer1Id,
168
+ topology: new StarTopology(peer1Id),
169
+ transportFactory: createWebRTCTransportFactory()
170
+ });
171
+ const { plugin: plugin2 } = await createPeer({
172
+ topic,
173
+ peerId: peer2Id,
174
+ topology: new StarTopology(peer1Id),
175
+ transportFactory: createWebRTCTransportFactory()
176
+ });
177
+
178
+ const received: any[] = [];
179
+ const mockReceive = (p: Protocol, s: string) => {
180
+ received.push(p, s);
181
+ return undefined;
182
+ };
183
+
184
+ plugin1.on('receive', mockReceive);
185
+ plugin2.on('connect', async () => {
186
+ await plugin2.send(peer1Id.asBuffer(), '{"message": "Hello"}');
187
+ });
188
+
189
+ await waitForExpect(() => {
190
+ expect(received.length).toBe(2);
191
+ expect(received[0]).toBeInstanceOf(Protocol);
192
+ expect(received[1]).toBe('{"message": "Hello"}');
193
+ });
194
+
195
+ // TODO(burdon): Close.
196
+ }).timeout(10_000);
197
+ });
198
+ }).timeout(10_000);
199
+
200
+ describe('WebRTC proxy transport', function () {
201
+ const createTransportFactory = async () => {
202
+ const [rpcPortA, rpcPortB] = createLinkedPorts();
203
+
204
+ const webRTCTransportService: BridgeService = new WebRTCTransportService();
205
+ const service = createProtoRpcPeer({
206
+ requested: {},
207
+ exposed: {
208
+ BridgeService: schema.getService('dxos.mesh.bridge.BridgeService')
209
+ },
210
+ handlers: { BridgeService: webRTCTransportService },
211
+ port: rpcPortA,
212
+ noHandshake: true,
213
+ encodingOptions: {
214
+ preserveAny: true
215
+ }
216
+ });
217
+
218
+ const rpcClient = createProtoRpcPeer({
219
+ requested: {
220
+ BridgeService: schema.getService('dxos.mesh.bridge.BridgeService')
221
+ },
222
+ exposed: {},
223
+ handlers: {},
224
+ port: rpcPortB,
225
+ noHandshake: true,
226
+ encodingOptions: {
227
+ preserveAny: true
228
+ }
229
+ });
230
+
231
+ await service.open();
232
+ afterTest(() => service.close());
233
+ await rpcClient.open();
234
+ afterTest(() => rpcClient.close());
235
+
236
+ return new WebRTCTransportProxyFactory().setBridgeService(rpcClient.rpc.BridgeService);
237
+ };
238
+
239
+ sharedTests({
240
+ inMemory: false,
241
+ signalUrl: SIGNAL_URL,
242
+ getTransportFactory: createTransportFactory
243
+ });
244
+ });
245
+
246
+ describe('memory transport', function () {
247
+ sharedTests({
248
+ inMemory: true,
249
+ getTransportFactory: async () => MemoryTransportFactory
250
+ });
251
+
252
+ it('large amount of peers and connections', async function () {
253
+ const numTopics = 5;
254
+ const peersPerTopic = 5;
255
+
256
+ await Promise.all(
257
+ range(numTopics).map(async () => {
258
+ const topic = PublicKey.random();
259
+
260
+ await Promise.all(
261
+ range(peersPerTopic).map(async (_, index) => {
262
+ const peerId = PublicKey.random();
263
+ const { plugin } = await createPeer({
264
+ topic,
265
+ peerId,
266
+ transportFactory: MemoryTransportFactory
267
+ });
268
+
269
+ const [done, pongReceived] = latch({ count: peersPerTopic - 1 });
270
+
271
+ plugin.on('connect', async (protocol: Protocol) => {
272
+ const { peerId } = protocol.getSession() ?? {};
273
+ const remoteId = PublicKey.from(peerId);
274
+ await plugin.send(remoteId.asBuffer(), 'ping');
275
+ });
276
+
277
+ plugin.on('receive', async (protocol: Protocol, data: any) => {
278
+ const { peerId } = protocol.getSession() ?? {};
279
+ const remoteId = PublicKey.from(peerId);
280
+
281
+ if (data === 'ping') {
282
+ await plugin.send(remoteId.asBuffer(), 'pong');
283
+ } else if (data === 'pong') {
284
+ pongReceived();
285
+ } else {
286
+ throw new Error(`Invalid message: ${data}`);
287
+ }
288
+ });
289
+
290
+ await done();
291
+ })
292
+ );
293
+ })
294
+ );
295
+ });
296
+
297
+ // This test performs random actions in the real system and compares it's state with a simplified model.
298
+ // TODO(dmaretskyi): Run this on with actual webrtc and signal servers.
299
+ it('property-based tests', async function () {
300
+ /**
301
+ * The simplified model of the system.
302
+ */
303
+ interface Model {
304
+ topic: PublicKey;
305
+ peers: ComplexSet<PublicKey>;
306
+ joinedPeers: ComplexSet<PublicKey>;
307
+ }
308
+
309
+ /**
310
+ * The real system being tested.
311
+ */
312
+ interface Real {
313
+ peers: ComplexMap<
314
+ PublicKey,
315
+ {
316
+ networkManager: NetworkManager;
317
+ presence?: PresencePlugin;
318
+ }
319
+ >;
320
+ }
321
+
322
+ const assertState = async (model: Model, real: Real) => {
323
+ await waitForExpect(() => {
324
+ for (const peer of real.peers.values()) {
325
+ if (peer.presence) {
326
+ for (const expectedJoinedPeer of model.joinedPeers) {
327
+ if (expectedJoinedPeer.equals(peer.presence.peerId)) {
328
+ continue;
329
+ }
330
+
331
+ const actuallyConnectedPeers = peer.presence!.peers;
332
+ if (!actuallyConnectedPeers.some((x) => PublicKey.equals(expectedJoinedPeer, x))) {
333
+ throw new Error(
334
+ `Expected ${expectedJoinedPeer} to be in the list of joined peers of peer ${peer.presence.peerId.toString(
335
+ 'hex'
336
+ )}, actually connected peers: ${actuallyConnectedPeers.map((key) => key.toString('hex'))}`
337
+ );
338
+ }
339
+ }
340
+ }
341
+ }
342
+ }, 5_000);
343
+
344
+ real.peers.forEach((peer) =>
345
+ peer.networkManager.topics.forEach((topic) => {
346
+ peer.networkManager.getSwarm(topic)!.errors.assertNoUnhandledErrors();
347
+ })
348
+ );
349
+ };
350
+
351
+ class CreatePeerCommand implements fc.AsyncCommand<Model, Real> {
352
+ constructor(readonly peerId: PublicKey) {}
353
+
354
+ check = (model: Model) => !model.peers.has(this.peerId);
355
+ async run(model: Model, real: Real) {
356
+ model.peers.add(this.peerId);
357
+
358
+ const networkManager = new NetworkManager({
359
+ signalManager: new MemorySignalManager(signalContext),
360
+ transportFactory: MemoryTransportFactory
361
+ });
362
+ afterTest(() => networkManager.destroy());
363
+
364
+ real.peers.set(this.peerId, {
365
+ networkManager
366
+ });
367
+
368
+ await assertState(model, real);
369
+ }
370
+
371
+ toString = () => `CreatePeer(${this.peerId})`;
372
+ }
373
+
374
+ class RemovePeerCommand implements fc.AsyncCommand<Model, Real> {
375
+ constructor(readonly peerId: PublicKey) {}
376
+
377
+ check = (model: Model) => model.peers.has(this.peerId);
378
+ async run(model: Model, real: Real) {
379
+ model.peers.delete(this.peerId);
380
+ model.joinedPeers.delete(this.peerId);
381
+
382
+ const peer = real.peers.get(this.peerId);
383
+ await peer!.networkManager.destroy();
384
+ real.peers.delete(this.peerId);
385
+
386
+ await assertState(model, real);
387
+ }
388
+
389
+ toString = () => `RemovePeer(${this.peerId})`;
390
+ }
391
+
392
+ class JoinTopicCommand implements fc.AsyncCommand<Model, Real> {
393
+ constructor(readonly peerId: PublicKey) {}
394
+
395
+ check = (model: Model) => model.peers.has(this.peerId) && !model.joinedPeers.has(this.peerId);
396
+ async run(model: Model, real: Real) {
397
+ model.joinedPeers.add(this.peerId);
398
+
399
+ const peer = real.peers.get(this.peerId)!;
400
+
401
+ const presence = new PresencePlugin(this.peerId.asBuffer());
402
+ afterTest(() => presence.stop());
403
+ const protocol = createProtocolFactory(model.topic, this.peerId, [presence]);
404
+
405
+ await peer.networkManager.openSwarmConnection({
406
+ peerId: this.peerId, // TODO(burdon): `this`?
407
+ topic: model.topic,
408
+ protocol,
409
+ topology: new FullyConnectedTopology(),
410
+ presence
411
+ });
412
+
413
+ peer.presence = presence;
414
+
415
+ await assertState(model, real);
416
+ }
417
+
418
+ toString = () => `JoinTopic(peerId=${this.peerId})`;
419
+ }
420
+
421
+ class LeaveTopicCommand implements fc.AsyncCommand<Model, Real> {
422
+ constructor(readonly peerId: PublicKey) {}
423
+
424
+ check = (model: Model) => model.peers.has(this.peerId) && model.joinedPeers.has(this.peerId);
425
+ async run(model: Model, real: Real) {
426
+ model.joinedPeers.delete(this.peerId);
427
+
428
+ const peer = real.peers.get(this.peerId)!;
429
+
430
+ await peer.networkManager.closeSwarmConnection(model.topic);
431
+ peer.presence = undefined;
432
+
433
+ await assertState(model, real);
434
+ }
435
+
436
+ toString = () => `LeaveTopic(peerId=${this.peerId})`;
437
+ }
438
+
439
+ const peerIds = range(10).map(() => PublicKey.random());
440
+ const aPeerId = fc.constantFrom(...peerIds);
441
+
442
+ const allCommands = [
443
+ aPeerId.map((x) => new CreatePeerCommand(x)),
444
+ aPeerId.map((x) => new RemovePeerCommand(x)),
445
+ aPeerId.map((p) => new JoinTopicCommand(p)),
446
+ aPeerId.map((p) => new LeaveTopicCommand(p))
447
+ ];
448
+
449
+ await fc.assert(
450
+ fc.asyncProperty(fc.commands(allCommands, { maxCommands: 30 }), async (cmds) => {
451
+ const s: ModelRunSetup<Model, Real> = () => ({
452
+ model: {
453
+ topic: PublicKey.random(),
454
+ peers: new ComplexSet(PublicKey.hash),
455
+ joinedPeers: new ComplexSet(PublicKey.hash)
456
+ },
457
+ real: {
458
+ peers: new ComplexMap(PublicKey.hash)
459
+ }
460
+ });
461
+ await fc.asyncModelRun(s, cmds);
462
+ }),
463
+ {
464
+ examples: [
465
+ [
466
+ [
467
+ new CreatePeerCommand(peerIds[0]),
468
+ new CreatePeerCommand(peerIds[1]),
469
+ new JoinTopicCommand(peerIds[0]),
470
+ new JoinTopicCommand(peerIds[1]),
471
+ new LeaveTopicCommand(peerIds[0]),
472
+ new LeaveTopicCommand(peerIds[1]),
473
+ new RemovePeerCommand(peerIds[0]),
474
+ new RemovePeerCommand(peerIds[1])
475
+ ]
476
+ ],
477
+ [
478
+ [
479
+ new CreatePeerCommand(peerIds[0]),
480
+ new JoinTopicCommand(peerIds[0]),
481
+ new CreatePeerCommand(peerIds[1]),
482
+ new RemovePeerCommand(peerIds[0]),
483
+ new JoinTopicCommand(peerIds[1]),
484
+ new RemovePeerCommand(peerIds[1])
485
+ ]
486
+ ],
487
+ [
488
+ [
489
+ new CreatePeerCommand(peerIds[0]),
490
+ new JoinTopicCommand(peerIds[0]),
491
+ new RemovePeerCommand(peerIds[0]),
492
+ new CreatePeerCommand(peerIds[1]),
493
+ new JoinTopicCommand(peerIds[1]),
494
+ new CreatePeerCommand(peerIds[2]),
495
+ new JoinTopicCommand(peerIds[2])
496
+ ]
497
+ ]
498
+ ]
499
+ }
500
+ );
501
+ })
502
+ .timeout(30_000)
503
+ .retries(10);
504
+ }).timeout(30_000);
505
+ });
506
+
507
+ function sharedTests({
508
+ inMemory,
509
+ signalUrl,
510
+ getTransportFactory
511
+ }: {
512
+ inMemory: boolean;
513
+ signalUrl?: string;
514
+ getTransportFactory: () => Promise<TransportFactory>;
515
+ }) {
516
+ it('two peers connect to each other', async function () {
517
+ const topic = PublicKey.random();
518
+ const peer1Id = PublicKey.random();
519
+ const peer2Id = PublicKey.random();
520
+
521
+ const { plugin: plugin1 } = await createPeer({
522
+ topic,
523
+ peerId: peer1Id,
524
+ signalHosts: !inMemory ? [signalUrl!] : undefined,
525
+ transportFactory: await getTransportFactory()
526
+ });
527
+ const { plugin: plugin2 } = await createPeer({
528
+ topic,
529
+ peerId: peer2Id,
530
+ signalHosts: !inMemory ? [signalUrl!] : undefined,
531
+ transportFactory: await getTransportFactory()
532
+ });
533
+
534
+ const received: any[] = [];
535
+ const mockReceive = (p: Protocol, s: string) => {
536
+ received.push(p, s);
537
+ return undefined;
538
+ };
539
+
540
+ plugin1.on('receive', mockReceive);
541
+ plugin2.on('connect', async () => {
542
+ await plugin2.send(peer1Id.asBuffer(), '{"message": "Hello"}');
543
+ });
544
+
545
+ await waitForExpect(() => {
546
+ expect(received.length).toBe(2);
547
+ expect(received[0]).toBeInstanceOf(Protocol);
548
+ expect(received[1]).toBe('{"message": "Hello"}');
549
+ });
550
+ })
551
+ .timeout(10_000)
552
+ .retries(10);
553
+
554
+ it('join and leave swarm', async function () {
555
+ const topic = PublicKey.random();
556
+ const peer1Id = PublicKey.random();
557
+ const peer2Id = PublicKey.random();
558
+
559
+ const { networkManager: networkManager1, plugin: plugin1 } = await createPeer({
560
+ topic,
561
+ peerId: peer1Id,
562
+ signalHosts: !inMemory ? [signalUrl!] : undefined,
563
+ transportFactory: await getTransportFactory()
564
+ });
565
+ const { networkManager: networkManager2, plugin: plugin2 } = await createPeer({
566
+ topic,
567
+ peerId: peer2Id,
568
+ signalHosts: !inMemory ? [signalUrl!] : undefined,
569
+ transportFactory: await getTransportFactory()
570
+ });
571
+
572
+ await Promise.all([Event.wrap(plugin1, 'connect').waitForCount(1), Event.wrap(plugin2, 'connect').waitForCount(1)]);
573
+ log('Connected');
574
+
575
+ const promise1 = Event.wrap(plugin1, 'disconnect').waitForCount(1);
576
+ const promise2 = Event.wrap(plugin2, 'disconnect').waitForCount(1);
577
+
578
+ await networkManager1.closeSwarmConnection(topic);
579
+
580
+ await promise1;
581
+ log('Peer1 disconnected');
582
+
583
+ await promise2;
584
+ log('Peer2 disconnected');
585
+
586
+ await networkManager1.destroy();
587
+ log('Peer1 destroyed');
588
+ await networkManager2.destroy();
589
+ log('Peer2 destroyed');
590
+ })
591
+ .timeout(10_000)
592
+ .retries(10);
593
+
594
+ it('join and leave swarm and reconnect', async function () {
595
+ const topic = PublicKey.random();
596
+ const peer1Id = PublicKey.random();
597
+ const peer2Id = PublicKey.random();
598
+
599
+ const { networkManager: networkManager1, plugin: plugin1 } = await createPeer({
600
+ topic,
601
+ peerId: peer1Id,
602
+ signalHosts: !inMemory ? [signalUrl!] : undefined,
603
+ transportFactory: await getTransportFactory()
604
+ });
605
+ const { networkManager: networkManager2, plugin: plugin2 } = await createPeer({
606
+ topic,
607
+ peerId: peer2Id,
608
+ signalHosts: !inMemory ? [signalUrl!] : undefined,
609
+ transportFactory: await getTransportFactory()
610
+ });
611
+
612
+ // prettier-ignore
613
+ await Promise.all([
614
+ Event.wrap(plugin1, 'connect').waitForCount(1),
615
+ Event.wrap(plugin2, 'connect').waitForCount(1)
616
+ ]);
617
+ log('Connected');
618
+
619
+ const disconnectPromises = Promise.all([
620
+ Event.wrap(plugin1, 'disconnect').waitForCount(1),
621
+ Event.wrap(plugin2, 'disconnect').waitForCount(1)
622
+ ]);
623
+
624
+ const connectPromises = Promise.all([
625
+ Event.wrap(plugin1, 'connect').waitForCount(1),
626
+ Event.wrap(plugin2, 'connect').waitForCount(1)
627
+ ]);
628
+
629
+ log('Disconnecting peer2');
630
+ await networkManager2.closeSwarmConnection(topic);
631
+
632
+ log('Reconnecting peer2');
633
+ const newPeer2Id = PublicKey.random();
634
+ await networkManager2.openSwarmConnection({
635
+ topic,
636
+ peerId: newPeer2Id,
637
+ protocol: testProtocolProvider(topic.asBuffer(), peer2Id.asBuffer(), plugin2),
638
+ topology: new FullyConnectedTopology()
639
+ });
640
+
641
+ await disconnectPromises;
642
+ await connectPromises;
643
+
644
+ await networkManager1.destroy();
645
+ log('Peer1 destroyed');
646
+ await networkManager2.destroy();
647
+ log('Peer2 destroyed');
648
+ })
649
+ .timeout(10_000)
650
+ .retries(10);
651
+
652
+ it('join 2 swarms', async function () {
653
+ const peerId = PublicKey.random();
654
+ const plugin1 = new TestProtocolPlugin(peerId.asBuffer());
655
+ const plugin2 = new TestProtocolPlugin(peerId.asBuffer());
656
+
657
+ const signalManager = inMemory ? new MemorySignalManager(signalContext) : new WebsocketSignalManager([signalUrl!]);
658
+ const networkManager = new NetworkManager({
659
+ signalManager,
660
+ transportFactory: await getTransportFactory()
661
+ });
662
+ afterTest(() => networkManager.destroy());
663
+
664
+ // Joining first swarm.
665
+ {
666
+ const topic = PublicKey.random();
667
+ const protocolProvider = testProtocolProvider(topic.asBuffer(), peerId.asBuffer(), plugin1);
668
+ await networkManager.openSwarmConnection({
669
+ topic,
670
+ peerId,
671
+ protocol: protocolProvider,
672
+ topology: new FullyConnectedTopology()
673
+ });
674
+ // Creating and joining second peer.
675
+ await createPeer({
676
+ topic,
677
+ peerId: PublicKey.random(),
678
+ signalHosts: !inMemory ? [signalUrl!] : undefined,
679
+ transportFactory: await getTransportFactory()
680
+ });
681
+ }
682
+
683
+ // Joining second swarm with same peerId.
684
+ {
685
+ const topic = PublicKey.random();
686
+ const protocolProvider = testProtocolProvider(topic.asBuffer(), peerId.asBuffer(), plugin2);
687
+ await networkManager.openSwarmConnection({
688
+ topic,
689
+ peerId,
690
+ protocol: protocolProvider,
691
+ topology: new FullyConnectedTopology()
692
+ });
693
+ // Creating and joining second peer.
694
+ await createPeer({
695
+ topic,
696
+ peerId: PublicKey.random(),
697
+ signalHosts: !inMemory ? [signalUrl!] : undefined,
698
+ transportFactory: await getTransportFactory()
699
+ });
700
+ }
701
+
702
+ await Promise.all([Event.wrap(plugin1, 'connect').waitForCount(1), Event.wrap(plugin2, 'connect').waitForCount(1)]);
703
+ });
704
+
705
+ it('two swarms at the same time', async function () {
706
+ const topicA = PublicKey.random();
707
+ const topicB = PublicKey.random();
708
+ const peerA1Id = PublicKey.random();
709
+ const peerA2Id = PublicKey.random();
710
+ const peerB1Id = PublicKey.random();
711
+ const peerB2Id = PublicKey.random();
712
+
713
+ const { plugin: pluginA1 } = await createPeer({
714
+ topic: topicA,
715
+ peerId: peerA1Id,
716
+ transportFactory: await getTransportFactory()
717
+ });
718
+ const { plugin: pluginA2 } = await createPeer({
719
+ topic: topicA,
720
+ peerId: peerA2Id,
721
+ transportFactory: await getTransportFactory()
722
+ });
723
+ const { plugin: pluginB1 } = await createPeer({
724
+ topic: topicB,
725
+ peerId: peerB1Id,
726
+ transportFactory: await getTransportFactory()
727
+ });
728
+ const { plugin: pluginB2 } = await createPeer({
729
+ topic: topicB,
730
+ peerId: peerB2Id,
731
+ transportFactory: await getTransportFactory()
732
+ });
733
+
734
+ const receivedA: any[] = [];
735
+ const mockReceiveA = (p: Protocol, s: string) => {
736
+ receivedA.push(p, s);
737
+ return undefined;
738
+ };
739
+ pluginA1.on('receive', mockReceiveA);
740
+
741
+ const receivedB: any[] = [];
742
+ const mockReceiveB = (p: Protocol, s: string) => {
743
+ receivedB.push(p, s);
744
+ return undefined;
745
+ };
746
+ pluginB1.on('receive', mockReceiveB);
747
+
748
+ pluginA2.on('connect', async () => {
749
+ await pluginA2.send(peerA1Id.asBuffer(), 'Test A');
750
+ });
751
+ pluginB2.on('connect', async () => {
752
+ await pluginB2.send(peerB1Id.asBuffer(), 'Test B');
753
+ });
754
+
755
+ await waitForExpect(() => {
756
+ expect(receivedA.length).toBe(2);
757
+ expect(receivedA[0]).toBeInstanceOf(Protocol);
758
+ expect(receivedA[1]).toBe('Test A');
759
+ expect(receivedB.length).toBe(2);
760
+ expect(receivedB[0]).toBeInstanceOf(Protocol);
761
+ expect(receivedB[1]).toBe('Test B');
762
+ });
763
+ });
764
+ }