@dxos/network-manager 0.1.57-main.e87098f → 0.1.57
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/dist/lib/browser/{chunk-UAB4ONRL.mjs → chunk-UKMTAO4L.mjs} +553 -121
- package/dist/lib/browser/chunk-UKMTAO4L.mjs.map +7 -0
- package/dist/lib/browser/chunk-YERMWDRB.mjs +24 -0
- package/dist/lib/browser/chunk-YERMWDRB.mjs.map +7 -0
- package/dist/lib/browser/index.browser-O53CPVK3.mjs +14 -0
- package/dist/lib/browser/index.browser-O53CPVK3.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +29 -13
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +83 -38
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/node/index.cjs +1048 -124
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +1109 -151
- package/dist/lib/node/testing/index.cjs.map +4 -4
- package/dist/types/src/swarm/connection.d.ts +6 -0
- package/dist/types/src/swarm/connection.d.ts.map +1 -1
- package/dist/types/src/swarm/peer.d.ts.map +1 -1
- package/dist/types/src/testing/test-builder.d.ts +5 -2
- package/dist/types/src/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/transport/datachannel/index.browser.d.ts +15 -0
- package/dist/types/src/transport/datachannel/index.browser.d.ts.map +1 -0
- package/dist/types/src/transport/datachannel/index.d.ts +8 -0
- package/dist/types/src/transport/datachannel/index.d.ts.map +1 -0
- package/dist/types/src/transport/datachannel/rtc-data-channel.d.ts +29 -0
- package/dist/types/src/transport/datachannel/rtc-data-channel.d.ts.map +1 -0
- package/dist/types/src/transport/datachannel/rtc-events.d.ts +9 -0
- package/dist/types/src/transport/datachannel/rtc-events.d.ts.map +1 -0
- package/dist/types/src/transport/datachannel/rtc-ice-candidate.d.ts +22 -0
- package/dist/types/src/transport/datachannel/rtc-ice-candidate.d.ts.map +1 -0
- package/dist/types/src/transport/datachannel/rtc-peer-connection.d.ts +47 -0
- package/dist/types/src/transport/datachannel/rtc-peer-connection.d.ts.map +1 -0
- package/dist/types/src/transport/datachannel/rtc-peer-connection.test.d.ts +2 -0
- package/dist/types/src/transport/datachannel/rtc-peer-connection.test.d.ts.map +1 -0
- package/dist/types/src/transport/datachannel/rtc-session-description.d.ts +10 -0
- package/dist/types/src/transport/datachannel/rtc-session-description.d.ts.map +1 -0
- package/dist/types/src/transport/index.d.ts +5 -3
- package/dist/types/src/transport/index.d.ts.map +1 -1
- package/dist/types/src/transport/libdatachannel-transport.d.ts +35 -0
- package/dist/types/src/transport/libdatachannel-transport.d.ts.map +1 -0
- package/dist/types/src/transport/libdatachannel-transport.test.d.ts +2 -0
- package/dist/types/src/transport/libdatachannel-transport.test.d.ts.map +1 -0
- package/dist/types/src/transport/simplepeer-simple-peer.d.ts +2 -0
- package/dist/types/src/transport/simplepeer-simple-peer.d.ts.map +1 -0
- package/dist/types/src/transport/simplepeer-transport-proxy-test.d.ts +2 -0
- package/dist/types/src/transport/simplepeer-transport-proxy-test.d.ts.map +1 -0
- package/dist/types/src/transport/{webrtc-transport-proxy.d.ts → simplepeer-transport-proxy.d.ts} +5 -5
- package/dist/types/src/transport/simplepeer-transport-proxy.d.ts.map +1 -0
- package/dist/types/src/transport/{webrtc-transport-service.d.ts → simplepeer-transport-service.d.ts} +2 -2
- package/dist/types/src/transport/simplepeer-transport-service.d.ts.map +1 -0
- package/dist/types/src/transport/{webrtc-transport.d.ts → simplepeer-transport.d.ts} +8 -8
- package/dist/types/src/transport/simplepeer-transport.d.ts.map +1 -0
- package/dist/types/src/transport/simplepeer-transport.test.d.ts +2 -0
- package/dist/types/src/transport/simplepeer-transport.test.d.ts.map +1 -0
- package/dist/types/src/transport/transport.d.ts +6 -0
- package/dist/types/src/transport/transport.d.ts.map +1 -1
- package/dist/types/src/wire-protocol.d.ts +3 -2
- package/dist/types/src/wire-protocol.d.ts.map +1 -1
- package/package.json +25 -20
- package/src/connection-log.ts +1 -1
- package/src/swarm/connection.test.ts +8 -3
- package/src/swarm/connection.ts +58 -6
- package/src/swarm/peer.ts +4 -0
- package/src/swarm/swarm.test.ts +3 -2
- package/src/testing/test-builder.ts +72 -41
- package/src/transport/datachannel/README.md +2 -0
- package/src/transport/datachannel/index.browser.ts +8 -0
- package/src/transport/datachannel/index.ts +17 -0
- package/src/transport/datachannel/rtc-data-channel.ts +148 -0
- package/src/transport/datachannel/rtc-events.ts +23 -0
- package/src/transport/datachannel/rtc-ice-candidate.ts +54 -0
- package/src/transport/datachannel/rtc-peer-connection.test.ts +112 -0
- package/src/transport/datachannel/rtc-peer-connection.ts +347 -0
- package/src/transport/datachannel/rtc-session-description.ts +23 -0
- package/src/transport/index.ts +5 -3
- package/src/transport/libdatachannel-transport.test.ts +99 -0
- package/src/transport/libdatachannel-transport.ts +252 -0
- package/src/transport/{webrtc-transport-proxy.test.ts → simplepeer-transport-proxy-test.ts} +15 -15
- package/src/transport/{webrtc-transport-proxy.ts → simplepeer-transport-proxy.ts} +8 -8
- package/src/transport/{webrtc-transport-service.ts → simplepeer-transport-service.ts} +4 -4
- package/src/transport/{webrtc-transport.test.ts → simplepeer-transport.test.ts} +5 -5
- package/src/transport/{webrtc-transport.ts → simplepeer-transport.ts} +13 -27
- package/src/transport/transport.ts +7 -0
- package/src/wire-protocol.ts +8 -4
- package/dist/lib/browser/chunk-UAB4ONRL.mjs.map +0 -7
- package/dist/types/src/transport/webrtc-simple-peer.test.d.ts +0 -2
- package/dist/types/src/transport/webrtc-simple-peer.test.d.ts.map +0 -1
- package/dist/types/src/transport/webrtc-transport-proxy.d.ts.map +0 -1
- package/dist/types/src/transport/webrtc-transport-proxy.test.d.ts +0 -2
- package/dist/types/src/transport/webrtc-transport-proxy.test.d.ts.map +0 -1
- package/dist/types/src/transport/webrtc-transport-service.d.ts.map +0 -1
- package/dist/types/src/transport/webrtc-transport.d.ts.map +0 -1
- package/dist/types/src/transport/webrtc-transport.test.d.ts +0 -2
- package/dist/types/src/transport/webrtc-transport.test.d.ts.map +0 -1
- /package/src/transport/{webrtc-simple-peer.test.ts → simplepeer-simple-peer.ts} +0 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Duplex } from 'stream';
|
|
6
|
+
|
|
7
|
+
import { Event, Trigger, synchronized } from '@dxos/async';
|
|
8
|
+
import { ErrorStream } from '@dxos/debug';
|
|
9
|
+
import { log } from '@dxos/log';
|
|
10
|
+
import { Signal } from '@dxos/protocols/proto/dxos/mesh/swarm';
|
|
11
|
+
|
|
12
|
+
import { type PeerConnection } from './datachannel/rtc-peer-connection';
|
|
13
|
+
import { Transport, TransportFactory } from './transport';
|
|
14
|
+
|
|
15
|
+
export type LibDataChannelTransportParams = {
|
|
16
|
+
initiator: boolean;
|
|
17
|
+
stream: NodeJS.ReadWriteStream;
|
|
18
|
+
webrtcConfig?: RTCConfiguration;
|
|
19
|
+
sendSignal: (signal: Signal) => Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const DATACHANNEL_LABEL = 'dxos.mesh.transport';
|
|
23
|
+
const MAX_BUFFERED_AMOUNT = 64 * 1024;
|
|
24
|
+
|
|
25
|
+
// https://viblast.com/blog/2015/2/5/webrtc-data-channel-message-size/
|
|
26
|
+
const MAX_MESSAGE_SIZE = 64 * 1024;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Transport
|
|
30
|
+
*/
|
|
31
|
+
export class LibDataChannelTransport implements Transport {
|
|
32
|
+
private _closed = false;
|
|
33
|
+
readonly closed = new Event();
|
|
34
|
+
private _connected = false;
|
|
35
|
+
readonly connected = new Event();
|
|
36
|
+
readonly errors = new ErrorStream();
|
|
37
|
+
private readonly _peer: Promise<PeerConnection>;
|
|
38
|
+
private _channel!: RTCDataChannel;
|
|
39
|
+
private _stream!: Duplex;
|
|
40
|
+
|
|
41
|
+
private readonly _readyForCandidates = new Trigger();
|
|
42
|
+
private _writeCallback: (() => void) | null = null;
|
|
43
|
+
|
|
44
|
+
constructor(private readonly params: LibDataChannelTransportParams) {
|
|
45
|
+
this._peer = (async () => {
|
|
46
|
+
const { RTCPeerConnection: PeerConnection } = await import('./datachannel');
|
|
47
|
+
if (this._closed) {
|
|
48
|
+
this.errors.raise(new Error('connection already closed'));
|
|
49
|
+
}
|
|
50
|
+
const peer = new PeerConnection(params.webrtcConfig);
|
|
51
|
+
|
|
52
|
+
peer.onicecandidateerror = (event) => {
|
|
53
|
+
log.error('peer.onicecandidateerror', { event });
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
peer.onconnectionstatechange = (event) => {
|
|
57
|
+
log.debug('peer.onconnectionstatechange', {
|
|
58
|
+
event,
|
|
59
|
+
peerConnectionState: peer.connectionState,
|
|
60
|
+
transportConnectionState: this._connected,
|
|
61
|
+
});
|
|
62
|
+
// TODO(nf): throw error if datachannel does not connect after some time?
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
peer.onicecandidate = async (event) => {
|
|
66
|
+
log.debug('peer.onicecandidate', { event });
|
|
67
|
+
if (event.candidate) {
|
|
68
|
+
try {
|
|
69
|
+
await params.sendSignal({
|
|
70
|
+
payload: {
|
|
71
|
+
data: {
|
|
72
|
+
type: 'candidate',
|
|
73
|
+
candidate: {
|
|
74
|
+
candidate: event.candidate.candidate,
|
|
75
|
+
// these fields never seem to be not null, but connecting to Chrome doesn't work if they are
|
|
76
|
+
sdpMLineIndex: event.candidate.sdpMLineIndex ?? 0,
|
|
77
|
+
sdpMid: event.candidate.sdpMid ?? 0,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
} catch (err) {
|
|
83
|
+
log.warn('signaling errror', { err });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
if (params.initiator) {
|
|
88
|
+
peer
|
|
89
|
+
.createOffer()
|
|
90
|
+
.then(async (offer) => {
|
|
91
|
+
if (peer.connectionState !== 'connecting') {
|
|
92
|
+
log.error('i am initiator but peer not in state connecting', { peer });
|
|
93
|
+
this.errors.raise(new Error('invalid state: peer is initiator, but other peer not in state connecting'));
|
|
94
|
+
}
|
|
95
|
+
log.debug(`im the initiator, creating offer, peer is in state ${peer.connectionState}`, { offer });
|
|
96
|
+
await peer.setLocalDescription(offer);
|
|
97
|
+
await params.sendSignal({ payload: { data: { type: offer.type, sdp: offer.sdp } } });
|
|
98
|
+
return offer;
|
|
99
|
+
})
|
|
100
|
+
.catch((err) => {
|
|
101
|
+
this.errors.raise(err);
|
|
102
|
+
});
|
|
103
|
+
this.handleChannel(peer.createDataChannel(DATACHANNEL_LABEL));
|
|
104
|
+
log.debug('created data channel');
|
|
105
|
+
peer.ondatachannel = (event) => {
|
|
106
|
+
this.errors.raise(new Error('got ondatachannel when i am the initiator?'));
|
|
107
|
+
};
|
|
108
|
+
} else {
|
|
109
|
+
peer.ondatachannel = (event) => {
|
|
110
|
+
log.debug('peer.ondatachannel (non-initiator)', { event });
|
|
111
|
+
// TODO(nf): should the label contain some identifier?
|
|
112
|
+
if (event.channel.label !== DATACHANNEL_LABEL) {
|
|
113
|
+
this.errors.raise(new Error(`unexpected channel label ${event.channel.label}`));
|
|
114
|
+
}
|
|
115
|
+
this.handleChannel(event.channel);
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return peer;
|
|
120
|
+
})();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private handleChannel(dataChannel: RTCDataChannel) {
|
|
124
|
+
this._channel = dataChannel;
|
|
125
|
+
this._channel.onopen = () => {
|
|
126
|
+
log.debug('dataChannel.onopen');
|
|
127
|
+
const duplex = new Duplex({
|
|
128
|
+
read: () => {},
|
|
129
|
+
write: (chunk, encoding, callback) => {
|
|
130
|
+
// todo wait to open
|
|
131
|
+
|
|
132
|
+
if (chunk.length > MAX_MESSAGE_SIZE) {
|
|
133
|
+
this.errors.raise(new Error(`message too large: ${chunk.length} > ${MAX_MESSAGE_SIZE}`));
|
|
134
|
+
}
|
|
135
|
+
dataChannel.send(chunk);
|
|
136
|
+
|
|
137
|
+
if (this._channel.bufferedAmount > MAX_BUFFERED_AMOUNT) {
|
|
138
|
+
if (this._writeCallback !== null) {
|
|
139
|
+
log.error("consumer trying to write before we're ready for more data");
|
|
140
|
+
}
|
|
141
|
+
this._writeCallback = callback;
|
|
142
|
+
} else {
|
|
143
|
+
callback();
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
duplex.pipe(this.params.stream).pipe(duplex);
|
|
149
|
+
this._stream = duplex;
|
|
150
|
+
this._connected = true;
|
|
151
|
+
this.connected.emit();
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
this._channel.onerror = async (err) => {
|
|
155
|
+
this.errors.raise(new Error('channel error: ' + err.toString()));
|
|
156
|
+
await this._close();
|
|
157
|
+
};
|
|
158
|
+
this._channel.onclose = async (err) => {
|
|
159
|
+
log.info('channel onclose', { err });
|
|
160
|
+
await this._close();
|
|
161
|
+
};
|
|
162
|
+
this._channel.onmessage = (event) => {
|
|
163
|
+
let data = event.data;
|
|
164
|
+
if (data instanceof ArrayBuffer) {
|
|
165
|
+
data = Buffer.from(data);
|
|
166
|
+
}
|
|
167
|
+
this._stream.push(data);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
this._channel.onbufferedamountlow = () => {
|
|
171
|
+
const cb = this._writeCallback;
|
|
172
|
+
this._writeCallback = null;
|
|
173
|
+
cb?.();
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
@synchronized
|
|
178
|
+
private async _close() {
|
|
179
|
+
if (this._closed) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
await this._disconnectStreams();
|
|
183
|
+
this._closed = true;
|
|
184
|
+
this.closed.emit();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
signal(signal: Signal): void {
|
|
188
|
+
this._peer
|
|
189
|
+
.then(async (peer) => {
|
|
190
|
+
const data = signal.payload.data;
|
|
191
|
+
switch (data.type) {
|
|
192
|
+
case 'offer': {
|
|
193
|
+
if ((await this._peer).connectionState !== 'new') {
|
|
194
|
+
log.error('received offer but peer not in state new', { peer });
|
|
195
|
+
this.errors.raise(new Error('invalid signalling state: received offer when peer is not in state new'));
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
await peer.setRemoteDescription({ type: data.type, sdp: data.sdp });
|
|
200
|
+
const answer = await peer.createAnswer();
|
|
201
|
+
await peer.setLocalDescription(answer);
|
|
202
|
+
await this.params.sendSignal({ payload: { data: { type: answer.type, sdp: answer.sdp } } });
|
|
203
|
+
this._readyForCandidates.wake();
|
|
204
|
+
} catch (err) {
|
|
205
|
+
log.error("can't handle offer from signalling server", { err });
|
|
206
|
+
this.errors.raise(new Error('error handling offer'));
|
|
207
|
+
}
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
case 'answer':
|
|
212
|
+
try {
|
|
213
|
+
await peer.setRemoteDescription({ type: data.type, sdp: data.sdp });
|
|
214
|
+
this._readyForCandidates.wake();
|
|
215
|
+
} catch (err) {
|
|
216
|
+
log.error("can't handle answer from signalling server", { err });
|
|
217
|
+
this.errors.raise(new Error('error handling answer'));
|
|
218
|
+
}
|
|
219
|
+
break;
|
|
220
|
+
|
|
221
|
+
case 'candidate':
|
|
222
|
+
await this._readyForCandidates.wait();
|
|
223
|
+
await peer.addIceCandidate({ candidate: data.candidate.candidate });
|
|
224
|
+
break;
|
|
225
|
+
|
|
226
|
+
default:
|
|
227
|
+
log.error('unhandled signal type', { type: data.type, signal });
|
|
228
|
+
this.errors.raise(new Error(`unhandled signal type ${data.type}`));
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
.catch((err) => {
|
|
232
|
+
log.catch(err);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// TODO(nf): add classmethod to call node-datachannel.cleanup() when all instances have been destroyed?
|
|
237
|
+
async destroy(): Promise<void> {
|
|
238
|
+
await this._close();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private async _disconnectStreams() {
|
|
242
|
+
this.params.stream.unpipe?.(this._stream)?.unpipe?.(this.params.stream);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export const createLibDataChannelTransportFactory = (webrtcConfig?: any): TransportFactory => ({
|
|
247
|
+
createTransport: (params) =>
|
|
248
|
+
new LibDataChannelTransport({
|
|
249
|
+
...params,
|
|
250
|
+
webrtcConfig,
|
|
251
|
+
}),
|
|
252
|
+
});
|
|
@@ -11,10 +11,10 @@ import { Signal } from '@dxos/protocols/proto/dxos/mesh/swarm';
|
|
|
11
11
|
import { createLinkedPorts, createProtoRpcPeer, ProtoRpcPeer } from '@dxos/rpc';
|
|
12
12
|
import { afterAll, afterTest, beforeAll, describe, test } from '@dxos/test';
|
|
13
13
|
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
14
|
+
import { SimplePeerTransportProxy } from './simplepeer-transport-proxy';
|
|
15
|
+
import { SimplePeerTransportService } from './simplepeer-transport-service';
|
|
16
16
|
|
|
17
|
-
describe('
|
|
17
|
+
describe('SimplePeerTransportProxy', () => {
|
|
18
18
|
const setupProxy = async ({
|
|
19
19
|
initiator = true,
|
|
20
20
|
stream = new TestStream(),
|
|
@@ -27,7 +27,7 @@ describe('WebRTCTransportProxy', () => {
|
|
|
27
27
|
const [port1, port2] = createLinkedPorts();
|
|
28
28
|
|
|
29
29
|
// Starting BridgeService
|
|
30
|
-
const
|
|
30
|
+
const simplePeerTransportService: BridgeService = new SimplePeerTransportService();
|
|
31
31
|
|
|
32
32
|
// Starting BridgeService
|
|
33
33
|
const rpcService = createProtoRpcPeer({
|
|
@@ -35,7 +35,7 @@ describe('WebRTCTransportProxy', () => {
|
|
|
35
35
|
exposed: {
|
|
36
36
|
BridgeService: schema.getService('dxos.mesh.bridge.BridgeService'),
|
|
37
37
|
},
|
|
38
|
-
handlers: { BridgeService:
|
|
38
|
+
handlers: { BridgeService: simplePeerTransportService },
|
|
39
39
|
port: port1,
|
|
40
40
|
noHandshake: true,
|
|
41
41
|
encodingOptions: {
|
|
@@ -59,20 +59,20 @@ describe('WebRTCTransportProxy', () => {
|
|
|
59
59
|
await rpcClient.open();
|
|
60
60
|
afterTest(() => rpcClient.close());
|
|
61
61
|
|
|
62
|
-
const
|
|
62
|
+
const simplePeerTransportProxy = new SimplePeerTransportProxy({
|
|
63
63
|
initiator,
|
|
64
64
|
stream,
|
|
65
65
|
sendSignal,
|
|
66
66
|
bridgeService: rpcClient.rpc.BridgeService,
|
|
67
67
|
});
|
|
68
|
-
afterTest(async () => await
|
|
68
|
+
afterTest(async () => await simplePeerTransportProxy.destroy());
|
|
69
69
|
|
|
70
|
-
return {
|
|
70
|
+
return { simplePeerService: rpcService, SimplePeerTransportProxy: simplePeerTransportProxy };
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
// This doesn't clean up correctly and crashes with SIGSEGV / SIGABRT at the end. Probably an issue with wrtc package.
|
|
74
74
|
test('open and close', async () => {
|
|
75
|
-
const {
|
|
75
|
+
const { SimplePeerTransportProxy: connection } = await setupProxy();
|
|
76
76
|
|
|
77
77
|
const wait = connection.closed.waitForCount(1);
|
|
78
78
|
await connection.destroy();
|
|
@@ -81,7 +81,7 @@ describe('WebRTCTransportProxy', () => {
|
|
|
81
81
|
|
|
82
82
|
test('establish connection and send data through with protocol', async () => {
|
|
83
83
|
const stream1 = new TestStream();
|
|
84
|
-
const {
|
|
84
|
+
const { SimplePeerTransportProxy: connection1 } = await setupProxy({
|
|
85
85
|
initiator: true,
|
|
86
86
|
stream: stream1,
|
|
87
87
|
sendSignal: async (signal) => {
|
|
@@ -91,7 +91,7 @@ describe('WebRTCTransportProxy', () => {
|
|
|
91
91
|
afterTest(() => connection1.errors.assertNoUnhandledErrors());
|
|
92
92
|
|
|
93
93
|
const stream2 = new TestStream();
|
|
94
|
-
const {
|
|
94
|
+
const { SimplePeerTransportProxy: connection2 } = await setupProxy({
|
|
95
95
|
initiator: false,
|
|
96
96
|
stream: stream2,
|
|
97
97
|
sendSignal: async (signal) => {
|
|
@@ -109,12 +109,12 @@ describe('WebRTCTransportProxy', () => {
|
|
|
109
109
|
beforeAll(async () => {
|
|
110
110
|
const [port1, port2] = createLinkedPorts();
|
|
111
111
|
|
|
112
|
-
const
|
|
112
|
+
const simplePeerTransportService: BridgeService = new SimplePeerTransportService();
|
|
113
113
|
service = createProtoRpcPeer({
|
|
114
114
|
exposed: {
|
|
115
115
|
BridgeService: schema.getService('dxos.mesh.bridge.BridgeService'),
|
|
116
116
|
},
|
|
117
|
-
handlers: { BridgeService:
|
|
117
|
+
handlers: { BridgeService: simplePeerTransportService },
|
|
118
118
|
port: port1,
|
|
119
119
|
noHandshake: true,
|
|
120
120
|
encodingOptions: {
|
|
@@ -143,7 +143,7 @@ describe('WebRTCTransportProxy', () => {
|
|
|
143
143
|
|
|
144
144
|
test('establish connection and send data through with protocol', async () => {
|
|
145
145
|
const stream1 = new TestStream();
|
|
146
|
-
const proxy1 = new
|
|
146
|
+
const proxy1 = new SimplePeerTransportProxy({
|
|
147
147
|
initiator: true,
|
|
148
148
|
stream: stream1,
|
|
149
149
|
sendSignal: async (signal) => {
|
|
@@ -157,7 +157,7 @@ describe('WebRTCTransportProxy', () => {
|
|
|
157
157
|
});
|
|
158
158
|
|
|
159
159
|
const stream2 = new TestStream();
|
|
160
|
-
const proxy2 = new
|
|
160
|
+
const proxy2 = new SimplePeerTransportProxy({
|
|
161
161
|
initiator: false,
|
|
162
162
|
stream: stream2,
|
|
163
163
|
sendSignal: async (signal) => {
|
|
@@ -17,7 +17,7 @@ import { arrayToBuffer } from '@dxos/util';
|
|
|
17
17
|
|
|
18
18
|
import { Transport, TransportFactory, TransportOptions } from './transport';
|
|
19
19
|
|
|
20
|
-
export type
|
|
20
|
+
export type SimplePeerTransportProxyParams = {
|
|
21
21
|
initiator: boolean;
|
|
22
22
|
stream: NodeJS.ReadWriteStream;
|
|
23
23
|
bridgeService: BridgeService;
|
|
@@ -25,7 +25,7 @@ export type WebRTCTransportProxyParams = {
|
|
|
25
25
|
sendSignal: (signal: Signal) => Promise<void>;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
export class
|
|
28
|
+
export class SimplePeerTransportProxy implements Transport {
|
|
29
29
|
private readonly _proxyId = PublicKey.random();
|
|
30
30
|
private readonly _ctx = new Context();
|
|
31
31
|
|
|
@@ -38,7 +38,7 @@ export class WebRTCTransportProxy implements Transport {
|
|
|
38
38
|
|
|
39
39
|
// prettier-ignore
|
|
40
40
|
constructor(
|
|
41
|
-
private readonly _params:
|
|
41
|
+
private readonly _params: SimplePeerTransportProxyParams
|
|
42
42
|
) {
|
|
43
43
|
this._serviceStream = this._params.bridgeService.open({
|
|
44
44
|
proxyId: this._proxyId,
|
|
@@ -48,7 +48,7 @@ export class WebRTCTransportProxy implements Transport {
|
|
|
48
48
|
this._serviceStream.waitUntilReady().then(
|
|
49
49
|
() => {
|
|
50
50
|
this._serviceStream.subscribe(async (event: BridgeEvent) => {
|
|
51
|
-
log('
|
|
51
|
+
log('SimplePeerTransportProxy: event', event);
|
|
52
52
|
if (event.connection) {
|
|
53
53
|
await this._handleConnection(event.connection);
|
|
54
54
|
} else if (event.data) {
|
|
@@ -142,9 +142,9 @@ export class WebRTCTransportProxy implements Transport {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
// TODO(burdon): Why is this named Proxy?
|
|
145
|
-
export class
|
|
145
|
+
export class SimplePeerTransportProxyFactory implements TransportFactory {
|
|
146
146
|
private _bridgeService: BridgeService | undefined;
|
|
147
|
-
private _connections = new Set<
|
|
147
|
+
private _connections = new Set<SimplePeerTransportProxy>();
|
|
148
148
|
|
|
149
149
|
/**
|
|
150
150
|
* Sets the current BridgeService to be used to open connections.
|
|
@@ -160,9 +160,9 @@ export class WebRTCTransportProxyFactory implements TransportFactory {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
createTransport(options: TransportOptions): Transport {
|
|
163
|
-
invariant(this._bridgeService, '
|
|
163
|
+
invariant(this._bridgeService, 'SimplePeerTransportProxyFactory is not ready to open connections');
|
|
164
164
|
|
|
165
|
-
const transport = new
|
|
165
|
+
const transport = new SimplePeerTransportProxy({
|
|
166
166
|
...options,
|
|
167
167
|
bridgeService: this._bridgeService,
|
|
168
168
|
});
|
|
@@ -19,15 +19,15 @@ import {
|
|
|
19
19
|
} from '@dxos/protocols/proto/dxos/mesh/bridge';
|
|
20
20
|
import { ComplexMap } from '@dxos/util';
|
|
21
21
|
|
|
22
|
-
import {
|
|
22
|
+
import { SimplePeerTransport } from './simplepeer-transport';
|
|
23
23
|
|
|
24
24
|
type TransportState = {
|
|
25
|
-
transport:
|
|
25
|
+
transport: SimplePeerTransport;
|
|
26
26
|
stream: Duplex;
|
|
27
27
|
writeCallbacks: (() => void)[];
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
export class
|
|
30
|
+
export class SimplePeerTransportService implements BridgeService {
|
|
31
31
|
private readonly transports = new ComplexMap<PublicKey, TransportState>(PublicKey.hash);
|
|
32
32
|
|
|
33
33
|
// prettier-ignore
|
|
@@ -51,7 +51,7 @@ export class WebRTCTransportService implements BridgeService {
|
|
|
51
51
|
},
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
const transport = new
|
|
54
|
+
const transport = new SimplePeerTransport({
|
|
55
55
|
initiator: request.initiator,
|
|
56
56
|
stream: duplex,
|
|
57
57
|
webrtcConfig: this._webrtcConfig,
|
|
@@ -7,12 +7,12 @@ import { Duplex } from 'stream';
|
|
|
7
7
|
import { sleep, TestStream } from '@dxos/async';
|
|
8
8
|
import { afterTest, describe, test } from '@dxos/test';
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { SimplePeerTransport } from './simplepeer-transport';
|
|
11
11
|
|
|
12
|
-
describe('
|
|
12
|
+
describe('SimplePeerTransport', () => {
|
|
13
13
|
// This doesn't clean up correctly and crashes with SIGSEGV / SIGABRT at the end. Probably an issue with wrtc package.
|
|
14
14
|
test('open and close', async () => {
|
|
15
|
-
const connection = new
|
|
15
|
+
const connection = new SimplePeerTransport({
|
|
16
16
|
initiator: true,
|
|
17
17
|
stream: new Duplex(),
|
|
18
18
|
sendSignal: async () => {},
|
|
@@ -27,7 +27,7 @@ describe('WebRTCTransport', () => {
|
|
|
27
27
|
|
|
28
28
|
test('establish connection and send data through with protocol', async () => {
|
|
29
29
|
const stream1 = new TestStream();
|
|
30
|
-
const connection1 = new
|
|
30
|
+
const connection1 = new SimplePeerTransport({
|
|
31
31
|
initiator: true,
|
|
32
32
|
stream: stream1,
|
|
33
33
|
sendSignal: async (signal) => {
|
|
@@ -39,7 +39,7 @@ describe('WebRTCTransport', () => {
|
|
|
39
39
|
afterTest(() => connection1.errors.assertNoUnhandledErrors());
|
|
40
40
|
|
|
41
41
|
const stream2 = new TestStream();
|
|
42
|
-
const connection2 = new
|
|
42
|
+
const connection2 = new SimplePeerTransport({
|
|
43
43
|
initiator: false,
|
|
44
44
|
stream: stream2,
|
|
45
45
|
sendSignal: async (signal) => {
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import SimplePeerConstructor, { Instance as SimplePeer } from 'simple-peer';
|
|
6
|
+
import invariant from 'tiny-invariant';
|
|
6
7
|
|
|
7
8
|
import { Event } from '@dxos/async';
|
|
8
9
|
import { ErrorStream, raise } from '@dxos/debug';
|
|
9
|
-
import { invariant } from '@dxos/invariant';
|
|
10
10
|
import { PublicKey } from '@dxos/keys';
|
|
11
11
|
import { log } from '@dxos/log';
|
|
12
12
|
import { trace } from '@dxos/protocols';
|
|
@@ -15,47 +15,34 @@ import { Signal } from '@dxos/protocols/proto/dxos/mesh/swarm';
|
|
|
15
15
|
import { Transport, TransportFactory } from './transport';
|
|
16
16
|
import { wrtc } from './webrtc';
|
|
17
17
|
|
|
18
|
-
export type
|
|
18
|
+
export type SimplePeerTransportParams = {
|
|
19
19
|
initiator: boolean;
|
|
20
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/RTCPeerConnection
|
|
21
|
-
webrtcConfig?: any;
|
|
22
20
|
stream: NodeJS.ReadWriteStream;
|
|
23
|
-
|
|
21
|
+
webrtcConfig?: any;
|
|
24
22
|
sendSignal: (signal: Signal) => Promise<void>;
|
|
25
23
|
};
|
|
26
24
|
|
|
27
|
-
// TODO(burdon): Refs:
|
|
28
|
-
// https://webrtc.github.io/samples
|
|
29
|
-
// Simple peer discord.
|
|
30
|
-
|
|
31
25
|
/**
|
|
32
|
-
* Implements WebRTC
|
|
26
|
+
* Implements Transport for WebRTC. Uses simple-peer under the hood.
|
|
33
27
|
*/
|
|
34
|
-
export class
|
|
35
|
-
private readonly _instanceId = PublicKey.random().toHex();
|
|
28
|
+
export class SimplePeerTransport implements Transport {
|
|
36
29
|
private readonly _peer: SimplePeer;
|
|
37
|
-
|
|
38
30
|
private _closed = false;
|
|
31
|
+
|
|
39
32
|
readonly closed = new Event();
|
|
40
33
|
readonly connected = new Event();
|
|
41
34
|
readonly errors = new ErrorStream();
|
|
42
35
|
|
|
43
|
-
|
|
36
|
+
private readonly _instanceId = PublicKey.random().toHex();
|
|
37
|
+
|
|
38
|
+
constructor(private readonly params: SimplePeerTransportParams) {
|
|
44
39
|
log.trace('dxos.mesh.webrtc-transport.constructor', trace.begin({ id: this._instanceId }));
|
|
45
40
|
log('created connection', params);
|
|
46
|
-
|
|
47
|
-
// https://www.npmjs.com/package/simple-peer#api
|
|
48
|
-
// NOTE: Set localStorage.debug = 'simple-peer*' to enable debug logging. (Or show verbose in Chrome).
|
|
49
|
-
// View throughput via: chrome://webrtc-internals (data-channel)
|
|
50
41
|
this._peer = new SimplePeerConstructor({
|
|
42
|
+
channelName: 'dxos.mesh.transport',
|
|
51
43
|
initiator: this.params.initiator,
|
|
52
|
-
config: this.params.webrtcConfig,
|
|
53
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createDataChannel
|
|
54
|
-
channelConfig: {
|
|
55
|
-
protocol: 'dxos.mesh',
|
|
56
|
-
},
|
|
57
|
-
// Custom WebRTC implementation (for Node); otherwise get-browser-rtc.
|
|
58
44
|
wrtc: SimplePeerConstructor.WEBRTC_SUPPORT ? undefined : wrtc ?? raise(new Error('wrtc not available')),
|
|
45
|
+
config: this.params.webrtcConfig,
|
|
59
46
|
});
|
|
60
47
|
|
|
61
48
|
this._peer.on('signal', async (data) => {
|
|
@@ -119,10 +106,9 @@ export class WebRTCTransport implements Transport {
|
|
|
119
106
|
}
|
|
120
107
|
}
|
|
121
108
|
|
|
122
|
-
|
|
123
|
-
export const createWebRTCTransportFactory = (webrtcConfig?: any): TransportFactory => ({
|
|
109
|
+
export const createSimplePeerTransportFactory = (webrtcConfig?: any): TransportFactory => ({
|
|
124
110
|
createTransport: (params) =>
|
|
125
|
-
new
|
|
111
|
+
new SimplePeerTransport({
|
|
126
112
|
...params,
|
|
127
113
|
webrtcConfig,
|
|
128
114
|
}),
|
|
@@ -6,6 +6,13 @@ import { Event } from '@dxos/async';
|
|
|
6
6
|
import { ErrorStream } from '@dxos/debug';
|
|
7
7
|
import { Signal } from '@dxos/protocols/proto/dxos/mesh/swarm';
|
|
8
8
|
|
|
9
|
+
export enum TransportKind {
|
|
10
|
+
SIMPLE_PEER = 'SIMPLE_PEER',
|
|
11
|
+
LIBDATACHANNEL = 'LIBDATACHANNEL',
|
|
12
|
+
SIMPLE_PEER_PROXY = 'SIMPLE_PEER_PROXY',
|
|
13
|
+
MEMORY = 'MEMORY',
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
/**
|
|
10
17
|
* Abstraction over a P2P connection transport. Currently either WebRTC or in-memory.
|
|
11
18
|
*/
|
package/src/wire-protocol.ts
CHANGED
|
@@ -23,8 +23,9 @@ export type WireProtocolProvider = (params: WireProtocolParams) => WireProtocol;
|
|
|
23
23
|
export interface WireProtocol {
|
|
24
24
|
stream: Duplex;
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
open(): Promise<void>;
|
|
27
|
+
close(): Promise<void>;
|
|
28
|
+
abort(): Promise<void>;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
/**
|
|
@@ -39,13 +40,16 @@ export const createTeleportProtocolFactory = (
|
|
|
39
40
|
const teleport = new Teleport(params);
|
|
40
41
|
return {
|
|
41
42
|
stream: teleport.stream,
|
|
42
|
-
|
|
43
|
+
open: async () => {
|
|
43
44
|
await teleport.open();
|
|
44
45
|
await onConnection(teleport);
|
|
45
46
|
},
|
|
46
|
-
|
|
47
|
+
close: async () => {
|
|
47
48
|
await teleport.close();
|
|
48
49
|
},
|
|
50
|
+
abort: async () => {
|
|
51
|
+
await teleport.abort();
|
|
52
|
+
},
|
|
49
53
|
};
|
|
50
54
|
};
|
|
51
55
|
};
|