@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.
- package/LICENSE +21 -0
- package/dist/src/connection-log.d.ts +23 -0
- package/dist/src/connection-log.d.ts.map +1 -0
- package/dist/src/connection-log.js +98 -0
- package/dist/src/connection-log.js.map +1 -0
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +28 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/network-manager.d.ts +80 -0
- package/dist/src/network-manager.d.ts.map +1 -0
- package/dist/src/network-manager.js +123 -0
- package/dist/src/network-manager.js.map +1 -0
- package/dist/src/network-manager.test.d.ts +2 -0
- package/dist/src/network-manager.test.d.ts.map +1 -0
- package/dist/src/network-manager.test.js +621 -0
- package/dist/src/network-manager.test.js.map +1 -0
- package/dist/src/presence.test.d.ts +2 -0
- package/dist/src/presence.test.d.ts.map +1 -0
- package/dist/src/presence.test.js +60 -0
- package/dist/src/presence.test.js.map +1 -0
- package/dist/src/protocol-factory.d.ts +30 -0
- package/dist/src/protocol-factory.d.ts.map +1 -0
- package/dist/src/protocol-factory.js +64 -0
- package/dist/src/protocol-factory.js.map +1 -0
- package/dist/src/signal/index.d.ts +4 -0
- package/dist/src/signal/index.d.ts.map +1 -0
- package/dist/src/signal/index.js +23 -0
- package/dist/src/signal/index.js.map +1 -0
- package/dist/src/signal/integration.test.d.ts +2 -0
- package/dist/src/signal/integration.test.d.ts.map +1 -0
- package/dist/src/signal/integration.test.js +105 -0
- package/dist/src/signal/integration.test.js.map +1 -0
- package/dist/src/signal/message-router.d.ts +39 -0
- package/dist/src/signal/message-router.d.ts.map +1 -0
- package/dist/src/signal/message-router.js +135 -0
- package/dist/src/signal/message-router.js.map +1 -0
- package/dist/src/signal/message-router.test.d.ts +2 -0
- package/dist/src/signal/message-router.test.d.ts.map +1 -0
- package/dist/src/signal/message-router.test.js +211 -0
- package/dist/src/signal/message-router.test.js.map +1 -0
- package/dist/src/signal/signal-connection.d.ts +21 -0
- package/dist/src/signal/signal-connection.d.ts.map +1 -0
- package/dist/src/signal/signal-connection.js +6 -0
- package/dist/src/signal/signal-connection.js.map +1 -0
- package/dist/src/signal/signal-messaging.d.ts +34 -0
- package/dist/src/signal/signal-messaging.d.ts.map +1 -0
- package/dist/src/signal/signal-messaging.js +6 -0
- package/dist/src/signal/signal-messaging.js.map +1 -0
- package/dist/src/swarm/connection.d.ts +69 -0
- package/dist/src/swarm/connection.d.ts.map +1 -0
- package/dist/src/swarm/connection.js +187 -0
- package/dist/src/swarm/connection.js.map +1 -0
- package/dist/src/swarm/index.d.ts +4 -0
- package/dist/src/swarm/index.d.ts.map +1 -0
- package/dist/src/swarm/index.js +23 -0
- package/dist/src/swarm/index.js.map +1 -0
- package/dist/src/swarm/swarm-mapper.d.ts +30 -0
- package/dist/src/swarm/swarm-mapper.d.ts.map +1 -0
- package/dist/src/swarm/swarm-mapper.js +90 -0
- package/dist/src/swarm/swarm-mapper.js.map +1 -0
- package/dist/src/swarm/swarm.d.ts +64 -0
- package/dist/src/swarm/swarm.d.ts.map +1 -0
- package/dist/src/swarm/swarm.js +276 -0
- package/dist/src/swarm/swarm.js.map +1 -0
- package/dist/src/swarm/swarm.test.d.ts +2 -0
- package/dist/src/swarm/swarm.test.d.ts.map +1 -0
- package/dist/src/swarm/swarm.test.js +145 -0
- package/dist/src/swarm/swarm.test.js.map +1 -0
- package/dist/src/testing/index.d.ts +2 -0
- package/dist/src/testing/index.d.ts.map +1 -0
- package/dist/src/testing/index.js +21 -0
- package/dist/src/testing/index.js.map +1 -0
- package/dist/src/testing/test-protocol.d.ts +59 -0
- package/dist/src/testing/test-protocol.d.ts.map +1 -0
- package/dist/src/testing/test-protocol.js +140 -0
- package/dist/src/testing/test-protocol.js.map +1 -0
- package/dist/src/topology/fully-connected-topology.d.ts +11 -0
- package/dist/src/topology/fully-connected-topology.d.ts.map +1 -0
- package/dist/src/topology/fully-connected-topology.js +34 -0
- package/dist/src/topology/fully-connected-topology.js.map +1 -0
- package/dist/src/topology/index.d.ts +5 -0
- package/dist/src/topology/index.d.ts.map +1 -0
- package/dist/src/topology/index.js +24 -0
- package/dist/src/topology/index.js.map +1 -0
- package/dist/src/topology/mmst-topology.d.ts +31 -0
- package/dist/src/topology/mmst-topology.d.ts.map +1 -0
- package/dist/src/topology/mmst-topology.js +73 -0
- package/dist/src/topology/mmst-topology.js.map +1 -0
- package/dist/src/topology/star-topology.d.ts +13 -0
- package/dist/src/topology/star-topology.d.ts.map +1 -0
- package/dist/src/topology/star-topology.js +60 -0
- package/dist/src/topology/star-topology.js.map +1 -0
- package/dist/src/topology/topology.d.ts +56 -0
- package/dist/src/topology/topology.d.ts.map +1 -0
- package/dist/src/topology/topology.js +6 -0
- package/dist/src/topology/topology.js.map +1 -0
- package/dist/src/transport/index.d.ts +6 -0
- package/dist/src/transport/index.d.ts.map +1 -0
- package/dist/src/transport/index.js +25 -0
- package/dist/src/transport/index.js.map +1 -0
- package/dist/src/transport/memory-transport.d.ts +32 -0
- package/dist/src/transport/memory-transport.d.ts.map +1 -0
- package/dist/src/transport/memory-transport.js +98 -0
- package/dist/src/transport/memory-transport.js.map +1 -0
- package/dist/src/transport/memory-transport.test.d.ts +2 -0
- package/dist/src/transport/memory-transport.test.d.ts.map +1 -0
- package/dist/src/transport/memory-transport.test.js +84 -0
- package/dist/src/transport/memory-transport.test.js.map +1 -0
- package/dist/src/transport/transport.d.ts +38 -0
- package/dist/src/transport/transport.d.ts.map +1 -0
- package/dist/src/transport/transport.js +6 -0
- package/dist/src/transport/transport.js.map +1 -0
- package/dist/src/transport/webrtc-transport-proxy.d.ts +48 -0
- package/dist/src/transport/webrtc-transport-proxy.d.ts.map +1 -0
- package/dist/src/transport/webrtc-transport-proxy.js +139 -0
- package/dist/src/transport/webrtc-transport-proxy.js.map +1 -0
- package/dist/src/transport/webrtc-transport-proxy.test.d.ts +2 -0
- package/dist/src/transport/webrtc-transport-proxy.test.d.ts.map +1 -0
- package/dist/src/transport/webrtc-transport-proxy.test.js +245 -0
- package/dist/src/transport/webrtc-transport-proxy.test.js.map +1 -0
- package/dist/src/transport/webrtc-transport-service.d.ts +15 -0
- package/dist/src/transport/webrtc-transport-service.d.ts.map +1 -0
- package/dist/src/transport/webrtc-transport-service.js +97 -0
- package/dist/src/transport/webrtc-transport-service.js.map +1 -0
- package/dist/src/transport/webrtc-transport.d.ts +34 -0
- package/dist/src/transport/webrtc-transport.d.ts.map +1 -0
- package/dist/src/transport/webrtc-transport.js +100 -0
- package/dist/src/transport/webrtc-transport.js.map +1 -0
- package/dist/src/transport/webrtc-transport.test.d.ts +2 -0
- package/dist/src/transport/webrtc-transport.test.d.ts.map +1 -0
- package/dist/src/transport/webrtc-transport.test.js +80 -0
- package/dist/src/transport/webrtc-transport.test.js.map +1 -0
- package/dist/src/transport/webrtc.d.ts +6 -0
- package/dist/src/transport/webrtc.d.ts.map +1 -0
- package/dist/src/transport/webrtc.js +17 -0
- package/dist/src/transport/webrtc.js.map +1 -0
- package/dist/src/transport/wrtc-simple-peer.test.d.ts +2 -0
- package/dist/src/transport/wrtc-simple-peer.test.d.ts.map +1 -0
- package/dist/src/transport/wrtc-simple-peer.test.js +24 -0
- package/dist/src/transport/wrtc-simple-peer.test.js.map +1 -0
- package/dist/src/types.d.ts +6 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +6 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +58 -0
- package/src/connection-log.ts +102 -0
- package/src/globals.d.ts +7 -0
- package/src/index.ts +12 -0
- package/src/network-manager.test.ts +764 -0
- package/src/network-manager.ts +204 -0
- package/src/presence.test.ts +78 -0
- package/src/protocol-factory.ts +83 -0
- package/src/signal/index.ts +7 -0
- package/src/signal/integration.test.ts +129 -0
- package/src/signal/message-router.test.ts +274 -0
- package/src/signal/message-router.ts +200 -0
- package/src/signal/signal-connection.ts +20 -0
- package/src/signal/signal-messaging.ts +38 -0
- package/src/swarm/connection.ts +206 -0
- package/src/swarm/index.ts +7 -0
- package/src/swarm/swarm-mapper.ts +120 -0
- package/src/swarm/swarm.test.ts +192 -0
- package/src/swarm/swarm.ts +328 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/test-protocol.ts +160 -0
- package/src/topology/fully-connected-topology.ts +38 -0
- package/src/topology/index.ts +8 -0
- package/src/topology/mmst-topology.ts +105 -0
- package/src/topology/star-topology.ts +67 -0
- package/src/topology/topology.ts +69 -0
- package/src/transport/index.ts +9 -0
- package/src/transport/memory-transport.test.ts +110 -0
- package/src/transport/memory-transport.ts +128 -0
- package/src/transport/transport.ts +47 -0
- package/src/transport/webrtc-transport-proxy.test.ts +290 -0
- package/src/transport/webrtc-transport-proxy.ts +167 -0
- package/src/transport/webrtc-transport-service.ts +115 -0
- package/src/transport/webrtc-transport.test.ts +112 -0
- package/src/transport/webrtc-transport.ts +122 -0
- package/src/transport/webrtc.ts +15 -0
- package/src/transport/wrtc-simple-peer.test.ts +24 -0
- package/src/types.ts +10 -0
- 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
|
+
});
|