@dxos/network-manager 2.33.8-dev.6bc74570 → 2.33.8
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/browser-mocha/bundle.js +15046 -15255
- package/dist/src/network-manager.d.ts +1 -0
- package/dist/src/network-manager.d.ts.map +1 -1
- package/dist/src/network-manager.js +8 -3
- package/dist/src/network-manager.js.map +1 -1
- package/dist/src/proto/gen/dxos/credentials.d.ts +26 -0
- package/dist/src/proto/gen/dxos/credentials.d.ts.map +1 -0
- package/dist/src/proto/gen/dxos/credentials.js +3 -0
- package/dist/src/proto/gen/dxos/credentials.js.map +1 -0
- package/dist/src/proto/gen/dxos/halo/keys.d.ts +60 -0
- package/dist/src/proto/gen/dxos/halo/keys.d.ts.map +1 -0
- package/dist/src/proto/gen/dxos/halo/keys.js +13 -0
- package/dist/src/proto/gen/dxos/halo/keys.js.map +1 -0
- package/dist/src/proto/gen/dxos/mesh/signal.d.ts +28 -0
- package/dist/src/proto/gen/dxos/mesh/signal.d.ts.map +1 -0
- package/dist/src/proto/gen/dxos/mesh/signal.js +3 -0
- package/dist/src/proto/gen/dxos/mesh/signal.js.map +1 -0
- package/dist/src/proto/gen/google/protobuf.d.ts +5 -0
- package/dist/src/proto/gen/google/protobuf.d.ts.map +1 -0
- package/dist/src/proto/gen/google/protobuf.js +3 -0
- package/dist/src/proto/gen/google/protobuf.js.map +1 -0
- package/dist/src/proto/gen/index.d.ts +28 -0
- package/dist/src/proto/gen/index.d.ts.map +1 -0
- package/dist/src/proto/gen/index.js +11 -0
- package/dist/src/proto/gen/index.js.map +1 -0
- package/dist/src/proto/substitutions.d.ts +17 -0
- package/dist/src/proto/substitutions.d.ts.map +1 -0
- package/dist/src/proto/substitutions.js +10 -0
- package/dist/src/proto/substitutions.js.map +1 -0
- package/dist/src/signal/in-memory-signal-manager.d.ts +5 -4
- package/dist/src/signal/in-memory-signal-manager.d.ts.map +1 -1
- package/dist/src/signal/in-memory-signal-manager.js +2 -0
- package/dist/src/signal/in-memory-signal-manager.js.map +1 -1
- package/dist/src/signal/message-router.d.ts +24 -0
- package/dist/src/signal/message-router.d.ts.map +1 -0
- package/dist/src/signal/message-router.js +71 -0
- package/dist/src/signal/message-router.js.map +1 -0
- package/dist/src/signal/message-router.test.d.ts +2 -0
- package/dist/src/signal/message-router.test.d.ts.map +1 -0
- package/dist/src/signal/message-router.test.js +158 -0
- package/dist/src/signal/message-router.test.js.map +1 -0
- package/dist/src/signal/signal-api.d.ts +6 -0
- package/dist/src/signal/signal-api.d.ts.map +1 -1
- package/dist/src/signal/signal-api.js.map +1 -1
- package/dist/src/signal/signal-client.d.ts +4 -3
- package/dist/src/signal/signal-client.d.ts.map +1 -1
- package/dist/src/signal/signal-client.js +12 -10
- package/dist/src/signal/signal-client.js.map +1 -1
- package/dist/src/signal/signal-client.test.js +23 -4
- package/dist/src/signal/signal-client.test.js.map +1 -1
- package/dist/src/signal/signal-manager.d.ts +22 -7
- package/dist/src/signal/signal-manager.d.ts.map +1 -1
- package/dist/src/signal/websocket-signal-manager.d.ts +5 -4
- package/dist/src/signal/websocket-signal-manager.d.ts.map +1 -1
- package/dist/src/signal/websocket-signal-manager.js.map +1 -1
- package/dist/src/swarm/connection.d.ts +3 -3
- package/dist/src/swarm/connection.d.ts.map +1 -1
- package/dist/src/swarm/connection.js +8 -5
- package/dist/src/swarm/connection.js.map +1 -1
- package/dist/src/swarm/swarm.d.ts +7 -5
- package/dist/src/swarm/swarm.d.ts.map +1 -1
- package/dist/src/swarm/swarm.js +16 -10
- package/dist/src/swarm/swarm.js.map +1 -1
- package/dist/src/swarm/swarm.test.js +42 -4
- package/dist/src/swarm/swarm.test.js.map +1 -1
- package/dist/src/transport/in-memory-transport.d.ts +2 -2
- package/dist/src/transport/in-memory-transport.d.ts.map +1 -1
- package/dist/src/transport/in-memory-transport.js.map +1 -1
- package/dist/src/transport/transport.d.ts +3 -3
- package/dist/src/transport/transport.d.ts.map +1 -1
- package/dist/src/transport/webrtc-transport.d.ts +3 -3
- package/dist/src/transport/webrtc-transport.d.ts.map +1 -1
- package/dist/src/transport/webrtc-transport.js +4 -2
- package/dist/src/transport/webrtc-transport.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -13
- package/src/network-manager.ts +16 -6
- package/src/proto/defs/dxos/mesh/signal.proto +38 -0
- package/src/proto/gen/dxos/credentials.ts +27 -0
- package/src/proto/gen/dxos/halo/keys.ts +62 -0
- package/src/proto/gen/dxos/mesh/signal.ts +31 -0
- package/src/proto/gen/google/protobuf.ts +9 -0
- package/src/proto/gen/index.ts +28 -0
- package/src/proto/substitutions.ts +9 -0
- package/src/signal/in-memory-signal-manager.ts +7 -4
- package/src/signal/message-router.test.ts +218 -0
- package/src/signal/message-router.ts +89 -0
- package/src/signal/signal-api.ts +6 -0
- package/src/signal/signal-client.test.ts +35 -12
- package/src/signal/signal-client.ts +19 -18
- package/src/signal/signal-manager.ts +24 -9
- package/src/signal/websocket-signal-manager.ts +5 -4
- package/src/swarm/connection.ts +11 -9
- package/src/swarm/swarm.test.ts +61 -11
- package/src/swarm/swarm.ts +17 -12
- package/src/transport/in-memory-transport.ts +2 -2
- package/src/transport/transport.ts +3 -3
- package/src/transport/webrtc-transport.ts +6 -5
|
@@ -11,7 +11,7 @@ import { PublicKey } from '@dxos/protocols';
|
|
|
11
11
|
import { createTestBroker } from '@dxos/signal';
|
|
12
12
|
import { randomInt } from '@dxos/util';
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import { Answer, Message } from '../proto/gen/dxos/mesh/signal';
|
|
15
15
|
import { SignalClient } from './signal-client';
|
|
16
16
|
|
|
17
17
|
describe('SignalApi', () => {
|
|
@@ -47,6 +47,29 @@ describe('SignalApi', () => {
|
|
|
47
47
|
// code await broker2.stop();
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
+
test('message between 2 clients', async () => {
|
|
51
|
+
const signalMock1 = mockFn<(msg: Message) => Promise<void>>()
|
|
52
|
+
.resolvesTo();
|
|
53
|
+
api1 = new SignalClient(signalApiUrl1, (async () => {}) as any, signalMock1);
|
|
54
|
+
api2 = new SignalClient(signalApiUrl1, (async () => {}) as any, (async () => {}) as any);
|
|
55
|
+
|
|
56
|
+
await api1.join(topic, peer1);
|
|
57
|
+
await api2.join(topic, peer2);
|
|
58
|
+
|
|
59
|
+
const msg: Message = {
|
|
60
|
+
id: peer2,
|
|
61
|
+
remoteId: peer1,
|
|
62
|
+
sessionId: PublicKey.random(),
|
|
63
|
+
topic,
|
|
64
|
+
data: { signal: { json: "foo: 'bar'" } }
|
|
65
|
+
};
|
|
66
|
+
await api2.signal(msg);
|
|
67
|
+
|
|
68
|
+
await waitForExpect(() => {
|
|
69
|
+
expect(signalMock1).toHaveBeenCalledWith([msg]);
|
|
70
|
+
}, 4_000);
|
|
71
|
+
}).timeout(5_000);
|
|
72
|
+
|
|
50
73
|
test('join', async () => {
|
|
51
74
|
api1 = new SignalClient(signalApiUrl1, (async () => {}) as any, async () => {});
|
|
52
75
|
|
|
@@ -58,14 +81,14 @@ describe('SignalApi', () => {
|
|
|
58
81
|
}).timeout(1_000);
|
|
59
82
|
|
|
60
83
|
test('offer', async () => {
|
|
61
|
-
const offerMock = mockFn<(msg:
|
|
84
|
+
const offerMock = mockFn<(msg: Message) => Promise<Answer>>()
|
|
62
85
|
.resolvesTo({ accept: true });
|
|
63
86
|
api1 = new SignalClient(signalApiUrl1, offerMock, async () => {});
|
|
64
87
|
|
|
65
88
|
await api1.join(topic, peer1);
|
|
66
89
|
|
|
67
|
-
const offer:
|
|
68
|
-
data: {
|
|
90
|
+
const offer: Message = {
|
|
91
|
+
data: { offer: {} },
|
|
69
92
|
id: peer2,
|
|
70
93
|
remoteId: peer1,
|
|
71
94
|
sessionId: PublicKey.random(),
|
|
@@ -77,18 +100,18 @@ describe('SignalApi', () => {
|
|
|
77
100
|
}).timeout(5_000);
|
|
78
101
|
|
|
79
102
|
test('signal', async () => {
|
|
80
|
-
const signalMock = mockFn<(msg:
|
|
103
|
+
const signalMock = mockFn<(msg: Message) => Promise<void>>()
|
|
81
104
|
.resolvesTo();
|
|
82
105
|
api1 = new SignalClient(signalApiUrl1, (async () => {}) as any, signalMock);
|
|
83
106
|
|
|
84
107
|
await api1.join(topic, peer1);
|
|
85
108
|
|
|
86
|
-
const msg:
|
|
109
|
+
const msg: Message = {
|
|
87
110
|
id: peer2,
|
|
88
111
|
remoteId: peer1,
|
|
89
112
|
sessionId: PublicKey.random(),
|
|
90
113
|
topic,
|
|
91
|
-
data: {
|
|
114
|
+
data: { signal: { json: 'bar' } }
|
|
92
115
|
};
|
|
93
116
|
await api1.signal(msg);
|
|
94
117
|
|
|
@@ -118,9 +141,9 @@ describe('SignalApi', () => {
|
|
|
118
141
|
|
|
119
142
|
// Skip because communication between signal servers is not yet implemented.
|
|
120
143
|
test.skip('newly joined peer can receive signals from other signal servers', async () => {
|
|
121
|
-
const offerMock = mockFn<(msg:
|
|
144
|
+
const offerMock = mockFn<(msg: Message) => Promise<Answer>>()
|
|
122
145
|
.resolvesTo({ accept: true });
|
|
123
|
-
const signalMock = mockFn<(msg:
|
|
146
|
+
const signalMock = mockFn<(msg: Message) => Promise<void>>()
|
|
124
147
|
.resolvesTo();
|
|
125
148
|
|
|
126
149
|
api1 = new SignalClient(signalApiUrl1, offerMock, async () => {});
|
|
@@ -136,16 +159,16 @@ describe('SignalApi', () => {
|
|
|
136
159
|
id: peer2,
|
|
137
160
|
topic,
|
|
138
161
|
sessionId,
|
|
139
|
-
data: {}
|
|
162
|
+
data: { offer: {} }
|
|
140
163
|
});
|
|
141
164
|
expect(answer).toEqual({ accept: true });
|
|
142
165
|
|
|
143
|
-
const msg:
|
|
166
|
+
const msg: Message = {
|
|
144
167
|
id: peer2,
|
|
145
168
|
remoteId: peer1,
|
|
146
169
|
sessionId,
|
|
147
170
|
topic,
|
|
148
|
-
data: {
|
|
171
|
+
data: { offer: { json: 'bar' } }
|
|
149
172
|
};
|
|
150
173
|
await api1.signal(msg);
|
|
151
174
|
|
|
@@ -7,6 +7,7 @@ import debug from 'debug';
|
|
|
7
7
|
import { Event } from '@dxos/async';
|
|
8
8
|
import { PublicKey } from '@dxos/protocols';
|
|
9
9
|
|
|
10
|
+
import { Answer, Message } from '../proto/gen/dxos/mesh/signal';
|
|
10
11
|
import { SignalApi } from './signal-api';
|
|
11
12
|
import { WebsocketRpc } from './websocket-rpc';
|
|
12
13
|
|
|
@@ -53,8 +54,8 @@ export class SignalClient {
|
|
|
53
54
|
*/
|
|
54
55
|
constructor (
|
|
55
56
|
private readonly _host: string,
|
|
56
|
-
private readonly _onOffer: (message:
|
|
57
|
-
private readonly _onSignal: (message:
|
|
57
|
+
private readonly _onOffer: (message: Message) => Promise<Answer>,
|
|
58
|
+
private readonly _onSignal: (message: Message) => Promise<void>
|
|
58
59
|
) {
|
|
59
60
|
this._setState(SignalApi.State.CONNECTING);
|
|
60
61
|
this._createClient();
|
|
@@ -89,11 +90,11 @@ export class SignalClient {
|
|
|
89
90
|
data: message.data
|
|
90
91
|
}));
|
|
91
92
|
|
|
92
|
-
this._client.subscribe('signal', (msg:
|
|
93
|
-
id: PublicKey.from(msg.id),
|
|
94
|
-
remoteId: PublicKey.from(msg.remoteId),
|
|
95
|
-
topic: PublicKey.from(msg.topic),
|
|
96
|
-
sessionId: PublicKey.from(msg.sessionId),
|
|
93
|
+
this._client.subscribe('signal', (msg: Message) => this._onSignal({
|
|
94
|
+
id: PublicKey.from(msg.id!),
|
|
95
|
+
remoteId: PublicKey.from(msg.remoteId!),
|
|
96
|
+
topic: PublicKey.from(msg.topic!),
|
|
97
|
+
sessionId: PublicKey.from(msg.sessionId!),
|
|
97
98
|
data: msg.data
|
|
98
99
|
}));
|
|
99
100
|
|
|
@@ -210,25 +211,25 @@ export class SignalClient {
|
|
|
210
211
|
* Routes an offer to the other peer's _onOffer callback.
|
|
211
212
|
* @returns Other peer's _onOffer callback return value.
|
|
212
213
|
*/
|
|
213
|
-
async offer (
|
|
214
|
+
async offer (msg: Message): Promise<Answer> {
|
|
214
215
|
return this._client.call('offer', {
|
|
215
|
-
id:
|
|
216
|
-
remoteId:
|
|
217
|
-
topic:
|
|
218
|
-
sessionId:
|
|
219
|
-
data:
|
|
216
|
+
id: msg.id?.asBuffer(),
|
|
217
|
+
remoteId: msg.remoteId?.asBuffer(),
|
|
218
|
+
topic: msg.topic?.asBuffer(),
|
|
219
|
+
sessionId: msg.sessionId?.asBuffer(),
|
|
220
|
+
data: msg.data
|
|
220
221
|
});
|
|
221
222
|
}
|
|
222
223
|
|
|
223
224
|
/**
|
|
224
225
|
* Routes an offer to the other peer's _onSignal callback.
|
|
225
226
|
*/
|
|
226
|
-
async signal (payload:
|
|
227
|
+
async signal (payload: Message): Promise<void> {
|
|
227
228
|
return this._client.emit('signal', {
|
|
228
|
-
id: payload.id
|
|
229
|
-
remoteId: payload.remoteId
|
|
230
|
-
topic: payload.topic
|
|
231
|
-
sessionId: payload.sessionId
|
|
229
|
+
id: payload.id?.asBuffer(),
|
|
230
|
+
remoteId: payload.remoteId?.asBuffer(),
|
|
231
|
+
topic: payload.topic?.asBuffer(),
|
|
232
|
+
sessionId: payload.sessionId?.asBuffer(),
|
|
232
233
|
data: payload.data
|
|
233
234
|
});
|
|
234
235
|
}
|
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
import { Event } from '@dxos/async';
|
|
6
6
|
import { PublicKey } from '@dxos/protocols';
|
|
7
7
|
|
|
8
|
+
import { Answer, Message } from '../proto/gen/dxos/mesh/signal';
|
|
8
9
|
import { SignalApi } from './signal-api';
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Signal peer discovery interface.
|
|
13
|
+
*/
|
|
11
14
|
export interface SignalConnection {
|
|
12
15
|
/**
|
|
13
16
|
* Find peers (triggers async event).
|
|
@@ -15,25 +18,37 @@ export interface SignalConnection {
|
|
|
15
18
|
lookup (topic: PublicKey): void
|
|
16
19
|
|
|
17
20
|
/**
|
|
18
|
-
*
|
|
21
|
+
* Join topic on signal network, to be discoverable by other peers.
|
|
19
22
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
join (topic: PublicKey, peerId: PublicKey): void
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Leave topic on signal network, to stop being discoverable by other peers.
|
|
27
|
+
*/
|
|
28
|
+
leave (topic: PublicKey, peerId: PublicKey): void
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Signal peer messaging interface.
|
|
33
|
+
*/
|
|
34
|
+
export interface SignalMessaging {
|
|
35
|
+
/**
|
|
36
|
+
* Offer/answer RPC.
|
|
37
|
+
*/
|
|
38
|
+
offer (msg: Message): Promise<Answer>
|
|
22
39
|
|
|
23
40
|
/**
|
|
24
41
|
* Send message to peer.
|
|
25
42
|
*/
|
|
26
|
-
signal (msg:
|
|
43
|
+
signal (msg: Message): Promise<void>
|
|
27
44
|
}
|
|
28
45
|
|
|
29
|
-
export interface SignalManager extends SignalConnection {
|
|
46
|
+
export interface SignalManager extends SignalConnection, SignalMessaging {
|
|
30
47
|
statusChanged: Event<SignalApi.Status[]>
|
|
31
48
|
commandTrace: Event<SignalApi.CommandTrace>
|
|
32
49
|
peerCandidatesChanged: Event<[topic: PublicKey, candidates: PublicKey[]]>
|
|
33
|
-
onSignal: Event<
|
|
50
|
+
onSignal: Event<Message>
|
|
34
51
|
|
|
35
52
|
getStatus (): SignalApi.Status[]
|
|
36
|
-
join (topic: PublicKey, peerId: PublicKey): void
|
|
37
|
-
leave (topic: PublicKey, peerId: PublicKey): void
|
|
38
53
|
destroy(): Promise<void>
|
|
39
54
|
}
|
|
@@ -9,6 +9,7 @@ import { Event, synchronized } from '@dxos/async';
|
|
|
9
9
|
import { PublicKey } from '@dxos/protocols';
|
|
10
10
|
import { ComplexMap } from '@dxos/util';
|
|
11
11
|
|
|
12
|
+
import { Answer, Message } from '../proto/gen/dxos/mesh/signal';
|
|
12
13
|
import { SignalApi } from './signal-api';
|
|
13
14
|
import { SignalClient } from './signal-client';
|
|
14
15
|
import { SignalManager } from './signal-manager';
|
|
@@ -27,11 +28,11 @@ export class WebsocketSignalManager implements SignalManager {
|
|
|
27
28
|
readonly statusChanged = new Event<SignalApi.Status[]>();
|
|
28
29
|
readonly commandTrace = new Event<SignalApi.CommandTrace>();
|
|
29
30
|
readonly peerCandidatesChanged = new Event<[topic: PublicKey, candidates: PublicKey[]]>()
|
|
30
|
-
readonly onSignal = new Event<
|
|
31
|
+
readonly onSignal = new Event<Message>();
|
|
31
32
|
|
|
32
33
|
constructor (
|
|
33
34
|
private readonly _hosts: string[],
|
|
34
|
-
private readonly _onOffer: (message:
|
|
35
|
+
private readonly _onOffer: (message: Message) => Promise<Answer>
|
|
35
36
|
) {
|
|
36
37
|
log(`Created WebsocketSignalManager with signal servers: ${_hosts}`);
|
|
37
38
|
assert(_hosts.length === 1, 'Only a single signaling server connection is supported');
|
|
@@ -137,13 +138,13 @@ export class WebsocketSignalManager implements SignalManager {
|
|
|
137
138
|
}
|
|
138
139
|
}
|
|
139
140
|
|
|
140
|
-
offer (msg:
|
|
141
|
+
offer (msg: Message) {
|
|
141
142
|
log(`Offer ${msg.remoteId}`);
|
|
142
143
|
// TODO(marik-d): Broadcast to all signal servers.
|
|
143
144
|
return Array.from(this._servers.values())[0].offer(msg);
|
|
144
145
|
}
|
|
145
146
|
|
|
146
|
-
async signal (msg:
|
|
147
|
+
async signal (msg: Message) {
|
|
147
148
|
log(`Signal ${msg.remoteId}`);
|
|
148
149
|
for (const server of this._servers.values()) {
|
|
149
150
|
void server.signal(msg);
|
package/src/swarm/connection.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { ErrorStream } from '@dxos/debug';
|
|
|
10
10
|
import { Protocol } from '@dxos/mesh-protocol';
|
|
11
11
|
import { PublicKey } from '@dxos/protocols';
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { Message } from '../proto/gen/dxos/mesh/signal';
|
|
14
14
|
import { Transport, TransportFactory } from '../transport';
|
|
15
15
|
|
|
16
16
|
const log = debug('dxos:network-manager:swarm:connection');
|
|
@@ -51,7 +51,7 @@ export enum ConnectionState {
|
|
|
51
51
|
export class Connection {
|
|
52
52
|
private _state: ConnectionState = ConnectionState.INITIAL;
|
|
53
53
|
private _transport: Transport | undefined;
|
|
54
|
-
private _bufferedSignals:
|
|
54
|
+
private _bufferedSignals: Message[] = [];
|
|
55
55
|
|
|
56
56
|
readonly stateChanged = new Event<ConnectionState>();
|
|
57
57
|
readonly errors = new ErrorStream();
|
|
@@ -62,7 +62,7 @@ export class Connection {
|
|
|
62
62
|
public readonly remoteId: PublicKey,
|
|
63
63
|
public readonly sessionId: PublicKey,
|
|
64
64
|
public readonly initiator: boolean,
|
|
65
|
-
private readonly _sendSignal: (msg:
|
|
65
|
+
private readonly _sendSignal: (msg: Message) => Promise<void>,
|
|
66
66
|
private readonly _protocol: Protocol,
|
|
67
67
|
private readonly _transportFactory: TransportFactory
|
|
68
68
|
) {}
|
|
@@ -116,25 +116,27 @@ export class Connection {
|
|
|
116
116
|
this._bufferedSignals = [];
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
async signal (msg:
|
|
119
|
+
async signal (msg: Message) {
|
|
120
|
+
assert(msg.sessionId);
|
|
120
121
|
if (!msg.sessionId.equals(this.sessionId)) {
|
|
121
122
|
log('Dropping signal for incorrect session id.');
|
|
122
123
|
return;
|
|
123
124
|
}
|
|
124
|
-
|
|
125
|
+
assert(msg.data);
|
|
126
|
+
if (msg.data.offer && this._state === ConnectionState.INITIATING_CONNECTION) {
|
|
125
127
|
throw new Error('Invalid state: Cannot send offer to an initiating peer.');
|
|
126
128
|
}
|
|
127
|
-
assert(msg.id
|
|
128
|
-
assert(msg.remoteId
|
|
129
|
+
assert(msg.id?.equals(this.remoteId));
|
|
130
|
+
assert(msg.remoteId?.equals(this.ownId));
|
|
129
131
|
|
|
130
132
|
if (this._state === ConnectionState.INITIAL) {
|
|
131
|
-
log(`${this.ownId} buffered signal from ${this.remoteId}: ${msg.data
|
|
133
|
+
log(`${this.ownId} buffered signal from ${this.remoteId}: ${msg.data}`);
|
|
132
134
|
this._bufferedSignals.push(msg);
|
|
133
135
|
return;
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
assert(this._transport, 'Connection not ready to accept signals.');
|
|
137
|
-
log(`${this.ownId} received signal from ${this.remoteId}: ${msg.data
|
|
139
|
+
log(`${this.ownId} received signal from ${this.remoteId}: ${msg.data}`);
|
|
138
140
|
await this._transport.signal(msg);
|
|
139
141
|
}
|
|
140
142
|
|
package/src/swarm/swarm.test.ts
CHANGED
|
@@ -12,53 +12,75 @@ import { Protocol } from '@dxos/mesh-protocol';
|
|
|
12
12
|
import { PublicKey } from '@dxos/protocols';
|
|
13
13
|
import { afterTest } from '@dxos/testutils';
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import { Message } from '../proto/gen/dxos/mesh/signal';
|
|
16
|
+
import { SignalMessaging } from '../signal';
|
|
17
|
+
import { MessageRouter } from '../signal/message-router';
|
|
16
18
|
import { FullyConnectedTopology } from '../topology';
|
|
17
19
|
import { createWebRTCTransportFactory, WebRTCTransport } from '../transport';
|
|
18
20
|
import { Swarm } from './swarm';
|
|
19
21
|
|
|
20
22
|
const log = debug('dxos:network-manager:swarm:test');
|
|
21
23
|
|
|
22
|
-
class MockSignalConnection implements
|
|
24
|
+
class MockSignalConnection implements SignalMessaging {
|
|
23
25
|
constructor (
|
|
24
26
|
readonly _swarm: () => Swarm,
|
|
25
27
|
readonly _delay = 10
|
|
26
28
|
) {}
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
async offer (msg: SignalApi.SignalMessage) {
|
|
30
|
+
async offer (msg: Message) {
|
|
31
31
|
await sleep(this._delay);
|
|
32
32
|
return this._swarm().onOffer(msg);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
async signal (msg:
|
|
35
|
+
async signal (msg: Message) {
|
|
36
36
|
await sleep(this._delay);
|
|
37
37
|
await this._swarm().onSignal(msg);
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
const setup = () => {
|
|
41
|
+
const setup = ({ router = false } = {}) => {
|
|
42
42
|
const topic = PublicKey.random();
|
|
43
43
|
const peerId1 = PublicKey.random();
|
|
44
44
|
const peerId2 = PublicKey.random();
|
|
45
|
+
// eslint-disable-next-line prefer-const
|
|
46
|
+
let swarm1: Swarm;
|
|
47
|
+
// eslint-disable-next-line prefer-const
|
|
48
|
+
let swarm2: Swarm;
|
|
49
|
+
|
|
50
|
+
const mr1: MessageRouter = new MessageRouter({
|
|
51
|
+
sendMessage: msg => mr2.receiveMessage(msg),
|
|
52
|
+
onSignal: msg => swarm1.onSignal(msg),
|
|
53
|
+
onOffer: msg => swarm1.onOffer(msg)
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const mr2: MessageRouter = new MessageRouter({
|
|
57
|
+
sendMessage: msg => mr1.receiveMessage(msg),
|
|
58
|
+
onSignal: msg => swarm2.onSignal(msg),
|
|
59
|
+
onOffer: msg => swarm2.onOffer(msg)
|
|
60
|
+
});
|
|
45
61
|
|
|
46
|
-
const
|
|
62
|
+
const sm1: SignalMessaging = router ? mr1 : new MockSignalConnection(() => swarm2);
|
|
63
|
+
|
|
64
|
+
const sm2: SignalMessaging = router ? mr2 : new MockSignalConnection(() => swarm1);
|
|
65
|
+
|
|
66
|
+
swarm1 = new Swarm(
|
|
47
67
|
topic,
|
|
48
68
|
peerId1,
|
|
49
69
|
new FullyConnectedTopology(),
|
|
50
70
|
() => new Protocol(),
|
|
51
|
-
|
|
71
|
+
sm1,
|
|
72
|
+
() => {},
|
|
52
73
|
createWebRTCTransportFactory(),
|
|
53
74
|
undefined
|
|
54
75
|
);
|
|
55
76
|
|
|
56
|
-
|
|
77
|
+
swarm2 = new Swarm(
|
|
57
78
|
topic,
|
|
58
79
|
peerId2,
|
|
59
80
|
new FullyConnectedTopology(),
|
|
60
81
|
() => new Protocol(),
|
|
61
|
-
|
|
82
|
+
sm2,
|
|
83
|
+
() => {},
|
|
62
84
|
createWebRTCTransportFactory(),
|
|
63
85
|
undefined
|
|
64
86
|
);
|
|
@@ -141,3 +163,31 @@ test('second peer discovered after delay', async () => {
|
|
|
141
163
|
expect(onData).toHaveBeenCalledWith([data]);
|
|
142
164
|
});
|
|
143
165
|
}).timeout(5_000);
|
|
166
|
+
|
|
167
|
+
test('swarming with message router', async () => {
|
|
168
|
+
const { swarm1, swarm2, peerId2 } = setup({ router: true });
|
|
169
|
+
|
|
170
|
+
const promise = Promise.all([
|
|
171
|
+
promiseTimeout(swarm1.connected.waitForCount(1), 3000, new Error('Swarm1 connect timeout.')),
|
|
172
|
+
promiseTimeout(swarm2.connected.waitForCount(1), 3000, new Error('Swarm2 connect timeout.'))
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
swarm1.onPeerCandidatesChanged([peerId2]);
|
|
176
|
+
|
|
177
|
+
log('Candidates changed');
|
|
178
|
+
await promise;
|
|
179
|
+
log('Swarms connected');
|
|
180
|
+
|
|
181
|
+
const swarm1Connection = swarm1.connections[0];
|
|
182
|
+
const swarm2Connection = swarm2.connections[0];
|
|
183
|
+
const onData = mockFn<(data: Buffer) => void>().returns(undefined);
|
|
184
|
+
(swarm2Connection.transport as WebRTCTransport).peer!.on('data', onData);
|
|
185
|
+
|
|
186
|
+
const data = Buffer.from('1234');
|
|
187
|
+
(swarm1Connection.transport as WebRTCTransport).peer!.send(data);
|
|
188
|
+
await waitForExpect(() => {
|
|
189
|
+
expect(onData).toHaveBeenCalledWith([data]);
|
|
190
|
+
});
|
|
191
|
+
await swarm1.destroy();
|
|
192
|
+
await swarm2.destroy();
|
|
193
|
+
}).timeout(5_000);
|
package/src/swarm/swarm.ts
CHANGED
|
@@ -12,7 +12,8 @@ import { PublicKey } from '@dxos/protocols';
|
|
|
12
12
|
import { ComplexMap, ComplexSet } from '@dxos/util';
|
|
13
13
|
|
|
14
14
|
import { ProtocolProvider } from '../network-manager';
|
|
15
|
-
import {
|
|
15
|
+
import { Answer, Message } from '../proto/gen/dxos/mesh/signal';
|
|
16
|
+
import { SignalMessaging } from '../signal';
|
|
16
17
|
import { SwarmController, Topology } from '../topology';
|
|
17
18
|
import { TransportFactory } from '../transport';
|
|
18
19
|
import { Topic } from '../types';
|
|
@@ -62,7 +63,8 @@ export class Swarm {
|
|
|
62
63
|
private readonly _ownPeerId: PublicKey,
|
|
63
64
|
private _topology: Topology,
|
|
64
65
|
private readonly _protocolProvider: ProtocolProvider,
|
|
65
|
-
private readonly
|
|
66
|
+
private readonly _signalMessaging: SignalMessaging,
|
|
67
|
+
private readonly _lookupPeers: (topic: PublicKey) => void,
|
|
66
68
|
private readonly _transportFactory: TransportFactory,
|
|
67
69
|
private readonly _label: string | undefined
|
|
68
70
|
) {
|
|
@@ -98,12 +100,13 @@ export class Swarm {
|
|
|
98
100
|
this._topology.update();
|
|
99
101
|
}
|
|
100
102
|
|
|
101
|
-
async onOffer (message:
|
|
103
|
+
async onOffer (message: Message): Promise<Answer> {
|
|
102
104
|
log(`Offer from ${message.id} topic=${this._topic}`);
|
|
103
105
|
// Id of the peer offering us the connection.
|
|
106
|
+
assert(message.id);
|
|
104
107
|
const remoteId = message.id;
|
|
105
|
-
assert(message.remoteId
|
|
106
|
-
assert(message.topic
|
|
108
|
+
assert(message.remoteId?.equals(this._ownPeerId));
|
|
109
|
+
assert(message.topic?.equals(this._topic));
|
|
107
110
|
|
|
108
111
|
// Check if we are already trying to connect to that peer.
|
|
109
112
|
if (this._connections.has(remoteId)) {
|
|
@@ -123,6 +126,7 @@ export class Swarm {
|
|
|
123
126
|
let accept = false;
|
|
124
127
|
if (await this._topology.onOffer(remoteId)) {
|
|
125
128
|
if (!this._connections.has(remoteId)) { // Connection might have been already established.
|
|
129
|
+
assert(message.sessionId);
|
|
126
130
|
const connection = this._createConnection(false, message.id, message.sessionId);
|
|
127
131
|
try {
|
|
128
132
|
connection.connect();
|
|
@@ -136,10 +140,11 @@ export class Swarm {
|
|
|
136
140
|
return { accept };
|
|
137
141
|
}
|
|
138
142
|
|
|
139
|
-
async onSignal (message:
|
|
143
|
+
async onSignal (message: Message): Promise<void> {
|
|
140
144
|
log(`Signal ${this._topic} ${JSON.stringify(message)}`);
|
|
141
|
-
assert(message.remoteId
|
|
142
|
-
assert(message.topic
|
|
145
|
+
assert(message.remoteId?.equals(this._ownPeerId), `Invalid signal peer id expected=${this.ownPeerId}, actual=${message.remoteId}`);
|
|
146
|
+
assert(message.topic?.equals(this._topic));
|
|
147
|
+
assert(message.id);
|
|
143
148
|
const connection = this._connections.get(message.id);
|
|
144
149
|
if (!connection) {
|
|
145
150
|
log(`Dropping signal message for non-existent connection: topic=${this._topic}, peerId=${message.id}`);
|
|
@@ -183,7 +188,7 @@ export class Swarm {
|
|
|
183
188
|
this._topology.update();
|
|
184
189
|
},
|
|
185
190
|
lookup: () => {
|
|
186
|
-
this.
|
|
191
|
+
this._lookupPeers(this._topic);
|
|
187
192
|
}
|
|
188
193
|
};
|
|
189
194
|
}
|
|
@@ -197,12 +202,12 @@ export class Swarm {
|
|
|
197
202
|
const sessionId = PublicKey.random();
|
|
198
203
|
|
|
199
204
|
const connection = this._createConnection(true, remoteId, sessionId);
|
|
200
|
-
this.
|
|
205
|
+
this._signalMessaging.offer({
|
|
201
206
|
id: this._ownPeerId,
|
|
202
207
|
remoteId,
|
|
203
208
|
sessionId,
|
|
204
209
|
topic: this._topic,
|
|
205
|
-
data: {}
|
|
210
|
+
data: { offer: {} }
|
|
206
211
|
})
|
|
207
212
|
.then(answer => {
|
|
208
213
|
log(`Received answer: ${JSON.stringify(answer)} topic=${this._topic} ownId=${this._ownPeerId} remoteId=${remoteId}`);
|
|
@@ -240,7 +245,7 @@ export class Swarm {
|
|
|
240
245
|
remoteId,
|
|
241
246
|
sessionId,
|
|
242
247
|
initiator,
|
|
243
|
-
(msg:
|
|
248
|
+
(msg: Message) => this._signalMessaging.signal(msg),
|
|
244
249
|
this._protocolProvider({ channel: discoveryKey(this._topic), initiator }),
|
|
245
250
|
this._transportFactory
|
|
246
251
|
);
|
|
@@ -11,7 +11,7 @@ import { ErrorStream } from '@dxos/debug';
|
|
|
11
11
|
import { PublicKey } from '@dxos/protocols';
|
|
12
12
|
import { ComplexMap } from '@dxos/util';
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import { Message } from '../proto/gen/dxos/mesh/signal';
|
|
15
15
|
import { Transport, TransportFactory } from './transport';
|
|
16
16
|
|
|
17
17
|
const log = debug('dxos:network-manager:swarm:transport:in-memory-transport');
|
|
@@ -79,7 +79,7 @@ export class InMemoryTransport implements Transport {
|
|
|
79
79
|
return this._sessionId;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
async signal (msg:
|
|
82
|
+
async signal (msg: Message) {
|
|
83
83
|
// No-op.
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -6,7 +6,7 @@ import { Event } from '@dxos/async';
|
|
|
6
6
|
import { ErrorStream } from '@dxos/debug';
|
|
7
7
|
import { PublicKey } from '@dxos/protocols';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { Message } from '../proto/gen/dxos/mesh/signal';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Abstraction over a P2P connection transport. Currently either WebRTC or in-memory.
|
|
@@ -15,7 +15,7 @@ export interface Transport {
|
|
|
15
15
|
closed: Event
|
|
16
16
|
connected: Event
|
|
17
17
|
errors: ErrorStream
|
|
18
|
-
signal (msg:
|
|
18
|
+
signal (msg: Message): Promise<void> // TODO(burdon): Remove async?
|
|
19
19
|
close (): Promise<void>
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -38,7 +38,7 @@ export interface TransportOptions {
|
|
|
38
38
|
/**
|
|
39
39
|
* Send a signal message to remote peer.
|
|
40
40
|
*/
|
|
41
|
-
sendSignal: (msg:
|
|
41
|
+
sendSignal: (msg: Message) => Promise<void> // TODO(burdon): Remove async?
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export type TransportFactory = (options: TransportOptions) => Transport
|
|
@@ -11,7 +11,7 @@ import { Event } from '@dxos/async';
|
|
|
11
11
|
import { ErrorStream } from '@dxos/debug';
|
|
12
12
|
import { PublicKey } from '@dxos/protocols';
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import { Message } from '../proto/gen/dxos/mesh/signal';
|
|
15
15
|
import { Transport, TransportFactory } from './transport';
|
|
16
16
|
|
|
17
17
|
const log = debug('dxos:network-manager:swarm:transport:webrtc');
|
|
@@ -35,7 +35,7 @@ export class WebRTCTransport implements Transport {
|
|
|
35
35
|
private readonly _remoteId: PublicKey,
|
|
36
36
|
private readonly _sessionId: PublicKey,
|
|
37
37
|
private readonly _topic: PublicKey,
|
|
38
|
-
private readonly _sendSignal: (msg:
|
|
38
|
+
private readonly _sendSignal: (msg: Message) => void,
|
|
39
39
|
private readonly _webrtcConfig?: any
|
|
40
40
|
) {
|
|
41
41
|
log(`Created WebRTC connection ${this._ownId} -> ${this._remoteId} initiator=${this._initiator}`);
|
|
@@ -53,7 +53,7 @@ export class WebRTCTransport implements Transport {
|
|
|
53
53
|
remoteId: this._remoteId,
|
|
54
54
|
sessionId: this._sessionId,
|
|
55
55
|
topic: this._topic,
|
|
56
|
-
data
|
|
56
|
+
data: { signal: { json: JSON.stringify(data) } }
|
|
57
57
|
});
|
|
58
58
|
} catch (err: any) {
|
|
59
59
|
this.errors.raise(err);
|
|
@@ -89,9 +89,10 @@ export class WebRTCTransport implements Transport {
|
|
|
89
89
|
return this._peer;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
async signal (msg:
|
|
92
|
+
async signal (msg: Message) {
|
|
93
93
|
assert(this._peer, 'Connection not ready to accept signals.');
|
|
94
|
-
|
|
94
|
+
assert(msg.data?.signal?.json, 'Signal message must contain signal data.');
|
|
95
|
+
this._peer.signal(JSON.parse(msg.data.signal.json));
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
async close () {
|