@dxos/network-manager 0.4.10-main.fa5a270 → 0.4.10-main.fe71b4c
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-KEN4UCZF.mjs → chunk-OSMVZUEK.mjs} +503 -453
- package/dist/lib/browser/chunk-OSMVZUEK.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-5D3NBEP2.cjs → chunk-M4CGCR2I.cjs} +519 -468
- package/dist/lib/node/chunk-M4CGCR2I.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/swarm/swarm.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 +0 -1
- package/src/swarm/swarm.ts +1 -0
- 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 +57 -50
- 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-KEN4UCZF.mjs.map +0 -7
- package/dist/lib/node/chunk-5D3NBEP2.cjs.map +0 -7
|
@@ -12,7 +12,7 @@ import { log, logInfo } from '@dxos/log';
|
|
|
12
12
|
import { type Signal } from '@dxos/protocols/proto/dxos/mesh/swarm';
|
|
13
13
|
import { ComplexMap } from '@dxos/util';
|
|
14
14
|
|
|
15
|
-
import { type Transport, type TransportFactory, type TransportOptions
|
|
15
|
+
import { type Transport, type TransportFactory, type TransportOptions } from './transport';
|
|
16
16
|
|
|
17
17
|
// TODO(burdon): Make configurable.
|
|
18
18
|
// Delay (in milliseconds) for data being sent through in-memory connections to simulate network latency.
|
|
@@ -31,7 +31,7 @@ const createStreamDelay = (delay: number): NodeJS.ReadWriteStream => {
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
export const MemoryTransportFactory: TransportFactory = {
|
|
34
|
-
createTransport: (
|
|
34
|
+
createTransport: (options) => new MemoryTransport(options),
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
/**
|
|
@@ -41,10 +41,6 @@ export class MemoryTransport implements Transport {
|
|
|
41
41
|
// TODO(burdon): Remove static properties (inject context into constructor).
|
|
42
42
|
private static readonly _connections = new ComplexMap<PublicKey, MemoryTransport>(PublicKey.hash);
|
|
43
43
|
|
|
44
|
-
public readonly closed = new Event<void>();
|
|
45
|
-
public readonly connected = new Event<void>();
|
|
46
|
-
public readonly errors = new ErrorStream();
|
|
47
|
-
|
|
48
44
|
@logInfo
|
|
49
45
|
private readonly _instanceId = PublicKey.random(); // TODO(burdon): Rename peerId? (Use local/remote labels in logs).
|
|
50
46
|
|
|
@@ -53,37 +49,46 @@ export class MemoryTransport implements Transport {
|
|
|
53
49
|
private readonly _outgoingDelay = createStreamDelay(MEMORY_TRANSPORT_DELAY);
|
|
54
50
|
private readonly _incomingDelay = createStreamDelay(MEMORY_TRANSPORT_DELAY);
|
|
55
51
|
|
|
56
|
-
private
|
|
52
|
+
private _closed = false;
|
|
57
53
|
|
|
58
54
|
@logInfo
|
|
59
55
|
private _remoteInstanceId!: PublicKey;
|
|
60
56
|
|
|
61
57
|
private _remoteConnection?: MemoryTransport;
|
|
62
58
|
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
public readonly closed = new Event<void>();
|
|
60
|
+
public readonly connected = new Event<void>();
|
|
61
|
+
public readonly errors = new ErrorStream();
|
|
65
62
|
|
|
63
|
+
constructor(private readonly _options: TransportOptions) {
|
|
66
64
|
invariant(!MemoryTransport._connections.has(this._instanceId), 'Duplicate memory connection');
|
|
67
65
|
MemoryTransport._connections.set(this._instanceId, this);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get isOpen() {
|
|
69
|
+
// TODO(burdon): Open state?
|
|
70
|
+
return !this._closed;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async open() {
|
|
74
|
+
log('opening...');
|
|
68
75
|
|
|
69
76
|
// Initiator will send a signal, the receiver will receive the unique ID and connect the streams.
|
|
70
|
-
if (this.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
});
|
|
77
|
+
if (this._options.initiator) {
|
|
78
|
+
log('sending signal');
|
|
79
|
+
try {
|
|
80
|
+
await this._options.sendSignal({ payload: { transportId: this._instanceId.toHex() } });
|
|
81
|
+
} catch (err) {
|
|
82
|
+
if (!this._closed) {
|
|
83
|
+
this.errors.raise(toError(err));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
82
86
|
} else {
|
|
87
|
+
// Don't block the open method.
|
|
83
88
|
this._remote
|
|
84
|
-
.wait({ timeout: this.
|
|
89
|
+
.wait({ timeout: this._options.timeout ?? 1_000 })
|
|
85
90
|
.then((remoteId) => {
|
|
86
|
-
if (this.
|
|
91
|
+
if (this._closed) {
|
|
87
92
|
return;
|
|
88
93
|
}
|
|
89
94
|
|
|
@@ -91,7 +96,7 @@ export class MemoryTransport implements Transport {
|
|
|
91
96
|
this._remoteConnection = MemoryTransport._connections.get(this._remoteInstanceId);
|
|
92
97
|
if (!this._remoteConnection) {
|
|
93
98
|
// Remote connection was destroyed before we could connect.
|
|
94
|
-
this.
|
|
99
|
+
this._closed = true;
|
|
95
100
|
this.closed.emit();
|
|
96
101
|
return;
|
|
97
102
|
}
|
|
@@ -101,17 +106,17 @@ export class MemoryTransport implements Transport {
|
|
|
101
106
|
this._remoteConnection._remoteInstanceId = this._instanceId;
|
|
102
107
|
|
|
103
108
|
log('connected');
|
|
104
|
-
this.
|
|
109
|
+
this._options.stream
|
|
105
110
|
.pipe(this._outgoingDelay)
|
|
106
|
-
.pipe(this._remoteConnection.
|
|
111
|
+
.pipe(this._remoteConnection._options.stream)
|
|
107
112
|
.pipe(this._incomingDelay)
|
|
108
|
-
.pipe(this.
|
|
113
|
+
.pipe(this._options.stream);
|
|
109
114
|
|
|
110
115
|
this.connected.emit();
|
|
111
116
|
this._remoteConnection.connected.emit();
|
|
112
117
|
})
|
|
113
118
|
.catch((err) => {
|
|
114
|
-
if (this.
|
|
119
|
+
if (this._closed) {
|
|
115
120
|
return;
|
|
116
121
|
}
|
|
117
122
|
|
|
@@ -120,14 +125,13 @@ export class MemoryTransport implements Transport {
|
|
|
120
125
|
}
|
|
121
126
|
}
|
|
122
127
|
|
|
123
|
-
async
|
|
124
|
-
log('closing');
|
|
125
|
-
this.
|
|
128
|
+
async close() {
|
|
129
|
+
log('closing...');
|
|
130
|
+
this._closed = true;
|
|
126
131
|
|
|
127
132
|
MemoryTransport._connections.delete(this._instanceId);
|
|
128
133
|
if (this._remoteConnection) {
|
|
129
|
-
|
|
130
|
-
this._remoteConnection._destroyed = true;
|
|
134
|
+
this._remoteConnection._closed = true;
|
|
131
135
|
MemoryTransport._connections.delete(this._remoteInstanceId);
|
|
132
136
|
|
|
133
137
|
// TODO(dmaretskyi): Hypercore streams do not seem to have the unpipe method.
|
|
@@ -138,27 +142,27 @@ export class MemoryTransport implements Transport {
|
|
|
138
142
|
// code .unpipe(this._incomingDelay)
|
|
139
143
|
// code .unpipe(this._stream);
|
|
140
144
|
|
|
141
|
-
this.
|
|
142
|
-
this._incomingDelay.unpipe(this._remoteConnection.
|
|
143
|
-
this._remoteConnection.
|
|
144
|
-
this._outgoingDelay.unpipe(this.
|
|
145
|
-
this.
|
|
145
|
+
this._options.stream.unpipe(this._incomingDelay);
|
|
146
|
+
this._incomingDelay.unpipe(this._remoteConnection._options.stream);
|
|
147
|
+
this._remoteConnection._options.stream.unpipe(this._outgoingDelay);
|
|
148
|
+
this._outgoingDelay.unpipe(this._options.stream);
|
|
149
|
+
this._options.stream.unpipe(this._outgoingDelay);
|
|
146
150
|
|
|
147
151
|
this._remoteConnection.closed.emit();
|
|
148
152
|
this._remoteConnection._remoteConnection = undefined;
|
|
149
153
|
this._remoteConnection = undefined;
|
|
150
|
-
log('closed');
|
|
151
154
|
}
|
|
152
155
|
|
|
153
156
|
this.closed.emit();
|
|
154
157
|
log('closed');
|
|
155
158
|
}
|
|
156
159
|
|
|
157
|
-
|
|
160
|
+
async onSignal({ payload }: Signal) {
|
|
158
161
|
log('received signal', { payload });
|
|
159
162
|
if (!payload?.transportId) {
|
|
160
163
|
return;
|
|
161
164
|
}
|
|
165
|
+
|
|
162
166
|
// TODO(burdon): Check open?
|
|
163
167
|
const transportId = payload.transportId as string;
|
|
164
168
|
if (transportId) {
|
|
@@ -167,11 +171,11 @@ export class MemoryTransport implements Transport {
|
|
|
167
171
|
}
|
|
168
172
|
}
|
|
169
173
|
|
|
170
|
-
async getDetails()
|
|
174
|
+
async getDetails() {
|
|
171
175
|
return this._instanceId.toHex();
|
|
172
176
|
}
|
|
173
177
|
|
|
174
|
-
async getStats()
|
|
178
|
+
async getStats() {
|
|
175
179
|
return {
|
|
176
180
|
bytesSent: 0,
|
|
177
181
|
bytesReceived: 0,
|
|
@@ -180,3 +184,6 @@ export class MemoryTransport implements Transport {
|
|
|
180
184
|
};
|
|
181
185
|
}
|
|
182
186
|
}
|
|
187
|
+
|
|
188
|
+
// TODO(burdon): Factor out.
|
|
189
|
+
const toError = (err: any): Error => (err instanceof Error ? err : new Error(String(err)));
|
|
@@ -65,7 +65,8 @@ describe('SimplePeerTransportProxy', () => {
|
|
|
65
65
|
sendSignal,
|
|
66
66
|
bridgeService: rpcClient.rpc.BridgeService,
|
|
67
67
|
});
|
|
68
|
-
|
|
68
|
+
await simplePeerTransportProxy.open();
|
|
69
|
+
afterTest(async () => await simplePeerTransportProxy.close());
|
|
69
70
|
|
|
70
71
|
return { simplePeerService: rpcService, SimplePeerTransportProxy: simplePeerTransportProxy };
|
|
71
72
|
};
|
|
@@ -75,7 +76,7 @@ describe('SimplePeerTransportProxy', () => {
|
|
|
75
76
|
const { SimplePeerTransportProxy: connection } = await setupProxy();
|
|
76
77
|
|
|
77
78
|
const wait = connection.closed.waitForCount(1);
|
|
78
|
-
await connection.
|
|
79
|
+
await connection.close();
|
|
79
80
|
await wait;
|
|
80
81
|
}).timeout(1_000);
|
|
81
82
|
|
|
@@ -85,7 +86,7 @@ describe('SimplePeerTransportProxy', () => {
|
|
|
85
86
|
initiator: true,
|
|
86
87
|
stream: stream1,
|
|
87
88
|
sendSignal: async (signal) => {
|
|
88
|
-
connection2.
|
|
89
|
+
await connection2.onSignal(signal);
|
|
89
90
|
},
|
|
90
91
|
});
|
|
91
92
|
afterTest(() => connection1.errors.assertNoUnhandledErrors());
|
|
@@ -95,10 +96,11 @@ describe('SimplePeerTransportProxy', () => {
|
|
|
95
96
|
initiator: false,
|
|
96
97
|
stream: stream2,
|
|
97
98
|
sendSignal: async (signal) => {
|
|
98
|
-
connection1.
|
|
99
|
+
await connection1.onSignal(signal);
|
|
99
100
|
},
|
|
100
101
|
});
|
|
101
102
|
afterTest(() => connection2.errors.assertNoUnhandledErrors());
|
|
103
|
+
|
|
102
104
|
await TestStream.assertConnectivity(stream1, stream2);
|
|
103
105
|
}).timeout(2_000);
|
|
104
106
|
|
|
@@ -147,13 +149,13 @@ describe('SimplePeerTransportProxy', () => {
|
|
|
147
149
|
initiator: true,
|
|
148
150
|
stream: stream1,
|
|
149
151
|
sendSignal: async (signal) => {
|
|
150
|
-
proxy2.
|
|
152
|
+
await proxy2.onSignal(signal);
|
|
151
153
|
},
|
|
152
154
|
bridgeService: rpcClient.rpc.BridgeService,
|
|
153
155
|
});
|
|
154
156
|
afterTest(async () => {
|
|
155
157
|
proxy1.errors.assertNoUnhandledErrors();
|
|
156
|
-
await proxy1.
|
|
158
|
+
await proxy1.close();
|
|
157
159
|
});
|
|
158
160
|
|
|
159
161
|
const stream2 = new TestStream();
|
|
@@ -161,15 +163,18 @@ describe('SimplePeerTransportProxy', () => {
|
|
|
161
163
|
initiator: false,
|
|
162
164
|
stream: stream2,
|
|
163
165
|
sendSignal: async (signal) => {
|
|
164
|
-
proxy1.
|
|
166
|
+
await proxy1.onSignal(signal);
|
|
165
167
|
},
|
|
166
168
|
bridgeService: rpcClient.rpc.BridgeService,
|
|
167
169
|
});
|
|
168
170
|
afterTest(async () => {
|
|
169
171
|
proxy2.errors.assertNoUnhandledErrors();
|
|
170
|
-
await proxy2.
|
|
172
|
+
await proxy2.close();
|
|
171
173
|
});
|
|
172
174
|
|
|
175
|
+
await proxy1.open();
|
|
176
|
+
await proxy2.open();
|
|
177
|
+
|
|
173
178
|
await TestStream.assertConnectivity(stream1, stream2);
|
|
174
179
|
}).timeout(3_000);
|
|
175
180
|
});
|
|
@@ -24,15 +24,12 @@ import { arrayToBuffer } from '@dxos/util';
|
|
|
24
24
|
|
|
25
25
|
import { type Transport, type TransportFactory, type TransportOptions, type TransportStats } from './transport';
|
|
26
26
|
|
|
27
|
+
const RPC_TIMEOUT = 10_000;
|
|
27
28
|
const RESP_MIN_THRESHOLD = 500;
|
|
28
29
|
const TIMEOUT_THRESHOLD = 10;
|
|
29
30
|
|
|
30
|
-
export type
|
|
31
|
-
initiator: boolean;
|
|
32
|
-
stream: NodeJS.ReadWriteStream;
|
|
31
|
+
export type SimplePeerTransportProxyOptions = TransportOptions & {
|
|
33
32
|
bridgeService: BridgeService;
|
|
34
|
-
// TODO(burdon): Rename onSignal.
|
|
35
|
-
sendSignal: (signal: Signal) => Promise<void>;
|
|
36
33
|
};
|
|
37
34
|
|
|
38
35
|
export class SimplePeerTransportProxy implements Transport {
|
|
@@ -47,11 +44,21 @@ export class SimplePeerTransportProxy implements Transport {
|
|
|
47
44
|
private _closed = false;
|
|
48
45
|
private _serviceStream!: Stream<BridgeEvent>;
|
|
49
46
|
|
|
50
|
-
constructor(private readonly
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
+
);
|
|
55
62
|
|
|
56
63
|
this._serviceStream.waitUntilReady().then(
|
|
57
64
|
() => {
|
|
@@ -69,13 +76,13 @@ export class SimplePeerTransportProxy implements Transport {
|
|
|
69
76
|
const proxyStream = new Writable({
|
|
70
77
|
write: (chunk, _, callback) => {
|
|
71
78
|
const then = performance.now();
|
|
72
|
-
this.
|
|
79
|
+
this._options.bridgeService
|
|
73
80
|
.sendData(
|
|
74
81
|
{
|
|
75
82
|
proxyId: this._proxyId,
|
|
76
83
|
payload: chunk,
|
|
77
84
|
},
|
|
78
|
-
{ timeout:
|
|
85
|
+
{ timeout: RPC_TIMEOUT },
|
|
79
86
|
)
|
|
80
87
|
.then(
|
|
81
88
|
() => {
|
|
@@ -90,7 +97,7 @@ export class SimplePeerTransportProxy implements Transport {
|
|
|
90
97
|
(err: any) => {
|
|
91
98
|
if (err instanceof TimeoutError || err.constructor.name === 'TimeoutError') {
|
|
92
99
|
if (this._timeoutCount++ > TIMEOUT_THRESHOLD) {
|
|
93
|
-
throw new TimeoutError(`too many
|
|
100
|
+
throw new TimeoutError(`too many timeouts (${this._timeoutCount} > ${TIMEOUT_THRESHOLD}`);
|
|
94
101
|
} else {
|
|
95
102
|
log('timeout error, but still invoking callback');
|
|
96
103
|
callback();
|
|
@@ -107,12 +114,42 @@ export class SimplePeerTransportProxy implements Transport {
|
|
|
107
114
|
log('proxystream error', { err });
|
|
108
115
|
});
|
|
109
116
|
|
|
110
|
-
this.
|
|
117
|
+
this._options.stream.pipe(proxyStream);
|
|
111
118
|
},
|
|
112
119
|
(error) => log.catch(error),
|
|
113
120
|
);
|
|
114
121
|
}
|
|
115
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
|
+
|
|
116
153
|
private async _handleConnection(connectionEvent: BridgeEvent.ConnectionEvent): Promise<void> {
|
|
117
154
|
if (connectionEvent.error) {
|
|
118
155
|
this.errors.raise(decodeError(connectionEvent.error));
|
|
@@ -124,7 +161,7 @@ export class SimplePeerTransportProxy implements Transport {
|
|
|
124
161
|
break;
|
|
125
162
|
}
|
|
126
163
|
case ConnectionState.CLOSED: {
|
|
127
|
-
await this.
|
|
164
|
+
await this.close();
|
|
128
165
|
break;
|
|
129
166
|
}
|
|
130
167
|
}
|
|
@@ -132,47 +169,20 @@ export class SimplePeerTransportProxy implements Transport {
|
|
|
132
169
|
|
|
133
170
|
private _handleData(dataEvent: BridgeEvent.DataEvent) {
|
|
134
171
|
// NOTE: This must be a Buffer otherwise hypercore-protocol breaks.
|
|
135
|
-
this.
|
|
172
|
+
this._options.stream.write(arrayToBuffer(dataEvent.payload));
|
|
136
173
|
}
|
|
137
174
|
|
|
138
175
|
private async _handleSignal(signalEvent: BridgeEvent.SignalEvent) {
|
|
139
|
-
await this.
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
signal(signal: Signal): void {
|
|
143
|
-
this._params.bridgeService
|
|
144
|
-
.sendSignal({
|
|
145
|
-
proxyId: this._proxyId,
|
|
146
|
-
signal,
|
|
147
|
-
})
|
|
148
|
-
.catch((err) => this.errors.raise(decodeError(err)));
|
|
176
|
+
await this._options.sendSignal(signalEvent.payload);
|
|
149
177
|
}
|
|
150
178
|
|
|
151
179
|
async getDetails(): Promise<string> {
|
|
152
|
-
return (await this.
|
|
180
|
+
return (await this._options.bridgeService.getDetails({ proxyId: this._proxyId }, { timeout: RPC_TIMEOUT })).details;
|
|
153
181
|
}
|
|
154
182
|
|
|
155
183
|
async getStats(): Promise<TransportStats> {
|
|
156
|
-
return (await this.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
// TODO(burdon): Move open from constructor.
|
|
160
|
-
async destroy(): Promise<void> {
|
|
161
|
-
await this._ctx.dispose();
|
|
162
|
-
if (this._closed) {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
await this._serviceStream.close();
|
|
167
|
-
|
|
168
|
-
try {
|
|
169
|
-
await this._params.bridgeService.close({ proxyId: this._proxyId });
|
|
170
|
-
} catch (err: any) {
|
|
171
|
-
log.catch(err);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
this.closed.emit();
|
|
175
|
-
this._closed = true;
|
|
184
|
+
return (await this._options.bridgeService.getStats({ proxyId: this._proxyId }, { timeout: RPC_TIMEOUT }))
|
|
185
|
+
.stats as TransportStats;
|
|
176
186
|
}
|
|
177
187
|
|
|
178
188
|
/**
|
|
@@ -206,7 +216,6 @@ export class SimplePeerTransportProxyFactory implements TransportFactory {
|
|
|
206
216
|
|
|
207
217
|
createTransport(options: TransportOptions): Transport {
|
|
208
218
|
invariant(this._bridgeService, 'SimplePeerTransportProxyFactory is not ready to open connections');
|
|
209
|
-
|
|
210
219
|
const transport = new SimplePeerTransportProxy({
|
|
211
220
|
...options,
|
|
212
221
|
bridgeService: this._bridgeService,
|
|
@@ -214,7 +223,6 @@ export class SimplePeerTransportProxyFactory implements TransportFactory {
|
|
|
214
223
|
|
|
215
224
|
this._connections.add(transport);
|
|
216
225
|
transport.closed.on(() => this._connections.delete(transport));
|
|
217
|
-
|
|
218
226
|
return transport;
|
|
219
227
|
}
|
|
220
228
|
}
|
|
@@ -222,7 +230,6 @@ export class SimplePeerTransportProxyFactory implements TransportFactory {
|
|
|
222
230
|
// TODO(nf): fix so Errors crossing RPC boundary preserve class
|
|
223
231
|
const decodeError = (err: Error | string) => {
|
|
224
232
|
const message = typeof err === 'string' ? err : err.message;
|
|
225
|
-
|
|
226
233
|
if (message.includes('CONNECTION_RESET')) {
|
|
227
234
|
return new ConnectionResetError(message);
|
|
228
235
|
} else if (message.includes('TIMEOUT')) {
|
|
@@ -64,6 +64,9 @@ export class SimplePeerTransportService implements BridgeService {
|
|
|
64
64
|
},
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
+
// TODO(burdon): Async.
|
|
68
|
+
void transport.open();
|
|
69
|
+
|
|
67
70
|
next({
|
|
68
71
|
connection: {
|
|
69
72
|
state: ConnectionState.CONNECTING,
|
|
@@ -114,7 +117,7 @@ export class SimplePeerTransportService implements BridgeService {
|
|
|
114
117
|
|
|
115
118
|
async sendSignal({ proxyId, signal }: SignalRequest): Promise<void> {
|
|
116
119
|
invariant(this.transports.has(proxyId));
|
|
117
|
-
await this.transports.get(proxyId)!.transport.
|
|
120
|
+
await this.transports.get(proxyId)!.transport.onSignal(signal);
|
|
118
121
|
}
|
|
119
122
|
|
|
120
123
|
async getDetails({ proxyId }: DetailsRequest): Promise<DetailsResponse> {
|
|
@@ -142,7 +145,7 @@ export class SimplePeerTransportService implements BridgeService {
|
|
|
142
145
|
}
|
|
143
146
|
|
|
144
147
|
async close({ proxyId }: CloseRequest) {
|
|
145
|
-
await this.transports.get(proxyId)?.transport.
|
|
148
|
+
await this.transports.get(proxyId)?.transport.close();
|
|
146
149
|
await this.transports.get(proxyId)?.stream.end();
|
|
147
150
|
if (this.transports.get(proxyId)) {
|
|
148
151
|
this.transports.get(proxyId)!.state = 'CLOSED';
|
|
@@ -18,8 +18,9 @@ describe('SimplePeerTransport', () => {
|
|
|
18
18
|
sendSignal: async () => {},
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
await connection.open();
|
|
21
22
|
const wait = connection.closed.waitForCount(1);
|
|
22
|
-
await connection.
|
|
23
|
+
await connection.close();
|
|
23
24
|
await wait;
|
|
24
25
|
})
|
|
25
26
|
.timeout(1_000)
|
|
@@ -32,10 +33,10 @@ describe('SimplePeerTransport', () => {
|
|
|
32
33
|
stream: stream1,
|
|
33
34
|
sendSignal: async (signal) => {
|
|
34
35
|
await sleep(10);
|
|
35
|
-
await connection2.
|
|
36
|
+
await connection2.onSignal(signal);
|
|
36
37
|
},
|
|
37
38
|
});
|
|
38
|
-
afterTest(() => connection1.
|
|
39
|
+
afterTest(() => connection1.close());
|
|
39
40
|
afterTest(() => connection1.errors.assertNoUnhandledErrors());
|
|
40
41
|
|
|
41
42
|
const stream2 = new TestStream();
|
|
@@ -44,12 +45,15 @@ describe('SimplePeerTransport', () => {
|
|
|
44
45
|
stream: stream2,
|
|
45
46
|
sendSignal: async (signal) => {
|
|
46
47
|
await sleep(10);
|
|
47
|
-
await connection1.
|
|
48
|
+
await connection1.onSignal(signal);
|
|
48
49
|
},
|
|
49
50
|
});
|
|
50
|
-
afterTest(() => connection2.
|
|
51
|
+
afterTest(() => connection2.close());
|
|
51
52
|
afterTest(() => connection2.errors.assertNoUnhandledErrors());
|
|
52
53
|
|
|
54
|
+
await connection1.open();
|
|
55
|
+
await connection2.open();
|
|
56
|
+
|
|
53
57
|
await TestStream.assertConnectivity(stream1, stream2);
|
|
54
58
|
})
|
|
55
59
|
.timeout(2_000)
|
|
@@ -13,16 +13,17 @@ import { log } from '@dxos/log';
|
|
|
13
13
|
import { ConnectionResetError, ConnectivityError, ProtocolError, UnknownProtocolError, trace } from '@dxos/protocols';
|
|
14
14
|
import { type Signal } from '@dxos/protocols/proto/dxos/mesh/swarm';
|
|
15
15
|
|
|
16
|
-
import { type Transport, type TransportFactory, type TransportStats } from './transport';
|
|
16
|
+
import { type Transport, type TransportFactory, type TransportOptions, type TransportStats } from './transport';
|
|
17
17
|
import { wrtc } from './webrtc';
|
|
18
18
|
|
|
19
|
-
export type SimplePeerTransportParams = {
|
|
20
|
-
initiator: boolean;
|
|
21
|
-
stream: NodeJS.ReadWriteStream;
|
|
19
|
+
export type SimplePeerTransportParams = TransportOptions & {
|
|
22
20
|
webrtcConfig?: any;
|
|
23
|
-
sendSignal: (signal: Signal) => Promise<void>;
|
|
24
21
|
};
|
|
25
22
|
|
|
23
|
+
export const createSimplePeerTransportFactory = (webrtcConfig?: any): TransportFactory => ({
|
|
24
|
+
createTransport: (options) => new SimplePeerTransport({ ...options, webrtcConfig }),
|
|
25
|
+
});
|
|
26
|
+
|
|
26
27
|
/**
|
|
27
28
|
* Implements Transport for WebRTC. Uses simple-peer under the hood.
|
|
28
29
|
*/
|
|
@@ -37,34 +38,39 @@ export class SimplePeerTransport implements Transport {
|
|
|
37
38
|
|
|
38
39
|
private readonly _instanceId = PublicKey.random().toHex();
|
|
39
40
|
|
|
41
|
+
get isOpen() {
|
|
42
|
+
// TODO(burdon): Open state?
|
|
43
|
+
return this._piped && !this._closed;
|
|
44
|
+
}
|
|
45
|
+
|
|
40
46
|
/**
|
|
41
47
|
* @params opts.config formatted as per https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/RTCPeerConnection
|
|
42
48
|
*/
|
|
43
|
-
constructor(private readonly
|
|
49
|
+
constructor(private readonly _params: SimplePeerTransportParams) {
|
|
44
50
|
log.trace('dxos.mesh.webrtc-transport.constructor', trace.begin({ id: this._instanceId }));
|
|
45
|
-
log('created connection',
|
|
51
|
+
log('created connection', _params);
|
|
46
52
|
this._peer = new SimplePeerConstructor({
|
|
47
53
|
channelName: 'dxos.mesh.transport',
|
|
48
|
-
initiator: this.
|
|
54
|
+
initiator: this._params.initiator,
|
|
49
55
|
wrtc: SimplePeerConstructor.WEBRTC_SUPPORT ? undefined : wrtc ?? raise(new Error('wrtc not available')),
|
|
50
|
-
config: this.
|
|
56
|
+
config: this._params.webrtcConfig,
|
|
51
57
|
});
|
|
52
58
|
|
|
53
59
|
this._peer.on('signal', async (data) => {
|
|
54
60
|
log('signal', data);
|
|
55
|
-
await this.
|
|
61
|
+
await this._params.sendSignal({ payload: { data } });
|
|
56
62
|
});
|
|
57
63
|
|
|
58
64
|
this._peer.on('connect', () => {
|
|
59
65
|
log('connected');
|
|
60
|
-
this.
|
|
66
|
+
this._params.stream.pipe(this._peer!).pipe(this._params.stream);
|
|
61
67
|
this._piped = true;
|
|
62
68
|
this.connected.emit();
|
|
63
69
|
});
|
|
64
70
|
|
|
65
71
|
this._peer.on('close', async () => {
|
|
66
72
|
log('closed');
|
|
67
|
-
await this.
|
|
73
|
+
await this.close();
|
|
68
74
|
});
|
|
69
75
|
|
|
70
76
|
this._peer.on('error', async (err) => {
|
|
@@ -114,7 +120,7 @@ export class SimplePeerTransport implements Transport {
|
|
|
114
120
|
if (typeof (this._peer as any)?._pc?.getStats === 'function') {
|
|
115
121
|
(this._peer as any)._pc.getStats().then((stats: any) => {
|
|
116
122
|
log.info('report after webrtc error', {
|
|
117
|
-
config: this.
|
|
123
|
+
config: this._params.webrtcConfig,
|
|
118
124
|
stats: Object.fromEntries((stats as any).entries()),
|
|
119
125
|
});
|
|
120
126
|
});
|
|
@@ -123,7 +129,7 @@ export class SimplePeerTransport implements Transport {
|
|
|
123
129
|
log.catch(err);
|
|
124
130
|
}
|
|
125
131
|
|
|
126
|
-
await this.
|
|
132
|
+
await this.close();
|
|
127
133
|
});
|
|
128
134
|
|
|
129
135
|
log.trace('dxos.mesh.webrtc-transport.constructor', trace.end({ id: this._instanceId }));
|
|
@@ -188,7 +194,9 @@ export class SimplePeerTransport implements Transport {
|
|
|
188
194
|
return `${rc.ip}:${rc.port}/${rc.protocol} ${rc.candidateType}`;
|
|
189
195
|
}
|
|
190
196
|
|
|
191
|
-
async
|
|
197
|
+
async open() {}
|
|
198
|
+
|
|
199
|
+
async close() {
|
|
192
200
|
log('closing...');
|
|
193
201
|
if (this._closed) {
|
|
194
202
|
return;
|
|
@@ -200,7 +208,7 @@ export class SimplePeerTransport implements Transport {
|
|
|
200
208
|
log('closed');
|
|
201
209
|
}
|
|
202
210
|
|
|
203
|
-
|
|
211
|
+
async onSignal(signal: Signal) {
|
|
204
212
|
if (this._closed) {
|
|
205
213
|
return; // Ignore signals after close.
|
|
206
214
|
}
|
|
@@ -212,15 +220,7 @@ export class SimplePeerTransport implements Transport {
|
|
|
212
220
|
private _disconnectStreams() {
|
|
213
221
|
// TODO(rzadp): Find a way of unpiping this?
|
|
214
222
|
if (this._piped) {
|
|
215
|
-
this.
|
|
223
|
+
this._params.stream.unpipe?.(this._peer)?.unpipe?.(this._params.stream);
|
|
216
224
|
}
|
|
217
225
|
}
|
|
218
226
|
}
|
|
219
|
-
|
|
220
|
-
export const createSimplePeerTransportFactory = (webrtcConfig?: any): TransportFactory => ({
|
|
221
|
-
createTransport: (params) =>
|
|
222
|
-
new SimplePeerTransport({
|
|
223
|
-
...params,
|
|
224
|
-
webrtcConfig,
|
|
225
|
-
}),
|
|
226
|
-
});
|