@dxos/network-manager 0.4.10-main.fd4f2a3 → 0.4.10-next.71cec10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/{chunk-OIR45T43.mjs → chunk-SOVZDG44.mjs} +464 -425
- package/dist/lib/browser/chunk-SOVZDG44.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +1 -1
- package/dist/lib/node/{chunk-HUPX2JGP.cjs → chunk-BMZFS4HT.cjs} +480 -440
- package/dist/lib/node/chunk-BMZFS4HT.cjs.map +7 -0
- package/dist/lib/node/index.cjs +27 -27
- package/dist/lib/node/index.cjs.map +1 -1
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +18 -18
- package/dist/types/src/swarm/connection.d.ts.map +1 -1
- package/dist/types/src/swarm/peer.d.ts.map +1 -1
- package/dist/types/src/transport/libdatachannel-transport.d.ts +20 -19
- package/dist/types/src/transport/libdatachannel-transport.d.ts.map +1 -1
- package/dist/types/src/transport/memory-transport.d.ts +17 -10
- package/dist/types/src/transport/memory-transport.d.ts.map +1 -1
- package/dist/types/src/transport/simplepeer-transport-proxy.d.ts +7 -9
- package/dist/types/src/transport/simplepeer-transport-proxy.d.ts.map +1 -1
- package/dist/types/src/transport/simplepeer-transport-service.d.ts.map +1 -1
- package/dist/types/src/transport/simplepeer-transport.d.ts +9 -11
- package/dist/types/src/transport/simplepeer-transport.d.ts.map +1 -1
- package/dist/types/src/transport/tcp-transport.browser.d.ts +7 -2
- package/dist/types/src/transport/tcp-transport.browser.d.ts.map +1 -1
- package/dist/types/src/transport/tcp-transport.d.ts +6 -4
- package/dist/types/src/transport/tcp-transport.d.ts.map +1 -1
- package/dist/types/src/transport/transport.d.ts +18 -12
- package/dist/types/src/transport/transport.d.ts.map +1 -1
- package/package.json +17 -17
- package/src/swarm/connection.ts +7 -5
- package/src/swarm/peer.ts +3 -4
- package/src/transport/libdatachannel-transport.test.ts +14 -9
- package/src/transport/libdatachannel-transport.ts +226 -204
- package/src/transport/memory-transport.test.ts +10 -7
- package/src/transport/memory-transport.ts +49 -42
- package/src/transport/simplepeer-transport-proxy-test.ts +13 -8
- package/src/transport/simplepeer-transport-proxy.ts +48 -49
- package/src/transport/simplepeer-transport-service.ts +5 -2
- package/src/transport/simplepeer-transport.test.ts +9 -5
- package/src/transport/simplepeer-transport.ts +25 -25
- package/src/transport/tcp-transport.browser.ts +10 -5
- package/src/transport/tcp-transport.ts +31 -22
- package/src/transport/transport.ts +25 -14
- package/dist/lib/browser/chunk-OIR45T43.mjs.map +0 -7
- package/dist/lib/node/chunk-HUPX2JGP.cjs.map +0 -7
|
@@ -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
|
});
|
|
@@ -28,12 +28,8 @@ const RPC_TIMEOUT = 10_000;
|
|
|
28
28
|
const RESP_MIN_THRESHOLD = 500;
|
|
29
29
|
const TIMEOUT_THRESHOLD = 10;
|
|
30
30
|
|
|
31
|
-
export type
|
|
32
|
-
initiator: boolean;
|
|
33
|
-
stream: NodeJS.ReadWriteStream;
|
|
31
|
+
export type SimplePeerTransportProxyOptions = TransportOptions & {
|
|
34
32
|
bridgeService: BridgeService;
|
|
35
|
-
// TODO(burdon): Rename onSignal.
|
|
36
|
-
sendSignal: (signal: Signal) => Promise<void>;
|
|
37
33
|
};
|
|
38
34
|
|
|
39
35
|
export class SimplePeerTransportProxy implements Transport {
|
|
@@ -48,11 +44,18 @@ export class SimplePeerTransportProxy implements Transport {
|
|
|
48
44
|
private _closed = false;
|
|
49
45
|
private _serviceStream!: Stream<BridgeEvent>;
|
|
50
46
|
|
|
51
|
-
constructor(private readonly
|
|
52
|
-
|
|
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(
|
|
53
56
|
{
|
|
54
57
|
proxyId: this._proxyId,
|
|
55
|
-
initiator: this.
|
|
58
|
+
initiator: this._options.initiator,
|
|
56
59
|
},
|
|
57
60
|
{ timeout: RPC_TIMEOUT },
|
|
58
61
|
);
|
|
@@ -73,7 +76,7 @@ export class SimplePeerTransportProxy implements Transport {
|
|
|
73
76
|
const proxyStream = new Writable({
|
|
74
77
|
write: (chunk, _, callback) => {
|
|
75
78
|
const then = performance.now();
|
|
76
|
-
this.
|
|
79
|
+
this._options.bridgeService
|
|
77
80
|
.sendData(
|
|
78
81
|
{
|
|
79
82
|
proxyId: this._proxyId,
|
|
@@ -111,12 +114,42 @@ export class SimplePeerTransportProxy implements Transport {
|
|
|
111
114
|
log('proxystream error', { err });
|
|
112
115
|
});
|
|
113
116
|
|
|
114
|
-
this.
|
|
117
|
+
this._options.stream.pipe(proxyStream);
|
|
115
118
|
},
|
|
116
119
|
(error) => log.catch(error),
|
|
117
120
|
);
|
|
118
121
|
}
|
|
119
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
|
+
|
|
120
153
|
private async _handleConnection(connectionEvent: BridgeEvent.ConnectionEvent): Promise<void> {
|
|
121
154
|
if (connectionEvent.error) {
|
|
122
155
|
this.errors.raise(decodeError(connectionEvent.error));
|
|
@@ -128,7 +161,7 @@ export class SimplePeerTransportProxy implements Transport {
|
|
|
128
161
|
break;
|
|
129
162
|
}
|
|
130
163
|
case ConnectionState.CLOSED: {
|
|
131
|
-
await this.
|
|
164
|
+
await this.close();
|
|
132
165
|
break;
|
|
133
166
|
}
|
|
134
167
|
}
|
|
@@ -136,53 +169,22 @@ export class SimplePeerTransportProxy implements Transport {
|
|
|
136
169
|
|
|
137
170
|
private _handleData(dataEvent: BridgeEvent.DataEvent) {
|
|
138
171
|
// NOTE: This must be a Buffer otherwise hypercore-protocol breaks.
|
|
139
|
-
this.
|
|
172
|
+
this._options.stream.write(arrayToBuffer(dataEvent.payload));
|
|
140
173
|
}
|
|
141
174
|
|
|
142
175
|
private async _handleSignal(signalEvent: BridgeEvent.SignalEvent) {
|
|
143
|
-
await this.
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
signal(signal: Signal): void {
|
|
147
|
-
this._params.bridgeService
|
|
148
|
-
.sendSignal(
|
|
149
|
-
{
|
|
150
|
-
proxyId: this._proxyId,
|
|
151
|
-
signal,
|
|
152
|
-
},
|
|
153
|
-
{ timeout: RPC_TIMEOUT },
|
|
154
|
-
)
|
|
155
|
-
.catch((err) => this.errors.raise(decodeError(err)));
|
|
176
|
+
await this._options.sendSignal(signalEvent.payload);
|
|
156
177
|
}
|
|
157
178
|
|
|
158
179
|
async getDetails(): Promise<string> {
|
|
159
|
-
return (await this.
|
|
180
|
+
return (await this._options.bridgeService.getDetails({ proxyId: this._proxyId }, { timeout: RPC_TIMEOUT })).details;
|
|
160
181
|
}
|
|
161
182
|
|
|
162
183
|
async getStats(): Promise<TransportStats> {
|
|
163
|
-
return (await this.
|
|
184
|
+
return (await this._options.bridgeService.getStats({ proxyId: this._proxyId }, { timeout: RPC_TIMEOUT }))
|
|
164
185
|
.stats as TransportStats;
|
|
165
186
|
}
|
|
166
187
|
|
|
167
|
-
// TODO(burdon): Move open from constructor.
|
|
168
|
-
async destroy(): Promise<void> {
|
|
169
|
-
await this._ctx.dispose();
|
|
170
|
-
if (this._closed) {
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
await this._serviceStream.close();
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
await this._params.bridgeService.close({ proxyId: this._proxyId }, { timeout: RPC_TIMEOUT });
|
|
178
|
-
} catch (err: any) {
|
|
179
|
-
log.catch(err);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
this.closed.emit();
|
|
183
|
-
this._closed = true;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
188
|
/**
|
|
187
189
|
* Called when underlying proxy service becomes unavailable.
|
|
188
190
|
*/
|
|
@@ -214,7 +216,6 @@ export class SimplePeerTransportProxyFactory implements TransportFactory {
|
|
|
214
216
|
|
|
215
217
|
createTransport(options: TransportOptions): Transport {
|
|
216
218
|
invariant(this._bridgeService, 'SimplePeerTransportProxyFactory is not ready to open connections');
|
|
217
|
-
|
|
218
219
|
const transport = new SimplePeerTransportProxy({
|
|
219
220
|
...options,
|
|
220
221
|
bridgeService: this._bridgeService,
|
|
@@ -222,7 +223,6 @@ export class SimplePeerTransportProxyFactory implements TransportFactory {
|
|
|
222
223
|
|
|
223
224
|
this._connections.add(transport);
|
|
224
225
|
transport.closed.on(() => this._connections.delete(transport));
|
|
225
|
-
|
|
226
226
|
return transport;
|
|
227
227
|
}
|
|
228
228
|
}
|
|
@@ -230,7 +230,6 @@ export class SimplePeerTransportProxyFactory implements TransportFactory {
|
|
|
230
230
|
// TODO(nf): fix so Errors crossing RPC boundary preserve class
|
|
231
231
|
const decodeError = (err: Error | string) => {
|
|
232
232
|
const message = typeof err === 'string' ? err : err.message;
|
|
233
|
-
|
|
234
233
|
if (message.includes('CONNECTION_RESET')) {
|
|
235
234
|
return new ConnectionResetError(message);
|
|
236
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
|
-
});
|
|
@@ -7,22 +7,27 @@ import { ErrorStream } from '@dxos/debug';
|
|
|
7
7
|
|
|
8
8
|
import { type Transport, type TransportFactory, type TransportStats } from './transport';
|
|
9
9
|
|
|
10
|
-
// NOTE: Browser stub.
|
|
11
|
-
|
|
12
10
|
export const TcpTransportFactory: TransportFactory = {
|
|
13
11
|
createTransport: () => new TcpTransport(),
|
|
14
12
|
};
|
|
15
13
|
|
|
14
|
+
/**
|
|
15
|
+
* NOTE: Browser stub.
|
|
16
|
+
*/
|
|
16
17
|
export class TcpTransport implements Transport {
|
|
17
18
|
public readonly closed = new Event<void>();
|
|
18
19
|
public readonly connected = new Event<void>();
|
|
19
20
|
public readonly errors = new ErrorStream();
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
get isOpen() {
|
|
23
|
+
return true;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
async open() {}
|
|
27
|
+
|
|
28
|
+
async close() {}
|
|
29
|
+
|
|
30
|
+
async onSignal() {
|
|
26
31
|
throw new Error('Method not implemented.');
|
|
27
32
|
}
|
|
28
33
|
|