@dxos/network-manager 0.6.12-staging.e11e696 → 0.6.12
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-YOKKEU6T.mjs → chunk-XYSYUN63.mjs} +998 -1192
- package/dist/lib/browser/chunk-XYSYUN63.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +19 -10
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +27 -18
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/node/{chunk-7ZWQLO5T.cjs → chunk-4YAYC7WN.cjs} +1166 -1233
- package/dist/lib/node/chunk-4YAYC7WN.cjs.map +7 -0
- package/dist/lib/node/index.cjs +37 -27
- package/dist/lib/node/index.cjs.map +2 -2
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +29 -20
- package/dist/lib/node/testing/index.cjs.map +3 -3
- package/dist/types/src/signal/integration.test.d.ts +2 -0
- package/dist/types/src/signal/integration.test.d.ts.map +1 -0
- package/dist/types/src/signal/swarm-messenger.test.d.ts +2 -0
- package/dist/types/src/signal/swarm-messenger.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/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.test.d.ts +2 -0
- package/dist/types/src/tests/tcp-transport.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 +5 -1
- package/dist/types/src/transport/index.d.ts.map +1 -1
- package/dist/types/src/transport/libdatachannel-transport.d.ts +42 -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/memory-transport.d.ts +2 -2
- package/dist/types/src/transport/memory-transport.d.ts.map +1 -1
- package/dist/types/src/transport/memory-transport.test.d.ts +2 -0
- package/dist/types/src/transport/memory-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/rtc-transport-proxy.d.ts → simplepeer-transport-proxy.d.ts} +12 -10
- package/dist/types/src/transport/simplepeer-transport-proxy.d.ts.map +1 -0
- package/dist/types/src/transport/{webrtc/rtc-transport-service.d.ts → simplepeer-transport-service.d.ts} +7 -9
- package/dist/types/src/transport/simplepeer-transport-service.d.ts.map +1 -0
- package/dist/types/src/transport/simplepeer-transport.d.ts +36 -0
- 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/{tcp/tcp-transport.browser.d.ts → tcp-transport.browser.d.ts} +3 -3
- package/dist/types/src/transport/tcp-transport.browser.d.ts.map +1 -0
- package/dist/types/src/transport/{tcp/tcp-transport.d.ts → tcp-transport.d.ts} +3 -3
- package/dist/types/src/transport/tcp-transport.d.ts.map +1 -0
- package/dist/types/src/transport/transport.d.ts +6 -7
- package/dist/types/src/transport/transport.d.ts.map +1 -1
- package/dist/types/src/transport/webrtc.d.ts +6 -0
- package/dist/types/src/transport/webrtc.d.ts.map +1 -0
- package/package.json +30 -53
- package/src/globals.d.ts +7 -0
- package/src/signal/ice.test.ts +3 -1
- package/src/signal/{integration.node.test.ts → integration.test.ts} +15 -9
- package/src/signal/{swarm-messenger.node.test.ts → swarm-messenger.test.ts} +23 -13
- package/src/swarm/connection-limiter.test.ts +6 -3
- package/src/swarm/connection.test.ts +38 -63
- package/src/swarm/connection.ts +5 -5
- package/src/swarm/swarm.test.ts +11 -9
- package/src/swarm/swarm.ts +1 -1
- package/src/testing/test-builder.ts +28 -12
- package/src/tests/basic-test-suite.ts +33 -34
- package/src/tests/memory-transport.test.ts +42 -40
- package/src/tests/property-test-suite.ts +22 -21
- package/src/tests/tcp-transport.test.ts +67 -0
- package/src/tests/utils.ts +2 -3
- package/src/tests/webrtc-transport.test.ts +9 -9
- package/src/transport/index.ts +5 -1
- package/src/transport/libdatachannel-transport.test.ts +100 -0
- package/src/transport/libdatachannel-transport.ts +376 -0
- package/src/transport/memory-transport.test.ts +74 -0
- package/src/transport/memory-transport.ts +0 -2
- package/src/transport/simplepeer-simple-peer.ts +26 -0
- package/src/transport/simplepeer-transport-proxy-test.ts +181 -0
- package/src/transport/simplepeer-transport-proxy.ts +246 -0
- package/src/transport/simplepeer-transport-service.ts +160 -0
- package/src/transport/simplepeer-transport.test.ts +61 -0
- package/src/transport/simplepeer-transport.ts +250 -0
- package/src/transport/{tcp/tcp-transport.browser.ts → tcp-transport.browser.ts} +3 -7
- package/src/transport/{tcp/tcp-transport.ts → tcp-transport.ts} +1 -3
- package/src/transport/transport.ts +7 -8
- package/src/transport/webrtc.ts +15 -0
- package/src/typings.d.ts +2 -8
- package/dist/lib/browser/chunk-GW3YM55A.mjs +0 -14
- package/dist/lib/browser/chunk-GW3YM55A.mjs.map +0 -7
- package/dist/lib/browser/chunk-YOKKEU6T.mjs.map +0 -7
- package/dist/lib/browser/transport/tcp/index.mjs +0 -39
- package/dist/lib/browser/transport/tcp/index.mjs.map +0 -7
- package/dist/lib/node/chunk-7ZWQLO5T.cjs.map +0 -7
- package/dist/lib/node/transport/tcp/index.cjs +0 -191
- package/dist/lib/node/transport/tcp/index.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-4VO725JT.mjs +0 -4383
- package/dist/lib/node-esm/chunk-4VO725JT.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -50
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/testing/index.mjs +0 -280
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- package/dist/lib/node-esm/transport/tcp/index.mjs +0 -159
- package/dist/lib/node-esm/transport/tcp/index.mjs.map +0 -7
- package/dist/types/src/signal/integration.node.test.d.ts +0 -2
- package/dist/types/src/signal/integration.node.test.d.ts.map +0 -1
- package/dist/types/src/signal/swarm-messenger.node.test.d.ts +0 -2
- package/dist/types/src/signal/swarm-messenger.node.test.d.ts.map +0 -1
- package/dist/types/src/tests/tcp-transport.node.test.d.ts +0 -2
- package/dist/types/src/tests/tcp-transport.node.test.d.ts.map +0 -1
- package/dist/types/src/transport/tcp/index.d.ts +0 -2
- package/dist/types/src/transport/tcp/index.d.ts.map +0 -1
- package/dist/types/src/transport/tcp/tcp-transport.browser.d.ts.map +0 -1
- package/dist/types/src/transport/tcp/tcp-transport.d.ts.map +0 -1
- package/dist/types/src/transport/webrtc/index.d.ts +0 -4
- package/dist/types/src/transport/webrtc/index.d.ts.map +0 -1
- package/dist/types/src/transport/webrtc/rtc-connection-factory.d.ts +0 -14
- package/dist/types/src/transport/webrtc/rtc-connection-factory.d.ts.map +0 -1
- package/dist/types/src/transport/webrtc/rtc-peer-connection.d.ts +0 -68
- package/dist/types/src/transport/webrtc/rtc-peer-connection.d.ts.map +0 -1
- package/dist/types/src/transport/webrtc/rtc-transport-channel.d.ts +0 -33
- package/dist/types/src/transport/webrtc/rtc-transport-channel.d.ts.map +0 -1
- package/dist/types/src/transport/webrtc/rtc-transport-channel.test.d.ts +0 -2
- package/dist/types/src/transport/webrtc/rtc-transport-channel.test.d.ts.map +0 -1
- package/dist/types/src/transport/webrtc/rtc-transport-factory.d.ts +0 -4
- package/dist/types/src/transport/webrtc/rtc-transport-factory.d.ts.map +0 -1
- package/dist/types/src/transport/webrtc/rtc-transport-proxy.d.ts.map +0 -1
- package/dist/types/src/transport/webrtc/rtc-transport-proxy.test.d.ts +0 -2
- package/dist/types/src/transport/webrtc/rtc-transport-proxy.test.d.ts.map +0 -1
- package/dist/types/src/transport/webrtc/rtc-transport-service.d.ts.map +0 -1
- package/dist/types/src/transport/webrtc/rtc-transport-stats.d.ts +0 -4
- package/dist/types/src/transport/webrtc/rtc-transport-stats.d.ts.map +0 -1
- package/dist/types/src/transport/webrtc/rtc-transport.test.d.ts +0 -2
- package/dist/types/src/transport/webrtc/rtc-transport.test.d.ts.map +0 -1
- package/dist/types/src/transport/webrtc/test-utils.d.ts +0 -5
- package/dist/types/src/transport/webrtc/test-utils.d.ts.map +0 -1
- package/dist/types/src/transport/webrtc/utils.d.ts +0 -3
- package/dist/types/src/transport/webrtc/utils.d.ts.map +0 -1
- package/src/tests/tcp-transport.node.test.ts +0 -65
- package/src/transport/tcp/index.ts +0 -5
- package/src/transport/webrtc/index.ts +0 -7
- package/src/transport/webrtc/rtc-connection-factory.ts +0 -82
- package/src/transport/webrtc/rtc-peer-connection.ts +0 -472
- package/src/transport/webrtc/rtc-transport-channel.test.ts +0 -176
- package/src/transport/webrtc/rtc-transport-channel.ts +0 -195
- package/src/transport/webrtc/rtc-transport-factory.ts +0 -28
- package/src/transport/webrtc/rtc-transport-proxy.test.ts +0 -413
- package/src/transport/webrtc/rtc-transport-proxy.ts +0 -264
- package/src/transport/webrtc/rtc-transport-service.ts +0 -192
- package/src/transport/webrtc/rtc-transport-stats.ts +0 -67
- package/src/transport/webrtc/rtc-transport.test.ts +0 -198
- package/src/transport/webrtc/test-utils.ts +0 -22
- package/src/transport/webrtc/utils.ts +0 -36
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2022 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Writable } from 'node:stream';
|
|
6
|
+
|
|
7
|
+
import { Event, scheduleTask } from '@dxos/async';
|
|
8
|
+
import { type Stream } from '@dxos/codec-protobuf';
|
|
9
|
+
import { Context } from '@dxos/context';
|
|
10
|
+
import { ErrorStream } from '@dxos/debug';
|
|
11
|
+
import { invariant } from '@dxos/invariant';
|
|
12
|
+
import { PublicKey } from '@dxos/keys';
|
|
13
|
+
import { log } from '@dxos/log';
|
|
14
|
+
import {
|
|
15
|
+
ConnectionResetError,
|
|
16
|
+
TimeoutError,
|
|
17
|
+
ProtocolError,
|
|
18
|
+
ConnectivityError,
|
|
19
|
+
UnknownProtocolError,
|
|
20
|
+
} from '@dxos/protocols';
|
|
21
|
+
import { ConnectionState, type BridgeEvent, type BridgeService } from '@dxos/protocols/proto/dxos/mesh/bridge';
|
|
22
|
+
import { type Signal } from '@dxos/protocols/proto/dxos/mesh/swarm';
|
|
23
|
+
import { arrayToBuffer } from '@dxos/util';
|
|
24
|
+
|
|
25
|
+
import { type Transport, type TransportFactory, type TransportOptions, type TransportStats } from './transport';
|
|
26
|
+
|
|
27
|
+
const RPC_TIMEOUT = 10_000;
|
|
28
|
+
const RESP_MIN_THRESHOLD = 500;
|
|
29
|
+
const TIMEOUT_THRESHOLD = 10;
|
|
30
|
+
|
|
31
|
+
export type SimplePeerTransportProxyOptions = TransportOptions & {
|
|
32
|
+
bridgeService: BridgeService;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export class SimplePeerTransportProxy implements Transport {
|
|
36
|
+
private readonly _proxyId = PublicKey.random();
|
|
37
|
+
private readonly _ctx = new Context();
|
|
38
|
+
private _timeoutCount = 0;
|
|
39
|
+
|
|
40
|
+
readonly closed = new Event();
|
|
41
|
+
readonly connected = new Event();
|
|
42
|
+
readonly errors = new ErrorStream();
|
|
43
|
+
|
|
44
|
+
private _closed = false;
|
|
45
|
+
private _serviceStream!: Stream<BridgeEvent>;
|
|
46
|
+
|
|
47
|
+
constructor(private readonly _options: SimplePeerTransportProxyOptions) {}
|
|
48
|
+
|
|
49
|
+
get isOpen() {
|
|
50
|
+
// TODO(burdon): Open state?
|
|
51
|
+
return !this._closed;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async open() {
|
|
55
|
+
this._serviceStream = this._options.bridgeService.open(
|
|
56
|
+
{
|
|
57
|
+
proxyId: this._proxyId,
|
|
58
|
+
initiator: this._options.initiator,
|
|
59
|
+
},
|
|
60
|
+
{ timeout: RPC_TIMEOUT },
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
this._serviceStream.waitUntilReady().then(
|
|
64
|
+
() => {
|
|
65
|
+
this._serviceStream.subscribe(async (event: BridgeEvent) => {
|
|
66
|
+
log('SimplePeerTransportProxy: event', event);
|
|
67
|
+
if (event.connection) {
|
|
68
|
+
await this._handleConnection(event.connection);
|
|
69
|
+
} else if (event.data) {
|
|
70
|
+
this._handleData(event.data);
|
|
71
|
+
} else if (event.signal) {
|
|
72
|
+
await this._handleSignal(event.signal);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const proxyStream = new Writable({
|
|
77
|
+
write: (chunk, _, callback) => {
|
|
78
|
+
const then = performance.now();
|
|
79
|
+
this._options.bridgeService
|
|
80
|
+
.sendData(
|
|
81
|
+
{
|
|
82
|
+
proxyId: this._proxyId,
|
|
83
|
+
payload: chunk,
|
|
84
|
+
},
|
|
85
|
+
{ timeout: RPC_TIMEOUT },
|
|
86
|
+
)
|
|
87
|
+
.then(
|
|
88
|
+
() => {
|
|
89
|
+
if (performance.now() - then > RESP_MIN_THRESHOLD) {
|
|
90
|
+
log('slow response, delaying callback');
|
|
91
|
+
scheduleTask(this._ctx, () => callback(), RESP_MIN_THRESHOLD);
|
|
92
|
+
} else {
|
|
93
|
+
callback();
|
|
94
|
+
}
|
|
95
|
+
this._timeoutCount = 0;
|
|
96
|
+
},
|
|
97
|
+
(err: any) => {
|
|
98
|
+
if (err instanceof TimeoutError || err.constructor.name === 'TimeoutError') {
|
|
99
|
+
if (this._timeoutCount++ > TIMEOUT_THRESHOLD) {
|
|
100
|
+
throw new TimeoutError(`too many timeouts (${this._timeoutCount} > ${TIMEOUT_THRESHOLD}`);
|
|
101
|
+
} else {
|
|
102
|
+
log('timeout error, but still invoking callback');
|
|
103
|
+
callback();
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
log.catch(err);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
proxyStream.on('error', (err) => {
|
|
114
|
+
log('proxystream error', { err });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
this._options.stream.pipe(proxyStream);
|
|
118
|
+
},
|
|
119
|
+
(error) => log.catch(error),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async close() {
|
|
124
|
+
await this._ctx.dispose();
|
|
125
|
+
if (this._closed) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
await this._serviceStream.close();
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
await this._options.bridgeService.close({ proxyId: this._proxyId }, { timeout: RPC_TIMEOUT });
|
|
133
|
+
} catch (err: any) {
|
|
134
|
+
log.catch(err);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.closed.emit();
|
|
138
|
+
this._closed = true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async onSignal(signal: Signal) {
|
|
142
|
+
this._options.bridgeService
|
|
143
|
+
.sendSignal(
|
|
144
|
+
{
|
|
145
|
+
proxyId: this._proxyId,
|
|
146
|
+
signal,
|
|
147
|
+
},
|
|
148
|
+
{ timeout: RPC_TIMEOUT },
|
|
149
|
+
)
|
|
150
|
+
.catch((err) => this.errors.raise(decodeError(err)));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private async _handleConnection(connectionEvent: BridgeEvent.ConnectionEvent): Promise<void> {
|
|
154
|
+
if (connectionEvent.error) {
|
|
155
|
+
this.errors.raise(decodeError(connectionEvent.error));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
switch (connectionEvent.state) {
|
|
159
|
+
case ConnectionState.CONNECTED: {
|
|
160
|
+
this.connected.emit();
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
case ConnectionState.CLOSED: {
|
|
164
|
+
await this.close();
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private _handleData(dataEvent: BridgeEvent.DataEvent) {
|
|
171
|
+
// NOTE: This must be a Buffer otherwise hypercore-protocol breaks.
|
|
172
|
+
this._options.stream.write(arrayToBuffer(dataEvent.payload));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private async _handleSignal(signalEvent: BridgeEvent.SignalEvent) {
|
|
176
|
+
await this._options.sendSignal(signalEvent.payload);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async getDetails(): Promise<string> {
|
|
180
|
+
return (await this._options.bridgeService.getDetails({ proxyId: this._proxyId }, { timeout: RPC_TIMEOUT })).details;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async getStats(): Promise<TransportStats> {
|
|
184
|
+
return (await this._options.bridgeService.getStats({ proxyId: this._proxyId }, { timeout: RPC_TIMEOUT }))
|
|
185
|
+
.stats as TransportStats;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Called when underlying proxy service becomes unavailable.
|
|
190
|
+
*/
|
|
191
|
+
// TODO(burdon): Option on close method.
|
|
192
|
+
forceClose() {
|
|
193
|
+
void this._serviceStream.close();
|
|
194
|
+
this.closed.emit();
|
|
195
|
+
this._closed = true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// TODO(burdon): Why is this named Proxy?
|
|
200
|
+
export class SimplePeerTransportProxyFactory implements TransportFactory {
|
|
201
|
+
private _bridgeService: BridgeService | undefined;
|
|
202
|
+
private _connections = new Set<SimplePeerTransportProxy>();
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Sets the current BridgeService to be used to open connections.
|
|
206
|
+
* Calling this method will close any existing connections.
|
|
207
|
+
*/
|
|
208
|
+
setBridgeService(bridgeService: BridgeService | undefined): this {
|
|
209
|
+
this._bridgeService = bridgeService;
|
|
210
|
+
for (const connection of this._connections) {
|
|
211
|
+
connection.forceClose();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return this;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
createTransport(options: TransportOptions): Transport {
|
|
218
|
+
invariant(this._bridgeService, 'SimplePeerTransportProxyFactory is not ready to open connections');
|
|
219
|
+
const transport = new SimplePeerTransportProxy({
|
|
220
|
+
...options,
|
|
221
|
+
bridgeService: this._bridgeService,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
this._connections.add(transport);
|
|
225
|
+
transport.closed.on(() => this._connections.delete(transport));
|
|
226
|
+
return transport;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// TODO(nf): fix so Errors crossing RPC boundary preserve class
|
|
231
|
+
const decodeError = (err: Error | string) => {
|
|
232
|
+
const message = typeof err === 'string' ? err : err.message;
|
|
233
|
+
if (message.includes('CONNECTION_RESET')) {
|
|
234
|
+
return new ConnectionResetError(message);
|
|
235
|
+
} else if (message.includes('TIMEOUT')) {
|
|
236
|
+
return new TimeoutError(message);
|
|
237
|
+
} else if (message.includes('PROTOCOL_ERROR')) {
|
|
238
|
+
return new ProtocolError(message);
|
|
239
|
+
} else if (message.includes('CONNECTIVITY_ERROR')) {
|
|
240
|
+
return new ConnectivityError(message);
|
|
241
|
+
} else if (message.includes('UNKNOWN_PROTOCOL_ERROR')) {
|
|
242
|
+
return new UnknownProtocolError(message);
|
|
243
|
+
} else {
|
|
244
|
+
return typeof err === 'string' ? new Error(err) : err;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
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 { SimplePeerTransport } from './simplepeer-transport';
|
|
27
|
+
import { type IceProvider } from '../signal';
|
|
28
|
+
|
|
29
|
+
type TransportState = {
|
|
30
|
+
transport: SimplePeerTransport;
|
|
31
|
+
stream: Duplex;
|
|
32
|
+
writeCallbacks: (() => void)[];
|
|
33
|
+
state: 'OPEN' | 'CLOSED';
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export class SimplePeerTransportService implements BridgeService {
|
|
37
|
+
private readonly transports = new ComplexMap<PublicKey, TransportState>(PublicKey.hash);
|
|
38
|
+
|
|
39
|
+
constructor(
|
|
40
|
+
private readonly _webrtcConfig?: RTCConfiguration,
|
|
41
|
+
private readonly _iceProvider?: IceProvider,
|
|
42
|
+
) {}
|
|
43
|
+
|
|
44
|
+
open(request: ConnectionRequest): Stream<BridgeEvent> {
|
|
45
|
+
const rpcStream: Stream<BridgeEvent> = new Stream(({ ready, next, close }) => {
|
|
46
|
+
const duplex: Duplex = new Duplex({
|
|
47
|
+
read: () => {
|
|
48
|
+
const callbacks = [...transportState.writeCallbacks];
|
|
49
|
+
transportState.writeCallbacks.length = 0;
|
|
50
|
+
for (const cb of callbacks) {
|
|
51
|
+
cb();
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
write: function (chunk, _, callback) {
|
|
55
|
+
next({ data: { payload: chunk } });
|
|
56
|
+
callback();
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const transport = new SimplePeerTransport({
|
|
61
|
+
initiator: request.initiator,
|
|
62
|
+
stream: duplex,
|
|
63
|
+
webrtcConfig: this._webrtcConfig,
|
|
64
|
+
sendSignal: async (signal) => {
|
|
65
|
+
next({
|
|
66
|
+
signal: { payload: signal },
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
iceProvider: this._iceProvider,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// TODO(burdon): Async.
|
|
73
|
+
void transport.open();
|
|
74
|
+
|
|
75
|
+
next({
|
|
76
|
+
connection: {
|
|
77
|
+
state: ConnectionState.CONNECTING,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
transport.connected.on(() => {
|
|
82
|
+
next({
|
|
83
|
+
connection: {
|
|
84
|
+
state: ConnectionState.CONNECTED,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
transport.errors.handle((err) => {
|
|
90
|
+
next({
|
|
91
|
+
connection: {
|
|
92
|
+
state: ConnectionState.CLOSED,
|
|
93
|
+
error: err.toString(),
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
close(err);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
transport.closed.on(() => {
|
|
100
|
+
next({
|
|
101
|
+
connection: {
|
|
102
|
+
state: ConnectionState.CLOSED,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
close();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const transportState: TransportState = {
|
|
109
|
+
transport,
|
|
110
|
+
stream: duplex,
|
|
111
|
+
writeCallbacks: [],
|
|
112
|
+
state: 'OPEN',
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
ready();
|
|
116
|
+
|
|
117
|
+
this.transports.set(request.proxyId, transportState);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return rpcStream;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async sendSignal({ proxyId, signal }: SignalRequest): Promise<void> {
|
|
124
|
+
invariant(this.transports.has(proxyId));
|
|
125
|
+
await this.transports.get(proxyId)!.transport.onSignal(signal);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async getDetails({ proxyId }: DetailsRequest): Promise<DetailsResponse> {
|
|
129
|
+
invariant(this.transports.has(proxyId));
|
|
130
|
+
return { details: await this.transports.get(proxyId)!.transport.getDetails() };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async getStats({ proxyId }: StatsRequest): Promise<StatsResponse> {
|
|
134
|
+
invariant(this.transports.has(proxyId));
|
|
135
|
+
return { stats: await this.transports.get(proxyId)!.transport.getStats() };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async sendData({ proxyId, payload }: DataRequest): Promise<void> {
|
|
139
|
+
if (this.transports.get(proxyId)?.state !== 'OPEN') {
|
|
140
|
+
log.debug('transport is closed');
|
|
141
|
+
}
|
|
142
|
+
invariant(this.transports.has(proxyId));
|
|
143
|
+
const state = this.transports.get(proxyId)!;
|
|
144
|
+
const bufferHasSpace = state.stream.push(payload);
|
|
145
|
+
if (!bufferHasSpace) {
|
|
146
|
+
await new Promise<void>((resolve) => {
|
|
147
|
+
state.writeCallbacks.push(resolve);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async close({ proxyId }: CloseRequest) {
|
|
153
|
+
await this.transports.get(proxyId)?.transport.close();
|
|
154
|
+
await this.transports.get(proxyId)?.stream.end();
|
|
155
|
+
if (this.transports.get(proxyId)) {
|
|
156
|
+
this.transports.get(proxyId)!.state = 'CLOSED';
|
|
157
|
+
}
|
|
158
|
+
log('Closed.');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2020 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Duplex } from 'stream';
|
|
6
|
+
|
|
7
|
+
import { sleep, TestStream } from '@dxos/async';
|
|
8
|
+
import { afterTest, describe, test } from '@dxos/test';
|
|
9
|
+
|
|
10
|
+
import { SimplePeerTransport } from './simplepeer-transport';
|
|
11
|
+
|
|
12
|
+
describe('SimplePeerTransport', () => {
|
|
13
|
+
// This doesn't clean up correctly and crashes with SIGSEGV / SIGABRT at the end. Probably an issue with wrtc package.
|
|
14
|
+
test('open and close', async () => {
|
|
15
|
+
const connection = new SimplePeerTransport({
|
|
16
|
+
initiator: true,
|
|
17
|
+
stream: new Duplex(),
|
|
18
|
+
sendSignal: async () => {},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
await connection.open();
|
|
22
|
+
const wait = connection.closed.waitForCount(1);
|
|
23
|
+
await connection.close();
|
|
24
|
+
await wait;
|
|
25
|
+
})
|
|
26
|
+
.timeout(1_000)
|
|
27
|
+
.retries(3);
|
|
28
|
+
|
|
29
|
+
test('establish connection and send data through with protocol', async () => {
|
|
30
|
+
const stream1 = new TestStream();
|
|
31
|
+
const connection1 = new SimplePeerTransport({
|
|
32
|
+
initiator: true,
|
|
33
|
+
stream: stream1,
|
|
34
|
+
sendSignal: async (signal) => {
|
|
35
|
+
await sleep(10);
|
|
36
|
+
await connection2.onSignal(signal);
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
afterTest(() => connection1.close());
|
|
40
|
+
afterTest(() => connection1.errors.assertNoUnhandledErrors());
|
|
41
|
+
|
|
42
|
+
const stream2 = new TestStream();
|
|
43
|
+
const connection2 = new SimplePeerTransport({
|
|
44
|
+
initiator: false,
|
|
45
|
+
stream: stream2,
|
|
46
|
+
sendSignal: async (signal) => {
|
|
47
|
+
await sleep(10);
|
|
48
|
+
await connection1.onSignal(signal);
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
afterTest(() => connection2.close());
|
|
52
|
+
afterTest(() => connection2.errors.assertNoUnhandledErrors());
|
|
53
|
+
|
|
54
|
+
await connection1.open();
|
|
55
|
+
await connection2.open();
|
|
56
|
+
|
|
57
|
+
await TestStream.assertConnectivity(stream1, stream2);
|
|
58
|
+
})
|
|
59
|
+
.timeout(2_000)
|
|
60
|
+
.retries(3);
|
|
61
|
+
});
|