@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,290 @@
1
+ //
2
+ // Copyright 2020 DXOS.org
3
+ //
4
+
5
+ // @dxos/mocha platform=nodejs
6
+
7
+ import expect from 'expect';
8
+ import { Duplex } from 'stream';
9
+ import waitForExpect from 'wait-for-expect';
10
+
11
+ import { sleep } from '@dxos/async';
12
+ import { discoveryKey } from '@dxos/crypto';
13
+ import { PublicKey } from '@dxos/keys';
14
+ import { Protocol } from '@dxos/mesh-protocol';
15
+ import { schema } from '@dxos/protocols';
16
+ import { BridgeService } from '@dxos/protocols/proto/dxos/mesh/bridge';
17
+ import { createLinkedPorts, createProtoRpcPeer, ProtoRpcPeer } from '@dxos/rpc';
18
+ import { afterTest } from '@dxos/testutils';
19
+
20
+ import { SignalMessage } from '../signal';
21
+ import { TestProtocolPlugin, testProtocolProvider } from '../testing';
22
+ import { WebRTCTransportProxy } from './webrtc-transport-proxy';
23
+ import { WebRTCTransportService } from './webrtc-transport-service';
24
+
25
+ describe('WebRTCTransportProxy', function () {
26
+ const setupProxy = async ({
27
+ initiator = true,
28
+ ownId = PublicKey.random(),
29
+ remoteId = PublicKey.random(),
30
+ sessionId = PublicKey.random(),
31
+ topic = PublicKey.random(),
32
+ stream = new Duplex({ write: () => {}, read: () => {} }),
33
+ sendSignal = () => {}
34
+ }: {
35
+ initiator?: boolean;
36
+ ownId?: PublicKey;
37
+ remoteId?: PublicKey;
38
+ sessionId?: PublicKey;
39
+ topic?: PublicKey;
40
+ stream?: NodeJS.ReadWriteStream;
41
+ sendSignal?: (msg: SignalMessage) => void;
42
+ } = {}) => {
43
+ const [port1, port2] = createLinkedPorts();
44
+
45
+ const webRTCTransportService: BridgeService = new WebRTCTransportService();
46
+
47
+ // Starting BridgeService
48
+ const webRTCService = createProtoRpcPeer({
49
+ requested: {},
50
+ exposed: {
51
+ BridgeService: schema.getService('dxos.mesh.bridge.BridgeService')
52
+ },
53
+ handlers: { BridgeService: webRTCTransportService },
54
+ port: port1,
55
+ noHandshake: true,
56
+ encodingOptions: {
57
+ preserveAny: true
58
+ }
59
+ });
60
+ await webRTCService.open();
61
+ afterTest(() => webRTCService.close());
62
+
63
+ // Starting RPC client
64
+ const rpcClient = createProtoRpcPeer({
65
+ requested: {
66
+ BridgeService: schema.getService('dxos.mesh.bridge.BridgeService')
67
+ },
68
+ exposed: {},
69
+ handlers: {},
70
+ port: port2,
71
+ noHandshake: true,
72
+ encodingOptions: {
73
+ preserveAny: true
74
+ }
75
+ });
76
+ await rpcClient.open();
77
+ afterTest(() => rpcClient.close());
78
+
79
+ const webRTCTransportProxy = new WebRTCTransportProxy({
80
+ initiator,
81
+ stream,
82
+ ownId,
83
+ remoteId,
84
+ sessionId,
85
+ topic,
86
+ sendSignal,
87
+ bridgeService: rpcClient.rpc.BridgeService
88
+ });
89
+ afterTest(async () => await webRTCTransportProxy.close());
90
+
91
+ return { webRTCService, webRTCTransportProxy };
92
+ };
93
+
94
+ // This doesn't clean up correctly and crashes with SIGSEGV / SIGABRT at the end. Probably an issue with wrtc package.
95
+ it('open and close', async function () {
96
+ const { webRTCTransportProxy: connection } = await setupProxy();
97
+
98
+ let callsCounter = 0;
99
+ const closedCb = () => {
100
+ callsCounter++;
101
+ };
102
+ connection.closed.once(closedCb);
103
+
104
+ await sleep(10); // Let simple-peer process events.
105
+ await connection.close();
106
+
107
+ await sleep(1); // Process events.
108
+
109
+ expect(callsCounter).toEqual(1);
110
+ })
111
+ .timeout(1_000)
112
+ .retries(3);
113
+
114
+ it('establish connection and send data through with protocol', async function () {
115
+ if (mochaExecutor.environment !== 'nodejs') {
116
+ this.skip();
117
+ }
118
+
119
+ const topic = PublicKey.random();
120
+ const peer1Id = PublicKey.random();
121
+ const peer2Id = PublicKey.random();
122
+ const sessionId = PublicKey.random();
123
+
124
+ const plugin1 = new TestProtocolPlugin(peer1Id.asBuffer());
125
+ const protocolProvider1 = testProtocolProvider(topic.asBuffer(), peer1Id.asBuffer(), plugin1);
126
+ const { webRTCTransportProxy: connection1 } = await setupProxy({
127
+ initiator: true,
128
+ stream: protocolProvider1({
129
+ channel: discoveryKey(topic),
130
+ initiator: true
131
+ }).stream,
132
+ ownId: peer1Id,
133
+ remoteId: peer2Id,
134
+ sessionId,
135
+ topic,
136
+ sendSignal: async (msg) => {
137
+ await sleep(10);
138
+ await connection2.signal(msg.data.signal);
139
+ }
140
+ });
141
+ afterTest(() => connection1.errors.assertNoUnhandledErrors());
142
+
143
+ const plugin2 = new TestProtocolPlugin(peer2Id.asBuffer());
144
+ const protocolProvider2 = testProtocolProvider(topic.asBuffer(), peer2Id.asBuffer(), plugin2);
145
+ const { webRTCTransportProxy: connection2 } = await setupProxy({
146
+ initiator: false,
147
+ stream: protocolProvider2({
148
+ channel: discoveryKey(topic),
149
+ initiator: false
150
+ }).stream,
151
+ ownId: peer2Id,
152
+ remoteId: peer1Id,
153
+ sessionId,
154
+ topic,
155
+ sendSignal: async (msg) => {
156
+ await sleep(10);
157
+ await connection1.signal(msg.data.signal);
158
+ }
159
+ });
160
+ afterTest(() => connection2.errors.assertNoUnhandledErrors());
161
+
162
+ const received: any[] = [];
163
+ const mockReceive = (p: Protocol, s: string) => {
164
+ received.push(p, s);
165
+ return undefined;
166
+ };
167
+ plugin1.on('receive', mockReceive);
168
+
169
+ plugin2.on('connect', async (protocol) => {
170
+ await plugin2.send(peer1Id.asBuffer(), '{"message": "Hello"}');
171
+ });
172
+
173
+ await waitForExpect(() => {
174
+ expect(received.length).toBe(2);
175
+ expect(received[0]).toBeInstanceOf(Protocol);
176
+ expect(received[1]).toBe('{"message": "Hello"}');
177
+ });
178
+ })
179
+ .timeout(2_000)
180
+ .retries(3);
181
+
182
+ describe('Multiplexing', function () {
183
+ let service: any;
184
+ let rpcClient: ProtoRpcPeer<{ BridgeService: BridgeService }>;
185
+
186
+ before(async function () {
187
+ const [port1, port2] = createLinkedPorts();
188
+
189
+ const webRTCTransportService: BridgeService = new WebRTCTransportService();
190
+ service = createProtoRpcPeer({
191
+ requested: {},
192
+ exposed: {
193
+ BridgeService: schema.getService('dxos.mesh.bridge.BridgeService')
194
+ },
195
+ handlers: { BridgeService: webRTCTransportService },
196
+ port: port1,
197
+ noHandshake: true,
198
+ encodingOptions: {
199
+ preserveAny: true
200
+ }
201
+ });
202
+ await service.open();
203
+
204
+ rpcClient = createProtoRpcPeer({
205
+ requested: {
206
+ BridgeService: schema.getService('dxos.mesh.bridge.BridgeService')
207
+ },
208
+ exposed: {},
209
+ handlers: {},
210
+ port: port2,
211
+ noHandshake: true,
212
+ encodingOptions: {
213
+ preserveAny: true
214
+ }
215
+ });
216
+ await rpcClient.open();
217
+ });
218
+
219
+ after(async function () {
220
+ await service?.close();
221
+ await rpcClient?.close();
222
+ });
223
+
224
+ it('establish connection and send data through with protocol', async function () {
225
+ const topic = PublicKey.random();
226
+ const peer1Id = PublicKey.random();
227
+ const peer2Id = PublicKey.random();
228
+ const sessionId = PublicKey.random();
229
+
230
+ const plugin1 = new TestProtocolPlugin(peer1Id.asBuffer());
231
+ const protocolProvider1 = testProtocolProvider(topic.asBuffer(), peer1Id.asBuffer(), plugin1);
232
+ const proxy1 = new WebRTCTransportProxy({
233
+ initiator: true,
234
+ stream: protocolProvider1({
235
+ channel: discoveryKey(topic),
236
+ initiator: true
237
+ }).stream,
238
+ ownId: peer1Id,
239
+ remoteId: peer2Id,
240
+ sessionId,
241
+ topic,
242
+ sendSignal: async (msg) => {
243
+ await sleep(10);
244
+ await proxy2.signal(msg.data.signal);
245
+ },
246
+ bridgeService: rpcClient.rpc.BridgeService
247
+ });
248
+ afterTest(() => proxy1.errors.assertNoUnhandledErrors());
249
+
250
+ const plugin2 = new TestProtocolPlugin(peer2Id.asBuffer());
251
+ const protocolProvider2 = testProtocolProvider(topic.asBuffer(), peer2Id.asBuffer(), plugin2);
252
+ const proxy2 = new WebRTCTransportProxy({
253
+ initiator: false,
254
+ stream: protocolProvider2({
255
+ channel: discoveryKey(topic),
256
+ initiator: false
257
+ }).stream,
258
+ ownId: peer2Id,
259
+ remoteId: peer1Id,
260
+ sessionId,
261
+ topic,
262
+ sendSignal: async (msg) => {
263
+ await sleep(10);
264
+ await proxy1.signal(msg.data.signal);
265
+ },
266
+ bridgeService: rpcClient.rpc.BridgeService
267
+ });
268
+ afterTest(() => proxy2.errors.assertNoUnhandledErrors());
269
+
270
+ const received: any[] = [];
271
+ const mockReceive = (p: Protocol, s: string) => {
272
+ received.push(p, s);
273
+ return undefined;
274
+ };
275
+ plugin1.on('receive', mockReceive);
276
+
277
+ plugin2.on('connect', async (protocol) => {
278
+ await plugin2.send(peer1Id.asBuffer(), '{"message": "Hello"}');
279
+ });
280
+
281
+ await waitForExpect(() => {
282
+ expect(received.length).toBe(2);
283
+ expect(received[0]).toBeInstanceOf(Protocol);
284
+ expect(received[1]).toBe('{"message": "Hello"}');
285
+ });
286
+ })
287
+ .timeout(5_000)
288
+ .retries(3);
289
+ });
290
+ });
@@ -0,0 +1,167 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import assert from 'assert';
6
+
7
+ import { Event } from '@dxos/async';
8
+ import { Stream } from '@dxos/codec-protobuf';
9
+ import { ErrorStream } from '@dxos/debug';
10
+ import { PublicKey } from '@dxos/keys';
11
+ import { log } from '@dxos/log';
12
+ import { ConnectionState, BridgeEvent, BridgeService } from '@dxos/protocols/proto/dxos/mesh/bridge';
13
+ import { Signal } from '@dxos/protocols/proto/dxos/mesh/swarm';
14
+
15
+ import { SignalMessage } from '../signal';
16
+ import { Transport, TransportFactory, TransportOptions } from './transport';
17
+
18
+ export interface WebRTCTransportProxyParams {
19
+ initiator: boolean;
20
+ stream: NodeJS.ReadWriteStream;
21
+ ownId: PublicKey;
22
+ remoteId: PublicKey;
23
+ sessionId: PublicKey;
24
+ topic: PublicKey;
25
+ sendSignal: (msg: SignalMessage) => void;
26
+ bridgeService: BridgeService;
27
+ }
28
+
29
+ export class WebRTCTransportProxy implements Transport {
30
+ readonly closed = new Event();
31
+ private _closed = false;
32
+ readonly connected = new Event();
33
+ readonly errors = new ErrorStream();
34
+
35
+ private _serviceStream!: Stream<BridgeEvent>;
36
+ private readonly _proxyId = PublicKey.random();
37
+
38
+ // prettier-ignore
39
+ constructor(
40
+ private readonly _params: WebRTCTransportProxyParams
41
+ ) {
42
+ this._serviceStream = this._params.bridgeService.open({
43
+ proxyId: this._proxyId,
44
+ initiator: this._params.initiator
45
+ });
46
+
47
+ this._serviceStream.waitUntilReady().then(
48
+ () => {
49
+ this._serviceStream.subscribe(async (msg: BridgeEvent) => {
50
+ if (msg.connection) {
51
+ await this._handleConnection(msg.connection);
52
+ } else if (msg.data) {
53
+ this._handleData(msg.data);
54
+ } else if (msg.signal) {
55
+ await this._handleSignal(msg.signal);
56
+ }
57
+ });
58
+
59
+ this._params.stream.on('data', async (data: Uint8Array) => {
60
+ try {
61
+ await this._params.bridgeService.sendData({
62
+ proxyId: this._proxyId,
63
+ payload: data
64
+ });
65
+ } catch (err: any) {
66
+ log.catch(err);
67
+ }
68
+ });
69
+ },
70
+ (error) => log.catch(error)
71
+ );
72
+ }
73
+
74
+ private async _handleConnection(connectionEvent: BridgeEvent.ConnectionEvent): Promise<void> {
75
+ if (connectionEvent.error) {
76
+ this.errors.raise(new Error(connectionEvent.error));
77
+ }
78
+
79
+ switch (connectionEvent.state) {
80
+ case ConnectionState.CONNECTED: {
81
+ this.connected.emit();
82
+ break;
83
+ }
84
+ case ConnectionState.CLOSED: {
85
+ await this.close();
86
+ break;
87
+ }
88
+ }
89
+ }
90
+
91
+ private _handleData(dataEvent: BridgeEvent.DataEvent) {
92
+ // NOTE: This must be a Buffer otherwise hypercore-protocol breaks.
93
+ this._params.stream.write(Buffer.from(dataEvent.payload));
94
+ }
95
+
96
+ private async _handleSignal(signalEvent: BridgeEvent.SignalEvent) {
97
+ await this._params.sendSignal({
98
+ author: this._params.ownId,
99
+ recipient: this._params.remoteId,
100
+ topic: this._params.topic,
101
+ sessionId: this._params.sessionId,
102
+ data: { signal: signalEvent.payload }
103
+ });
104
+ }
105
+
106
+ async signal(signal: Signal): Promise<void> {
107
+ await this._params.bridgeService.sendSignal({
108
+ proxyId: this._proxyId,
109
+ signal
110
+ });
111
+ }
112
+
113
+ async close(): Promise<void> {
114
+ if (this._closed) {
115
+ return;
116
+ }
117
+ this._serviceStream.close();
118
+ try {
119
+ await this._params.bridgeService.close({ proxyId: this._proxyId });
120
+ } catch (err: any) {
121
+ log.catch(err);
122
+ }
123
+ this.closed.emit();
124
+ this._closed = true;
125
+ }
126
+
127
+ /**
128
+ * Called when underlying proxy service becomes unavailable.
129
+ */
130
+ forceClose() {
131
+ this._serviceStream.close();
132
+ this.closed.emit();
133
+ this._closed = true;
134
+ }
135
+ }
136
+
137
+ export class WebRTCTransportProxyFactory implements TransportFactory {
138
+ private _bridgeService: BridgeService | undefined;
139
+ private _connections = new Set<WebRTCTransportProxy>();
140
+
141
+ /**
142
+ * Sets the current BridgeService to be used to open connections.
143
+ * Calling this method will close any existing connections.
144
+ */
145
+ setBridgeService(bridgeService: BridgeService | undefined): this {
146
+ this._bridgeService = bridgeService;
147
+ for (const connection of this._connections) {
148
+ connection.forceClose();
149
+ }
150
+
151
+ return this;
152
+ }
153
+
154
+ create(options: TransportOptions): Transport {
155
+ assert(this._bridgeService, 'WebRTCTransportProxyFactory is not ready to open connections');
156
+
157
+ const transport = new WebRTCTransportProxy({
158
+ ...options,
159
+ bridgeService: this._bridgeService
160
+ });
161
+
162
+ this._connections.add(transport);
163
+ transport.closed.on(() => this._connections.delete(transport));
164
+
165
+ return transport;
166
+ }
167
+ }
@@ -0,0 +1,115 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import assert from 'assert';
6
+ import SimplePeerConstructor, { Instance as SimplePeer } from 'simple-peer';
7
+
8
+ import { Stream } from '@dxos/codec-protobuf';
9
+ import { raise } from '@dxos/debug';
10
+ import { PublicKey } from '@dxos/keys';
11
+ import { log } from '@dxos/log';
12
+ import {
13
+ BridgeService,
14
+ ConnectionRequest,
15
+ SignalRequest,
16
+ DataRequest,
17
+ BridgeEvent,
18
+ ConnectionState,
19
+ CloseRequest
20
+ } from '@dxos/protocols/proto/dxos/mesh/bridge';
21
+ import { ComplexMap } from '@dxos/util';
22
+
23
+ import { wrtc } from './webrtc';
24
+
25
+ export class WebRTCTransportService implements BridgeService {
26
+ protected peers = new ComplexMap<PublicKey, SimplePeer>(PublicKey.hash);
27
+
28
+ // prettier-ignore
29
+ constructor(
30
+ private readonly _webrtcConfig?: any
31
+ ) {}
32
+
33
+ open(request: ConnectionRequest): Stream<BridgeEvent> {
34
+ return new Stream(({ ready, next, close }) => {
35
+ log(
36
+ `Creating webrtc connection initiator=${request.initiator} webrtcConfig=${JSON.stringify(this._webrtcConfig)}`
37
+ );
38
+ const peer = new SimplePeerConstructor({
39
+ initiator: request.initiator,
40
+ wrtc: SimplePeerConstructor.WEBRTC_SUPPORT ? undefined : wrtc ?? raise(new Error('wrtc not available')),
41
+ config: this._webrtcConfig
42
+ });
43
+
44
+ next({
45
+ connection: {
46
+ state: ConnectionState.CONNECTING
47
+ }
48
+ });
49
+
50
+ peer.on('data', async (payload) => {
51
+ next({
52
+ data: {
53
+ payload
54
+ }
55
+ });
56
+ });
57
+
58
+ peer.on('signal', async (data) => {
59
+ next({
60
+ signal: {
61
+ payload: { json: JSON.stringify(data) }
62
+ }
63
+ });
64
+ });
65
+
66
+ peer.on('connect', () => {
67
+ next({
68
+ connection: {
69
+ state: ConnectionState.CONNECTED
70
+ }
71
+ });
72
+ });
73
+
74
+ peer.on('error', async (err) => {
75
+ next({
76
+ connection: {
77
+ state: ConnectionState.CLOSED,
78
+ error: err.toString()
79
+ }
80
+ });
81
+ close(err);
82
+ });
83
+
84
+ peer.on('close', async () => {
85
+ next({
86
+ connection: {
87
+ state: ConnectionState.CLOSED
88
+ }
89
+ });
90
+ close();
91
+ });
92
+
93
+ this.peers.set(request.proxyId, peer);
94
+
95
+ ready();
96
+ });
97
+ }
98
+
99
+ async sendSignal({ proxyId, signal }: SignalRequest): Promise<void> {
100
+ assert(this.peers.has(proxyId), 'Connection not ready to accept signals.');
101
+ assert(signal.json, 'Signal message must contain signal data.');
102
+ this.peers.get(proxyId)!.signal(JSON.parse(signal.json));
103
+ }
104
+
105
+ async sendData({ proxyId, payload }: DataRequest): Promise<void> {
106
+ assert(this.peers.has(proxyId));
107
+ this.peers.get(proxyId)!.write(payload);
108
+ }
109
+
110
+ async close({ proxyId }: CloseRequest) {
111
+ this.peers.get(proxyId)?.destroy();
112
+ this.peers.delete(proxyId);
113
+ log('Closed.');
114
+ }
115
+ }
@@ -0,0 +1,112 @@
1
+ //
2
+ // Copyright 2020 DXOS.org
3
+ //
4
+
5
+ import expect from 'expect';
6
+ import { Duplex } from 'stream';
7
+ import waitForExpect from 'wait-for-expect';
8
+
9
+ import { sleep } from '@dxos/async';
10
+ import { discoveryKey } from '@dxos/crypto';
11
+ import { PublicKey } from '@dxos/keys';
12
+ import { Protocol } from '@dxos/mesh-protocol';
13
+ import { afterTest } from '@dxos/testutils';
14
+
15
+ import { TestProtocolPlugin, testProtocolProvider } from '../testing';
16
+ import { WebRTCTransport } from './webrtc-transport';
17
+
18
+ describe('WebRTCTransport', function () {
19
+ // This doesn't clean up correctly and crashes with SIGSEGV / SIGABRT at the end. Probably an issue with wrtc package.
20
+ it('open and close', async function () {
21
+ const connection = new WebRTCTransport(
22
+ true,
23
+ new Duplex(),
24
+ PublicKey.random(),
25
+ PublicKey.random(),
26
+ PublicKey.random(),
27
+ PublicKey.random(),
28
+ async (msg) => {}
29
+ );
30
+
31
+ let callsCounter = 0;
32
+ const closedCb = () => {
33
+ callsCounter++;
34
+ };
35
+ connection.closed.once(closedCb);
36
+
37
+ await sleep(10); // Let simple-peer process events.
38
+ await connection.close();
39
+
40
+ await sleep(10); // Process events.
41
+
42
+ expect(callsCounter).toEqual(1);
43
+ })
44
+ .timeout(1_000)
45
+ .retries(3);
46
+
47
+ it('establish connection and send data through with protocol', async function () {
48
+ const topic = PublicKey.random();
49
+ const peer1Id = PublicKey.random();
50
+ const peer2Id = PublicKey.random();
51
+ const sessionId = PublicKey.random();
52
+
53
+ const plugin1 = new TestProtocolPlugin(peer1Id.asBuffer());
54
+ const protocolProvider1 = testProtocolProvider(topic.asBuffer(), peer1Id.asBuffer(), plugin1);
55
+ const connection1 = new WebRTCTransport(
56
+ true,
57
+ protocolProvider1({
58
+ channel: discoveryKey(topic),
59
+ initiator: true
60
+ }).stream,
61
+ peer1Id,
62
+ peer2Id,
63
+ sessionId,
64
+ topic,
65
+ async (msg) => {
66
+ await sleep(10);
67
+ await connection2.signal(msg.data.signal);
68
+ }
69
+ );
70
+ afterTest(() => connection1.close());
71
+ afterTest(() => connection1.errors.assertNoUnhandledErrors());
72
+
73
+ const plugin2 = new TestProtocolPlugin(peer2Id.asBuffer());
74
+ const protocolProvider2 = testProtocolProvider(topic.asBuffer(), peer2Id.asBuffer(), plugin2);
75
+ const connection2 = new WebRTCTransport(
76
+ false,
77
+ protocolProvider2({
78
+ channel: discoveryKey(topic),
79
+ initiator: false
80
+ }).stream,
81
+ peer2Id,
82
+ peer1Id,
83
+ sessionId,
84
+ topic,
85
+ async (msg) => {
86
+ await sleep(10);
87
+ await connection1.signal(msg.data.signal);
88
+ }
89
+ );
90
+ afterTest(() => connection2.close());
91
+ afterTest(() => connection2.errors.assertNoUnhandledErrors());
92
+
93
+ const received: any[] = [];
94
+ const mockReceive = (p: Protocol, s: string) => {
95
+ received.push(p, s);
96
+ return undefined;
97
+ };
98
+ plugin1.on('receive', mockReceive);
99
+
100
+ plugin2.on('connect', async (protocol) => {
101
+ await plugin2.send(peer1Id.asBuffer(), '{"message": "Hello"}');
102
+ });
103
+
104
+ await waitForExpect(() => {
105
+ expect(received.length).toBe(2);
106
+ expect(received[0]).toBeInstanceOf(Protocol);
107
+ expect(received[1]).toBe('{"message": "Hello"}');
108
+ });
109
+ })
110
+ .timeout(2_000)
111
+ .retries(3);
112
+ });