@dxos/network-manager 0.6.13 → 0.6.14-main.7bd9c89
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-GW3YM55A.mjs +14 -0
- package/dist/lib/browser/chunk-GW3YM55A.mjs.map +7 -0
- package/dist/lib/browser/{chunk-XYSYUN63.mjs → chunk-MKIVP7G3.mjs} +1249 -1065
- package/dist/lib/browser/chunk-MKIVP7G3.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +10 -19
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +22 -32
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/browser/transport/tcp/index.mjs +39 -0
- package/dist/lib/browser/transport/tcp/index.mjs.map +7 -0
- package/dist/lib/node/{chunk-4YAYC7WN.cjs → chunk-D6P7ACEM.cjs} +1262 -1205
- package/dist/lib/node/chunk-D6P7ACEM.cjs.map +7 -0
- package/dist/lib/node/index.cjs +27 -37
- package/dist/lib/node/index.cjs.map +2 -2
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +24 -34
- package/dist/lib/node/testing/index.cjs.map +3 -3
- package/dist/lib/node/transport/tcp/index.cjs +191 -0
- package/dist/lib/node/transport/tcp/index.cjs.map +7 -0
- package/dist/lib/node-esm/chunk-22DA2US6.mjs +4373 -0
- package/dist/lib/node-esm/chunk-22DA2US6.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +50 -0
- package/dist/lib/node-esm/index.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -0
- package/dist/lib/node-esm/testing/index.mjs +279 -0
- package/dist/lib/node-esm/testing/index.mjs.map +7 -0
- package/dist/lib/node-esm/transport/tcp/index.mjs +159 -0
- package/dist/lib/node-esm/transport/tcp/index.mjs.map +7 -0
- package/dist/types/src/network-manager.d.ts +2 -1
- package/dist/types/src/network-manager.d.ts.map +1 -1
- package/dist/types/src/signal/ice.d.ts.map +1 -1
- package/dist/types/src/signal/integration.node.test.d.ts +2 -0
- package/dist/types/src/signal/integration.node.test.d.ts.map +1 -0
- package/dist/types/src/signal/swarm-messenger.node.test.d.ts +2 -0
- package/dist/types/src/signal/swarm-messenger.node.test.d.ts.map +1 -0
- package/dist/types/src/swarm/connection.d.ts.map +1 -1
- package/dist/types/src/swarm/swarm.d.ts +1 -1
- package/dist/types/src/testing/test-builder.d.ts +2 -2
- package/dist/types/src/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/testing/test-wire-protocol.d.ts +1 -2
- package/dist/types/src/testing/test-wire-protocol.d.ts.map +1 -1
- package/dist/types/src/tests/basic-test-suite.d.ts.map +1 -1
- package/dist/types/src/tests/property-test-suite.d.ts.map +1 -1
- package/dist/types/src/tests/tcp-transport.node.test.d.ts +2 -0
- package/dist/types/src/tests/tcp-transport.node.test.d.ts.map +1 -0
- package/dist/types/src/tests/utils.d.ts.map +1 -1
- package/dist/types/src/transport/index.d.ts +1 -5
- package/dist/types/src/transport/index.d.ts.map +1 -1
- package/dist/types/src/transport/memory-transport.d.ts +2 -2
- package/dist/types/src/transport/memory-transport.d.ts.map +1 -1
- package/dist/types/src/transport/tcp/index.d.ts +2 -0
- package/dist/types/src/transport/tcp/index.d.ts.map +1 -0
- package/dist/types/src/transport/{tcp-transport.browser.d.ts → tcp/tcp-transport.browser.d.ts} +3 -3
- package/dist/types/src/transport/tcp/tcp-transport.browser.d.ts.map +1 -0
- package/dist/types/src/transport/{tcp-transport.d.ts → tcp/tcp-transport.d.ts} +3 -3
- package/dist/types/src/transport/tcp/tcp-transport.d.ts.map +1 -0
- package/dist/types/src/transport/transport.d.ts +7 -6
- package/dist/types/src/transport/transport.d.ts.map +1 -1
- package/dist/types/src/transport/webrtc/index.d.ts +4 -0
- package/dist/types/src/transport/webrtc/index.d.ts.map +1 -0
- package/dist/types/src/transport/webrtc/rtc-connection-factory.d.ts +14 -0
- package/dist/types/src/transport/webrtc/rtc-connection-factory.d.ts.map +1 -0
- package/dist/types/src/transport/webrtc/rtc-peer-connection.d.ts +68 -0
- package/dist/types/src/transport/webrtc/rtc-peer-connection.d.ts.map +1 -0
- package/dist/types/src/transport/webrtc/rtc-transport-channel.d.ts +33 -0
- package/dist/types/src/transport/webrtc/rtc-transport-channel.d.ts.map +1 -0
- package/dist/types/src/transport/webrtc/rtc-transport-channel.test.d.ts +2 -0
- package/dist/types/src/transport/webrtc/rtc-transport-channel.test.d.ts.map +1 -0
- package/dist/types/src/transport/webrtc/rtc-transport-factory.d.ts +4 -0
- package/dist/types/src/transport/webrtc/rtc-transport-factory.d.ts.map +1 -0
- package/dist/types/src/transport/{simplepeer-transport-proxy.d.ts → webrtc/rtc-transport-proxy.d.ts} +10 -12
- package/dist/types/src/transport/webrtc/rtc-transport-proxy.d.ts.map +1 -0
- package/dist/types/src/transport/webrtc/rtc-transport-proxy.test.d.ts +2 -0
- package/dist/types/src/transport/webrtc/rtc-transport-proxy.test.d.ts.map +1 -0
- package/dist/types/src/transport/{simplepeer-transport-service.d.ts → webrtc/rtc-transport-service.d.ts} +9 -7
- package/dist/types/src/transport/webrtc/rtc-transport-service.d.ts.map +1 -0
- package/dist/types/src/transport/webrtc/rtc-transport-stats.d.ts +4 -0
- package/dist/types/src/transport/webrtc/rtc-transport-stats.d.ts.map +1 -0
- package/dist/types/src/transport/webrtc/rtc-transport.test.d.ts +2 -0
- package/dist/types/src/transport/webrtc/rtc-transport.test.d.ts.map +1 -0
- package/dist/types/src/transport/webrtc/test-utils.d.ts +5 -0
- package/dist/types/src/transport/webrtc/test-utils.d.ts.map +1 -0
- package/dist/types/src/transport/webrtc/utils.d.ts +3 -0
- package/dist/types/src/transport/webrtc/utils.d.ts.map +1 -0
- package/package.json +55 -30
- package/src/network-manager.ts +5 -13
- package/src/signal/ice.test.ts +1 -3
- package/src/signal/ice.ts +6 -1
- package/src/signal/{integration.test.ts → integration.node.test.ts} +9 -15
- package/src/signal/{swarm-messenger.test.ts → swarm-messenger.node.test.ts} +13 -23
- package/src/swarm/connection-limiter.test.ts +3 -6
- package/src/swarm/connection.test.ts +63 -38
- package/src/swarm/connection.ts +5 -5
- package/src/swarm/swarm.test.ts +10 -12
- package/src/swarm/swarm.ts +1 -1
- package/src/testing/test-builder.ts +13 -29
- package/src/testing/test-wire-protocol.ts +1 -4
- package/src/tests/basic-test-suite.ts +34 -33
- package/src/tests/memory-transport.test.ts +40 -42
- package/src/tests/property-test-suite.ts +21 -22
- package/src/tests/tcp-transport.node.test.ts +65 -0
- package/src/tests/utils.ts +3 -2
- package/src/tests/webrtc-transport.test.ts +9 -9
- package/src/transport/index.ts +1 -5
- package/src/transport/memory-transport.ts +2 -0
- package/src/transport/tcp/index.ts +5 -0
- package/src/transport/{tcp-transport.browser.ts → tcp/tcp-transport.browser.ts} +7 -3
- package/src/transport/{tcp-transport.ts → tcp/tcp-transport.ts} +3 -1
- package/src/transport/transport.ts +8 -7
- package/src/transport/webrtc/index.ts +7 -0
- package/src/transport/webrtc/rtc-connection-factory.ts +82 -0
- package/src/transport/webrtc/rtc-peer-connection.ts +472 -0
- package/src/transport/webrtc/rtc-transport-channel.test.ts +176 -0
- package/src/transport/webrtc/rtc-transport-channel.ts +195 -0
- package/src/transport/webrtc/rtc-transport-factory.ts +28 -0
- package/src/transport/webrtc/rtc-transport-proxy.test.ts +413 -0
- package/src/transport/webrtc/rtc-transport-proxy.ts +264 -0
- package/src/transport/webrtc/rtc-transport-service.ts +192 -0
- package/src/transport/webrtc/rtc-transport-stats.ts +67 -0
- package/src/transport/webrtc/rtc-transport.test.ts +198 -0
- package/src/transport/webrtc/test-utils.ts +22 -0
- package/src/transport/webrtc/utils.ts +36 -0
- package/src/typings.d.ts +8 -2
- package/dist/lib/browser/chunk-XYSYUN63.mjs.map +0 -7
- package/dist/lib/node/chunk-4YAYC7WN.cjs.map +0 -7
- package/dist/types/src/signal/integration.test.d.ts +0 -2
- package/dist/types/src/signal/integration.test.d.ts.map +0 -1
- package/dist/types/src/signal/swarm-messenger.test.d.ts +0 -2
- package/dist/types/src/signal/swarm-messenger.test.d.ts.map +0 -1
- package/dist/types/src/tests/tcp-transport.test.d.ts +0 -2
- package/dist/types/src/tests/tcp-transport.test.d.ts.map +0 -1
- package/dist/types/src/transport/libdatachannel-transport.d.ts +0 -42
- package/dist/types/src/transport/libdatachannel-transport.d.ts.map +0 -1
- package/dist/types/src/transport/libdatachannel-transport.test.d.ts +0 -2
- package/dist/types/src/transport/libdatachannel-transport.test.d.ts.map +0 -1
- package/dist/types/src/transport/memory-transport.test.d.ts +0 -2
- package/dist/types/src/transport/memory-transport.test.d.ts.map +0 -1
- package/dist/types/src/transport/simplepeer-simple-peer.d.ts +0 -2
- package/dist/types/src/transport/simplepeer-simple-peer.d.ts.map +0 -1
- package/dist/types/src/transport/simplepeer-transport-proxy-test.d.ts +0 -2
- package/dist/types/src/transport/simplepeer-transport-proxy-test.d.ts.map +0 -1
- package/dist/types/src/transport/simplepeer-transport-proxy.d.ts.map +0 -1
- package/dist/types/src/transport/simplepeer-transport-service.d.ts.map +0 -1
- package/dist/types/src/transport/simplepeer-transport.d.ts +0 -36
- package/dist/types/src/transport/simplepeer-transport.d.ts.map +0 -1
- package/dist/types/src/transport/simplepeer-transport.test.d.ts +0 -2
- package/dist/types/src/transport/simplepeer-transport.test.d.ts.map +0 -1
- package/dist/types/src/transport/tcp-transport.browser.d.ts.map +0 -1
- package/dist/types/src/transport/tcp-transport.d.ts.map +0 -1
- package/dist/types/src/transport/webrtc.d.ts +0 -6
- package/dist/types/src/transport/webrtc.d.ts.map +0 -1
- package/src/globals.d.ts +0 -7
- package/src/tests/tcp-transport.test.ts +0 -67
- package/src/transport/libdatachannel-transport.test.ts +0 -100
- package/src/transport/libdatachannel-transport.ts +0 -376
- package/src/transport/memory-transport.test.ts +0 -74
- package/src/transport/simplepeer-simple-peer.ts +0 -26
- package/src/transport/simplepeer-transport-proxy-test.ts +0 -181
- package/src/transport/simplepeer-transport-proxy.ts +0 -246
- package/src/transport/simplepeer-transport-service.ts +0 -160
- package/src/transport/simplepeer-transport.test.ts +0 -61
- package/src/transport/simplepeer-transport.ts +0 -250
- package/src/transport/webrtc.ts +0 -15
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2022 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Duplex } from 'node:stream';
|
|
6
|
+
|
|
7
|
+
import { Stream } from '@dxos/codec-protobuf';
|
|
8
|
+
import { invariant } from '@dxos/invariant';
|
|
9
|
+
import { PublicKey } from '@dxos/keys';
|
|
10
|
+
import { log } from '@dxos/log';
|
|
11
|
+
import {
|
|
12
|
+
type BridgeService,
|
|
13
|
+
type ConnectionRequest,
|
|
14
|
+
type SignalRequest,
|
|
15
|
+
type DataRequest,
|
|
16
|
+
type BridgeEvent,
|
|
17
|
+
ConnectionState,
|
|
18
|
+
type CloseRequest,
|
|
19
|
+
type DetailsRequest,
|
|
20
|
+
type DetailsResponse,
|
|
21
|
+
type StatsRequest,
|
|
22
|
+
type StatsResponse,
|
|
23
|
+
} from '@dxos/protocols/proto/dxos/mesh/bridge';
|
|
24
|
+
import { ComplexMap } from '@dxos/util';
|
|
25
|
+
|
|
26
|
+
import { createRtcTransportFactory } from './rtc-transport-factory';
|
|
27
|
+
import { type IceProvider } from '../../signal';
|
|
28
|
+
import { type Transport, type TransportFactory } from '../transport';
|
|
29
|
+
|
|
30
|
+
type TransportState = {
|
|
31
|
+
proxyId: PublicKey;
|
|
32
|
+
transport: Transport;
|
|
33
|
+
connectorStream: Duplex;
|
|
34
|
+
writeProcessedCallbacks: (() => void)[];
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export class RtcTransportService implements BridgeService {
|
|
38
|
+
private readonly _openTransports = new ComplexMap<PublicKey, TransportState>(PublicKey.hash);
|
|
39
|
+
|
|
40
|
+
constructor(
|
|
41
|
+
webrtcConfig?: RTCConfiguration,
|
|
42
|
+
iceProvider?: IceProvider,
|
|
43
|
+
private readonly _transportFactory: TransportFactory = createRtcTransportFactory(webrtcConfig, iceProvider),
|
|
44
|
+
) {}
|
|
45
|
+
|
|
46
|
+
public hasOpenTransports() {
|
|
47
|
+
return this._openTransports.size > 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
open(request: ConnectionRequest): Stream<BridgeEvent> {
|
|
51
|
+
const existingTransport = this._openTransports.get(request.proxyId);
|
|
52
|
+
if (existingTransport) {
|
|
53
|
+
log.error('requesting a new transport bridge for an existing proxy');
|
|
54
|
+
void this._safeCloseTransport(existingTransport);
|
|
55
|
+
this._openTransports.delete(request.proxyId);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return new Stream<BridgeEvent>(({ ready, next, close }) => {
|
|
59
|
+
const pushNewState = createStateUpdater(next);
|
|
60
|
+
|
|
61
|
+
const transportStream: Duplex = new Duplex({
|
|
62
|
+
read: () => {
|
|
63
|
+
const callbacks = [...transportState.writeProcessedCallbacks];
|
|
64
|
+
transportState.writeProcessedCallbacks.length = 0;
|
|
65
|
+
callbacks.forEach((cb) => cb());
|
|
66
|
+
},
|
|
67
|
+
write: function (chunk, _, callback) {
|
|
68
|
+
next({ data: { payload: chunk } });
|
|
69
|
+
callback();
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const transport = this._transportFactory.createTransport({
|
|
74
|
+
initiator: request.initiator,
|
|
75
|
+
topic: request.topic,
|
|
76
|
+
ownPeerKey: request.ownPeerKey,
|
|
77
|
+
remotePeerKey: request.remotePeerKey,
|
|
78
|
+
stream: transportStream,
|
|
79
|
+
sendSignal: async (signal) => {
|
|
80
|
+
next({ signal: { payload: signal } });
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const transportState: TransportState = {
|
|
85
|
+
proxyId: request.proxyId,
|
|
86
|
+
transport,
|
|
87
|
+
connectorStream: transportStream,
|
|
88
|
+
writeProcessedCallbacks: [],
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
pushNewState(ConnectionState.CONNECTING);
|
|
92
|
+
|
|
93
|
+
transport.connected.on(() => pushNewState(ConnectionState.CONNECTED));
|
|
94
|
+
|
|
95
|
+
transport.errors.handle(async (err) => {
|
|
96
|
+
pushNewState(ConnectionState.CLOSED, err);
|
|
97
|
+
void this._safeCloseTransport(transportState);
|
|
98
|
+
close(err);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
transport.closed.on(async () => {
|
|
102
|
+
pushNewState(ConnectionState.CLOSED);
|
|
103
|
+
void this._safeCloseTransport(transportState);
|
|
104
|
+
close();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
this._openTransports.set(request.proxyId, transportState);
|
|
108
|
+
|
|
109
|
+
transport.open().catch(async (err) => {
|
|
110
|
+
pushNewState(ConnectionState.CLOSED, err);
|
|
111
|
+
void this._safeCloseTransport(transportState);
|
|
112
|
+
close(err);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
ready();
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async sendSignal({ proxyId, signal }: SignalRequest): Promise<void> {
|
|
120
|
+
const transport = this._openTransports.get(proxyId);
|
|
121
|
+
invariant(transport);
|
|
122
|
+
|
|
123
|
+
await transport.transport.onSignal(signal);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async getDetails({ proxyId }: DetailsRequest): Promise<DetailsResponse> {
|
|
127
|
+
const transport = this._openTransports.get(proxyId);
|
|
128
|
+
invariant(transport);
|
|
129
|
+
|
|
130
|
+
return { details: await transport.transport.getDetails() };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async getStats({ proxyId }: StatsRequest): Promise<StatsResponse> {
|
|
134
|
+
const transport = this._openTransports.get(proxyId);
|
|
135
|
+
invariant(transport);
|
|
136
|
+
|
|
137
|
+
return { stats: await transport.transport.getStats() };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async sendData({ proxyId, payload }: DataRequest): Promise<void> {
|
|
141
|
+
const transport = this._openTransports.get(proxyId);
|
|
142
|
+
invariant(transport);
|
|
143
|
+
|
|
144
|
+
const bufferHasSpace = transport.connectorStream.push(payload);
|
|
145
|
+
if (!bufferHasSpace) {
|
|
146
|
+
await new Promise<void>((resolve) => {
|
|
147
|
+
transport.writeProcessedCallbacks.push(resolve);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async close({ proxyId }: CloseRequest) {
|
|
153
|
+
const transport = this._openTransports.get(proxyId);
|
|
154
|
+
if (!transport) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this._openTransports.delete(proxyId);
|
|
159
|
+
await this._safeCloseTransport(transport);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private async _safeCloseTransport(transport: TransportState) {
|
|
163
|
+
if (this._openTransports.get(transport.proxyId) === transport) {
|
|
164
|
+
this._openTransports.delete(transport.proxyId);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
transport.writeProcessedCallbacks.forEach((cb) => cb());
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
await transport.transport.close();
|
|
171
|
+
} catch (error: any) {
|
|
172
|
+
log.warn('transport close error', { message: error?.message });
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
transport.connectorStream.end();
|
|
176
|
+
} catch (error: any) {
|
|
177
|
+
log.warn('connectorStream close error', { message: error?.message });
|
|
178
|
+
}
|
|
179
|
+
log('closed');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const createStateUpdater = (next: (event: BridgeEvent) => void) => {
|
|
184
|
+
return (state: ConnectionState, err?: Error) => {
|
|
185
|
+
next({
|
|
186
|
+
connection: {
|
|
187
|
+
state,
|
|
188
|
+
...(err ? { error: err.message } : undefined),
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import type { TransportStats } from '../transport';
|
|
6
|
+
|
|
7
|
+
export const describeSelectedRemoteCandidate = async (connection?: RTCPeerConnection): Promise<string> => {
|
|
8
|
+
const stats = connection && (await getRtcConnectionStats(connection));
|
|
9
|
+
const rc = stats?.remoteCandidate;
|
|
10
|
+
if (!rc) {
|
|
11
|
+
return 'unavailable';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (rc.candidateType === 'relay') {
|
|
15
|
+
return `${rc.ip}:${rc.port} relay for ${rc.relatedAddress}:${rc.relatedPort}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return `${rc.ip}:${rc.port} ${rc.candidateType}`;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const createRtcTransportStats = async (
|
|
22
|
+
connection: RTCPeerConnection | undefined,
|
|
23
|
+
topic: string,
|
|
24
|
+
): Promise<TransportStats> => {
|
|
25
|
+
const stats = connection && (await getRtcConnectionStats(connection, topic));
|
|
26
|
+
if (!stats) {
|
|
27
|
+
return {
|
|
28
|
+
bytesSent: 0,
|
|
29
|
+
bytesReceived: 0,
|
|
30
|
+
packetsSent: 0,
|
|
31
|
+
packetsReceived: 0,
|
|
32
|
+
rawStats: {},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
bytesSent: stats.dataChannel?.bytesSent,
|
|
38
|
+
bytesReceived: stats.dataChannel?.bytesReceived,
|
|
39
|
+
packetsSent: 0,
|
|
40
|
+
packetsReceived: 0,
|
|
41
|
+
rawStats: stats.raw,
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const getRtcConnectionStats = async (connection: RTCPeerConnection, channelTopic?: string): Promise<any> => {
|
|
46
|
+
const stats = await connection.getStats();
|
|
47
|
+
|
|
48
|
+
const statsEntries: [string, any][] = Array.from((stats as any).entries());
|
|
49
|
+
const transport = statsEntries.find(([_, entry]) => entry.type === 'transport')?.[1];
|
|
50
|
+
|
|
51
|
+
const selectedCandidatePair =
|
|
52
|
+
transport && statsEntries.find(([entryId]) => entryId === transport.selectedCandidatePairId)?.[1];
|
|
53
|
+
const remoteCandidate =
|
|
54
|
+
selectedCandidatePair && statsEntries.find(([entryId]) => entryId === selectedCandidatePair.remoteCandidateId)?.[1];
|
|
55
|
+
|
|
56
|
+
const dataChannel =
|
|
57
|
+
channelTopic &&
|
|
58
|
+
statsEntries.find(([_, entry]) => entry.type === 'data-channel' && entry.label === channelTopic)?.[1];
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
transport,
|
|
62
|
+
selectedCandidatePair,
|
|
63
|
+
dataChannel,
|
|
64
|
+
remoteCandidate,
|
|
65
|
+
raw: Object.fromEntries(stats as any),
|
|
66
|
+
};
|
|
67
|
+
};
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2020 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { onTestFinished, describe, expect, test } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import { sleep, TestStream } from '@dxos/async';
|
|
8
|
+
import { PublicKey } from '@dxos/keys';
|
|
9
|
+
|
|
10
|
+
import { getRtcConnectionFactory } from './rtc-connection-factory';
|
|
11
|
+
import { RtcPeerConnection } from './rtc-peer-connection';
|
|
12
|
+
import { type RtcTransportChannel } from './rtc-transport-channel';
|
|
13
|
+
import { chooseInitiatorPeer } from './utils';
|
|
14
|
+
import { type TransportOptions } from '../transport';
|
|
15
|
+
|
|
16
|
+
const connectionFactory = getRtcConnectionFactory();
|
|
17
|
+
|
|
18
|
+
describe('RtcTransport', () => {
|
|
19
|
+
test('channel open and close', async () => {
|
|
20
|
+
const peer = await createConnection();
|
|
21
|
+
const channel = createChannel(peer);
|
|
22
|
+
|
|
23
|
+
await channel.open();
|
|
24
|
+
const wait = channel.closed.waitForCount(1);
|
|
25
|
+
await channel.close();
|
|
26
|
+
await wait;
|
|
27
|
+
|
|
28
|
+
expect(peer.connection.transportChannelCount).to.eq(0);
|
|
29
|
+
await expect.poll(() => channel.isRtcChannelCreationInProgress).toBeFalsy();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('establish connection and send data through with protocol', async () => {
|
|
33
|
+
const { initiator, another } = await createConnectedPeers();
|
|
34
|
+
const initiatorChannel = createChannel(initiator);
|
|
35
|
+
const anotherChannel = createChannel(another);
|
|
36
|
+
|
|
37
|
+
await initiatorChannel.open();
|
|
38
|
+
await anotherChannel.open();
|
|
39
|
+
|
|
40
|
+
await TestStream.assertConnectivity(initiator.stream, another.stream, { timeout: 1500 });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('initiator opens a channel before another peer', async () => {
|
|
44
|
+
const { initiator, another } = await createConnectedPeers();
|
|
45
|
+
const initiatorChannel = createChannel(initiator);
|
|
46
|
+
const anotherChannel = createChannel(another);
|
|
47
|
+
|
|
48
|
+
await initiatorChannel.open();
|
|
49
|
+
await sleep(50);
|
|
50
|
+
await anotherChannel.open();
|
|
51
|
+
|
|
52
|
+
await TestStream.assertConnectivity(initiator.stream, another.stream, { timeout: 1500 });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('initiator opens a channel after another peer', async () => {
|
|
56
|
+
const { initiator, another } = await createConnectedPeers();
|
|
57
|
+
const initiatorChannel = createChannel(initiator);
|
|
58
|
+
const anotherChannel = createChannel(another);
|
|
59
|
+
|
|
60
|
+
await anotherChannel.open();
|
|
61
|
+
await sleep(50);
|
|
62
|
+
await initiatorChannel.open();
|
|
63
|
+
|
|
64
|
+
await TestStream.assertConnectivity(initiator.stream, another.stream, { timeout: 1500 });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('signal delivery idempotency', async () => {
|
|
68
|
+
const { initiator, another } = await createConnectedPeers({ duplicateSignals: true });
|
|
69
|
+
const initiatorChannel = createChannel(initiator);
|
|
70
|
+
const anotherChannel = createChannel(another);
|
|
71
|
+
|
|
72
|
+
await anotherChannel.open();
|
|
73
|
+
await sleep(50);
|
|
74
|
+
await initiatorChannel.open();
|
|
75
|
+
|
|
76
|
+
await TestStream.assertConnectivity(initiator.stream, another.stream, { timeout: 1500 });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('initiator closes before connectivity is established', async () => {
|
|
80
|
+
const { initiator, another } = await createConnectedPeers();
|
|
81
|
+
const initiatorChannel = createChannel(initiator);
|
|
82
|
+
const anotherChannel = createChannel(another);
|
|
83
|
+
|
|
84
|
+
await initiatorChannel.open();
|
|
85
|
+
await sleep(20);
|
|
86
|
+
await anotherChannel.open();
|
|
87
|
+
await initiatorChannel.close();
|
|
88
|
+
|
|
89
|
+
initiatorChannel.errors.assertNoUnhandledErrors();
|
|
90
|
+
anotherChannel.errors.assertNoUnhandledErrors();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('initiator opens a channel after another peer closed it', async () => {
|
|
94
|
+
const { initiator, another } = await createConnectedPeers({ duplicateSignals: true });
|
|
95
|
+
const initiatorChannel = createChannel(initiator);
|
|
96
|
+
const anotherChannel = createChannel(another);
|
|
97
|
+
|
|
98
|
+
await anotherChannel.open();
|
|
99
|
+
await sleep(20);
|
|
100
|
+
await anotherChannel.close();
|
|
101
|
+
await initiatorChannel.open();
|
|
102
|
+
await initiatorChannel.close();
|
|
103
|
+
|
|
104
|
+
await expect.poll(() => anotherChannel.isRtcChannelCreationInProgress).toBeFalsy();
|
|
105
|
+
initiatorChannel.errors.assertNoUnhandledErrors();
|
|
106
|
+
anotherChannel.errors.assertNoUnhandledErrors();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const createConnectedPeers = async (options?: { duplicateSignals?: boolean }) => {
|
|
110
|
+
const peer1Signal = createSignalSender(options);
|
|
111
|
+
const peer2Signal = createSignalSender(options);
|
|
112
|
+
|
|
113
|
+
const peer1 = await createConnection({ sendSignal: peer1Signal.sendSignal }, peer2Signal.onChannelCreated);
|
|
114
|
+
const peer2 = await createConnection(
|
|
115
|
+
{
|
|
116
|
+
ownPeerKey: peer1.options.remotePeerKey,
|
|
117
|
+
remotePeerKey: peer1.options.ownPeerKey,
|
|
118
|
+
topic: peer1.options.topic,
|
|
119
|
+
sendSignal: peer2Signal.sendSignal,
|
|
120
|
+
},
|
|
121
|
+
peer1Signal.onChannelCreated,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return chooseInitiator(peer1, peer2);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const createSignalSender = (options?: { duplicateSignals?: boolean }) => {
|
|
128
|
+
const deliverSignal = (channel: RtcTransportChannel, signal: any) => {
|
|
129
|
+
void channel.onSignal(signal);
|
|
130
|
+
if (options?.duplicateSignals) {
|
|
131
|
+
void channel.onSignal(signal);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
let remoteChannel: RtcTransportChannel | undefined;
|
|
135
|
+
const signalBuffer: any[] = [];
|
|
136
|
+
const sendSignal = async (signal: any) => {
|
|
137
|
+
await sleep(10);
|
|
138
|
+
if (remoteChannel) {
|
|
139
|
+
deliverSignal(remoteChannel, signal);
|
|
140
|
+
} else {
|
|
141
|
+
signalBuffer.push(signal);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const onChannelCreated = (channel: RtcTransportChannel): void => {
|
|
145
|
+
remoteChannel = channel;
|
|
146
|
+
for (const signal of signalBuffer) {
|
|
147
|
+
deliverSignal(channel, signal);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
return { sendSignal, onChannelCreated };
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const createConnection = async (
|
|
154
|
+
optionOverrides?: Partial<TransportOptions>,
|
|
155
|
+
onChannelCreated: (channel: RtcTransportChannel) => void = () => {},
|
|
156
|
+
): Promise<TestSetup> => {
|
|
157
|
+
const stream = new TestStream();
|
|
158
|
+
const options: TransportOptions = {
|
|
159
|
+
initiator: false,
|
|
160
|
+
stream,
|
|
161
|
+
sendSignal: async () => {},
|
|
162
|
+
remotePeerKey: PublicKey.random().toHex(),
|
|
163
|
+
ownPeerKey: PublicKey.random().toHex(),
|
|
164
|
+
topic: PublicKey.random().toHex(),
|
|
165
|
+
...optionOverrides,
|
|
166
|
+
};
|
|
167
|
+
const connection = new RtcPeerConnection(connectionFactory, options);
|
|
168
|
+
return { options, connection, stream, onChannelOpen: onChannelCreated };
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const chooseInitiator = (peer1: TestSetup, peer2: TestSetup) => {
|
|
172
|
+
const [initiator, another] =
|
|
173
|
+
chooseInitiatorPeer(peer1.options.ownPeerKey, peer2.options.ownPeerKey) === peer1.options.ownPeerKey
|
|
174
|
+
? [peer1, peer2]
|
|
175
|
+
: [peer2, peer1];
|
|
176
|
+
return { initiator, another };
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const createChannel = (args: TestSetup) => {
|
|
180
|
+
const channel = args.connection.createTransportChannel(args.options);
|
|
181
|
+
const originalOpen = channel.open.bind(channel);
|
|
182
|
+
(channel as any).open = async () => {
|
|
183
|
+
await originalOpen();
|
|
184
|
+
args.onChannelOpen(channel);
|
|
185
|
+
};
|
|
186
|
+
onTestFinished(async () => {
|
|
187
|
+
await channel.close();
|
|
188
|
+
});
|
|
189
|
+
return channel;
|
|
190
|
+
};
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
type TestSetup = {
|
|
194
|
+
stream: TestStream;
|
|
195
|
+
connection: RtcPeerConnection;
|
|
196
|
+
options: TransportOptions;
|
|
197
|
+
onChannelOpen: (channel: RtcTransportChannel) => void;
|
|
198
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { expect } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import { sleep } from '@dxos/async';
|
|
8
|
+
|
|
9
|
+
import { type Transport } from '../transport';
|
|
10
|
+
|
|
11
|
+
export const handleChannelErrors = (channel: Transport) => {
|
|
12
|
+
let handled = false;
|
|
13
|
+
channel.errors.handle(() => (handled = true));
|
|
14
|
+
return {
|
|
15
|
+
expectErrorRaised: async () => {
|
|
16
|
+
if (!handled) {
|
|
17
|
+
await sleep(5);
|
|
18
|
+
}
|
|
19
|
+
expect(handled).toBeTruthy();
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
export const chooseInitiatorPeer = (peer1Key: string, peer2Key: string) => (peer1Key < peer2Key ? peer1Key : peer2Key);
|
|
6
|
+
|
|
7
|
+
export const areSdpEqual = (sdp1: string, sdp2: string) => {
|
|
8
|
+
const sdp1Lines = deduplicatedSdpLines(sdp1);
|
|
9
|
+
const sdp2Lines = deduplicatedSdpLines(sdp2);
|
|
10
|
+
if (sdp1Lines.length !== sdp2Lines.length) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
return sdp1Lines.every((line, idx) => line === sdp2Lines[idx]);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* For some reason libdatachannel duplicates some attributes after an sdp is set.
|
|
18
|
+
* So the following test might fail:
|
|
19
|
+
* conn.setRemoteDescription(sdp);
|
|
20
|
+
* expect(conn.remoteDescription.sdp).toEqual(sdp);
|
|
21
|
+
*/
|
|
22
|
+
const deduplicatedSdpLines = (sdp: string) => {
|
|
23
|
+
const deduplicatedLines: string[] = [];
|
|
24
|
+
const seenLines: string[] = [];
|
|
25
|
+
for (const line of sdp.split('\r\n')) {
|
|
26
|
+
if (line.startsWith('m')) {
|
|
27
|
+
seenLines.length = 0;
|
|
28
|
+
}
|
|
29
|
+
if (seenLines.includes(line)) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
seenLines.push(line);
|
|
33
|
+
deduplicatedLines.push(line);
|
|
34
|
+
}
|
|
35
|
+
return deduplicatedLines;
|
|
36
|
+
};
|
package/src/typings.d.ts
CHANGED
|
@@ -3,5 +3,11 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
declare module '@koush/wrtc';
|
|
6
|
-
|
|
7
|
-
declare module '
|
|
6
|
+
|
|
7
|
+
declare module '#node-datachannel' {
|
|
8
|
+
export * from 'node-datachannel';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare module '#node-datachannel/polyfill' {
|
|
12
|
+
export * from 'node-datachannel/polyfill';
|
|
13
|
+
}
|