@dxos/network-manager 2.33.8-dev.c77ba621 → 2.33.9-dev.2637427f
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 +41836 -38304
- 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 +15 -9
- 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 +49 -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 +29 -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/protocol-factory.js +7 -6
- package/dist/src/protocol-factory.js.map +1 -1
- 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 +5 -3
- package/dist/src/signal/in-memory-signal-manager.js.map +1 -1
- package/dist/src/signal/message-router.d.ts +36 -0
- package/dist/src/signal/message-router.d.ts.map +1 -0
- package/dist/src/signal/message-router.js +145 -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 +265 -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 +5 -3
- package/dist/src/signal/signal-client.d.ts.map +1 -1
- package/dist/src/signal/signal-client.js +33 -21
- package/dist/src/signal/signal-client.js.map +1 -1
- package/dist/src/signal/signal-client.test.js +25 -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-rpc.js +3 -3
- package/dist/src/signal/websocket-rpc.js.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 +2 -2
- 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 +12 -9
- 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 +19 -13
- package/dist/src/swarm/swarm.js.map +1 -1
- package/dist/src/swarm/swarm.test.js +44 -4
- package/dist/src/swarm/swarm.test.js.map +1 -1
- package/dist/src/testing/test-protocol.d.ts.map +1 -1
- package/dist/src/testing/test-protocol.js +10 -10
- package/dist/src/testing/test-protocol.js.map +1 -1
- package/dist/src/topology/fully-connected-topology.js +3 -3
- package/dist/src/topology/fully-connected-topology.js.map +1 -1
- package/dist/src/topology/mmst-topology.js +5 -5
- package/dist/src/topology/mmst-topology.js.map +1 -1
- package/dist/src/topology/star-topology.js +4 -4
- package/dist/src/topology/star-topology.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 +2 -2
- 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 +6 -4
- package/dist/src/transport/webrtc-transport.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -13
- package/src/network-manager.ts +18 -7
- package/src/proto/defs/dxos/mesh/signal.proto +51 -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 +52 -0
- package/src/proto/gen/google/protobuf.ts +9 -0
- package/src/proto/gen/index.ts +29 -0
- package/src/proto/substitutions.ts +9 -0
- package/src/protocol-factory.ts +5 -5
- package/src/signal/in-memory-signal-manager.ts +8 -5
- package/src/signal/message-router.test.ts +334 -0
- package/src/signal/message-router.ts +178 -0
- package/src/signal/signal-api.ts +6 -0
- package/src/signal/signal-client.test.ts +37 -12
- package/src/signal/signal-client.ts +51 -23
- package/src/signal/signal-manager.ts +24 -9
- package/src/signal/websocket-rpc.ts +1 -1
- package/src/signal/websocket-signal-manager.ts +6 -5
- package/src/swarm/connection.ts +12 -10
- package/src/swarm/swarm.test.ts +63 -11
- package/src/swarm/swarm.ts +19 -14
- package/src/testing/test-protocol.ts +8 -8
- package/src/topology/fully-connected-topology.ts +1 -1
- package/src/topology/mmst-topology.ts +1 -1
- package/src/topology/star-topology.ts +1 -1
- package/src/transport/in-memory-transport.ts +3 -3
- package/src/transport/transport.ts +3 -3
- package/src/transport/webrtc-transport.ts +7 -6
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2022 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { expect, mockFn } from 'earljs';
|
|
6
|
+
import { it as test, describe } from 'mocha';
|
|
7
|
+
import waitForExpect from 'wait-for-expect';
|
|
8
|
+
|
|
9
|
+
import { Awaited } from '@dxos/async';
|
|
10
|
+
import { PublicKey } from '@dxos/protocols';
|
|
11
|
+
import { createTestBroker } from '@dxos/signal';
|
|
12
|
+
import { afterTest } from '@dxos/testutils';
|
|
13
|
+
import { randomInt } from '@dxos/util';
|
|
14
|
+
|
|
15
|
+
import { Answer, Message } from '../proto/gen/dxos/mesh/signal';
|
|
16
|
+
import { MessageRouter } from './message-router';
|
|
17
|
+
import { SignalClient } from './signal-client';
|
|
18
|
+
|
|
19
|
+
describe('MessageRouter', () => {
|
|
20
|
+
let topic: PublicKey;
|
|
21
|
+
let peer1: PublicKey;
|
|
22
|
+
let peer2: PublicKey;
|
|
23
|
+
|
|
24
|
+
let broker1: Awaited<ReturnType<typeof createTestBroker>>;
|
|
25
|
+
const signalApiPort1 = randomInt(10000, 50000);
|
|
26
|
+
const signalApiUrl1 = 'http://0.0.0.0:' + signalApiPort1;
|
|
27
|
+
|
|
28
|
+
before(async () => {
|
|
29
|
+
broker1 = await createTestBroker(signalApiPort1);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
topic = PublicKey.random();
|
|
34
|
+
peer1 = PublicKey.random();
|
|
35
|
+
peer2 = PublicKey.random();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
after(async function () {
|
|
39
|
+
this.timeout(0);
|
|
40
|
+
await broker1.stop();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const createSignalClientAndMessageRouter = async ({
|
|
44
|
+
signalApiUrl,
|
|
45
|
+
onSignal = (async () => { }) as any,
|
|
46
|
+
onOffer = async () => ({ accept: true })
|
|
47
|
+
}: {
|
|
48
|
+
signalApiUrl: string;
|
|
49
|
+
onSignal?: (msg: Message) => Promise<void>;
|
|
50
|
+
onOffer?: (msg: Message) => Promise<Answer>;
|
|
51
|
+
}) => {
|
|
52
|
+
|
|
53
|
+
// eslint-disable-next-line prefer-const
|
|
54
|
+
let api: SignalClient;
|
|
55
|
+
const router: MessageRouter = new MessageRouter({
|
|
56
|
+
// todo(mykola): added catch to avoid not finished request.
|
|
57
|
+
sendMessage: (msg: Message) => api.signal(msg).catch((_) => { }),
|
|
58
|
+
onSignal: onSignal,
|
|
59
|
+
onOffer: onOffer
|
|
60
|
+
});
|
|
61
|
+
afterTest(() => router.destroy());
|
|
62
|
+
|
|
63
|
+
api = new SignalClient(
|
|
64
|
+
signalApiUrl,
|
|
65
|
+
(async () => {}) as any,
|
|
66
|
+
async (msg: Message) => router.receiveMessage(msg)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
afterTest(() => api.close());
|
|
70
|
+
return {
|
|
71
|
+
api,
|
|
72
|
+
router
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
test('signaling between 2 clients', async () => {
|
|
77
|
+
const signalMock1 = mockFn<(msg: Message) => Promise<void>>().resolvesTo();
|
|
78
|
+
const { api: api1 } = await createSignalClientAndMessageRouter({ signalApiUrl: signalApiUrl1, onSignal: signalMock1 });
|
|
79
|
+
const { api: api2, router: router2 } = await createSignalClientAndMessageRouter({ signalApiUrl: signalApiUrl1 });
|
|
80
|
+
|
|
81
|
+
await api1.join(topic, peer1);
|
|
82
|
+
await api2.join(topic, peer2);
|
|
83
|
+
|
|
84
|
+
const msg: Message = {
|
|
85
|
+
id: peer2,
|
|
86
|
+
remoteId: peer1,
|
|
87
|
+
sessionId: PublicKey.random(),
|
|
88
|
+
topic,
|
|
89
|
+
data: { signal: { json: '{"asd": "asd"}' } }
|
|
90
|
+
};
|
|
91
|
+
await router2.signal(msg);
|
|
92
|
+
|
|
93
|
+
await waitForExpect(() => {
|
|
94
|
+
expect(signalMock1).toHaveBeenCalledWith([msg]);
|
|
95
|
+
}, 4_000);
|
|
96
|
+
}).timeout(5_000);
|
|
97
|
+
|
|
98
|
+
test('offer/answer', async () => {
|
|
99
|
+
const { api: api1, router: router1 } = await createSignalClientAndMessageRouter(
|
|
100
|
+
{
|
|
101
|
+
signalApiUrl: signalApiUrl1,
|
|
102
|
+
onSignal: (async () => { }) as any,
|
|
103
|
+
onOffer:
|
|
104
|
+
async () => ({ accept: true })
|
|
105
|
+
});
|
|
106
|
+
const { api: api2 } = await createSignalClientAndMessageRouter(
|
|
107
|
+
{
|
|
108
|
+
signalApiUrl: signalApiUrl1,
|
|
109
|
+
onSignal: (async () => { }) as any,
|
|
110
|
+
onOffer:
|
|
111
|
+
async () => ({ accept: true })
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
await api1.join(topic, peer1);
|
|
115
|
+
await api2.join(topic, peer2);
|
|
116
|
+
|
|
117
|
+
const answer = await router1.offer({
|
|
118
|
+
id: peer1,
|
|
119
|
+
remoteId: peer2,
|
|
120
|
+
sessionId: PublicKey.random(),
|
|
121
|
+
topic,
|
|
122
|
+
data: { offer: { } }
|
|
123
|
+
});
|
|
124
|
+
expect(answer.accept).toEqual(true);
|
|
125
|
+
}).timeout(5_000);
|
|
126
|
+
|
|
127
|
+
test('signaling between 3 clients', async () => {
|
|
128
|
+
const signalMock1 = mockFn<(msg: Message) => Promise<void>>().resolvesTo();
|
|
129
|
+
const { api: api1, router: router1 } = await createSignalClientAndMessageRouter(
|
|
130
|
+
{
|
|
131
|
+
signalApiUrl: signalApiUrl1,
|
|
132
|
+
onSignal: signalMock1,
|
|
133
|
+
onOffer:
|
|
134
|
+
async () => ({ accept: true })
|
|
135
|
+
});
|
|
136
|
+
const signalMock2 = mockFn<(msg: Message) => Promise<void>>().resolvesTo();
|
|
137
|
+
const { api: api2, router: router2 } = await createSignalClientAndMessageRouter(
|
|
138
|
+
{
|
|
139
|
+
signalApiUrl: signalApiUrl1,
|
|
140
|
+
onSignal: signalMock2,
|
|
141
|
+
onOffer:
|
|
142
|
+
async () => ({ accept: true })
|
|
143
|
+
});
|
|
144
|
+
const signalMock3 = mockFn<(msg: Message) => Promise<void>>().resolvesTo();
|
|
145
|
+
const { api: api3, router: router3 } = await createSignalClientAndMessageRouter(
|
|
146
|
+
{
|
|
147
|
+
signalApiUrl: signalApiUrl1,
|
|
148
|
+
onSignal: signalMock3,
|
|
149
|
+
onOffer:
|
|
150
|
+
async () => ({ accept: true })
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
await api1.join(topic, peer1);
|
|
154
|
+
await api2.join(topic, peer2);
|
|
155
|
+
const peer3 = PublicKey.random();
|
|
156
|
+
await api3.join(topic, peer3);
|
|
157
|
+
|
|
158
|
+
// sending signal from peer1 to peer3.
|
|
159
|
+
const msg1to3: Message = {
|
|
160
|
+
id: peer1,
|
|
161
|
+
remoteId: peer3,
|
|
162
|
+
sessionId: PublicKey.random(),
|
|
163
|
+
topic,
|
|
164
|
+
data: { signal: { json: '1to3' } }
|
|
165
|
+
};
|
|
166
|
+
await router1.signal(msg1to3);
|
|
167
|
+
await waitForExpect(() => {
|
|
168
|
+
expect(signalMock3).toHaveBeenCalledWith([msg1to3]);
|
|
169
|
+
}, 4_000);
|
|
170
|
+
|
|
171
|
+
// sending signal from peer2 to peer3.
|
|
172
|
+
const msg2to3: Message = {
|
|
173
|
+
id: peer2,
|
|
174
|
+
remoteId: peer3,
|
|
175
|
+
sessionId: PublicKey.random(),
|
|
176
|
+
topic,
|
|
177
|
+
data: { signal: { json: '2to3' } }
|
|
178
|
+
};
|
|
179
|
+
await router2.signal(msg2to3);
|
|
180
|
+
await waitForExpect(() => {
|
|
181
|
+
expect(signalMock3).toHaveBeenCalledWith([msg2to3]);
|
|
182
|
+
}, 4_000);
|
|
183
|
+
|
|
184
|
+
// sending signal from peer3 to peer1.
|
|
185
|
+
const msg3to1: Message = {
|
|
186
|
+
id: peer3,
|
|
187
|
+
remoteId: peer1,
|
|
188
|
+
sessionId: PublicKey.random(),
|
|
189
|
+
topic,
|
|
190
|
+
data: { signal: { json: '3to1' } }
|
|
191
|
+
};
|
|
192
|
+
await router3.signal(msg3to1);
|
|
193
|
+
await waitForExpect(() => {
|
|
194
|
+
expect(signalMock1).toHaveBeenCalledWith([msg3to1]);
|
|
195
|
+
}, 4_000);
|
|
196
|
+
}).timeout(5_000);
|
|
197
|
+
|
|
198
|
+
test('two offers', async () => {
|
|
199
|
+
const { api: api1, router: router1 } = await createSignalClientAndMessageRouter(
|
|
200
|
+
{
|
|
201
|
+
signalApiUrl: signalApiUrl1,
|
|
202
|
+
onSignal: (async () => { }) as any,
|
|
203
|
+
onOffer:
|
|
204
|
+
async () => ({ accept: true })
|
|
205
|
+
});
|
|
206
|
+
const { api: api2, router: router2 } = await createSignalClientAndMessageRouter(
|
|
207
|
+
{
|
|
208
|
+
signalApiUrl: signalApiUrl1,
|
|
209
|
+
onSignal: (async () => { }) as any,
|
|
210
|
+
onOffer:
|
|
211
|
+
async () => ({ accept: true })
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
await api1.join(topic, peer1);
|
|
215
|
+
await api2.join(topic, peer2);
|
|
216
|
+
|
|
217
|
+
// sending offer from peer1 to peer2.
|
|
218
|
+
const answer1 = await router1.offer({
|
|
219
|
+
id: peer1,
|
|
220
|
+
remoteId: peer2,
|
|
221
|
+
sessionId: PublicKey.random(),
|
|
222
|
+
topic,
|
|
223
|
+
data: { offer: {} }
|
|
224
|
+
});
|
|
225
|
+
expect(answer1.accept).toEqual(true);
|
|
226
|
+
|
|
227
|
+
// sending offer from peer2 to peer1.
|
|
228
|
+
const answer2 = await router2.offer({
|
|
229
|
+
id: peer2,
|
|
230
|
+
remoteId: peer1,
|
|
231
|
+
sessionId: PublicKey.random(),
|
|
232
|
+
topic,
|
|
233
|
+
data: { offer: {} }
|
|
234
|
+
});
|
|
235
|
+
expect(answer2.accept).toEqual(true);
|
|
236
|
+
}).timeout(5_000);
|
|
237
|
+
|
|
238
|
+
describe('Reliability', () => {
|
|
239
|
+
const setup = ({
|
|
240
|
+
onSignal1 = async () => { },
|
|
241
|
+
onSignal2 = async () => { },
|
|
242
|
+
// Imitates signal network disruptions (e. g. message doubling, ).
|
|
243
|
+
messageDisruption = msg => [msg]
|
|
244
|
+
}: {
|
|
245
|
+
onSignal1?: (msg: Message) => Promise<void>;
|
|
246
|
+
onSignal2?: (msg: Message) => Promise<void>;
|
|
247
|
+
messageDisruption?: (msg: Message) => Message[];
|
|
248
|
+
}): {mr1: MessageRouter; mr2: MessageRouter} => {
|
|
249
|
+
|
|
250
|
+
const mr1: MessageRouter = new MessageRouter({
|
|
251
|
+
sendMessage: async msg => messageDisruption(msg).forEach(msg => mr2.receiveMessage(msg)),
|
|
252
|
+
onOffer: async () => ({ accept: true }),
|
|
253
|
+
onSignal: onSignal1
|
|
254
|
+
});
|
|
255
|
+
afterTest(() => mr1.destroy());
|
|
256
|
+
|
|
257
|
+
const mr2: MessageRouter = new MessageRouter({
|
|
258
|
+
sendMessage: async msg => messageDisruption(msg).forEach(msg => mr1.receiveMessage(msg)),
|
|
259
|
+
onOffer: async () => ({ accept: true }),
|
|
260
|
+
onSignal: onSignal2
|
|
261
|
+
});
|
|
262
|
+
afterTest(() => mr1.destroy());
|
|
263
|
+
|
|
264
|
+
return { mr1, mr2 };
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
test('signaling with non reliable connection', async () => {
|
|
268
|
+
// Simulate unreliable connection.
|
|
269
|
+
// Only each 3rd message is sent.
|
|
270
|
+
let i = 0;
|
|
271
|
+
const unreliableConnection = (msg: Message): Message[] => {
|
|
272
|
+
i++;
|
|
273
|
+
if (i % 3 !== 0) {
|
|
274
|
+
return [msg];
|
|
275
|
+
}
|
|
276
|
+
return [];
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const received: Message[] = [];
|
|
280
|
+
const signalMock1 = async (msg: Message) => {
|
|
281
|
+
received.push(msg);
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const { mr2 } = await setup({
|
|
285
|
+
onSignal1: signalMock1,
|
|
286
|
+
messageDisruption: unreliableConnection
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Sending 3 messages.
|
|
290
|
+
// Setup sends messages directly to between. So we don`t need to specify any ids.
|
|
291
|
+
Array(3).fill(0).forEach(async () => {
|
|
292
|
+
await mr2.signal({
|
|
293
|
+
id: PublicKey.random(),
|
|
294
|
+
remoteId: PublicKey.random(),
|
|
295
|
+
sessionId: PublicKey.random(),
|
|
296
|
+
topic: PublicKey.random(),
|
|
297
|
+
data: { signal: { json: 'asd' } }
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
// expect to receive 3 messages.
|
|
301
|
+
await waitForExpect(() => {
|
|
302
|
+
expect(received.length).toEqual(3);
|
|
303
|
+
}, 4_000);
|
|
304
|
+
}).timeout(5_000);
|
|
305
|
+
|
|
306
|
+
test('ignoring doubled messages', async () => {
|
|
307
|
+
// Message got doubled going through signal network.
|
|
308
|
+
const doublingMessage = (msg: Message) => [msg, msg];
|
|
309
|
+
|
|
310
|
+
const received: Message[] = [];
|
|
311
|
+
const signalMock1 = async (msg: Message) => {
|
|
312
|
+
received.push(msg);
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const { mr2 } = setup({
|
|
316
|
+
onSignal1: signalMock1,
|
|
317
|
+
messageDisruption: doublingMessage
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// sending message.
|
|
321
|
+
await mr2.signal({
|
|
322
|
+
id: PublicKey.random(),
|
|
323
|
+
remoteId: PublicKey.random(),
|
|
324
|
+
sessionId: PublicKey.random(),
|
|
325
|
+
topic: PublicKey.random(),
|
|
326
|
+
data: { signal: { json: 'asd' } }
|
|
327
|
+
});
|
|
328
|
+
// expect to receive 1 message.
|
|
329
|
+
await waitForExpect(() => {
|
|
330
|
+
expect(received.length).toEqual(1);
|
|
331
|
+
}, 4_000);
|
|
332
|
+
}).timeout(5_000);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2020 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import assert from 'assert';
|
|
6
|
+
import debug from 'debug';
|
|
7
|
+
|
|
8
|
+
import { PublicKey } from '@dxos/protocols';
|
|
9
|
+
import { ComplexMap, ComplexSet, exponentialBackoffInterval, SubscriptionGroup } from '@dxos/util';
|
|
10
|
+
|
|
11
|
+
import { Answer, Message } from '../proto/gen/dxos/mesh/signal';
|
|
12
|
+
import { SignalMessaging } from './signal-manager';
|
|
13
|
+
|
|
14
|
+
interface OfferRecord {
|
|
15
|
+
resolve: (answer: Answer) => void;
|
|
16
|
+
reject: (error?: Error) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface MessageRouterOptions {
|
|
20
|
+
onSignal?: (message: Message) => Promise<void>;
|
|
21
|
+
sendMessage?: (message: Message) => Promise<void>;
|
|
22
|
+
onOffer?: (message: Message) => Promise<Answer>;
|
|
23
|
+
retryDelay?: number;
|
|
24
|
+
timeout?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const log = debug('dxos:network-manager:message-router');
|
|
28
|
+
/**
|
|
29
|
+
* Adds offer/answer RPC and reliable messaging.
|
|
30
|
+
*/
|
|
31
|
+
// TODO(mykola): https://github.com/dxos/protocols/issues/1316
|
|
32
|
+
export class MessageRouter implements SignalMessaging {
|
|
33
|
+
private readonly _offerRecords: ComplexMap<PublicKey, OfferRecord> = new ComplexMap(key => key.toHex());
|
|
34
|
+
private readonly _onSignal: (message: Message) => Promise<void>;
|
|
35
|
+
private readonly _sendMessage: (message: Message) => Promise<void>;
|
|
36
|
+
private readonly _onOffer: (message: Message) => Promise<Answer>;
|
|
37
|
+
|
|
38
|
+
private readonly _onAckCallbacks = new ComplexMap<PublicKey, () => void>(key => key.toHex());
|
|
39
|
+
private readonly _receivedMessages = new ComplexSet<PublicKey>(key => key.toHex());
|
|
40
|
+
private readonly _retryDelay: number;
|
|
41
|
+
private readonly _timeout: number;
|
|
42
|
+
|
|
43
|
+
private readonly _subscriptions = new SubscriptionGroup();
|
|
44
|
+
|
|
45
|
+
constructor ({
|
|
46
|
+
sendMessage,
|
|
47
|
+
onSignal,
|
|
48
|
+
onOffer,
|
|
49
|
+
retryDelay = 100,
|
|
50
|
+
timeout = 3000
|
|
51
|
+
}: MessageRouterOptions = {}) {
|
|
52
|
+
assert(sendMessage);
|
|
53
|
+
this._sendMessage = sendMessage;
|
|
54
|
+
assert(onSignal);
|
|
55
|
+
this._onSignal = onSignal;
|
|
56
|
+
assert(onOffer);
|
|
57
|
+
this._onOffer = onOffer;
|
|
58
|
+
this._retryDelay = retryDelay;
|
|
59
|
+
this._timeout = timeout;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async receiveMessage (message: Message): Promise<void> {
|
|
63
|
+
log(`receive message: ${JSON.stringify(message)}`);
|
|
64
|
+
if (!message.data?.ack) {
|
|
65
|
+
if (this._receivedMessages.has(message.messageId!)) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this._receivedMessages.add(message.messageId!);
|
|
70
|
+
await this._sendAcknowledgement(message);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (message.data?.offer) {
|
|
74
|
+
await this._handleOffer(message);
|
|
75
|
+
} else if (message.data?.answer) {
|
|
76
|
+
await this._resolveAnswers(message);
|
|
77
|
+
} else if (message.data?.signal) {
|
|
78
|
+
await this._handleSignal(message);
|
|
79
|
+
} else if (message.data?.ack) {
|
|
80
|
+
await this._handleAcknowledgement(message);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async signal (message: Message): Promise<void> {
|
|
85
|
+
assert(message.data?.signal);
|
|
86
|
+
await this._sendReliableMessage(message);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async offer (message: Message): Promise<Answer> {
|
|
90
|
+
message.messageId = PublicKey.random();
|
|
91
|
+
const promise = new Promise<Answer>((resolve, reject) => {
|
|
92
|
+
this._offerRecords.set(message.messageId!, { resolve, reject });
|
|
93
|
+
return this._sendReliableMessage(message);
|
|
94
|
+
});
|
|
95
|
+
return promise;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private async _sendReliableMessage (message: Message): Promise<PublicKey> {
|
|
99
|
+
// Setting unique messageId if it not specified yet.
|
|
100
|
+
message.messageId = message.messageId ?? PublicKey.random();
|
|
101
|
+
log(`sent message: ${JSON.stringify(message)}`);
|
|
102
|
+
|
|
103
|
+
// Setting retry interval if signal was not acknowledged.
|
|
104
|
+
const cancelRetry = exponentialBackoffInterval(async () => {
|
|
105
|
+
log(`retrying message: ${JSON.stringify(message)}`);
|
|
106
|
+
await this._sendMessage(message);
|
|
107
|
+
}, this._retryDelay);
|
|
108
|
+
|
|
109
|
+
const timeout = setTimeout(() => {
|
|
110
|
+
log('Signal was not delivered!');
|
|
111
|
+
this._onAckCallbacks.delete(message.messageId!);
|
|
112
|
+
cancelRetry();
|
|
113
|
+
}, this._timeout);
|
|
114
|
+
|
|
115
|
+
assert(!this._onAckCallbacks.has(message.messageId!));
|
|
116
|
+
this._onAckCallbacks.set(message.messageId, () => {
|
|
117
|
+
this._onAckCallbacks.delete(message.messageId!);
|
|
118
|
+
cancelRetry();
|
|
119
|
+
clearTimeout(timeout);
|
|
120
|
+
});
|
|
121
|
+
this._subscriptions.push(cancelRetry);
|
|
122
|
+
this._subscriptions.push(() => clearTimeout(timeout));
|
|
123
|
+
|
|
124
|
+
await this._sendMessage(message);
|
|
125
|
+
return message.messageId;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private async _resolveAnswers (message: Message): Promise<void> {
|
|
129
|
+
assert(message.data?.answer?.offerMessageId, 'No offerMessageId');
|
|
130
|
+
const offerRecord = this._offerRecords.get(message.data.answer.offerMessageId);
|
|
131
|
+
if (offerRecord) {
|
|
132
|
+
this._offerRecords.delete(message.data.answer.offerMessageId);
|
|
133
|
+
assert(message.data?.answer, 'No Answer');
|
|
134
|
+
log(`resolving answer with ${message.data.answer}`);
|
|
135
|
+
offerRecord.resolve(message.data.answer);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private async _handleOffer (message: Message): Promise<void> {
|
|
140
|
+
const answer = await this._onOffer(message);
|
|
141
|
+
answer.offerMessageId = message.messageId;
|
|
142
|
+
const answerMessage: Message = {
|
|
143
|
+
id: message.remoteId,
|
|
144
|
+
remoteId: message.id,
|
|
145
|
+
topic: message.topic,
|
|
146
|
+
sessionId: message.sessionId,
|
|
147
|
+
data: { answer }
|
|
148
|
+
};
|
|
149
|
+
await this._sendReliableMessage(answerMessage);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private async _handleSignal (message: Message): Promise<void> {
|
|
153
|
+
assert(message.messageId);
|
|
154
|
+
await this._onSignal(message);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private async _handleAcknowledgement (message: Message): Promise<void> {
|
|
158
|
+
assert(message.data?.ack?.messageId);
|
|
159
|
+
this._onAckCallbacks.get(message.data.ack.messageId)?.();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private async _sendAcknowledgement (message: Message): Promise<void> {
|
|
163
|
+
assert(message.messageId);
|
|
164
|
+
const ackMessage = {
|
|
165
|
+
id: message.remoteId,
|
|
166
|
+
remoteId: message.id,
|
|
167
|
+
topic: message.topic,
|
|
168
|
+
sessionId: message.sessionId,
|
|
169
|
+
data: { ack: { messageId: message.messageId } }
|
|
170
|
+
};
|
|
171
|
+
log(`sent ack: ${JSON.stringify(ackMessage)}`);
|
|
172
|
+
await this._sendMessage(ackMessage);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
destroy (): void {
|
|
176
|
+
this._subscriptions.unsubscribe();
|
|
177
|
+
}
|
|
178
|
+
}
|
package/src/signal/signal-api.ts
CHANGED
|
@@ -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,30 @@ 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
|
+
messageId: undefined
|
|
66
|
+
};
|
|
67
|
+
await api2.signal(msg);
|
|
68
|
+
|
|
69
|
+
await waitForExpect(() => {
|
|
70
|
+
expect(signalMock1).toHaveBeenCalledWith([msg]);
|
|
71
|
+
}, 4_000);
|
|
72
|
+
}).timeout(5_000);
|
|
73
|
+
|
|
50
74
|
test('join', async () => {
|
|
51
75
|
api1 = new SignalClient(signalApiUrl1, (async () => {}) as any, async () => {});
|
|
52
76
|
|
|
@@ -58,14 +82,14 @@ describe('SignalApi', () => {
|
|
|
58
82
|
}).timeout(1_000);
|
|
59
83
|
|
|
60
84
|
test('offer', async () => {
|
|
61
|
-
const offerMock = mockFn<(msg:
|
|
85
|
+
const offerMock = mockFn<(msg: Message) => Promise<Answer>>()
|
|
62
86
|
.resolvesTo({ accept: true });
|
|
63
87
|
api1 = new SignalClient(signalApiUrl1, offerMock, async () => {});
|
|
64
88
|
|
|
65
89
|
await api1.join(topic, peer1);
|
|
66
90
|
|
|
67
|
-
const offer:
|
|
68
|
-
data: {
|
|
91
|
+
const offer: Message = {
|
|
92
|
+
data: { offer: {} },
|
|
69
93
|
id: peer2,
|
|
70
94
|
remoteId: peer1,
|
|
71
95
|
sessionId: PublicKey.random(),
|
|
@@ -77,18 +101,19 @@ describe('SignalApi', () => {
|
|
|
77
101
|
}).timeout(5_000);
|
|
78
102
|
|
|
79
103
|
test('signal', async () => {
|
|
80
|
-
const signalMock = mockFn<(msg:
|
|
104
|
+
const signalMock = mockFn<(msg: Message) => Promise<void>>()
|
|
81
105
|
.resolvesTo();
|
|
82
106
|
api1 = new SignalClient(signalApiUrl1, (async () => {}) as any, signalMock);
|
|
83
107
|
|
|
84
108
|
await api1.join(topic, peer1);
|
|
85
109
|
|
|
86
|
-
const msg:
|
|
110
|
+
const msg: Message = {
|
|
87
111
|
id: peer2,
|
|
88
112
|
remoteId: peer1,
|
|
89
113
|
sessionId: PublicKey.random(),
|
|
90
114
|
topic,
|
|
91
|
-
data: {
|
|
115
|
+
data: { signal: { json: 'bar' } },
|
|
116
|
+
messageId: undefined
|
|
92
117
|
};
|
|
93
118
|
await api1.signal(msg);
|
|
94
119
|
|
|
@@ -118,9 +143,9 @@ describe('SignalApi', () => {
|
|
|
118
143
|
|
|
119
144
|
// Skip because communication between signal servers is not yet implemented.
|
|
120
145
|
test.skip('newly joined peer can receive signals from other signal servers', async () => {
|
|
121
|
-
const offerMock = mockFn<(msg:
|
|
146
|
+
const offerMock = mockFn<(msg: Message) => Promise<Answer>>()
|
|
122
147
|
.resolvesTo({ accept: true });
|
|
123
|
-
const signalMock = mockFn<(msg:
|
|
148
|
+
const signalMock = mockFn<(msg: Message) => Promise<void>>()
|
|
124
149
|
.resolvesTo();
|
|
125
150
|
|
|
126
151
|
api1 = new SignalClient(signalApiUrl1, offerMock, async () => {});
|
|
@@ -136,16 +161,16 @@ describe('SignalApi', () => {
|
|
|
136
161
|
id: peer2,
|
|
137
162
|
topic,
|
|
138
163
|
sessionId,
|
|
139
|
-
data: {}
|
|
164
|
+
data: { offer: {} }
|
|
140
165
|
});
|
|
141
166
|
expect(answer).toEqual({ accept: true });
|
|
142
167
|
|
|
143
|
-
const msg:
|
|
168
|
+
const msg: Message = {
|
|
144
169
|
id: peer2,
|
|
145
170
|
remoteId: peer1,
|
|
146
171
|
sessionId,
|
|
147
172
|
topic,
|
|
148
|
-
data: {
|
|
173
|
+
data: { offer: { json: 'bar' } }
|
|
149
174
|
};
|
|
150
175
|
await api1.signal(msg);
|
|
151
176
|
|