@dxos/network-manager 2.33.9-dev.d70ac9ee → 2.33.9-dev.d7113edd
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 +119276 -0
- package/dist/browser-mocha/main.js +27 -0
- package/dist/src/network-manager.d.ts.map +1 -1
- package/dist/src/network-manager.js +7 -6
- package/dist/src/network-manager.js.map +1 -1
- package/dist/src/proto/gen/dxos/mesh/signal.d.ts +21 -0
- package/dist/src/proto/gen/dxos/mesh/signal.d.ts.map +1 -1
- package/dist/src/proto/gen/index.d.ts +1 -0
- package/dist/src/proto/gen/index.d.ts.map +1 -1
- package/dist/src/proto/gen/index.js +1 -1
- package/dist/src/proto/gen/index.js.map +1 -1
- package/dist/src/protocol-factory.js +3 -3
- package/dist/src/protocol-factory.js.map +1 -1
- package/dist/src/signal/in-memory-signal-manager.js +5 -5
- package/dist/src/signal/in-memory-signal-manager.js.map +1 -1
- package/dist/src/signal/message-router.d.ts +16 -4
- package/dist/src/signal/message-router.d.ts.map +1 -1
- package/dist/src/signal/message-router.js +91 -17
- package/dist/src/signal/message-router.js.map +1 -1
- package/dist/src/signal/message-router.test.js +120 -13
- package/dist/src/signal/message-router.test.js.map +1 -1
- package/dist/src/signal/signal-client.d.ts +2 -1
- package/dist/src/signal/signal-client.d.ts.map +1 -1
- package/dist/src/signal/signal-client.js +26 -16
- package/dist/src/signal/signal-client.js.map +1 -1
- package/dist/src/signal/signal-client.test.js +4 -2
- package/dist/src/signal/signal-client.test.js.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.js +2 -2
- package/dist/src/signal/websocket-signal-manager.js.map +1 -1
- package/dist/src/swarm/connection.js +8 -8
- package/dist/src/swarm/connection.js.map +1 -1
- package/dist/src/swarm/swarm.js +10 -10
- package/dist/src/swarm/swarm.js.map +1 -1
- package/dist/src/swarm/swarm.test.js +2 -0
- 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 +3 -3
- 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.js +2 -2
- package/dist/src/transport/in-memory-transport.js.map +1 -1
- package/dist/src/transport/webrtc-transport.js +3 -3
- package/dist/src/transport/webrtc-transport.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -11
- package/src/network-manager.ts +2 -1
- package/src/proto/defs/dxos/mesh/signal.proto +13 -0
- package/src/proto/gen/dxos/mesh/signal.ts +21 -0
- package/src/proto/gen/index.ts +2 -1
- package/src/protocol-factory.ts +1 -1
- package/src/signal/in-memory-signal-manager.ts +1 -1
- package/src/signal/message-router.test.ts +154 -38
- package/src/signal/message-router.ts +107 -18
- package/src/signal/signal-client.test.ts +4 -2
- package/src/signal/signal-client.ts +42 -15
- package/src/signal/websocket-rpc.ts +1 -1
- package/src/signal/websocket-signal-manager.ts +1 -1
- package/src/swarm/connection.ts +1 -1
- package/src/swarm/swarm.test.ts +2 -0
- package/src/swarm/swarm.ts +2 -2
- package/src/testing/test-protocol.ts +1 -1
- 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 +1 -1
- package/src/transport/webrtc-transport.ts +1 -1
|
@@ -13,9 +13,14 @@ message Message {
|
|
|
13
13
|
PubKey id = 1;
|
|
14
14
|
/// Receiver`s public key.
|
|
15
15
|
PubKey remoteId = 2;
|
|
16
|
+
/// Swarm identefier.
|
|
16
17
|
PubKey topic = 3;
|
|
18
|
+
/// Unique connection identifier.
|
|
17
19
|
PubKey sessionId = 4;
|
|
20
|
+
/// Message payload.
|
|
18
21
|
MessageData data = 5;
|
|
22
|
+
/// Unique message identifier. Used for Acknolegment and matching Anwers to Offers.
|
|
23
|
+
PubKey messageId = 6;
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
message MessageData {
|
|
@@ -23,6 +28,7 @@ message MessageData {
|
|
|
23
28
|
Offer offer = 1;
|
|
24
29
|
Answer answer = 2;
|
|
25
30
|
Signal signal = 3;
|
|
31
|
+
Acknowledgement ack = 4;
|
|
26
32
|
}
|
|
27
33
|
}
|
|
28
34
|
|
|
@@ -31,8 +37,15 @@ message Offer {
|
|
|
31
37
|
|
|
32
38
|
message Answer {
|
|
33
39
|
bool accept = 1;
|
|
40
|
+
/// MessageId of the Offer being answered.
|
|
41
|
+
PubKey offerMessageId = 2;
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
message Signal {
|
|
37
45
|
string json = 1;
|
|
38
46
|
}
|
|
47
|
+
|
|
48
|
+
message Acknowledgement {
|
|
49
|
+
// MessageId of the Message being acknowledged.
|
|
50
|
+
PubKey messageId = 1;
|
|
51
|
+
}
|
|
@@ -12,20 +12,41 @@ export interface Message {
|
|
|
12
12
|
* Receiver`s public key.
|
|
13
13
|
*/
|
|
14
14
|
remoteId?: ReturnType<(typeof substitutions)["dxos.halo.keys.PubKey"]["decode"]>;
|
|
15
|
+
/**
|
|
16
|
+
* Swarm identefier.
|
|
17
|
+
*/
|
|
15
18
|
topic?: ReturnType<(typeof substitutions)["dxos.halo.keys.PubKey"]["decode"]>;
|
|
19
|
+
/**
|
|
20
|
+
* Unique connection identifier.
|
|
21
|
+
*/
|
|
16
22
|
sessionId?: ReturnType<(typeof substitutions)["dxos.halo.keys.PubKey"]["decode"]>;
|
|
23
|
+
/**
|
|
24
|
+
* Message payload.
|
|
25
|
+
*/
|
|
17
26
|
data?: MessageData;
|
|
27
|
+
/**
|
|
28
|
+
* Unique message identifier. Used for Acknolegment and matching Anwers to Offers.
|
|
29
|
+
*/
|
|
30
|
+
messageId?: ReturnType<(typeof substitutions)["dxos.halo.keys.PubKey"]["decode"]>;
|
|
18
31
|
}
|
|
19
32
|
export interface MessageData {
|
|
20
33
|
offer?: Offer;
|
|
21
34
|
answer?: Answer;
|
|
22
35
|
signal?: Signal;
|
|
36
|
+
ack?: Acknowledgement;
|
|
23
37
|
}
|
|
24
38
|
export interface Offer {
|
|
25
39
|
}
|
|
26
40
|
export interface Answer {
|
|
27
41
|
accept?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* MessageId of the Offer being answered.
|
|
44
|
+
*/
|
|
45
|
+
offerMessageId?: ReturnType<(typeof substitutions)["dxos.halo.keys.PubKey"]["decode"]>;
|
|
28
46
|
}
|
|
29
47
|
export interface Signal {
|
|
30
48
|
json?: string;
|
|
31
49
|
}
|
|
50
|
+
export interface Acknowledgement {
|
|
51
|
+
messageId?: ReturnType<(typeof substitutions)["dxos.halo.keys.PubKey"]["decode"]>;
|
|
52
|
+
}
|
package/src/proto/gen/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ export interface TYPES {
|
|
|
15
15
|
"dxos.halo.keys.KeyType": dxos_halo_keys.KeyType;
|
|
16
16
|
"dxos.halo.keys.PrivKey": dxos_halo_keys.PrivKey;
|
|
17
17
|
"dxos.halo.keys.PubKey": dxos_halo_keys.PubKey;
|
|
18
|
+
"dxos.mesh.signal.Acknowledgement": dxos_mesh_signal.Acknowledgement;
|
|
18
19
|
"dxos.mesh.signal.Answer": dxos_mesh_signal.Answer;
|
|
19
20
|
"dxos.mesh.signal.Message": dxos_mesh_signal.Message;
|
|
20
21
|
"dxos.mesh.signal.MessageData": dxos_mesh_signal.MessageData;
|
|
@@ -24,5 +25,5 @@ export interface TYPES {
|
|
|
24
25
|
}
|
|
25
26
|
export interface SERVICES {
|
|
26
27
|
}
|
|
27
|
-
export const schemaJson = JSON.parse("{\"nested\":{\"dxos\":{\"nested\":{\"credentials\":{\"nested\":{\"Message\":{\"fields\":{\"payload\":{\"rule\":\"required\",\"type\":\"google.protobuf.Any\",\"id\":1,\"comment\":null}},\"comment\":null},\"SignedMessage\":{\"fields\":{\"signed\":{\"rule\":\"required\",\"type\":\"Signed\",\"id\":1,\"comment\":null},\"signatures\":{\"rule\":\"repeated\",\"type\":\"Signature\",\"id\":2,\"comment\":null}},\"nested\":{\"Signature\":{\"fields\":{\"key\":{\"rule\":\"required\",\"type\":\"PubKey\",\"id\":1,\"comment\":null},\"signature\":{\"rule\":\"required\",\"type\":\"bytes\",\"id\":2,\"comment\":null},\"keyChain\":{\"type\":\"KeyChain\",\"id\":3,\"comment\":null}},\"comment\":null},\"Signed\":{\"fields\":{\"created\":{\"rule\":\"required\",\"type\":\"string\",\"id\":1,\"comment\":null},\"nonce\":{\"rule\":\"required\",\"type\":\"bytes\",\"id\":2,\"comment\":null},\"payload\":{\"rule\":\"required\",\"type\":\"google.protobuf.Any\",\"id\":10,\"comment\":null}},\"comment\":null}},\"comment\":\"A generic container message used whenever messages are signed (e.g. PartyCredential)\"}}},\"halo\":{\"nested\":{\"keys\":{\"nested\":{\"KeyChain\":{\"fields\":{\"publicKey\":{\"rule\":\"required\",\"type\":\"PubKey\",\"id\":1,\"comment\":null},\"message\":{\"rule\":\"required\",\"type\":\"SignedMessage\",\"id\":2,\"comment\":null},\"parents\":{\"rule\":\"repeated\",\"type\":\"KeyChain\",\"id\":3,\"comment\":null}},\"comment\":null},\"KeyRecord\":{\"fields\":{\"type\":{\"rule\":\"required\",\"type\":\"KeyType\",\"id\":1,\"comment\":\"The `KeyType` type of the key. This is often unknown for keys from other sources.\"},\"publicKey\":{\"rule\":\"required\",\"type\":\"PubKey\",\"id\":2,\"comment\":\"The public key as a Buffer (required).\"},\"secretKey\":{\"type\":\"PrivKey\",\"id\":3,\"comment\":\"The secret key as a Buffer (this will never be visible outside the Keyring).\"},\"hint\":{\"type\":\"bool\",\"id\":4,\"comment\":\"Is this key from a Greeting \\\"hint\\\"?\"},\"own\":{\"type\":\"bool\",\"id\":5,\"comment\":\"Determines if this is our key?\\nUsually true if `secretKey` is present; may be false for \\\"inception keys\\\" such as the Party key.\"},\"trusted\":{\"type\":\"bool\",\"id\":6,\"comment\":\"Is this key to be trusted?\"},\"added\":{\"type\":\"string\",\"id\":7,\"comment\":\"An RFC-3339 date/time string for when the key was added to the Keyring.\"},\"created\":{\"type\":\"string\",\"id\":8,\"comment\":\"An RFC-3339 date/time string for when the key was created.\"}},\"comment\":null},\"KeyRecordList\":{\"fields\":{\"keys\":{\"rule\":\"repeated\",\"type\":\"KeyRecord\",\"id\":1,\"comment\":null}},\"comment\":null},\"KeyType\":{\"values\":{\"UNKNOWN\":0,\"IDENTITY\":1,\"DEVICE\":2,\"PARTY\":3,\"FEED\":4,\"DXNS_ADDRESS\":5},\"comment\":null,\"comments\":{\"UNKNOWN\":null,\"IDENTITY\":null,\"DEVICE\":null,\"PARTY\":null,\"FEED\":null,\"DXNS_ADDRESS\":null}},\"PrivKey\":{\"fields\":{\"data\":{\"type\":\"bytes\",\"id\":1,\"comment\":null}},\"comment\":null},\"PubKey\":{\"fields\":{\"data\":{\"type\":\"bytes\",\"id\":1,\"comment\":null}},\"comment\":null}}}}},\"mesh\":{\"nested\":{\"signal\":{\"nested\":{\"Answer\":{\"fields\":{\"accept\":{\"type\":\"bool\",\"id\":1,\"comment\":null}},\"comment\":null},\"Message\":{\"fields\":{\"id\":{\"type\":\"PubKey\",\"id\":1,\"comment\":\"Sender's public key.\"},\"remoteId\":{\"type\":\"PubKey\",\"id\":2,\"comment\":\"Receiver`s public key.\"},\"topic\":{\"type\":\"PubKey\",\"id\":3,\"comment\"
|
|
28
|
+
export const schemaJson = JSON.parse("{\"nested\":{\"dxos\":{\"nested\":{\"credentials\":{\"nested\":{\"Message\":{\"fields\":{\"payload\":{\"rule\":\"required\",\"type\":\"google.protobuf.Any\",\"id\":1,\"comment\":null}},\"comment\":null},\"SignedMessage\":{\"fields\":{\"signed\":{\"rule\":\"required\",\"type\":\"Signed\",\"id\":1,\"comment\":null},\"signatures\":{\"rule\":\"repeated\",\"type\":\"Signature\",\"id\":2,\"comment\":null}},\"nested\":{\"Signature\":{\"fields\":{\"key\":{\"rule\":\"required\",\"type\":\"PubKey\",\"id\":1,\"comment\":null},\"signature\":{\"rule\":\"required\",\"type\":\"bytes\",\"id\":2,\"comment\":null},\"keyChain\":{\"type\":\"KeyChain\",\"id\":3,\"comment\":null}},\"comment\":null},\"Signed\":{\"fields\":{\"created\":{\"rule\":\"required\",\"type\":\"string\",\"id\":1,\"comment\":null},\"nonce\":{\"rule\":\"required\",\"type\":\"bytes\",\"id\":2,\"comment\":null},\"payload\":{\"rule\":\"required\",\"type\":\"google.protobuf.Any\",\"id\":10,\"comment\":null}},\"comment\":null}},\"comment\":\"A generic container message used whenever messages are signed (e.g. PartyCredential)\"}}},\"halo\":{\"nested\":{\"keys\":{\"nested\":{\"KeyChain\":{\"fields\":{\"publicKey\":{\"rule\":\"required\",\"type\":\"PubKey\",\"id\":1,\"comment\":null},\"message\":{\"rule\":\"required\",\"type\":\"SignedMessage\",\"id\":2,\"comment\":null},\"parents\":{\"rule\":\"repeated\",\"type\":\"KeyChain\",\"id\":3,\"comment\":null}},\"comment\":null},\"KeyRecord\":{\"fields\":{\"type\":{\"rule\":\"required\",\"type\":\"KeyType\",\"id\":1,\"comment\":\"The `KeyType` type of the key. This is often unknown for keys from other sources.\"},\"publicKey\":{\"rule\":\"required\",\"type\":\"PubKey\",\"id\":2,\"comment\":\"The public key as a Buffer (required).\"},\"secretKey\":{\"type\":\"PrivKey\",\"id\":3,\"comment\":\"The secret key as a Buffer (this will never be visible outside the Keyring).\"},\"hint\":{\"type\":\"bool\",\"id\":4,\"comment\":\"Is this key from a Greeting \\\"hint\\\"?\"},\"own\":{\"type\":\"bool\",\"id\":5,\"comment\":\"Determines if this is our key?\\nUsually true if `secretKey` is present; may be false for \\\"inception keys\\\" such as the Party key.\"},\"trusted\":{\"type\":\"bool\",\"id\":6,\"comment\":\"Is this key to be trusted?\"},\"added\":{\"type\":\"string\",\"id\":7,\"comment\":\"An RFC-3339 date/time string for when the key was added to the Keyring.\"},\"created\":{\"type\":\"string\",\"id\":8,\"comment\":\"An RFC-3339 date/time string for when the key was created.\"}},\"comment\":null},\"KeyRecordList\":{\"fields\":{\"keys\":{\"rule\":\"repeated\",\"type\":\"KeyRecord\",\"id\":1,\"comment\":null}},\"comment\":null},\"KeyType\":{\"values\":{\"UNKNOWN\":0,\"IDENTITY\":1,\"DEVICE\":2,\"PARTY\":3,\"FEED\":4,\"DXNS_ADDRESS\":5},\"comment\":null,\"comments\":{\"UNKNOWN\":null,\"IDENTITY\":null,\"DEVICE\":null,\"PARTY\":null,\"FEED\":null,\"DXNS_ADDRESS\":null}},\"PrivKey\":{\"fields\":{\"data\":{\"type\":\"bytes\",\"id\":1,\"comment\":null}},\"comment\":null},\"PubKey\":{\"fields\":{\"data\":{\"type\":\"bytes\",\"id\":1,\"comment\":null}},\"comment\":null}}}}},\"mesh\":{\"nested\":{\"signal\":{\"nested\":{\"Acknowledgement\":{\"fields\":{\"messageId\":{\"type\":\"PubKey\",\"id\":1,\"comment\":null}},\"comment\":null},\"Answer\":{\"fields\":{\"accept\":{\"type\":\"bool\",\"id\":1,\"comment\":null},\"offerMessageId\":{\"type\":\"PubKey\",\"id\":2,\"comment\":\"MessageId of the Offer being answered.\"}},\"comment\":null},\"Message\":{\"fields\":{\"id\":{\"type\":\"PubKey\",\"id\":1,\"comment\":\"Sender's public key.\"},\"remoteId\":{\"type\":\"PubKey\",\"id\":2,\"comment\":\"Receiver`s public key.\"},\"topic\":{\"type\":\"PubKey\",\"id\":3,\"comment\":\"Swarm identefier.\"},\"sessionId\":{\"type\":\"PubKey\",\"id\":4,\"comment\":\"Unique connection identifier.\"},\"data\":{\"type\":\"MessageData\",\"id\":5,\"comment\":\"Message payload.\"},\"messageId\":{\"type\":\"PubKey\",\"id\":6,\"comment\":\"Unique message identifier. Used for Acknolegment and matching Anwers to Offers.\"}},\"comment\":null},\"MessageData\":{\"oneofs\":{\"payload\":{\"oneof\":[\"offer\",\"answer\",\"signal\",\"ack\"],\"comment\":null}},\"fields\":{\"offer\":{\"type\":\"Offer\",\"id\":1,\"comment\":null},\"answer\":{\"type\":\"Answer\",\"id\":2,\"comment\":null},\"signal\":{\"type\":\"Signal\",\"id\":3,\"comment\":null},\"ack\":{\"type\":\"Acknowledgement\",\"id\":4,\"comment\":null}},\"comment\":null},\"Offer\":{\"fields\":{},\"comment\":null},\"Signal\":{\"fields\":{\"json\":{\"type\":\"string\",\"id\":1,\"comment\":null}},\"comment\":null}}}}}}},\"google\":{\"nested\":{\"protobuf\":{\"nested\":{\"Any\":{\"fields\":{\"type_url\":{\"type\":\"string\",\"id\":1},\"value\":{\"type\":\"bytes\",\"id\":2}},\"comment\":null}}}}}}}");
|
|
28
29
|
export const schema = Schema.fromJson<TYPES, SERVICES>(schemaJson, substitutions);
|
package/src/protocol-factory.ts
CHANGED
|
@@ -40,11 +40,15 @@ describe('MessageRouter', () => {
|
|
|
40
40
|
await broker1.stop();
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
const createSignalClientAndMessageRouter = async (
|
|
44
|
-
signalApiUrl
|
|
45
|
-
onSignal
|
|
46
|
-
onOffer
|
|
47
|
-
|
|
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
|
+
}) => {
|
|
48
52
|
|
|
49
53
|
// eslint-disable-next-line prefer-const
|
|
50
54
|
let api: SignalClient;
|
|
@@ -54,6 +58,7 @@ describe('MessageRouter', () => {
|
|
|
54
58
|
onSignal: onSignal,
|
|
55
59
|
onOffer: onOffer
|
|
56
60
|
});
|
|
61
|
+
afterTest(() => router.destroy());
|
|
57
62
|
|
|
58
63
|
api = new SignalClient(
|
|
59
64
|
signalApiUrl,
|
|
@@ -70,8 +75,8 @@ describe('MessageRouter', () => {
|
|
|
70
75
|
|
|
71
76
|
test('signaling between 2 clients', async () => {
|
|
72
77
|
const signalMock1 = mockFn<(msg: Message) => Promise<void>>().resolvesTo();
|
|
73
|
-
const { api: api1 } = await createSignalClientAndMessageRouter(signalApiUrl1, signalMock1);
|
|
74
|
-
const { api: api2, router: router2 } = await createSignalClientAndMessageRouter(signalApiUrl1);
|
|
78
|
+
const { api: api1 } = await createSignalClientAndMessageRouter({ signalApiUrl: signalApiUrl1, onSignal: signalMock1 });
|
|
79
|
+
const { api: api2, router: router2 } = await createSignalClientAndMessageRouter({ signalApiUrl: signalApiUrl1 });
|
|
75
80
|
|
|
76
81
|
await api1.join(topic, peer1);
|
|
77
82
|
await api2.join(topic, peer2);
|
|
@@ -92,15 +97,19 @@ describe('MessageRouter', () => {
|
|
|
92
97
|
|
|
93
98
|
test('offer/answer', async () => {
|
|
94
99
|
const { api: api1, router: router1 } = await createSignalClientAndMessageRouter(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
{
|
|
101
|
+
signalApiUrl: signalApiUrl1,
|
|
102
|
+
onSignal: (async () => { }) as any,
|
|
103
|
+
onOffer:
|
|
104
|
+
async () => ({ accept: true })
|
|
105
|
+
});
|
|
99
106
|
const { api: api2 } = await createSignalClientAndMessageRouter(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
{
|
|
108
|
+
signalApiUrl: signalApiUrl1,
|
|
109
|
+
onSignal: (async () => { }) as any,
|
|
110
|
+
onOffer:
|
|
111
|
+
async () => ({ accept: true })
|
|
112
|
+
});
|
|
104
113
|
|
|
105
114
|
await api1.join(topic, peer1);
|
|
106
115
|
await api2.join(topic, peer2);
|
|
@@ -112,28 +121,34 @@ describe('MessageRouter', () => {
|
|
|
112
121
|
topic,
|
|
113
122
|
data: { offer: { } }
|
|
114
123
|
});
|
|
115
|
-
expect(answer).toEqual(
|
|
124
|
+
expect(answer.accept).toEqual(true);
|
|
116
125
|
}).timeout(5_000);
|
|
117
126
|
|
|
118
127
|
test('signaling between 3 clients', async () => {
|
|
119
128
|
const signalMock1 = mockFn<(msg: Message) => Promise<void>>().resolvesTo();
|
|
120
129
|
const { api: api1, router: router1 } = await createSignalClientAndMessageRouter(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
130
|
+
{
|
|
131
|
+
signalApiUrl: signalApiUrl1,
|
|
132
|
+
onSignal: signalMock1,
|
|
133
|
+
onOffer:
|
|
134
|
+
async () => ({ accept: true })
|
|
135
|
+
});
|
|
125
136
|
const signalMock2 = mockFn<(msg: Message) => Promise<void>>().resolvesTo();
|
|
126
137
|
const { api: api2, router: router2 } = await createSignalClientAndMessageRouter(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
138
|
+
{
|
|
139
|
+
signalApiUrl: signalApiUrl1,
|
|
140
|
+
onSignal: signalMock2,
|
|
141
|
+
onOffer:
|
|
142
|
+
async () => ({ accept: true })
|
|
143
|
+
});
|
|
131
144
|
const signalMock3 = mockFn<(msg: Message) => Promise<void>>().resolvesTo();
|
|
132
145
|
const { api: api3, router: router3 } = await createSignalClientAndMessageRouter(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
146
|
+
{
|
|
147
|
+
signalApiUrl: signalApiUrl1,
|
|
148
|
+
onSignal: signalMock3,
|
|
149
|
+
onOffer:
|
|
150
|
+
async () => ({ accept: true })
|
|
151
|
+
});
|
|
137
152
|
|
|
138
153
|
await api1.join(topic, peer1);
|
|
139
154
|
await api2.join(topic, peer2);
|
|
@@ -182,15 +197,19 @@ describe('MessageRouter', () => {
|
|
|
182
197
|
|
|
183
198
|
test('two offers', async () => {
|
|
184
199
|
const { api: api1, router: router1 } = await createSignalClientAndMessageRouter(
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
200
|
+
{
|
|
201
|
+
signalApiUrl: signalApiUrl1,
|
|
202
|
+
onSignal: (async () => { }) as any,
|
|
203
|
+
onOffer:
|
|
204
|
+
async () => ({ accept: true })
|
|
205
|
+
});
|
|
189
206
|
const { api: api2, router: router2 } = await createSignalClientAndMessageRouter(
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
207
|
+
{
|
|
208
|
+
signalApiUrl: signalApiUrl1,
|
|
209
|
+
onSignal: (async () => { }) as any,
|
|
210
|
+
onOffer:
|
|
211
|
+
async () => ({ accept: true })
|
|
212
|
+
});
|
|
194
213
|
|
|
195
214
|
await api1.join(topic, peer1);
|
|
196
215
|
await api2.join(topic, peer2);
|
|
@@ -203,7 +222,7 @@ describe('MessageRouter', () => {
|
|
|
203
222
|
topic,
|
|
204
223
|
data: { offer: {} }
|
|
205
224
|
});
|
|
206
|
-
expect(answer1).toEqual(
|
|
225
|
+
expect(answer1.accept).toEqual(true);
|
|
207
226
|
|
|
208
227
|
// sending offer from peer2 to peer1.
|
|
209
228
|
const answer2 = await router2.offer({
|
|
@@ -213,6 +232,103 @@ describe('MessageRouter', () => {
|
|
|
213
232
|
topic,
|
|
214
233
|
data: { offer: {} }
|
|
215
234
|
});
|
|
216
|
-
expect(answer2).toEqual(
|
|
235
|
+
expect(answer2.accept).toEqual(true);
|
|
217
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
|
+
});
|
|
218
334
|
});
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import assert from 'assert';
|
|
6
|
+
import debug from 'debug';
|
|
6
7
|
|
|
7
8
|
import { PublicKey } from '@dxos/protocols';
|
|
8
|
-
import { ComplexMap } from '@dxos/util';
|
|
9
|
+
import { ComplexMap, ComplexSet, exponentialBackoffInterval, SubscriptionGroup } from '@dxos/util';
|
|
9
10
|
|
|
10
11
|
import { Answer, Message } from '../proto/gen/dxos/mesh/signal';
|
|
11
12
|
import { SignalMessaging } from './signal-manager';
|
|
@@ -16,11 +17,14 @@ interface OfferRecord {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
interface MessageRouterOptions {
|
|
19
|
-
onSignal
|
|
20
|
-
sendMessage
|
|
21
|
-
onOffer
|
|
20
|
+
onSignal?: (message: Message) => Promise<void>;
|
|
21
|
+
sendMessage?: (message: Message) => Promise<void>;
|
|
22
|
+
onOffer?: (message: Message) => Promise<Answer>;
|
|
23
|
+
retryDelay?: number;
|
|
24
|
+
timeout?: number;
|
|
22
25
|
}
|
|
23
26
|
|
|
27
|
+
const log = debug('dxos:network-manager:message-router');
|
|
24
28
|
/**
|
|
25
29
|
* Adds offer/answer RPC and reliable messaging.
|
|
26
30
|
*/
|
|
@@ -31,59 +35,144 @@ export class MessageRouter implements SignalMessaging {
|
|
|
31
35
|
private readonly _sendMessage: (message: Message) => Promise<void>;
|
|
32
36
|
private readonly _onOffer: (message: Message) => Promise<Answer>;
|
|
33
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
|
+
|
|
34
45
|
constructor ({
|
|
35
46
|
sendMessage,
|
|
36
47
|
onSignal,
|
|
37
|
-
onOffer
|
|
38
|
-
|
|
48
|
+
onOffer,
|
|
49
|
+
retryDelay = 100,
|
|
50
|
+
timeout = 3000
|
|
51
|
+
}: MessageRouterOptions = {}) {
|
|
52
|
+
assert(sendMessage);
|
|
39
53
|
this._sendMessage = sendMessage;
|
|
54
|
+
assert(onSignal);
|
|
40
55
|
this._onSignal = onSignal;
|
|
56
|
+
assert(onOffer);
|
|
41
57
|
this._onOffer = onOffer;
|
|
58
|
+
this._retryDelay = retryDelay;
|
|
59
|
+
this._timeout = timeout;
|
|
42
60
|
}
|
|
43
61
|
|
|
44
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
|
+
|
|
45
73
|
if (message.data?.offer) {
|
|
46
74
|
await this._handleOffer(message);
|
|
47
75
|
} else if (message.data?.answer) {
|
|
48
76
|
await this._resolveAnswers(message);
|
|
49
77
|
} else if (message.data?.signal) {
|
|
50
|
-
await this.
|
|
78
|
+
await this._handleSignal(message);
|
|
79
|
+
} else if (message.data?.ack) {
|
|
80
|
+
await this._handleAcknowledgement(message);
|
|
51
81
|
}
|
|
52
82
|
}
|
|
53
83
|
|
|
54
84
|
async signal (message: Message): Promise<void> {
|
|
55
85
|
assert(message.data?.signal);
|
|
56
|
-
await this.
|
|
86
|
+
await this._sendReliableMessage(message);
|
|
57
87
|
}
|
|
58
88
|
|
|
59
89
|
async offer (message: Message): Promise<Answer> {
|
|
90
|
+
message.messageId = PublicKey.random();
|
|
60
91
|
const promise = new Promise<Answer>((resolve, reject) => {
|
|
61
|
-
|
|
62
|
-
this.
|
|
92
|
+
this._offerRecords.set(message.messageId!, { resolve, reject });
|
|
93
|
+
return this._sendReliableMessage(message);
|
|
63
94
|
});
|
|
64
|
-
await this._sendMessage(message);
|
|
65
95
|
return promise;
|
|
66
96
|
}
|
|
67
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
|
+
|
|
68
128
|
private async _resolveAnswers (message: Message): Promise<void> {
|
|
69
|
-
assert(message.
|
|
70
|
-
const offerRecord = this._offerRecords.get(message.
|
|
129
|
+
assert(message.data?.answer?.offerMessageId, 'No offerMessageId');
|
|
130
|
+
const offerRecord = this._offerRecords.get(message.data.answer.offerMessageId);
|
|
71
131
|
if (offerRecord) {
|
|
72
|
-
this._offerRecords.delete(message.
|
|
73
|
-
assert(message.data?.answer);
|
|
132
|
+
this._offerRecords.delete(message.data.answer.offerMessageId);
|
|
133
|
+
assert(message.data?.answer, 'No Answer');
|
|
134
|
+
log(`resolving answer with ${message.data.answer}`);
|
|
74
135
|
offerRecord.resolve(message.data.answer);
|
|
75
136
|
}
|
|
76
137
|
}
|
|
77
138
|
|
|
78
139
|
private async _handleOffer (message: Message): Promise<void> {
|
|
79
140
|
const answer = await this._onOffer(message);
|
|
80
|
-
|
|
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 = {
|
|
81
165
|
id: message.remoteId,
|
|
82
166
|
remoteId: message.id,
|
|
83
167
|
topic: message.topic,
|
|
84
168
|
sessionId: message.sessionId,
|
|
85
|
-
data: {
|
|
169
|
+
data: { ack: { messageId: message.messageId } }
|
|
86
170
|
};
|
|
87
|
-
|
|
171
|
+
log(`sent ack: ${JSON.stringify(ackMessage)}`);
|
|
172
|
+
await this._sendMessage(ackMessage);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
destroy (): void {
|
|
176
|
+
this._subscriptions.unsubscribe();
|
|
88
177
|
}
|
|
89
178
|
}
|
|
@@ -61,7 +61,8 @@ describe('SignalApi', () => {
|
|
|
61
61
|
remoteId: peer1,
|
|
62
62
|
sessionId: PublicKey.random(),
|
|
63
63
|
topic,
|
|
64
|
-
data: { signal: { json: "foo: 'bar'" } }
|
|
64
|
+
data: { signal: { json: "foo: 'bar'" } },
|
|
65
|
+
messageId: undefined
|
|
65
66
|
};
|
|
66
67
|
await api2.signal(msg);
|
|
67
68
|
|
|
@@ -111,7 +112,8 @@ describe('SignalApi', () => {
|
|
|
111
112
|
remoteId: peer1,
|
|
112
113
|
sessionId: PublicKey.random(),
|
|
113
114
|
topic,
|
|
114
|
-
data: { signal: { json: 'bar' } }
|
|
115
|
+
data: { signal: { json: 'bar' } },
|
|
116
|
+
messageId: undefined
|
|
115
117
|
};
|
|
116
118
|
await api1.signal(msg);
|
|
117
119
|
|