@dxos/network-manager 0.4.10-main.fd4f2a3 → 0.4.10-next.71cec10
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-OIR45T43.mjs → chunk-SOVZDG44.mjs} +464 -425
- package/dist/lib/browser/chunk-SOVZDG44.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +1 -1
- package/dist/lib/node/{chunk-HUPX2JGP.cjs → chunk-BMZFS4HT.cjs} +480 -440
- package/dist/lib/node/chunk-BMZFS4HT.cjs.map +7 -0
- package/dist/lib/node/index.cjs +27 -27
- package/dist/lib/node/index.cjs.map +1 -1
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +18 -18
- 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/transport/libdatachannel-transport.d.ts +20 -19
- package/dist/types/src/transport/libdatachannel-transport.d.ts.map +1 -1
- package/dist/types/src/transport/memory-transport.d.ts +17 -10
- package/dist/types/src/transport/memory-transport.d.ts.map +1 -1
- package/dist/types/src/transport/simplepeer-transport-proxy.d.ts +7 -9
- package/dist/types/src/transport/simplepeer-transport-proxy.d.ts.map +1 -1
- package/dist/types/src/transport/simplepeer-transport-service.d.ts.map +1 -1
- package/dist/types/src/transport/simplepeer-transport.d.ts +9 -11
- package/dist/types/src/transport/simplepeer-transport.d.ts.map +1 -1
- package/dist/types/src/transport/tcp-transport.browser.d.ts +7 -2
- package/dist/types/src/transport/tcp-transport.browser.d.ts.map +1 -1
- package/dist/types/src/transport/tcp-transport.d.ts +6 -4
- package/dist/types/src/transport/tcp-transport.d.ts.map +1 -1
- package/dist/types/src/transport/transport.d.ts +18 -12
- package/dist/types/src/transport/transport.d.ts.map +1 -1
- package/package.json +17 -17
- package/src/swarm/connection.ts +7 -5
- package/src/swarm/peer.ts +3 -4
- package/src/transport/libdatachannel-transport.test.ts +14 -9
- package/src/transport/libdatachannel-transport.ts +226 -204
- package/src/transport/memory-transport.test.ts +10 -7
- package/src/transport/memory-transport.ts +49 -42
- package/src/transport/simplepeer-transport-proxy-test.ts +13 -8
- package/src/transport/simplepeer-transport-proxy.ts +48 -49
- package/src/transport/simplepeer-transport-service.ts +5 -2
- package/src/transport/simplepeer-transport.test.ts +9 -5
- package/src/transport/simplepeer-transport.ts +25 -25
- package/src/transport/tcp-transport.browser.ts +10 -5
- package/src/transport/tcp-transport.ts +31 -22
- package/src/transport/transport.ts +25 -14
- package/dist/lib/browser/chunk-OIR45T43.mjs.map +0 -7
- package/dist/lib/node/chunk-HUPX2JGP.cjs.map +0 -7
|
@@ -6,146 +6,196 @@ import { Duplex } from 'stream';
|
|
|
6
6
|
|
|
7
7
|
import { Event, Trigger, synchronized } from '@dxos/async';
|
|
8
8
|
import { ErrorStream } from '@dxos/debug';
|
|
9
|
+
import { invariant } from '@dxos/invariant';
|
|
9
10
|
import { log } from '@dxos/log';
|
|
10
11
|
import { type Signal } from '@dxos/protocols/proto/dxos/mesh/swarm';
|
|
11
12
|
|
|
12
|
-
import { type Transport, type TransportFactory, type TransportStats } from './transport';
|
|
13
|
-
|
|
14
|
-
export type LibDataChannelTransportParams = {
|
|
15
|
-
initiator: boolean;
|
|
16
|
-
stream: NodeJS.ReadWriteStream;
|
|
17
|
-
webrtcConfig?: RTCConfiguration;
|
|
18
|
-
sendSignal: (signal: Signal) => Promise<void>;
|
|
19
|
-
};
|
|
13
|
+
import { type Transport, type TransportFactory, type TransportOptions, type TransportStats } from './transport';
|
|
20
14
|
|
|
21
15
|
const DATACHANNEL_LABEL = 'dxos.mesh.transport';
|
|
22
16
|
const MAX_BUFFERED_AMOUNT = 64 * 1024;
|
|
23
17
|
|
|
24
|
-
// https://viblast.com/blog/2015/2/5/webrtc-data-channel-message-size
|
|
18
|
+
// https://viblast.com/blog/2015/2/5/webrtc-data-channel-message-size
|
|
25
19
|
const MAX_MESSAGE_SIZE = 64 * 1024;
|
|
26
20
|
|
|
21
|
+
export type LibDataChannelTransportOptions = TransportOptions & {
|
|
22
|
+
webrtcConfig?: RTCConfiguration;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const createLibDataChannelTransportFactory = (webrtcConfig?: any): TransportFactory => ({
|
|
26
|
+
createTransport: (options) => new LibDataChannelTransport({ ...options, webrtcConfig }),
|
|
27
|
+
});
|
|
28
|
+
|
|
27
29
|
/**
|
|
28
30
|
* Transport
|
|
29
31
|
*/
|
|
32
|
+
// TODO(burdon): Purpose (e.g., platform).
|
|
30
33
|
export class LibDataChannelTransport implements Transport {
|
|
31
34
|
private static _instanceCount = 0;
|
|
35
|
+
|
|
36
|
+
private _peer?: RTCPeerConnection;
|
|
37
|
+
private _channel!: RTCDataChannel;
|
|
38
|
+
private _stream!: Duplex;
|
|
39
|
+
|
|
32
40
|
private _closed = false;
|
|
33
|
-
readonly closed = new Event();
|
|
34
41
|
private _connected = false;
|
|
42
|
+
|
|
43
|
+
private _writeCallback: (() => void) | null = null;
|
|
44
|
+
private readonly _readyForCandidates = new Trigger();
|
|
45
|
+
|
|
46
|
+
readonly closed = new Event();
|
|
35
47
|
readonly connected = new Event();
|
|
36
48
|
readonly errors = new ErrorStream();
|
|
37
|
-
private readonly _peer: Promise<RTCPeerConnection>;
|
|
38
|
-
private _channel!: RTCDataChannel;
|
|
39
|
-
private _stream!: Duplex;
|
|
40
49
|
|
|
41
|
-
private readonly
|
|
42
|
-
private _writeCallback: (() => void) | null = null;
|
|
50
|
+
constructor(private readonly _options: LibDataChannelTransportOptions) {}
|
|
43
51
|
|
|
44
|
-
|
|
45
|
-
this._peer
|
|
46
|
-
|
|
47
|
-
const { RTCPeerConnection } = (await importESM('node-datachannel/polyfill'))
|
|
48
|
-
.default as typeof import('node-datachannel/polyfill');
|
|
49
|
-
/* eslint-enable @typescript-eslint/consistent-type-imports */
|
|
50
|
-
if (this._closed) {
|
|
51
|
-
this.errors.raise(new Error('connection already closed'));
|
|
52
|
-
}
|
|
53
|
-
// workaround https://github.com/murat-dogan/node-datachannel/pull/207
|
|
54
|
-
if (params.webrtcConfig) {
|
|
55
|
-
params.webrtcConfig.iceServers = params.webrtcConfig.iceServers ?? [];
|
|
56
|
-
} else {
|
|
57
|
-
params.webrtcConfig = { iceServers: [] };
|
|
58
|
-
}
|
|
59
|
-
const peer = new RTCPeerConnection(params.webrtcConfig);
|
|
60
|
-
LibDataChannelTransport._instanceCount++;
|
|
52
|
+
get isOpen() {
|
|
53
|
+
return !!this._peer && !this._closed;
|
|
54
|
+
}
|
|
61
55
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
56
|
+
async open() {
|
|
57
|
+
if (this._closed) {
|
|
58
|
+
// TODO(burdon): Make idempotent?
|
|
59
|
+
this.errors.raise(new Error('connection already closed'));
|
|
60
|
+
}
|
|
65
61
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
transportConnectionState: this._connected,
|
|
71
|
-
});
|
|
72
|
-
// TODO(nf): throw error if datachannel does not connect after some time?
|
|
73
|
-
};
|
|
62
|
+
// TODO(burdon): Move to factory?
|
|
63
|
+
/* eslint-disable @typescript-eslint/consistent-type-imports */
|
|
64
|
+
const { RTCPeerConnection } = (await importESM('node-datachannel/polyfill'))
|
|
65
|
+
.default as typeof import('node-datachannel/polyfill');
|
|
74
66
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
67
|
+
// workaround https://github.com/murat-dogan/node-datachannel/pull/207
|
|
68
|
+
if (this._options.webrtcConfig) {
|
|
69
|
+
this._options.webrtcConfig.iceServers = this._options.webrtcConfig.iceServers ?? [];
|
|
70
|
+
} else {
|
|
71
|
+
this._options.webrtcConfig = { iceServers: [] };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this._peer = new RTCPeerConnection(this._options.webrtcConfig);
|
|
75
|
+
|
|
76
|
+
this._peer.onicecandidateerror = (event) => {
|
|
77
|
+
log.error('peer.onicecandidateerror', { event });
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
this._peer.onconnectionstatechange = (event) => {
|
|
81
|
+
log.debug('peer.onconnectionstatechange', {
|
|
82
|
+
event,
|
|
83
|
+
peerConnectionState: this._peer?.connectionState,
|
|
84
|
+
transportConnectionState: this._connected,
|
|
85
|
+
});
|
|
86
|
+
// TODO(nf): throw error if datachannel does not connect after some time?
|
|
87
|
+
// TODO(burdon): Restart ICE.
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
this._peer.onicecandidate = async (event) => {
|
|
91
|
+
log.debug('peer.onicecandidate', { event });
|
|
92
|
+
if (event.candidate) {
|
|
93
|
+
try {
|
|
94
|
+
await this._options.sendSignal({
|
|
95
|
+
payload: {
|
|
96
|
+
data: {
|
|
97
|
+
type: 'candidate',
|
|
98
|
+
candidate: {
|
|
99
|
+
candidate: event.candidate.candidate,
|
|
100
|
+
// These fields never seem to be not null, but connecting to Chrome doesn't work if they are.
|
|
101
|
+
sdpMLineIndex: event.candidate.sdpMLineIndex ?? 0,
|
|
102
|
+
sdpMid: event.candidate.sdpMid ?? 0,
|
|
89
103
|
},
|
|
90
104
|
},
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
} catch (err) {
|
|
108
|
+
log.info('signaling error', { err });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (this._options.initiator) {
|
|
114
|
+
invariant(this._peer, 'not open');
|
|
115
|
+
// TODO(burdon): Deprecated negotiation pattern?
|
|
116
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Perfect_negotiation
|
|
117
|
+
this._peer
|
|
118
|
+
.createOffer()
|
|
119
|
+
.then(async (offer) => {
|
|
120
|
+
if (this._closed) {
|
|
121
|
+
return;
|
|
94
122
|
}
|
|
123
|
+
|
|
124
|
+
if (this._peer?.connectionState !== 'connecting') {
|
|
125
|
+
log.error('peer not connecting', { peer: this._peer });
|
|
126
|
+
this.errors.raise(new Error('invalid state: peer is initiator, but other peer not in state connecting'));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
log.debug('creating offer', { peer: this._peer, offer });
|
|
130
|
+
await this._peer!.setLocalDescription(offer);
|
|
131
|
+
await this._options.sendSignal({ payload: { data: { type: offer.type, sdp: offer.sdp } } });
|
|
132
|
+
})
|
|
133
|
+
.catch((err) => {
|
|
134
|
+
this.errors.raise(err);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
this._handleChannel(this._peer.createDataChannel(DATACHANNEL_LABEL));
|
|
138
|
+
|
|
139
|
+
log.debug('created data channel');
|
|
140
|
+
this._peer.ondatachannel = () => {
|
|
141
|
+
this.errors.raise(new Error('unexpected ondatachannel event for initiator'));
|
|
142
|
+
};
|
|
143
|
+
} else {
|
|
144
|
+
this._peer.ondatachannel = (event) => {
|
|
145
|
+
log.debug('peer.ondatachannel (non-initiator)', { event });
|
|
146
|
+
// TODO(nf): should the label contain some identifier?
|
|
147
|
+
if (event.channel.label !== DATACHANNEL_LABEL) {
|
|
148
|
+
this.errors.raise(new Error(`unexpected channel label ${event.channel.label}`));
|
|
95
149
|
}
|
|
150
|
+
|
|
151
|
+
this._handleChannel(event.channel);
|
|
96
152
|
};
|
|
153
|
+
}
|
|
97
154
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
.createOffer()
|
|
101
|
-
.then(async (offer) => {
|
|
102
|
-
if (this._closed) {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
if (peer.connectionState !== 'connecting') {
|
|
106
|
-
log.error('i am initiator but peer not in state connecting', { peer });
|
|
107
|
-
this.errors.raise(new Error('invalid state: peer is initiator, but other peer not in state connecting'));
|
|
108
|
-
}
|
|
109
|
-
log.debug(`im the initiator, creating offer, peer is in state ${peer.connectionState}`, { offer });
|
|
110
|
-
await peer.setLocalDescription(offer);
|
|
111
|
-
await params.sendSignal({ payload: { data: { type: offer.type, sdp: offer.sdp } } });
|
|
112
|
-
return offer;
|
|
113
|
-
})
|
|
114
|
-
.catch((err) => {
|
|
115
|
-
this.errors.raise(err);
|
|
116
|
-
});
|
|
117
|
-
this.handleChannel(peer.createDataChannel(DATACHANNEL_LABEL));
|
|
118
|
-
log.debug('created data channel');
|
|
119
|
-
peer.ondatachannel = (event) => {
|
|
120
|
-
this.errors.raise(new Error('got ondatachannel when i am the initiator?'));
|
|
121
|
-
};
|
|
122
|
-
} else {
|
|
123
|
-
peer.ondatachannel = (event) => {
|
|
124
|
-
log.debug('peer.ondatachannel (non-initiator)', { event });
|
|
125
|
-
// TODO(nf): should the label contain some identifier?
|
|
126
|
-
if (event.channel.label !== DATACHANNEL_LABEL) {
|
|
127
|
-
this.errors.raise(new Error(`unexpected channel label ${event.channel.label}`));
|
|
128
|
-
}
|
|
129
|
-
this.handleChannel(event.channel);
|
|
130
|
-
};
|
|
131
|
-
}
|
|
155
|
+
LibDataChannelTransport._instanceCount++;
|
|
156
|
+
}
|
|
132
157
|
|
|
133
|
-
|
|
134
|
-
|
|
158
|
+
async close() {
|
|
159
|
+
await this._close();
|
|
160
|
+
if (--LibDataChannelTransport._instanceCount === 0) {
|
|
161
|
+
(await importESM('node-datachannel')).cleanup();
|
|
162
|
+
}
|
|
135
163
|
}
|
|
136
164
|
|
|
137
|
-
|
|
165
|
+
@synchronized
|
|
166
|
+
private async _close() {
|
|
167
|
+
if (this._closed) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
await this._disconnectStreams();
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
this._peer?.close();
|
|
174
|
+
} catch (err: any) {
|
|
175
|
+
this.errors.raise(err);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
this._peer = undefined;
|
|
179
|
+
this._closed = true;
|
|
180
|
+
this.closed.emit();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Handle data channel events.
|
|
185
|
+
*/
|
|
186
|
+
private _handleChannel(dataChannel: RTCDataChannel) {
|
|
138
187
|
this._channel = dataChannel;
|
|
188
|
+
|
|
139
189
|
this._channel.onopen = () => {
|
|
140
|
-
log.debug('
|
|
190
|
+
log.debug('channel.onopen');
|
|
141
191
|
const duplex = new Duplex({
|
|
142
192
|
read: () => {},
|
|
143
193
|
write: async (chunk, encoding, callback) => {
|
|
144
|
-
//
|
|
145
|
-
|
|
194
|
+
// TODO(nf): Wait to open.
|
|
146
195
|
if (chunk.length > MAX_MESSAGE_SIZE) {
|
|
147
196
|
this.errors.raise(new Error(`message too large: ${chunk.length} > ${MAX_MESSAGE_SIZE}`));
|
|
148
197
|
}
|
|
198
|
+
|
|
149
199
|
try {
|
|
150
200
|
dataChannel.send(chunk);
|
|
151
201
|
} catch (err: any) {
|
|
@@ -154,7 +204,7 @@ export class LibDataChannelTransport implements Transport {
|
|
|
154
204
|
}
|
|
155
205
|
if (this._channel.bufferedAmount > MAX_BUFFERED_AMOUNT) {
|
|
156
206
|
if (this._writeCallback !== null) {
|
|
157
|
-
log.error(
|
|
207
|
+
log.error('consumer trying to write before we are ready for more data');
|
|
158
208
|
}
|
|
159
209
|
this._writeCallback = callback;
|
|
160
210
|
} else {
|
|
@@ -163,98 +213,86 @@ export class LibDataChannelTransport implements Transport {
|
|
|
163
213
|
},
|
|
164
214
|
});
|
|
165
215
|
|
|
166
|
-
duplex.pipe(this.
|
|
216
|
+
duplex.pipe(this._options.stream).pipe(duplex);
|
|
167
217
|
this._stream = duplex;
|
|
168
218
|
this._connected = true;
|
|
169
219
|
this.connected.emit();
|
|
170
220
|
};
|
|
171
221
|
|
|
222
|
+
this._channel.onclose = async (err) => {
|
|
223
|
+
log.info('channel.onclose', { err });
|
|
224
|
+
await this._close();
|
|
225
|
+
};
|
|
226
|
+
|
|
172
227
|
this._channel.onerror = async (err) => {
|
|
173
228
|
this.errors.raise(new Error('channel error: ' + err.toString()));
|
|
174
229
|
await this._close();
|
|
175
230
|
};
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
231
|
+
|
|
232
|
+
this._channel.onbufferedamountlow = () => {
|
|
233
|
+
const cb = this._writeCallback;
|
|
234
|
+
this._writeCallback = null;
|
|
235
|
+
cb?.();
|
|
179
236
|
};
|
|
237
|
+
|
|
180
238
|
this._channel.onmessage = (event) => {
|
|
181
239
|
let data = event.data;
|
|
182
240
|
if (data instanceof ArrayBuffer) {
|
|
183
241
|
data = Buffer.from(data);
|
|
184
242
|
}
|
|
185
|
-
this._stream.push(data);
|
|
186
|
-
};
|
|
187
243
|
|
|
188
|
-
|
|
189
|
-
const cb = this._writeCallback;
|
|
190
|
-
this._writeCallback = null;
|
|
191
|
-
cb?.();
|
|
244
|
+
this._stream.push(data);
|
|
192
245
|
};
|
|
193
246
|
}
|
|
194
247
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (this._closed) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
await this._disconnectStreams();
|
|
201
|
-
const peer = await this._peer;
|
|
202
|
-
try {
|
|
203
|
-
peer.close();
|
|
204
|
-
} catch (err: any) {
|
|
205
|
-
this.errors.raise(err);
|
|
206
|
-
}
|
|
207
|
-
this._closed = true;
|
|
208
|
-
this.closed.emit();
|
|
209
|
-
}
|
|
248
|
+
async onSignal(signal: Signal) {
|
|
249
|
+
invariant(this._peer, 'not open');
|
|
210
250
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
log.error('received offer but peer not in state new', { peer });
|
|
219
|
-
this.errors.raise(new Error('invalid signalling state: received offer when peer is not in state new'));
|
|
220
|
-
break;
|
|
221
|
-
}
|
|
222
|
-
try {
|
|
223
|
-
await peer.setRemoteDescription({ type: data.type, sdp: data.sdp });
|
|
224
|
-
const answer = await peer.createAnswer();
|
|
225
|
-
await peer.setLocalDescription(answer);
|
|
226
|
-
await this.params.sendSignal({ payload: { data: { type: answer.type, sdp: answer.sdp } } });
|
|
227
|
-
this._readyForCandidates.wake();
|
|
228
|
-
} catch (err) {
|
|
229
|
-
log.error("can't handle offer from signalling server", { err });
|
|
230
|
-
this.errors.raise(new Error('error handling offer'));
|
|
231
|
-
}
|
|
251
|
+
try {
|
|
252
|
+
const data = signal.payload.data;
|
|
253
|
+
switch (data.type) {
|
|
254
|
+
case 'offer': {
|
|
255
|
+
if (this._peer.connectionState !== 'new') {
|
|
256
|
+
log.error('received offer but peer not in state new', { peer: this._peer });
|
|
257
|
+
this.errors.raise(new Error('invalid signalling state: received offer when peer is not in state new'));
|
|
232
258
|
break;
|
|
233
259
|
}
|
|
234
260
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
|
|
261
|
+
try {
|
|
262
|
+
await this._peer.setRemoteDescription({ type: data.type, sdp: data.sdp });
|
|
263
|
+
const answer = await this._peer.createAnswer();
|
|
264
|
+
await this._peer.setLocalDescription(answer);
|
|
265
|
+
await this._options.sendSignal({ payload: { data: { type: answer.type, sdp: answer.sdp } } });
|
|
266
|
+
this._readyForCandidates.wake();
|
|
267
|
+
} catch (err) {
|
|
268
|
+
log.error('cannot handle offer from signalling server', { err });
|
|
269
|
+
this.errors.raise(new Error('error handling offer'));
|
|
270
|
+
}
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
244
273
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
await
|
|
248
|
-
|
|
274
|
+
case 'answer':
|
|
275
|
+
try {
|
|
276
|
+
await this._peer.setRemoteDescription({ type: data.type, sdp: data.sdp });
|
|
277
|
+
this._readyForCandidates.wake();
|
|
278
|
+
} catch (err) {
|
|
279
|
+
log.error('cannot handle answer from signalling server', { err });
|
|
280
|
+
this.errors.raise(new Error('error handling answer'));
|
|
281
|
+
}
|
|
282
|
+
break;
|
|
249
283
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
284
|
+
case 'candidate':
|
|
285
|
+
await this._readyForCandidates.wait();
|
|
286
|
+
await this._peer.addIceCandidate({ candidate: data.candidate.candidate });
|
|
287
|
+
break;
|
|
288
|
+
|
|
289
|
+
default:
|
|
290
|
+
log.error('unhandled signal type', { type: data.type, signal });
|
|
291
|
+
this.errors.raise(new Error(`unhandled signal type ${data.type}`));
|
|
292
|
+
}
|
|
293
|
+
} catch (err) {
|
|
294
|
+
log.catch(err);
|
|
295
|
+
}
|
|
258
296
|
}
|
|
259
297
|
|
|
260
298
|
async getDetails(): Promise<string> {
|
|
@@ -267,30 +305,8 @@ export class LibDataChannelTransport implements Transport {
|
|
|
267
305
|
if (rc.candidateType === 'relay') {
|
|
268
306
|
return `${rc.ip}:${rc.port} relay for ${rc.relatedAddress}:${rc.relatedPort}`;
|
|
269
307
|
}
|
|
270
|
-
return `${rc.ip}:${rc.port} ${rc.candidateType}`;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
async _getStats(): Promise<any> {
|
|
274
|
-
return this._peer.then(async (peer) => {
|
|
275
|
-
const stats = await peer.getStats();
|
|
276
|
-
|
|
277
|
-
const statsEntries = Array.from((stats as any).entries() as any[]);
|
|
278
|
-
const transport = statsEntries.filter((s) => s[1].type === 'transport')[0][1];
|
|
279
|
-
const candidatePair = statsEntries.filter((s: any) => s[0] === transport.selectedCandidatePairId);
|
|
280
|
-
let selectedCandidatePair: any;
|
|
281
|
-
let remoteCandidate: any;
|
|
282
|
-
if (candidatePair.length > 0) {
|
|
283
|
-
selectedCandidatePair = candidatePair[0][1];
|
|
284
|
-
remoteCandidate = statsEntries.filter((s: any) => s[0] === selectedCandidatePair.remoteCandidateId)[0][1];
|
|
285
|
-
}
|
|
286
308
|
|
|
287
|
-
|
|
288
|
-
transport,
|
|
289
|
-
selectedCandidatePair,
|
|
290
|
-
remoteCandidate,
|
|
291
|
-
raw: Object.fromEntries(stats as any),
|
|
292
|
-
};
|
|
293
|
-
});
|
|
309
|
+
return `${rc.ip}:${rc.port} ${rc.candidateType}`;
|
|
294
310
|
}
|
|
295
311
|
|
|
296
312
|
async getStats(): Promise<TransportStats> {
|
|
@@ -314,25 +330,31 @@ export class LibDataChannelTransport implements Transport {
|
|
|
314
330
|
};
|
|
315
331
|
}
|
|
316
332
|
|
|
317
|
-
async
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
333
|
+
async _getStats(): Promise<any> {
|
|
334
|
+
invariant(this._peer, 'not open');
|
|
335
|
+
const stats = await this._peer.getStats();
|
|
336
|
+
const statsEntries = Array.from((stats as any).entries() as any[]);
|
|
337
|
+
const transport = statsEntries.filter((s) => s[1].type === 'transport')[0][1];
|
|
338
|
+
const candidatePair = statsEntries.filter((s: any) => s[0] === transport.selectedCandidatePairId);
|
|
339
|
+
let selectedCandidatePair: any;
|
|
340
|
+
let remoteCandidate: any;
|
|
341
|
+
if (candidatePair.length > 0) {
|
|
342
|
+
selectedCandidatePair = candidatePair[0][1];
|
|
343
|
+
remoteCandidate = statsEntries.filter((s: any) => s[0] === selectedCandidatePair.remoteCandidateId)[0][1];
|
|
321
344
|
}
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
transport,
|
|
348
|
+
selectedCandidatePair,
|
|
349
|
+
remoteCandidate,
|
|
350
|
+
raw: Object.fromEntries(stats as any),
|
|
351
|
+
};
|
|
322
352
|
}
|
|
323
353
|
|
|
324
354
|
private async _disconnectStreams() {
|
|
325
|
-
this.
|
|
355
|
+
this._options.stream.unpipe?.(this._stream)?.unpipe?.(this._options.stream);
|
|
326
356
|
}
|
|
327
357
|
}
|
|
328
358
|
|
|
329
|
-
export const createLibDataChannelTransportFactory = (webrtcConfig?: any): TransportFactory => ({
|
|
330
|
-
createTransport: (params) =>
|
|
331
|
-
new LibDataChannelTransport({
|
|
332
|
-
...params,
|
|
333
|
-
webrtcConfig,
|
|
334
|
-
}),
|
|
335
|
-
});
|
|
336
|
-
|
|
337
359
|
// eslint-disable-next-line no-new-func
|
|
338
360
|
const importESM = Function('path', 'return import(path)');
|
|
@@ -14,7 +14,7 @@ import { MemoryTransport } from './memory-transport';
|
|
|
14
14
|
// Attempted to log "Ignoring unsupported ICE candidate.".
|
|
15
15
|
|
|
16
16
|
// TODO(burdon): Move to TestBuilder.
|
|
17
|
-
const createPair = () => {
|
|
17
|
+
const createPair = async () => {
|
|
18
18
|
const topic = PublicKey.random();
|
|
19
19
|
const peer1Id = PublicKey.random();
|
|
20
20
|
const peer2Id = PublicKey.random();
|
|
@@ -23,26 +23,29 @@ const createPair = () => {
|
|
|
23
23
|
const connection1 = new MemoryTransport({
|
|
24
24
|
stream: stream1,
|
|
25
25
|
sendSignal: async (signal) => {
|
|
26
|
-
await connection2.
|
|
26
|
+
await connection2.onSignal(signal);
|
|
27
27
|
},
|
|
28
28
|
initiator: true,
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
afterTest(() => connection1.
|
|
31
|
+
afterTest(() => connection1.close());
|
|
32
32
|
afterTest(() => connection1.errors.assertNoUnhandledErrors());
|
|
33
33
|
|
|
34
34
|
const stream2 = new TestStream();
|
|
35
35
|
const connection2 = new MemoryTransport({
|
|
36
36
|
stream: stream2,
|
|
37
37
|
sendSignal: async (signal) => {
|
|
38
|
-
await connection1.
|
|
38
|
+
await connection1.onSignal(signal);
|
|
39
39
|
},
|
|
40
40
|
initiator: false,
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
afterTest(() => connection2.
|
|
43
|
+
afterTest(() => connection2.close());
|
|
44
44
|
afterTest(() => connection2.errors.assertNoUnhandledErrors());
|
|
45
45
|
|
|
46
|
+
await connection1.open();
|
|
47
|
+
await connection2.open();
|
|
48
|
+
|
|
46
49
|
return {
|
|
47
50
|
connection1,
|
|
48
51
|
connection2,
|
|
@@ -56,14 +59,14 @@ const createPair = () => {
|
|
|
56
59
|
|
|
57
60
|
describe('MemoryTransport', () => {
|
|
58
61
|
test('establish connection and send data through with protocol', async () => {
|
|
59
|
-
const { stream1, stream2 } = createPair();
|
|
62
|
+
const { stream1, stream2 } = await createPair();
|
|
60
63
|
await TestStream.assertConnectivity(stream1, stream2);
|
|
61
64
|
});
|
|
62
65
|
|
|
63
66
|
test('10 pairs of peers connecting at the same time', async () => {
|
|
64
67
|
await Promise.all(
|
|
65
68
|
range(10).map(async () => {
|
|
66
|
-
const { stream1, stream2 } = createPair();
|
|
69
|
+
const { stream1, stream2 } = await createPair();
|
|
67
70
|
await TestStream.assertConnectivity(stream1, stream2);
|
|
68
71
|
}),
|
|
69
72
|
);
|