@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
|
@@ -8,7 +8,7 @@ import { log } from '@dxos/log';
|
|
|
8
8
|
import { describe, test } from '@dxos/test';
|
|
9
9
|
|
|
10
10
|
import { TestWireProtocol } from '../testing/test-wire-protocol';
|
|
11
|
-
import {
|
|
11
|
+
import { createSimplePeerTransportFactory } from '../transport';
|
|
12
12
|
import { Connection } from './connection';
|
|
13
13
|
|
|
14
14
|
describe('Connection', () => {
|
|
@@ -37,7 +37,8 @@ describe('Connection', () => {
|
|
|
37
37
|
remotePeerId: peerId2,
|
|
38
38
|
topic,
|
|
39
39
|
}),
|
|
40
|
-
|
|
40
|
+
// TODO(nf): configure?
|
|
41
|
+
createSimplePeerTransportFactory(),
|
|
41
42
|
);
|
|
42
43
|
|
|
43
44
|
const protocol2 = new TestWireProtocol(peerId2);
|
|
@@ -58,11 +59,15 @@ describe('Connection', () => {
|
|
|
58
59
|
},
|
|
59
60
|
},
|
|
60
61
|
protocol2.factory({ initiator: false, localPeerId: peerId2, remotePeerId: peerId1, topic }),
|
|
61
|
-
|
|
62
|
+
// TODO(nf): configure?
|
|
63
|
+
createSimplePeerTransportFactory(),
|
|
62
64
|
);
|
|
63
65
|
|
|
66
|
+
connection2.initiate();
|
|
64
67
|
await connection2.openConnection();
|
|
65
68
|
await sleep(100);
|
|
69
|
+
|
|
70
|
+
connection1.initiate();
|
|
66
71
|
await connection1.openConnection();
|
|
67
72
|
await Promise.all([
|
|
68
73
|
protocol1.testConnection(peerId2, 'test message 1'),
|
package/src/swarm/connection.ts
CHANGED
|
@@ -43,6 +43,11 @@ interface ConnectionCallbacks {
|
|
|
43
43
|
* State machine for each connection.
|
|
44
44
|
*/
|
|
45
45
|
export enum ConnectionState {
|
|
46
|
+
/**
|
|
47
|
+
* Connection is created, but not yet passed through the connection limiter.
|
|
48
|
+
*/
|
|
49
|
+
CREATED = 'CREATED',
|
|
50
|
+
|
|
46
51
|
/**
|
|
47
52
|
* Initial state. Connection is registered but no attempt to connect to the remote peer has been performed.
|
|
48
53
|
* Might mean that we are waiting for the answer signal from the remote peer.
|
|
@@ -77,7 +82,7 @@ export enum ConnectionState {
|
|
|
77
82
|
export class Connection {
|
|
78
83
|
private readonly _ctx = new Context();
|
|
79
84
|
|
|
80
|
-
private _state: ConnectionState = ConnectionState.
|
|
85
|
+
private _state: ConnectionState = ConnectionState.CREATED;
|
|
81
86
|
private _transport: Transport | undefined;
|
|
82
87
|
closeReason?: string;
|
|
83
88
|
|
|
@@ -144,7 +149,7 @@ export class Connection {
|
|
|
144
149
|
this._changeState(ConnectionState.CONNECTING);
|
|
145
150
|
|
|
146
151
|
// TODO(dmaretskyi): Initialize only after the transport has established connection.
|
|
147
|
-
this._protocol.
|
|
152
|
+
this._protocol.open().catch((err) => {
|
|
148
153
|
this.errors.raise(err);
|
|
149
154
|
});
|
|
150
155
|
|
|
@@ -168,13 +173,21 @@ export class Connection {
|
|
|
168
173
|
|
|
169
174
|
this._transport.closed.once(() => {
|
|
170
175
|
this._transport = undefined;
|
|
171
|
-
|
|
176
|
+
log('abort triggered by transport close');
|
|
177
|
+
this.abort().catch((err) => this.errors.raise(err));
|
|
172
178
|
});
|
|
173
179
|
|
|
174
180
|
this._transport.errors.handle((err) => {
|
|
181
|
+
log('transport error:', { err });
|
|
182
|
+
// TODO(nf): collect a list of errors?
|
|
175
183
|
if (!this.closeReason) {
|
|
176
184
|
this.closeReason = err?.message;
|
|
177
185
|
}
|
|
186
|
+
if (err?.message.includes('OperationError')) {
|
|
187
|
+
log('aborting due to transport OperationError');
|
|
188
|
+
this.abort().catch((err) => this.errors.raise(err));
|
|
189
|
+
}
|
|
190
|
+
|
|
178
191
|
if (this._state !== ConnectionState.CLOSED && this._state !== ConnectionState.CLOSING) {
|
|
179
192
|
this.errors.raise(err);
|
|
180
193
|
}
|
|
@@ -190,6 +203,41 @@ export class Connection {
|
|
|
190
203
|
log.trace('dxos.mesh.connection.open-connection', trace.end({ id: this._instanceId }));
|
|
191
204
|
}
|
|
192
205
|
|
|
206
|
+
@synchronized
|
|
207
|
+
// TODO(nf): make the caller responsible for recording the reason and determining flow control.
|
|
208
|
+
async abort(err?: Error) {
|
|
209
|
+
log('aborting...', { err });
|
|
210
|
+
if (!this.closeReason) {
|
|
211
|
+
this.closeReason = err?.message;
|
|
212
|
+
}
|
|
213
|
+
if (this._state === ConnectionState.CLOSED) {
|
|
214
|
+
log('abort ignored: already closed', this.closeReason);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// TODO(nf): different state?
|
|
218
|
+
this._changeState(ConnectionState.CLOSING);
|
|
219
|
+
|
|
220
|
+
await this._ctx.dispose();
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
// Forcefully close the stream flushing any unsent data packets.
|
|
224
|
+
log('aborting protocol... ', { proto: this._protocol });
|
|
225
|
+
await this._protocol.abort();
|
|
226
|
+
} catch (err: any) {
|
|
227
|
+
log.catch(err);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
// After the transport is closed streams are disconnected.
|
|
232
|
+
await this._transport?.destroy();
|
|
233
|
+
} catch (err: any) {
|
|
234
|
+
log.catch(err);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
this._changeState(ConnectionState.CLOSED);
|
|
238
|
+
this._callbacks?.onClosed?.(err);
|
|
239
|
+
}
|
|
240
|
+
|
|
193
241
|
@synchronized
|
|
194
242
|
async close(err?: Error) {
|
|
195
243
|
if (!this.closeReason) {
|
|
@@ -206,7 +254,7 @@ export class Connection {
|
|
|
206
254
|
|
|
207
255
|
try {
|
|
208
256
|
// Gracefully close the stream flushing any unsent data packets.
|
|
209
|
-
await this._protocol.
|
|
257
|
+
await this._protocol.close();
|
|
210
258
|
} catch (err: any) {
|
|
211
259
|
log.catch(err);
|
|
212
260
|
}
|
|
@@ -255,7 +303,7 @@ export class Connection {
|
|
|
255
303
|
}
|
|
256
304
|
|
|
257
305
|
// If signal fails treat connection as failed
|
|
258
|
-
log.warn('
|
|
306
|
+
log.warn('signal failed', { err });
|
|
259
307
|
await this.close();
|
|
260
308
|
}
|
|
261
309
|
}
|
|
@@ -279,7 +327,7 @@ export class Connection {
|
|
|
279
327
|
continue;
|
|
280
328
|
}
|
|
281
329
|
|
|
282
|
-
if (
|
|
330
|
+
if ([ConnectionState.CREATED, ConnectionState.INITIAL].includes(this.state)) {
|
|
283
331
|
log('buffered signal', { peerId: this.ownId, remoteId: this.remoteId, msg: msg.data });
|
|
284
332
|
this._incomingSignalBuffer.push(signal);
|
|
285
333
|
} else {
|
|
@@ -290,6 +338,10 @@ export class Connection {
|
|
|
290
338
|
}
|
|
291
339
|
}
|
|
292
340
|
|
|
341
|
+
initiate() {
|
|
342
|
+
this._changeState(ConnectionState.INITIAL);
|
|
343
|
+
}
|
|
344
|
+
|
|
293
345
|
private _changeState(state: ConnectionState): void {
|
|
294
346
|
log('stateChanged', { from: this._state, too: state, peerId: this.ownId });
|
|
295
347
|
invariant(state !== this._state, 'Already in this state.');
|
package/src/swarm/peer.ts
CHANGED
|
@@ -113,6 +113,7 @@ export class Peer {
|
|
|
113
113
|
// Peer with the highest Id closes its connection, and accepts remote peer's offer.
|
|
114
114
|
if (remoteId.toHex() < this.localPeerId.toHex()) {
|
|
115
115
|
// TODO(burdon): Too verbose.
|
|
116
|
+
// TODO(nf): gets stuck when remote connection is aborted (i.e. closed tab)
|
|
116
117
|
log('close local connection', {
|
|
117
118
|
localPeerId: this.id,
|
|
118
119
|
topic: this.topic,
|
|
@@ -138,6 +139,8 @@ export class Peer {
|
|
|
138
139
|
|
|
139
140
|
try {
|
|
140
141
|
await this._connectionLimiter.connecting(message.sessionId);
|
|
142
|
+
connection.initiate();
|
|
143
|
+
|
|
141
144
|
await connection.openConnection();
|
|
142
145
|
} catch (err: any) {
|
|
143
146
|
if (!(err instanceof CancelledError)) {
|
|
@@ -168,6 +171,7 @@ export class Peer {
|
|
|
168
171
|
|
|
169
172
|
try {
|
|
170
173
|
await this._connectionLimiter.connecting(sessionId);
|
|
174
|
+
connection.initiate();
|
|
171
175
|
|
|
172
176
|
const answer = await this._signalMessaging.offer({
|
|
173
177
|
author: this.localPeerId,
|
package/src/swarm/swarm.test.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { ComplexSet } from '@dxos/util';
|
|
|
12
12
|
|
|
13
13
|
import { TestWireProtocol } from '../testing/test-wire-protocol';
|
|
14
14
|
import { FullyConnectedTopology } from '../topology';
|
|
15
|
-
import {
|
|
15
|
+
import { createSimplePeerTransportFactory } from '../transport';
|
|
16
16
|
import { ConnectionState } from './connection';
|
|
17
17
|
import { ConnectionLimiter } from './connection-limiter';
|
|
18
18
|
import { Swarm } from './swarm';
|
|
@@ -48,7 +48,8 @@ describe('Swarm', () => {
|
|
|
48
48
|
new FullyConnectedTopology(),
|
|
49
49
|
protocol.factory,
|
|
50
50
|
new Messenger({ signalManager }),
|
|
51
|
-
|
|
51
|
+
// TODO(nf): configure?
|
|
52
|
+
createSimplePeerTransportFactory(),
|
|
52
53
|
undefined,
|
|
53
54
|
connectionLimiter,
|
|
54
55
|
initiationDelay,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { PublicKey } from '@dxos/keys';
|
|
6
|
+
import { log } from '@dxos/log';
|
|
6
7
|
import {
|
|
7
8
|
MemorySignalManager,
|
|
8
9
|
MemorySignalManagerContext,
|
|
@@ -20,9 +21,11 @@ import { FullyConnectedTopology } from '../topology';
|
|
|
20
21
|
import {
|
|
21
22
|
MemoryTransportFactory,
|
|
22
23
|
TransportFactory,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
SimplePeerTransportProxyFactory,
|
|
25
|
+
SimplePeerTransportService,
|
|
26
|
+
createSimplePeerTransportFactory,
|
|
27
|
+
createLibDataChannelTransportFactory,
|
|
28
|
+
TransportKind,
|
|
26
29
|
} from '../transport';
|
|
27
30
|
import { TestWireProtocol, type TestTeleportExtensionFactory } from './test-wire-protocol';
|
|
28
31
|
|
|
@@ -35,6 +38,7 @@ export const TEST_SIGNAL_HOSTS: Runtime.Services.Signal[] = [
|
|
|
35
38
|
export type TestBuilderOptions = {
|
|
36
39
|
signalHosts?: Runtime.Services.Signal[];
|
|
37
40
|
bridge?: boolean;
|
|
41
|
+
transport?: TransportKind;
|
|
38
42
|
};
|
|
39
43
|
|
|
40
44
|
/**
|
|
@@ -54,7 +58,7 @@ export class TestBuilder {
|
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
createPeer(peerId: PublicKey = PublicKey.random()) {
|
|
57
|
-
return new TestPeer(this, peerId);
|
|
61
|
+
return new TestPeer(this, peerId, this.options.transport);
|
|
58
62
|
}
|
|
59
63
|
}
|
|
60
64
|
|
|
@@ -77,49 +81,76 @@ export class TestPeer {
|
|
|
77
81
|
private _proxy?: ProtoRpcPeer<any>;
|
|
78
82
|
private _service?: ProtoRpcPeer<any>;
|
|
79
83
|
|
|
80
|
-
constructor(
|
|
84
|
+
constructor(
|
|
85
|
+
private readonly testBuilder: TestBuilder,
|
|
86
|
+
public readonly peerId: PublicKey,
|
|
87
|
+
public readonly transport?: TransportKind,
|
|
88
|
+
) {
|
|
81
89
|
this._signalManager = this.testBuilder.createSignalManager();
|
|
82
|
-
|
|
90
|
+
if (!transport) {
|
|
91
|
+
if (this.testBuilder.options.signalHosts) {
|
|
92
|
+
transport = TransportKind.SIMPLE_PEER;
|
|
93
|
+
} else {
|
|
94
|
+
transport = TransportKind.MEMORY;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
this._networkManager = this.createNetworkManager(transport);
|
|
83
98
|
}
|
|
84
99
|
|
|
85
100
|
// TODO(burdon): Move to TestBuilder.
|
|
86
|
-
createNetworkManager() {
|
|
87
|
-
let transportFactory: TransportFactory
|
|
88
|
-
|
|
101
|
+
createNetworkManager(transport: TransportKind) {
|
|
102
|
+
let transportFactory: TransportFactory;
|
|
89
103
|
if (this.testBuilder.options.signalHosts) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
104
|
+
log.info(`using ${transport} transport with signal server.`);
|
|
105
|
+
switch (transport) {
|
|
106
|
+
case TransportKind.MEMORY:
|
|
107
|
+
throw new Error('Memory transport not supported with signal server.');
|
|
108
|
+
case TransportKind.SIMPLE_PEER:
|
|
109
|
+
transportFactory = createSimplePeerTransportFactory();
|
|
110
|
+
break;
|
|
111
|
+
case TransportKind.LIBDATACHANNEL:
|
|
112
|
+
transportFactory = createLibDataChannelTransportFactory();
|
|
113
|
+
break;
|
|
114
|
+
case TransportKind.SIMPLE_PEER_PROXY:
|
|
115
|
+
{
|
|
116
|
+
// Simulates bridge to shared worker.
|
|
117
|
+
const [proxyPort, servicePort] = createLinkedPorts();
|
|
118
|
+
|
|
119
|
+
this._proxy = createProtoRpcPeer({
|
|
120
|
+
port: proxyPort,
|
|
121
|
+
requested: {
|
|
122
|
+
BridgeService: schema.getService('dxos.mesh.bridge.BridgeService'),
|
|
123
|
+
},
|
|
124
|
+
noHandshake: true,
|
|
125
|
+
encodingOptions: {
|
|
126
|
+
preserveAny: true,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
this._service = createProtoRpcPeer({
|
|
131
|
+
port: servicePort,
|
|
132
|
+
exposed: {
|
|
133
|
+
BridgeService: schema.getService('dxos.mesh.bridge.BridgeService'),
|
|
134
|
+
},
|
|
135
|
+
handlers: { BridgeService: new SimplePeerTransportService() },
|
|
136
|
+
noHandshake: true,
|
|
137
|
+
encodingOptions: {
|
|
138
|
+
preserveAny: true,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
transportFactory = new SimplePeerTransportProxyFactory().setBridgeService(this._proxy.rpc.BridgeService);
|
|
143
|
+
}
|
|
144
|
+
break;
|
|
145
|
+
default:
|
|
146
|
+
throw new Error(`Unsupported transport: ${transport}`);
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
if (transport !== TransportKind.MEMORY) {
|
|
150
|
+
log.warn(`specified transport ${transport} but no signalling configured, using memory transport instead`);
|
|
122
151
|
}
|
|
152
|
+
log.info(`using ${transport} transport without signal server.`);
|
|
153
|
+
transportFactory = MemoryTransportFactory;
|
|
123
154
|
}
|
|
124
155
|
|
|
125
156
|
return new NetworkManager({
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
export const RTCPeerConnection = globalThis.RTCPeerConnection;
|
|
6
|
+
export const RTCSessionDescription = globalThis.RTCSessionDescription;
|
|
7
|
+
export const RTCIceCandidate = globalThis.RTCIceCandidate;
|
|
8
|
+
export const cleanup = (): void => {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import node from 'node-datachannel';
|
|
6
|
+
|
|
7
|
+
import { IceCandidate } from './rtc-ice-candidate';
|
|
8
|
+
import { PeerConnection } from './rtc-peer-connection';
|
|
9
|
+
import { SessionDescription } from './rtc-session-description';
|
|
10
|
+
|
|
11
|
+
export { SessionDescription as RTCSessionDescription };
|
|
12
|
+
export { IceCandidate as RTCIceCandidate };
|
|
13
|
+
export { PeerConnection as RTCPeerConnection };
|
|
14
|
+
|
|
15
|
+
export const cleanup = (): void => {
|
|
16
|
+
node.cleanup();
|
|
17
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import type node from 'node-datachannel';
|
|
6
|
+
|
|
7
|
+
export class DataChannel extends EventTarget implements RTCDataChannel {
|
|
8
|
+
binaryType: BinaryType;
|
|
9
|
+
|
|
10
|
+
readonly maxPacketLifeTime: number | null;
|
|
11
|
+
readonly maxRetransmits: number | null;
|
|
12
|
+
readonly negotiated: boolean;
|
|
13
|
+
readonly ordered: boolean;
|
|
14
|
+
|
|
15
|
+
onbufferedamountlow: ((this: RTCDataChannel, ev: Event) => any) | null;
|
|
16
|
+
onclose: ((this: RTCDataChannel, ev: Event) => any) | null;
|
|
17
|
+
onclosing: ((this: RTCDataChannel, ev: Event) => any) | null;
|
|
18
|
+
onerror: ((this: RTCDataChannel, ev: Event) => any) | null;
|
|
19
|
+
onmessage: ((this: RTCDataChannel, ev: MessageEvent) => any) | null;
|
|
20
|
+
onopen: ((this: RTCDataChannel, ev: Event) => any) | null;
|
|
21
|
+
|
|
22
|
+
#dataChannel: node.DataChannel;
|
|
23
|
+
#bufferedAmountLowThreshold: number;
|
|
24
|
+
#readyState: RTCDataChannelState;
|
|
25
|
+
|
|
26
|
+
constructor(dataChannel: node.DataChannel, dataChannelDict: RTCDataChannelInit = {}) {
|
|
27
|
+
super();
|
|
28
|
+
|
|
29
|
+
this.#dataChannel = dataChannel;
|
|
30
|
+
this.#readyState = 'connecting';
|
|
31
|
+
this.#bufferedAmountLowThreshold = 0;
|
|
32
|
+
|
|
33
|
+
this.binaryType = 'arraybuffer';
|
|
34
|
+
|
|
35
|
+
this.#dataChannel.onOpen(() => {
|
|
36
|
+
this.#readyState = 'open';
|
|
37
|
+
this.dispatchEvent(new Event('open'));
|
|
38
|
+
});
|
|
39
|
+
this.#dataChannel.onClosed(() => {
|
|
40
|
+
this.#readyState = 'closed';
|
|
41
|
+
this.dispatchEvent(new Event('close'));
|
|
42
|
+
});
|
|
43
|
+
this.#dataChannel.onError((msg) => {
|
|
44
|
+
this.#readyState = 'closed';
|
|
45
|
+
this.dispatchEvent(
|
|
46
|
+
new RTCErrorEvent('error', {
|
|
47
|
+
error: new RTCError(
|
|
48
|
+
{
|
|
49
|
+
errorDetail: 'data-channel-failure',
|
|
50
|
+
},
|
|
51
|
+
msg,
|
|
52
|
+
),
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
this.#dataChannel.onBufferedAmountLow(() => {
|
|
57
|
+
this.dispatchEvent(new Event('bufferedamountlow'));
|
|
58
|
+
});
|
|
59
|
+
this.#dataChannel.onMessage((data: string | Uint8Array) => {
|
|
60
|
+
if (typeof data === 'string') {
|
|
61
|
+
data = Buffer.from(data);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.dispatchEvent(new MessageEvent('message', { data }));
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// forward events to properties
|
|
68
|
+
this.addEventListener('message', (event) => {
|
|
69
|
+
this.onmessage?.(event as MessageEvent<ArrayBuffer>);
|
|
70
|
+
});
|
|
71
|
+
this.addEventListener('bufferedamountlow', (event) => {
|
|
72
|
+
this.onbufferedamountlow?.(event);
|
|
73
|
+
});
|
|
74
|
+
this.addEventListener('error', (event) => {
|
|
75
|
+
this.onerror?.(event);
|
|
76
|
+
});
|
|
77
|
+
this.addEventListener('close', (event) => {
|
|
78
|
+
this.onclose?.(event);
|
|
79
|
+
});
|
|
80
|
+
this.addEventListener('closing', (event) => {
|
|
81
|
+
this.onclosing?.(event);
|
|
82
|
+
});
|
|
83
|
+
this.addEventListener('open', (event) => {
|
|
84
|
+
this.onopen?.(event);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
this.onbufferedamountlow = null;
|
|
88
|
+
this.onclose = null;
|
|
89
|
+
this.onclosing = null;
|
|
90
|
+
this.onerror = null;
|
|
91
|
+
this.onmessage = null;
|
|
92
|
+
this.onopen = null;
|
|
93
|
+
|
|
94
|
+
this.maxPacketLifeTime = dataChannelDict.maxPacketLifeTime ?? null;
|
|
95
|
+
this.maxRetransmits = dataChannelDict.maxRetransmits ?? null;
|
|
96
|
+
this.negotiated = dataChannelDict.negotiated ?? false;
|
|
97
|
+
this.ordered = dataChannelDict.ordered ?? true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
get id(): number {
|
|
101
|
+
return this.#dataChannel.getId();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
get label(): string {
|
|
105
|
+
return this.#dataChannel.getLabel();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
get protocol(): string {
|
|
109
|
+
return this.#dataChannel.getProtocol();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
get bufferedAmount(): number {
|
|
113
|
+
return this.#dataChannel.bufferedAmount();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
set bufferedAmountLowThreshold(threshold: number) {
|
|
117
|
+
this.#bufferedAmountLowThreshold = threshold;
|
|
118
|
+
this.#dataChannel.setBufferedAmountLowThreshold(threshold);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
get bufferedAmountLowThreshold(): number {
|
|
122
|
+
return this.#bufferedAmountLowThreshold;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
get readyState(): RTCDataChannelState {
|
|
126
|
+
return this.#readyState;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
close(): void {
|
|
130
|
+
this.#readyState = 'closing';
|
|
131
|
+
this.dispatchEvent(new Event('closing'));
|
|
132
|
+
|
|
133
|
+
this.#dataChannel.close();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
send(data: string): void;
|
|
137
|
+
send(data: Blob): void;
|
|
138
|
+
send(data: ArrayBuffer): void;
|
|
139
|
+
send(data: ArrayBufferView): void;
|
|
140
|
+
send(data: any): void {
|
|
141
|
+
// TODO: sending Blobs
|
|
142
|
+
if (typeof data === 'string') {
|
|
143
|
+
this.#dataChannel.sendMessage(data);
|
|
144
|
+
} else {
|
|
145
|
+
this.#dataChannel.sendMessageBinary(data);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
export class PeerConnectionIceEvent extends Event implements RTCPeerConnectionIceEvent {
|
|
6
|
+
readonly candidate: RTCIceCandidate | null;
|
|
7
|
+
|
|
8
|
+
constructor(candidate: RTCIceCandidate) {
|
|
9
|
+
super('icecandidate');
|
|
10
|
+
|
|
11
|
+
this.candidate = candidate;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class DataChannelEvent extends Event implements RTCDataChannelEvent {
|
|
16
|
+
readonly channel: RTCDataChannel;
|
|
17
|
+
|
|
18
|
+
constructor(channel: RTCDataChannel) {
|
|
19
|
+
super('datachannel');
|
|
20
|
+
|
|
21
|
+
this.channel = channel;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @see https://developer.mozilla.org/docs/Web/API/RTCIceCandidate
|
|
7
|
+
*/
|
|
8
|
+
export class IceCandidate implements RTCIceCandidate {
|
|
9
|
+
readonly address: string | null;
|
|
10
|
+
readonly candidate: string;
|
|
11
|
+
readonly component: RTCIceComponent | null;
|
|
12
|
+
readonly foundation: string | null;
|
|
13
|
+
readonly port: number | null;
|
|
14
|
+
readonly priority: number | null;
|
|
15
|
+
readonly protocol: RTCIceProtocol | null;
|
|
16
|
+
readonly relatedAddress: string | null;
|
|
17
|
+
readonly relatedPort: number | null;
|
|
18
|
+
readonly sdpMLineIndex: number | null;
|
|
19
|
+
readonly sdpMid: string | null;
|
|
20
|
+
readonly tcpType: RTCIceTcpCandidateType | null;
|
|
21
|
+
readonly type: RTCIceCandidateType | null;
|
|
22
|
+
readonly usernameFragment: string | null;
|
|
23
|
+
|
|
24
|
+
constructor(init: RTCIceCandidateInit) {
|
|
25
|
+
if (init.candidate == null) {
|
|
26
|
+
throw new DOMException('candidate must be specified');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.candidate = init.candidate;
|
|
30
|
+
this.sdpMLineIndex = init.sdpMLineIndex ?? null;
|
|
31
|
+
this.sdpMid = init.sdpMid ?? null;
|
|
32
|
+
this.usernameFragment = init.usernameFragment ?? null;
|
|
33
|
+
|
|
34
|
+
this.address = null;
|
|
35
|
+
this.component = null;
|
|
36
|
+
this.foundation = null;
|
|
37
|
+
this.port = null;
|
|
38
|
+
this.priority = null;
|
|
39
|
+
this.protocol = null;
|
|
40
|
+
this.relatedAddress = null;
|
|
41
|
+
this.relatedPort = null;
|
|
42
|
+
this.tcpType = null;
|
|
43
|
+
this.type = null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
toJSON(): RTCIceCandidateInit {
|
|
47
|
+
return {
|
|
48
|
+
candidate: this.candidate,
|
|
49
|
+
sdpMLineIndex: this.sdpMLineIndex,
|
|
50
|
+
sdpMid: this.sdpMid,
|
|
51
|
+
usernameFragment: this.usernameFragment,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|