@dxos/network-manager 0.1.55 → 0.1.56-main.09ca29d
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-IBLQYMBY.mjs → chunk-FC6CWDES.mjs} +823 -294
- package/dist/lib/browser/chunk-FC6CWDES.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +5 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +2 -2
- package/dist/lib/node/index.cjs +843 -312
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +772 -301
- package/dist/lib/node/testing/index.cjs.map +4 -4
- package/dist/types/src/network-manager.d.ts +1 -1
- package/dist/types/src/network-manager.d.ts.map +1 -1
- package/dist/types/src/signal/swarm-messenger.d.ts.map +1 -1
- package/dist/types/src/swarm/connection-limiter.d.ts +28 -0
- package/dist/types/src/swarm/connection-limiter.d.ts.map +1 -0
- package/dist/types/src/swarm/connection-limiter.test.d.ts +2 -0
- package/dist/types/src/swarm/connection-limiter.test.d.ts.map +1 -0
- package/dist/types/src/swarm/connection.d.ts +1 -1
- package/dist/types/src/swarm/connection.d.ts.map +1 -1
- package/dist/types/src/swarm/connection.test.d.ts +2 -0
- package/dist/types/src/swarm/connection.test.d.ts.map +1 -0
- package/dist/types/src/swarm/index.d.ts +1 -0
- package/dist/types/src/swarm/index.d.ts.map +1 -1
- package/dist/types/src/swarm/peer.d.ts +3 -1
- package/dist/types/src/swarm/peer.d.ts.map +1 -1
- package/dist/types/src/swarm/swarm.d.ts +3 -1
- package/dist/types/src/swarm/swarm.d.ts.map +1 -1
- package/dist/types/src/topology/fully-connected-topology.d.ts.map +1 -1
- package/dist/types/src/topology/star-topology.d.ts.map +1 -1
- package/dist/types/src/transport/memory-transport.d.ts.map +1 -1
- package/dist/types/src/transport/webrtc-transport-proxy.d.ts.map +1 -1
- package/dist/types/src/transport/webrtc-transport-service.d.ts.map +1 -1
- package/dist/types/src/transport/webrtc-transport.d.ts.map +1 -1
- package/package.json +18 -18
- package/src/network-manager.ts +19 -6
- package/src/signal/swarm-messenger.ts +1 -2
- package/src/swarm/connection-limiter.test.ts +44 -0
- package/src/swarm/connection-limiter.ts +72 -0
- package/src/swarm/connection.test.ts +72 -0
- package/src/swarm/connection.ts +18 -5
- package/src/swarm/index.ts +1 -0
- package/src/swarm/peer.ts +46 -9
- package/src/swarm/swarm.test.ts +140 -90
- package/src/swarm/swarm.ts +4 -3
- package/src/topology/fully-connected-topology.ts +1 -2
- package/src/topology/mmst-topology.ts +1 -1
- package/src/topology/star-topology.ts +1 -2
- package/src/transport/memory-transport.ts +1 -1
- package/src/transport/webrtc-transport-proxy.ts +14 -12
- package/src/transport/webrtc-transport-service.ts +31 -8
- package/src/transport/webrtc-transport.ts +1 -1
- package/dist/lib/browser/chunk-IBLQYMBY.mjs.map +0 -7
package/src/swarm/peer.ts
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import invariant from 'tiny-invariant';
|
|
6
|
-
|
|
7
5
|
import { scheduleTask, synchronized } from '@dxos/async';
|
|
8
6
|
import { Context } from '@dxos/context';
|
|
7
|
+
import { CancelledError } from '@dxos/errors';
|
|
8
|
+
import { invariant } from '@dxos/invariant';
|
|
9
9
|
import { PublicKey } from '@dxos/keys';
|
|
10
10
|
import { log } from '@dxos/log';
|
|
11
11
|
import { Answer } from '@dxos/protocols/proto/dxos/mesh/swarm';
|
|
@@ -14,6 +14,7 @@ import { OfferMessage, SignalMessage, SignalMessenger } from '../signal';
|
|
|
14
14
|
import { TransportFactory } from '../transport';
|
|
15
15
|
import { WireProtocolProvider } from '../wire-protocol';
|
|
16
16
|
import { Connection, ConnectionState } from './connection';
|
|
17
|
+
import { ConnectionLimiter } from './connection-limiter';
|
|
17
18
|
|
|
18
19
|
interface PeerCallbacks {
|
|
19
20
|
/**
|
|
@@ -80,7 +81,6 @@ export class Peer {
|
|
|
80
81
|
public advertizing = false;
|
|
81
82
|
|
|
82
83
|
public initiating = false;
|
|
83
|
-
|
|
84
84
|
constructor(
|
|
85
85
|
public readonly id: PublicKey,
|
|
86
86
|
public readonly topic: PublicKey,
|
|
@@ -88,6 +88,7 @@ export class Peer {
|
|
|
88
88
|
private readonly _signalMessaging: SignalMessenger,
|
|
89
89
|
private readonly _protocolProvider: WireProtocolProvider,
|
|
90
90
|
private readonly _transportFactory: TransportFactory,
|
|
91
|
+
private readonly _connectionLimiter: ConnectionLimiter,
|
|
91
92
|
private readonly _callbacks: PeerCallbacks,
|
|
92
93
|
) {}
|
|
93
94
|
|
|
@@ -102,10 +103,11 @@ export class Peer {
|
|
|
102
103
|
// Peer with the highest Id closes its connection, and accepts remote peer's offer.
|
|
103
104
|
if (remoteId.toHex() < this.localPeerId.toHex()) {
|
|
104
105
|
// TODO(burdon): Too verbose.
|
|
105
|
-
log(
|
|
106
|
-
|
|
106
|
+
log('close local connection', {
|
|
107
|
+
localPeerId: this.id,
|
|
107
108
|
topic: this.topic,
|
|
108
|
-
|
|
109
|
+
remotePeerId: this.localPeerId,
|
|
110
|
+
sessionId: this.connection?.sessionId,
|
|
109
111
|
});
|
|
110
112
|
|
|
111
113
|
if (this.connection) {
|
|
@@ -123,10 +125,15 @@ export class Peer {
|
|
|
123
125
|
// Connection might have been already established.
|
|
124
126
|
invariant(message.sessionId);
|
|
125
127
|
const connection = this._createConnection(false, message.sessionId);
|
|
128
|
+
|
|
126
129
|
try {
|
|
127
|
-
|
|
130
|
+
await this._connectionLimiter.connecting(message.sessionId);
|
|
131
|
+
await connection.openConnection();
|
|
128
132
|
} catch (err: any) {
|
|
129
|
-
|
|
133
|
+
if (!(err instanceof CancelledError)) {
|
|
134
|
+
log.warn('connection error', { topic: this.topic, peerId: this.localPeerId, remoteId: this.id, err });
|
|
135
|
+
}
|
|
136
|
+
|
|
130
137
|
// Calls `onStateChange` with CLOSED state.
|
|
131
138
|
await this.closeConnection();
|
|
132
139
|
}
|
|
@@ -145,10 +152,13 @@ export class Peer {
|
|
|
145
152
|
invariant(!this.connection, 'Already connected.');
|
|
146
153
|
const sessionId = PublicKey.random();
|
|
147
154
|
log('initiating...', { id: this.id, topic: this.topic, peerId: this.id, sessionId });
|
|
155
|
+
|
|
148
156
|
const connection = this._createConnection(true, sessionId);
|
|
149
157
|
this.initiating = true;
|
|
150
158
|
|
|
151
159
|
try {
|
|
160
|
+
await this._connectionLimiter.connecting(sessionId);
|
|
161
|
+
|
|
152
162
|
const answer = await this._signalMessaging.offer({
|
|
153
163
|
author: this.localPeerId,
|
|
154
164
|
recipient: this.id,
|
|
@@ -166,7 +176,7 @@ export class Peer {
|
|
|
166
176
|
this._callbacks.onRejected();
|
|
167
177
|
return;
|
|
168
178
|
}
|
|
169
|
-
connection.openConnection();
|
|
179
|
+
await connection.openConnection();
|
|
170
180
|
this._callbacks.onAccepted();
|
|
171
181
|
} catch (err: any) {
|
|
172
182
|
log('initiation error', { err, topic: this.topic, peerId: this.localPeerId, remoteId: this.id });
|
|
@@ -214,6 +224,15 @@ export class Peer {
|
|
|
214
224
|
this.availableToConnect = true;
|
|
215
225
|
this._lastConnectionTime = Date.now();
|
|
216
226
|
this._callbacks.onConnected();
|
|
227
|
+
|
|
228
|
+
this._connectionLimiter.doneConnecting(sessionId);
|
|
229
|
+
log.trace('dxos.mesh.connection.connected', {
|
|
230
|
+
topic: this.topic,
|
|
231
|
+
localPeerId: this.localPeerId,
|
|
232
|
+
remotePeerId: this.id,
|
|
233
|
+
sessionId,
|
|
234
|
+
initiator,
|
|
235
|
+
});
|
|
217
236
|
break;
|
|
218
237
|
}
|
|
219
238
|
|
|
@@ -221,6 +240,14 @@ export class Peer {
|
|
|
221
240
|
log('connection closed', { topic: this.topic, peerId: this.localPeerId, remoteId: this.id, initiator });
|
|
222
241
|
invariant(this.connection === connection, 'Connection mismatch (race condition).');
|
|
223
242
|
|
|
243
|
+
log.trace('dxos.mesh.connection.closed', {
|
|
244
|
+
topic: this.topic,
|
|
245
|
+
localPeerId: this.localPeerId,
|
|
246
|
+
remotePeerId: this.id,
|
|
247
|
+
sessionId,
|
|
248
|
+
initiator,
|
|
249
|
+
});
|
|
250
|
+
|
|
224
251
|
if (this._lastConnectionTime && this._lastConnectionTime + CONNECTION_COUNTS_STABLE_AFTER < Date.now()) {
|
|
225
252
|
// If we're closing the connection, and it has been connected for a while, reset the backoff.
|
|
226
253
|
this._availableAfter = 0;
|
|
@@ -231,6 +258,7 @@ export class Peer {
|
|
|
231
258
|
|
|
232
259
|
this.connection = undefined;
|
|
233
260
|
this._callbacks.onDisconnected();
|
|
261
|
+
this._connectionLimiter.doneConnecting(sessionId);
|
|
234
262
|
|
|
235
263
|
scheduleTask(
|
|
236
264
|
this._connectionCtx!,
|
|
@@ -247,6 +275,14 @@ export class Peer {
|
|
|
247
275
|
});
|
|
248
276
|
connection.errors.handle((err) => {
|
|
249
277
|
log.warn('connection error', { topic: this.topic, peerId: this.localPeerId, remoteId: this.id, initiator, err });
|
|
278
|
+
log.trace('dxos.mesh.connection.error', {
|
|
279
|
+
topic: this.topic,
|
|
280
|
+
localPeerId: this.localPeerId,
|
|
281
|
+
remotePeerId: this.id,
|
|
282
|
+
sessionId,
|
|
283
|
+
initiator,
|
|
284
|
+
err,
|
|
285
|
+
});
|
|
250
286
|
|
|
251
287
|
// Calls `onStateChange` with CLOSED state.
|
|
252
288
|
void this.closeConnection();
|
|
@@ -276,6 +312,7 @@ export class Peer {
|
|
|
276
312
|
log('dropping signal message for non-existent connection', { message });
|
|
277
313
|
return;
|
|
278
314
|
}
|
|
315
|
+
|
|
279
316
|
await this.connection.signal(message);
|
|
280
317
|
}
|
|
281
318
|
|
package/src/swarm/swarm.test.ts
CHANGED
|
@@ -2,31 +2,43 @@
|
|
|
2
2
|
// Copyright 2020 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { expect } from 'earljs';
|
|
5
|
+
import { expect } from 'chai';
|
|
8
6
|
|
|
9
7
|
import { asyncTimeout, sleep } from '@dxos/async';
|
|
10
8
|
import { PublicKey } from '@dxos/keys';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
9
|
+
import { MemorySignalManager, MemorySignalManagerContext, Messenger, SignalManager } from '@dxos/messaging';
|
|
10
|
+
import { afterTest, describe, test } from '@dxos/test';
|
|
11
|
+
import { ComplexSet } from '@dxos/util';
|
|
14
12
|
|
|
15
13
|
import { TestWireProtocol } from '../testing/test-wire-protocol';
|
|
16
14
|
import { FullyConnectedTopology } from '../topology';
|
|
17
15
|
import { createWebRTCTransportFactory } from '../transport';
|
|
16
|
+
import { ConnectionState } from './connection';
|
|
17
|
+
import { ConnectionLimiter } from './connection-limiter';
|
|
18
18
|
import { Swarm } from './swarm';
|
|
19
19
|
|
|
20
|
+
type TestPeer = {
|
|
21
|
+
swarm: Swarm;
|
|
22
|
+
peerId: PublicKey;
|
|
23
|
+
protocol: TestWireProtocol;
|
|
24
|
+
topic: PublicKey;
|
|
25
|
+
signalManager: SignalManager;
|
|
26
|
+
};
|
|
27
|
+
|
|
20
28
|
describe('Swarm', () => {
|
|
21
29
|
const context = new MemorySignalManagerContext();
|
|
22
|
-
let signalManager: MemorySignalManager;
|
|
23
|
-
|
|
24
|
-
beforeEach(() => {
|
|
25
|
-
signalManager = new MemorySignalManager(context);
|
|
26
|
-
afterTest(() => signalManager.close());
|
|
27
|
-
});
|
|
28
30
|
|
|
29
|
-
const setupSwarm = async ({
|
|
31
|
+
const setupSwarm = async ({
|
|
32
|
+
topic = PublicKey.random(),
|
|
33
|
+
peerId = PublicKey.random(),
|
|
34
|
+
connectionLimiter = new ConnectionLimiter(),
|
|
35
|
+
signalManager = new MemorySignalManager(context),
|
|
36
|
+
}: {
|
|
37
|
+
topic?: PublicKey;
|
|
38
|
+
peerId?: PublicKey;
|
|
39
|
+
connectionLimiter?: ConnectionLimiter;
|
|
40
|
+
signalManager?: SignalManager;
|
|
41
|
+
}): Promise<TestPeer> => {
|
|
30
42
|
const protocol = new TestWireProtocol(peerId);
|
|
31
43
|
const swarm = new Swarm(
|
|
32
44
|
topic,
|
|
@@ -36,116 +48,154 @@ describe('Swarm', () => {
|
|
|
36
48
|
new Messenger({ signalManager }),
|
|
37
49
|
createWebRTCTransportFactory(),
|
|
38
50
|
undefined,
|
|
51
|
+
connectionLimiter,
|
|
39
52
|
);
|
|
40
53
|
|
|
41
54
|
afterTest(async () => {
|
|
42
55
|
await swarm.destroy();
|
|
56
|
+
await signalManager.close();
|
|
43
57
|
});
|
|
44
58
|
|
|
45
59
|
await swarm.open();
|
|
46
60
|
|
|
47
|
-
return { swarm, protocol };
|
|
61
|
+
return { swarm, protocol, topic, peerId, signalManager };
|
|
48
62
|
};
|
|
49
63
|
|
|
50
64
|
test('connects two peers in a swarm', async () => {
|
|
51
65
|
const topic = PublicKey.random();
|
|
52
|
-
const peerId1 = PublicKey.random();
|
|
53
|
-
const peerId2 = PublicKey.random();
|
|
54
66
|
|
|
55
|
-
const
|
|
67
|
+
const peer1 = await setupSwarm({ topic });
|
|
68
|
+
const peer2 = await setupSwarm({ topic });
|
|
56
69
|
|
|
57
|
-
|
|
70
|
+
expect(peer1.swarm.connections.length).to.equal(0);
|
|
71
|
+
expect(peer2.swarm.connections.length).to.equal(0);
|
|
58
72
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const promise = Promise.all([
|
|
63
|
-
asyncTimeout(swarm1.connected.waitForCount(1), 3000, new Error('Swarm1 connect timeout.')),
|
|
64
|
-
asyncTimeout(swarm2.connected.waitForCount(1), 3000, new Error('Swarm2 connect timeout.')),
|
|
65
|
-
]);
|
|
73
|
+
await connectSwarms(peer1, peer2);
|
|
74
|
+
}).timeout(5_000);
|
|
66
75
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
peerAvailable: {
|
|
70
|
-
peer: peerId1.asUint8Array(),
|
|
71
|
-
since: new Date(),
|
|
72
|
-
},
|
|
73
|
-
});
|
|
76
|
+
test('two peers try to originate connections to each other simultaneously', async () => {
|
|
77
|
+
const topic = PublicKey.random();
|
|
74
78
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
peer: peerId2.asUint8Array(),
|
|
78
|
-
since: new Date(),
|
|
79
|
-
},
|
|
80
|
-
});
|
|
79
|
+
const peer1 = await setupSwarm({ topic });
|
|
80
|
+
const peer2 = await setupSwarm({ topic });
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
log('Swarms connected');
|
|
82
|
+
expect(peer1.swarm.connections.length).to.equal(0);
|
|
83
|
+
expect(peer2.swarm.connections.length).to.equal(0);
|
|
85
84
|
|
|
86
|
-
await
|
|
85
|
+
await connectSwarms(peer1, peer2);
|
|
87
86
|
}).timeout(5_000);
|
|
88
87
|
|
|
89
|
-
test('
|
|
88
|
+
test('second peer discovered after delay', async () => {
|
|
90
89
|
const topic = PublicKey.random();
|
|
91
|
-
const peerId1 = PublicKey.random();
|
|
92
|
-
const peerId2 = PublicKey.random();
|
|
93
90
|
|
|
94
|
-
const
|
|
95
|
-
const
|
|
91
|
+
const peer1 = await setupSwarm({ topic });
|
|
92
|
+
const peer2 = await setupSwarm({ topic });
|
|
96
93
|
|
|
97
|
-
expect(
|
|
98
|
-
expect(
|
|
94
|
+
expect(peer1.swarm.connections.length).to.equal(0);
|
|
95
|
+
expect(peer2.swarm.connections.length).to.equal(0);
|
|
99
96
|
|
|
100
|
-
|
|
97
|
+
expect(peer1.swarm.connections.length).to.equal(0);
|
|
98
|
+
expect(peer2.swarm.connections.length).to.equal(0);
|
|
101
99
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
peer: peerId2.asUint8Array(),
|
|
105
|
-
since: new Date(),
|
|
106
|
-
},
|
|
107
|
-
});
|
|
100
|
+
await connectSwarms(peer1, peer2, () => sleep(15));
|
|
101
|
+
}).timeout(10_000);
|
|
108
102
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
peer: peerId1.asUint8Array(),
|
|
112
|
-
since: new Date(),
|
|
113
|
-
},
|
|
114
|
-
});
|
|
103
|
+
test('connection limiter', async () => {
|
|
104
|
+
// remotePeer1 <--> peer (connectionLimiter: max = 1) <--> remotePeer2
|
|
115
105
|
|
|
116
|
-
|
|
117
|
-
|
|
106
|
+
const peerId = PublicKey.fromHex('7701dc2d');
|
|
107
|
+
const remotePeerId1 = PublicKey.fromHex('7d2bc6aa');
|
|
108
|
+
const remotePeerId2 = PublicKey.fromHex('39ba0e41');
|
|
118
109
|
|
|
119
|
-
test('second peer discovered after delay', async () => {
|
|
120
110
|
const topic = PublicKey.random();
|
|
121
|
-
const
|
|
122
|
-
const peerId2 = PublicKey.random();
|
|
111
|
+
const connectionLimiter = new ConnectionLimiter({ maxConcurrentInitConnections: 1 });
|
|
123
112
|
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
const
|
|
113
|
+
const signalManager = new MemorySignalManager(context);
|
|
114
|
+
const sendOriginal = signalManager.sendMessage.bind(signalManager);
|
|
115
|
+
const messages = new ComplexSet<{ author: PublicKey; recipient: PublicKey }>(
|
|
116
|
+
({ author, recipient }) => author.toHex() + recipient.toHex(),
|
|
117
|
+
);
|
|
118
|
+
signalManager.sendMessage = async (message) => {
|
|
119
|
+
messages.add({ author: message.author, recipient: message.recipient });
|
|
120
|
+
return sendOriginal(message);
|
|
121
|
+
};
|
|
122
|
+
// Stop signaling to stop connection in initiation state.
|
|
123
|
+
signalManager.freeze();
|
|
124
|
+
|
|
125
|
+
const peer = await setupSwarm({
|
|
126
|
+
topic,
|
|
127
|
+
peerId,
|
|
128
|
+
connectionLimiter,
|
|
129
|
+
signalManager,
|
|
130
|
+
});
|
|
131
|
+
const remotePeer1 = await setupSwarm({ peerId: remotePeerId1, topic });
|
|
132
|
+
const remotePeer2 = await setupSwarm({ peerId: remotePeerId2, topic });
|
|
133
|
+
|
|
134
|
+
let connected1: Promise<void>;
|
|
135
|
+
{
|
|
136
|
+
// Connection limiter allow only one connection to be started.
|
|
137
|
+
const connectionInit = peer.swarm.connectionAdded.waitForCount(1);
|
|
138
|
+
connected1 = connectSwarms(peer, remotePeer1);
|
|
139
|
+
await asyncTimeout(connectionInit, 1000);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let connected2: Promise<void>;
|
|
143
|
+
{
|
|
144
|
+
// Connection limiter should prevent second connection from being started (only created).
|
|
145
|
+
const connectionInit = peer.swarm.connectionAdded.waitForCount(1);
|
|
146
|
+
connected2 = connectSwarms(peer, remotePeer2);
|
|
147
|
+
await asyncTimeout(connectionInit, 1000);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
{
|
|
151
|
+
// Peer sent messages only to first remote peer.
|
|
152
|
+
expect(messages.has({ author: peer.peerId, recipient: remotePeer1.peerId })).to.equal(true);
|
|
153
|
+
expect(messages.has({ author: peer.peerId, recipient: remotePeer2.peerId })).to.equal(false);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
signalManager.unfreeze();
|
|
157
|
+
|
|
158
|
+
await Promise.all([connected1, connected2]);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
127
161
|
|
|
128
|
-
|
|
129
|
-
|
|
162
|
+
const connectSwarms = async (peer1: TestPeer, peer2: TestPeer, delay = async () => {}) => {
|
|
163
|
+
const connect1 = peer1.swarm.connected.waitForCount(1);
|
|
164
|
+
const connect2 = peer2.swarm.connected.waitForCount(1);
|
|
130
165
|
|
|
131
|
-
|
|
166
|
+
peer1.swarm.onSwarmEvent({
|
|
167
|
+
peerAvailable: {
|
|
168
|
+
peer: peer2.peerId.asUint8Array(),
|
|
169
|
+
since: new Date(),
|
|
170
|
+
},
|
|
171
|
+
});
|
|
132
172
|
|
|
133
|
-
|
|
134
|
-
peerAvailable: {
|
|
135
|
-
peer: peerId2.asUint8Array(),
|
|
136
|
-
since: new Date(),
|
|
137
|
-
},
|
|
138
|
-
});
|
|
139
|
-
await sleep(15);
|
|
140
|
-
swarm2.onSwarmEvent({
|
|
141
|
-
peerAvailable: {
|
|
142
|
-
peer: peerId1.asUint8Array(),
|
|
143
|
-
since: new Date(),
|
|
144
|
-
},
|
|
145
|
-
});
|
|
173
|
+
await delay();
|
|
146
174
|
|
|
147
|
-
|
|
175
|
+
peer2.swarm.onSwarmEvent({
|
|
176
|
+
peerAvailable: {
|
|
177
|
+
peer: peer1.peerId.asUint8Array(),
|
|
178
|
+
since: new Date(),
|
|
179
|
+
},
|
|
180
|
+
});
|
|
148
181
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
182
|
+
if (
|
|
183
|
+
!(
|
|
184
|
+
peer1.swarm.connections.find((connection) => connection.remoteId.equals(peer2.peerId))?.state ===
|
|
185
|
+
ConnectionState.CONNECTED
|
|
186
|
+
)
|
|
187
|
+
) {
|
|
188
|
+
await asyncTimeout(connect1, 3000);
|
|
189
|
+
}
|
|
190
|
+
if (
|
|
191
|
+
!(
|
|
192
|
+
peer2.swarm.connections.find((connection) => connection.remoteId.equals(peer1.peerId))?.state ===
|
|
193
|
+
ConnectionState.CONNECTED
|
|
194
|
+
)
|
|
195
|
+
) {
|
|
196
|
+
await asyncTimeout(connect2, 3000);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
await peer1.protocol.testConnection(peer2.peerId, 'test message 1');
|
|
200
|
+
await peer2.protocol.testConnection(peer1.peerId, 'test message 2');
|
|
201
|
+
};
|
package/src/swarm/swarm.ts
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
// Copyright 2020 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import invariant from 'tiny-invariant';
|
|
6
|
-
|
|
7
5
|
import { Event, scheduleTask, sleep, synchronized } from '@dxos/async';
|
|
8
6
|
import { Context } from '@dxos/context';
|
|
9
7
|
import { ErrorStream } from '@dxos/debug';
|
|
8
|
+
import { invariant } from '@dxos/invariant';
|
|
10
9
|
import { PublicKey } from '@dxos/keys';
|
|
11
10
|
import { log, logInfo } from '@dxos/log';
|
|
12
11
|
import { ListeningHandle, Messenger } from '@dxos/messaging';
|
|
@@ -21,6 +20,7 @@ import { TransportFactory } from '../transport';
|
|
|
21
20
|
import { Topic } from '../types';
|
|
22
21
|
import { WireProtocolProvider } from '../wire-protocol';
|
|
23
22
|
import { Connection, ConnectionState } from './connection';
|
|
23
|
+
import { ConnectionLimiter } from './connection-limiter';
|
|
24
24
|
import { Peer } from './peer';
|
|
25
25
|
|
|
26
26
|
const INITIATION_DELAY = 100;
|
|
@@ -82,6 +82,7 @@ export class Swarm {
|
|
|
82
82
|
private readonly _messenger: Messenger,
|
|
83
83
|
private readonly _transportFactory: TransportFactory,
|
|
84
84
|
private readonly _label: string | undefined,
|
|
85
|
+
private readonly _connectionLimiter: ConnectionLimiter,
|
|
85
86
|
) {
|
|
86
87
|
log.trace(
|
|
87
88
|
'dxos.mesh.swarm.constructor',
|
|
@@ -217,7 +218,6 @@ export class Swarm {
|
|
|
217
218
|
return answer;
|
|
218
219
|
}
|
|
219
220
|
|
|
220
|
-
@synchronized
|
|
221
221
|
async onSignal(message: SignalMessage): Promise<void> {
|
|
222
222
|
log('signal', { message });
|
|
223
223
|
if (this._ctx.disposed) {
|
|
@@ -258,6 +258,7 @@ export class Swarm {
|
|
|
258
258
|
this._swarmMessenger,
|
|
259
259
|
this._protocolProvider,
|
|
260
260
|
this._transportFactory,
|
|
261
|
+
this._connectionLimiter,
|
|
261
262
|
{
|
|
262
263
|
onInitiated: (connection) => {
|
|
263
264
|
this.connectionAdded.emit(connection);
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Transform } from 'node:stream';
|
|
6
|
-
import invariant from 'tiny-invariant';
|
|
7
6
|
|
|
8
7
|
import { Event, Trigger } from '@dxos/async';
|
|
9
8
|
import { ErrorStream } from '@dxos/debug';
|
|
9
|
+
import { invariant } from '@dxos/invariant';
|
|
10
10
|
import { PublicKey } from '@dxos/keys';
|
|
11
11
|
import { log, logInfo } from '@dxos/log';
|
|
12
12
|
import { Signal } from '@dxos/protocols/proto/dxos/mesh/swarm';
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { Writable } from 'node:stream';
|
|
6
6
|
|
|
7
7
|
import { Event } from '@dxos/async';
|
|
8
8
|
import { Stream } from '@dxos/codec-protobuf';
|
|
9
9
|
import { Context } from '@dxos/context';
|
|
10
10
|
import { ErrorStream } from '@dxos/debug';
|
|
11
|
+
import { invariant } from '@dxos/invariant';
|
|
11
12
|
import { PublicKey } from '@dxos/keys';
|
|
12
13
|
import { log } from '@dxos/log';
|
|
13
14
|
import { ConnectionState, BridgeEvent, BridgeService } from '@dxos/protocols/proto/dxos/mesh/bridge';
|
|
@@ -57,18 +58,19 @@ export class WebRTCTransportProxy implements Transport {
|
|
|
57
58
|
}
|
|
58
59
|
});
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
this._params.stream.pipe(new Writable({
|
|
62
|
+
write: (chunk, _, callback) => {
|
|
63
|
+
this._params.bridgeService.sendData({
|
|
63
64
|
proxyId: this._proxyId,
|
|
64
|
-
payload:
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
payload: chunk
|
|
66
|
+
}).then(
|
|
67
|
+
() => callback(),
|
|
68
|
+
(err: any) => {
|
|
69
|
+
log.catch(err);
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
},
|
|
73
|
+
}));
|
|
72
74
|
},
|
|
73
75
|
(error) => log.catch(error)
|
|
74
76
|
);
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Duplex } from 'node:stream';
|
|
6
|
-
import invariant from 'tiny-invariant';
|
|
7
6
|
|
|
8
7
|
import { Stream } from '@dxos/codec-protobuf';
|
|
8
|
+
import { invariant } from '@dxos/invariant';
|
|
9
9
|
import { PublicKey } from '@dxos/keys';
|
|
10
10
|
import { log } from '@dxos/log';
|
|
11
11
|
import {
|
|
@@ -21,20 +21,30 @@ import { ComplexMap } from '@dxos/util';
|
|
|
21
21
|
|
|
22
22
|
import { WebRTCTransport } from './webrtc-transport';
|
|
23
23
|
|
|
24
|
+
type TransportState = {
|
|
25
|
+
transport: WebRTCTransport;
|
|
26
|
+
stream: Duplex;
|
|
27
|
+
writeCallbacks: (() => void)[];
|
|
28
|
+
};
|
|
29
|
+
|
|
24
30
|
export class WebRTCTransportService implements BridgeService {
|
|
25
|
-
private readonly transports = new ComplexMap<PublicKey,
|
|
26
|
-
PublicKey.hash,
|
|
27
|
-
);
|
|
31
|
+
private readonly transports = new ComplexMap<PublicKey, TransportState>(PublicKey.hash);
|
|
28
32
|
|
|
29
33
|
// prettier-ignore
|
|
30
34
|
constructor(
|
|
31
35
|
private readonly _webrtcConfig?: any
|
|
32
|
-
) {}
|
|
36
|
+
) { }
|
|
33
37
|
|
|
34
38
|
open(request: ConnectionRequest): Stream<BridgeEvent> {
|
|
35
39
|
const rpcStream: Stream<BridgeEvent> = new Stream(({ ready, next, close }) => {
|
|
36
40
|
const duplex: Duplex = new Duplex({
|
|
37
|
-
read: () => {
|
|
41
|
+
read: () => {
|
|
42
|
+
const callbacks = [...transportState.writeCallbacks];
|
|
43
|
+
transportState.writeCallbacks.length = 0;
|
|
44
|
+
for (const cb of callbacks) {
|
|
45
|
+
cb();
|
|
46
|
+
}
|
|
47
|
+
},
|
|
38
48
|
write: function (chunk, _, callback) {
|
|
39
49
|
next({ data: { payload: chunk } });
|
|
40
50
|
callback();
|
|
@@ -85,8 +95,15 @@ export class WebRTCTransportService implements BridgeService {
|
|
|
85
95
|
close();
|
|
86
96
|
});
|
|
87
97
|
|
|
98
|
+
const transportState: TransportState = {
|
|
99
|
+
transport,
|
|
100
|
+
stream: duplex,
|
|
101
|
+
writeCallbacks: [],
|
|
102
|
+
};
|
|
103
|
+
|
|
88
104
|
ready();
|
|
89
|
-
|
|
105
|
+
|
|
106
|
+
this.transports.set(request.proxyId, transportState);
|
|
90
107
|
});
|
|
91
108
|
|
|
92
109
|
return rpcStream;
|
|
@@ -99,7 +116,13 @@ export class WebRTCTransportService implements BridgeService {
|
|
|
99
116
|
|
|
100
117
|
async sendData({ proxyId, payload }: DataRequest): Promise<void> {
|
|
101
118
|
invariant(this.transports.has(proxyId));
|
|
102
|
-
|
|
119
|
+
const state = this.transports.get(proxyId)!;
|
|
120
|
+
const bufferHasSpace = state.stream.push(payload);
|
|
121
|
+
if (!bufferHasSpace) {
|
|
122
|
+
await new Promise<void>((resolve) => {
|
|
123
|
+
state.writeCallbacks.push(resolve);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
103
126
|
}
|
|
104
127
|
|
|
105
128
|
async close({ proxyId }: CloseRequest) {
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import SimplePeerConstructor, { Instance as SimplePeer } from 'simple-peer';
|
|
6
|
-
import invariant from 'tiny-invariant';
|
|
7
6
|
|
|
8
7
|
import { Event } from '@dxos/async';
|
|
9
8
|
import { ErrorStream, raise } from '@dxos/debug';
|
|
9
|
+
import { invariant } from '@dxos/invariant';
|
|
10
10
|
import { PublicKey } from '@dxos/keys';
|
|
11
11
|
import { log } from '@dxos/log';
|
|
12
12
|
import { trace } from '@dxos/protocols';
|