@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
package/src/network-manager.ts
CHANGED
|
@@ -12,7 +12,9 @@ import { PublicKey } from '@dxos/protocols';
|
|
|
12
12
|
import { ComplexMap } from '@dxos/util';
|
|
13
13
|
|
|
14
14
|
import { ConnectionLog } from './connection-log';
|
|
15
|
-
import {
|
|
15
|
+
import { Message } from './proto/gen/dxos/mesh/signal';
|
|
16
|
+
import { InMemorySignalManager, SignalManager, WebsocketSignalManager } from './signal';
|
|
17
|
+
import { MessageRouter } from './signal/message-router';
|
|
16
18
|
import { Swarm, SwarmMapper } from './swarm';
|
|
17
19
|
import { Topology } from './topology';
|
|
18
20
|
import { createWebRTCTransportFactory, inMemoryTransportFactory } from './transport';
|
|
@@ -38,6 +40,7 @@ export class NetworkManager {
|
|
|
38
40
|
private readonly _swarms = new ComplexMap<PublicKey, Swarm>(key => key.toHex());
|
|
39
41
|
private readonly _maps = new ComplexMap<PublicKey, SwarmMapper>(key => key.toHex());
|
|
40
42
|
private readonly _signalManager: SignalManager;
|
|
43
|
+
private readonly _messageRouter: MessageRouter;
|
|
41
44
|
private readonly _connectionLog?: ConnectionLog;
|
|
42
45
|
|
|
43
46
|
public readonly topicsUpdated = new Event<void>();
|
|
@@ -45,8 +48,8 @@ export class NetworkManager {
|
|
|
45
48
|
constructor (options: NetworkManagerOptions = {}) {
|
|
46
49
|
this._ice = options.ice ?? [];
|
|
47
50
|
|
|
48
|
-
const onOffer = async (message:
|
|
49
|
-
await this._swarms.get(message.topic)?.onOffer(message) ?? { accept: false };
|
|
51
|
+
const onOffer = async (message: Message) =>
|
|
52
|
+
await this._swarms.get(message.topic!)?.onOffer(message) ?? { accept: false };
|
|
50
53
|
|
|
51
54
|
this._signalManager = options.signal
|
|
52
55
|
? new WebsocketSignalManager(options.signal, onOffer)
|
|
@@ -54,8 +57,14 @@ export class NetworkManager {
|
|
|
54
57
|
|
|
55
58
|
this._signalManager.peerCandidatesChanged
|
|
56
59
|
.on(([topic, candidates]) => this._swarms.get(topic)?.onPeerCandidatesChanged(candidates));
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
|
|
61
|
+
this._signalManager.onSignal.on(msg => this._messageRouter.receiveMessage(msg));
|
|
62
|
+
|
|
63
|
+
this._messageRouter = new MessageRouter({
|
|
64
|
+
sendMessage: msg => this._signalManager.signal(msg),
|
|
65
|
+
onSignal: async (msg) => this._swarms.get(msg.topic!)?.onSignal(msg),
|
|
66
|
+
onOffer: msg => onOffer(msg)
|
|
67
|
+
});
|
|
59
68
|
|
|
60
69
|
if (options.log) {
|
|
61
70
|
this._connectionLog = new ConnectionLog();
|
|
@@ -108,7 +117,8 @@ export class NetworkManager {
|
|
|
108
117
|
peerId,
|
|
109
118
|
topology,
|
|
110
119
|
protocol,
|
|
111
|
-
this.
|
|
120
|
+
this._messageRouter,
|
|
121
|
+
this._signalManager.lookup.bind(this._signalManager),
|
|
112
122
|
transportFactory,
|
|
113
123
|
options.label
|
|
114
124
|
);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2020 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
syntax = "proto3";
|
|
6
|
+
|
|
7
|
+
import "@dxos/protocols/src/proto/dxos/halo/keys.proto";
|
|
8
|
+
|
|
9
|
+
package dxos.mesh.signal;
|
|
10
|
+
|
|
11
|
+
message Message {
|
|
12
|
+
/// Sender's public key.
|
|
13
|
+
PubKey id = 1;
|
|
14
|
+
/// Receiver`s public key.
|
|
15
|
+
PubKey remoteId = 2;
|
|
16
|
+
PubKey topic = 3;
|
|
17
|
+
PubKey sessionId = 4;
|
|
18
|
+
MessageData data = 5;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
message MessageData {
|
|
22
|
+
oneof payload {
|
|
23
|
+
Offer offer = 1;
|
|
24
|
+
Answer answer = 2;
|
|
25
|
+
Signal signal = 3;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
message Offer {
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
message Answer {
|
|
33
|
+
bool accept = 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
message Signal {
|
|
37
|
+
string json = 1;
|
|
38
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Stream } from "@dxos/codec-protobuf";
|
|
2
|
+
import substitutions from "../../substitutions";
|
|
3
|
+
import * as dxos_halo_keys from "./halo/keys";
|
|
4
|
+
import * as dxos_mesh_signal from "./mesh/signal";
|
|
5
|
+
import * as google_protobuf from "../google/protobuf";
|
|
6
|
+
export interface Message {
|
|
7
|
+
payload: google_protobuf.Any;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A generic container message used whenever messages are signed (e.g. PartyCredential)
|
|
11
|
+
*/
|
|
12
|
+
export interface SignedMessage {
|
|
13
|
+
signed: SignedMessage.Signed;
|
|
14
|
+
signatures?: SignedMessage.Signature[];
|
|
15
|
+
}
|
|
16
|
+
export namespace SignedMessage {
|
|
17
|
+
export interface Signed {
|
|
18
|
+
created: string;
|
|
19
|
+
nonce: Uint8Array;
|
|
20
|
+
payload: google_protobuf.Any;
|
|
21
|
+
}
|
|
22
|
+
export interface Signature {
|
|
23
|
+
key: ReturnType<(typeof substitutions)["dxos.halo.keys.PubKey"]["decode"]>;
|
|
24
|
+
signature: Uint8Array;
|
|
25
|
+
keyChain?: dxos_halo_keys.KeyChain;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Stream } from "@dxos/codec-protobuf";
|
|
2
|
+
import substitutions from "../../../substitutions";
|
|
3
|
+
import * as dxos_credentials from "../credentials";
|
|
4
|
+
import * as dxos_mesh_signal from "../mesh/signal";
|
|
5
|
+
import * as google_protobuf from "../../google/protobuf";
|
|
6
|
+
export enum KeyType {
|
|
7
|
+
UNKNOWN = 0,
|
|
8
|
+
IDENTITY = 1,
|
|
9
|
+
DEVICE = 2,
|
|
10
|
+
PARTY = 3,
|
|
11
|
+
FEED = 4,
|
|
12
|
+
DXNS_ADDRESS = 5
|
|
13
|
+
}
|
|
14
|
+
export interface PubKey {
|
|
15
|
+
data?: Uint8Array;
|
|
16
|
+
}
|
|
17
|
+
export interface PrivKey {
|
|
18
|
+
data?: Uint8Array;
|
|
19
|
+
}
|
|
20
|
+
export interface KeyRecord {
|
|
21
|
+
/**
|
|
22
|
+
* The `KeyType` type of the key. This is often unknown for keys from other sources.
|
|
23
|
+
*/
|
|
24
|
+
type: KeyType;
|
|
25
|
+
/**
|
|
26
|
+
* The public key as a Buffer (required).
|
|
27
|
+
*/
|
|
28
|
+
publicKey: ReturnType<(typeof substitutions)["dxos.halo.keys.PubKey"]["decode"]>;
|
|
29
|
+
/**
|
|
30
|
+
* The secret key as a Buffer (this will never be visible outside the Keyring).
|
|
31
|
+
*/
|
|
32
|
+
secretKey?: ReturnType<(typeof substitutions)["dxos.halo.keys.PrivKey"]["decode"]>;
|
|
33
|
+
/**
|
|
34
|
+
* Is this key from a Greeting "hint"?
|
|
35
|
+
*/
|
|
36
|
+
hint?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Determines if this is our key?
|
|
39
|
+
* Usually true if `secretKey` is present; may be false for "inception keys" such as the Party key.
|
|
40
|
+
*/
|
|
41
|
+
own?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Is this key to be trusted?
|
|
44
|
+
*/
|
|
45
|
+
trusted?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* An RFC-3339 date/time string for when the key was added to the Keyring.
|
|
48
|
+
*/
|
|
49
|
+
added?: string;
|
|
50
|
+
/**
|
|
51
|
+
* An RFC-3339 date/time string for when the key was created.
|
|
52
|
+
*/
|
|
53
|
+
created?: string;
|
|
54
|
+
}
|
|
55
|
+
export interface KeyRecordList {
|
|
56
|
+
keys?: KeyRecord[];
|
|
57
|
+
}
|
|
58
|
+
export interface KeyChain {
|
|
59
|
+
publicKey: ReturnType<(typeof substitutions)["dxos.halo.keys.PubKey"]["decode"]>;
|
|
60
|
+
message: dxos_credentials.SignedMessage;
|
|
61
|
+
parents?: KeyChain[];
|
|
62
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Stream } from "@dxos/codec-protobuf";
|
|
2
|
+
import substitutions from "../../../substitutions";
|
|
3
|
+
import * as dxos_credentials from "../credentials";
|
|
4
|
+
import * as dxos_halo_keys from "../halo/keys";
|
|
5
|
+
import * as google_protobuf from "../../google/protobuf";
|
|
6
|
+
export interface Message {
|
|
7
|
+
/**
|
|
8
|
+
* Sender's public key.
|
|
9
|
+
*/
|
|
10
|
+
id?: ReturnType<(typeof substitutions)["dxos.halo.keys.PubKey"]["decode"]>;
|
|
11
|
+
/**
|
|
12
|
+
* Receiver`s public key.
|
|
13
|
+
*/
|
|
14
|
+
remoteId?: ReturnType<(typeof substitutions)["dxos.halo.keys.PubKey"]["decode"]>;
|
|
15
|
+
topic?: ReturnType<(typeof substitutions)["dxos.halo.keys.PubKey"]["decode"]>;
|
|
16
|
+
sessionId?: ReturnType<(typeof substitutions)["dxos.halo.keys.PubKey"]["decode"]>;
|
|
17
|
+
data?: MessageData;
|
|
18
|
+
}
|
|
19
|
+
export interface MessageData {
|
|
20
|
+
offer?: Offer;
|
|
21
|
+
answer?: Answer;
|
|
22
|
+
signal?: Signal;
|
|
23
|
+
}
|
|
24
|
+
export interface Offer {
|
|
25
|
+
}
|
|
26
|
+
export interface Answer {
|
|
27
|
+
accept?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface Signal {
|
|
30
|
+
json?: string;
|
|
31
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Stream } from "@dxos/codec-protobuf";
|
|
2
|
+
import substitutions from "../../substitutions";
|
|
3
|
+
import * as dxos_credentials from "../dxos/credentials";
|
|
4
|
+
import * as dxos_halo_keys from "../dxos/halo/keys";
|
|
5
|
+
import * as dxos_mesh_signal from "../dxos/mesh/signal";
|
|
6
|
+
export interface Any {
|
|
7
|
+
type_url?: string;
|
|
8
|
+
value?: Uint8Array;
|
|
9
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Schema } from "@dxos/codec-protobuf";
|
|
2
|
+
import * as dxos_credentials from "./dxos/credentials";
|
|
3
|
+
import * as dxos_halo_keys from "./dxos/halo/keys";
|
|
4
|
+
import * as dxos_mesh_signal from "./dxos/mesh/signal";
|
|
5
|
+
import * as google_protobuf from "./google/protobuf";
|
|
6
|
+
import substitutions from "../substitutions";
|
|
7
|
+
export interface TYPES {
|
|
8
|
+
"dxos.credentials.Message": dxos_credentials.Message;
|
|
9
|
+
"dxos.credentials.SignedMessage": dxos_credentials.SignedMessage;
|
|
10
|
+
"dxos.credentials.SignedMessage.Signature": dxos_credentials.SignedMessage.Signature;
|
|
11
|
+
"dxos.credentials.SignedMessage.Signed": dxos_credentials.SignedMessage.Signed;
|
|
12
|
+
"dxos.halo.keys.KeyChain": dxos_halo_keys.KeyChain;
|
|
13
|
+
"dxos.halo.keys.KeyRecord": dxos_halo_keys.KeyRecord;
|
|
14
|
+
"dxos.halo.keys.KeyRecordList": dxos_halo_keys.KeyRecordList;
|
|
15
|
+
"dxos.halo.keys.KeyType": dxos_halo_keys.KeyType;
|
|
16
|
+
"dxos.halo.keys.PrivKey": dxos_halo_keys.PrivKey;
|
|
17
|
+
"dxos.halo.keys.PubKey": dxos_halo_keys.PubKey;
|
|
18
|
+
"dxos.mesh.signal.Answer": dxos_mesh_signal.Answer;
|
|
19
|
+
"dxos.mesh.signal.Message": dxos_mesh_signal.Message;
|
|
20
|
+
"dxos.mesh.signal.MessageData": dxos_mesh_signal.MessageData;
|
|
21
|
+
"dxos.mesh.signal.Offer": dxos_mesh_signal.Offer;
|
|
22
|
+
"dxos.mesh.signal.Signal": dxos_mesh_signal.Signal;
|
|
23
|
+
"google.protobuf.Any": google_protobuf.Any;
|
|
24
|
+
}
|
|
25
|
+
export interface SERVICES {
|
|
26
|
+
}
|
|
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\":null},\"sessionId\":{\"type\":\"PubKey\",\"id\":4,\"comment\":null},\"data\":{\"type\":\"MessageData\",\"id\":5,\"comment\":null}},\"comment\":null},\"MessageData\":{\"oneofs\":{\"payload\":{\"oneof\":[\"offer\",\"answer\",\"signal\"],\"comment\":null}},\"fields\":{\"offer\":{\"type\":\"Offer\",\"id\":1,\"comment\":null},\"answer\":{\"type\":\"Answer\",\"id\":2,\"comment\":null},\"signal\":{\"type\":\"Signal\",\"id\":3,\"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
|
+
export const schema = Schema.fromJson<TYPES, SERVICES>(schemaJson, substitutions);
|
|
@@ -8,6 +8,7 @@ import { Event } from '@dxos/async';
|
|
|
8
8
|
import { PublicKey } from '@dxos/protocols';
|
|
9
9
|
import { ComplexMap, ComplexSet } from '@dxos/util';
|
|
10
10
|
|
|
11
|
+
import { Answer, Message } from '../proto/gen/dxos/mesh/signal';
|
|
11
12
|
import { SignalApi } from './signal-api';
|
|
12
13
|
import { SignalManager } from './signal-manager';
|
|
13
14
|
|
|
@@ -15,10 +16,10 @@ export class InMemorySignalManager implements SignalManager {
|
|
|
15
16
|
readonly statusChanged = new Event<SignalApi.Status[]>();
|
|
16
17
|
readonly commandTrace = new Event<SignalApi.CommandTrace>();
|
|
17
18
|
readonly peerCandidatesChanged = new Event<[topic: PublicKey, candidates: PublicKey[]]>()
|
|
18
|
-
readonly onSignal = new Event<
|
|
19
|
+
readonly onSignal = new Event<Message>();
|
|
19
20
|
|
|
20
21
|
constructor (
|
|
21
|
-
private readonly _onOffer: (message:
|
|
22
|
+
private readonly _onOffer: (message: Message) => Promise<Answer>
|
|
22
23
|
) {}
|
|
23
24
|
|
|
24
25
|
getStatus (): SignalApi.Status[] {
|
|
@@ -48,12 +49,14 @@ export class InMemorySignalManager implements SignalManager {
|
|
|
48
49
|
setTimeout(() => this.peerCandidatesChanged.emit([topic, Array.from(state.swarms.get(topic)!.values())]), 0);
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
offer (msg:
|
|
52
|
+
offer (msg: Message) {
|
|
53
|
+
assert(msg.remoteId);
|
|
52
54
|
assert(state.connections.has(msg.remoteId), 'Peer not connected');
|
|
53
55
|
return state.connections.get(msg.remoteId)!._onOffer(msg);
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
async signal (msg:
|
|
58
|
+
async signal (msg: Message) {
|
|
59
|
+
assert(msg.remoteId);
|
|
57
60
|
assert(state.connections.get(msg.remoteId), 'Peer not connected');
|
|
58
61
|
state.connections.get(msg.remoteId)!.onSignal.emit(msg);
|
|
59
62
|
}
|
|
@@ -0,0 +1,218 @@
|
|
|
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: string,
|
|
45
|
+
onSignal: (msg: Message) => Promise<void> = (async () => {}) as any,
|
|
46
|
+
onOffer: (msg: Message) => Promise<Answer> = async () => ({ accept: true })
|
|
47
|
+
) => {
|
|
48
|
+
|
|
49
|
+
// eslint-disable-next-line prefer-const
|
|
50
|
+
let api: SignalClient;
|
|
51
|
+
const router: MessageRouter = new MessageRouter({
|
|
52
|
+
// todo(mykola): added catch to avoid not finished request.
|
|
53
|
+
sendMessage: (msg: Message) => api.signal(msg).catch((_) => { }),
|
|
54
|
+
onSignal: onSignal,
|
|
55
|
+
onOffer: onOffer
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
api = new SignalClient(
|
|
59
|
+
signalApiUrl,
|
|
60
|
+
(async () => {}) as any,
|
|
61
|
+
async (msg: Message) => router.receiveMessage(msg)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
afterTest(() => api.close());
|
|
65
|
+
return {
|
|
66
|
+
api,
|
|
67
|
+
router
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
test('signaling between 2 clients', async () => {
|
|
72
|
+
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);
|
|
75
|
+
|
|
76
|
+
await api1.join(topic, peer1);
|
|
77
|
+
await api2.join(topic, peer2);
|
|
78
|
+
|
|
79
|
+
const msg: Message = {
|
|
80
|
+
id: peer2,
|
|
81
|
+
remoteId: peer1,
|
|
82
|
+
sessionId: PublicKey.random(),
|
|
83
|
+
topic,
|
|
84
|
+
data: { signal: { json: '{"asd": "asd"}' } }
|
|
85
|
+
};
|
|
86
|
+
await router2.signal(msg);
|
|
87
|
+
|
|
88
|
+
await waitForExpect(() => {
|
|
89
|
+
expect(signalMock1).toHaveBeenCalledWith([msg]);
|
|
90
|
+
}, 4_000);
|
|
91
|
+
}).timeout(5_000);
|
|
92
|
+
|
|
93
|
+
test('offer/answer', async () => {
|
|
94
|
+
const { api: api1, router: router1 } = await createSignalClientAndMessageRouter(
|
|
95
|
+
signalApiUrl1,
|
|
96
|
+
(async () => {}) as any,
|
|
97
|
+
async () => ({ accept: true })
|
|
98
|
+
);
|
|
99
|
+
const { api: api2 } = await createSignalClientAndMessageRouter(
|
|
100
|
+
signalApiUrl1,
|
|
101
|
+
(async () => {}) as any,
|
|
102
|
+
async () => ({ accept: true })
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
await api1.join(topic, peer1);
|
|
106
|
+
await api2.join(topic, peer2);
|
|
107
|
+
|
|
108
|
+
const answer = await router1.offer({
|
|
109
|
+
id: peer1,
|
|
110
|
+
remoteId: peer2,
|
|
111
|
+
sessionId: PublicKey.random(),
|
|
112
|
+
topic,
|
|
113
|
+
data: { offer: { } }
|
|
114
|
+
});
|
|
115
|
+
expect(answer).toEqual({ accept: true });
|
|
116
|
+
}).timeout(5_000);
|
|
117
|
+
|
|
118
|
+
test('signaling between 3 clients', async () => {
|
|
119
|
+
const signalMock1 = mockFn<(msg: Message) => Promise<void>>().resolvesTo();
|
|
120
|
+
const { api: api1, router: router1 } = await createSignalClientAndMessageRouter(
|
|
121
|
+
signalApiUrl1,
|
|
122
|
+
signalMock1,
|
|
123
|
+
async () => ({ accept: true })
|
|
124
|
+
);
|
|
125
|
+
const signalMock2 = mockFn<(msg: Message) => Promise<void>>().resolvesTo();
|
|
126
|
+
const { api: api2, router: router2 } = await createSignalClientAndMessageRouter(
|
|
127
|
+
signalApiUrl1,
|
|
128
|
+
signalMock2,
|
|
129
|
+
async () => ({ accept: true })
|
|
130
|
+
);
|
|
131
|
+
const signalMock3 = mockFn<(msg: Message) => Promise<void>>().resolvesTo();
|
|
132
|
+
const { api: api3, router: router3 } = await createSignalClientAndMessageRouter(
|
|
133
|
+
signalApiUrl1,
|
|
134
|
+
signalMock3,
|
|
135
|
+
async () => ({ accept: true })
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
await api1.join(topic, peer1);
|
|
139
|
+
await api2.join(topic, peer2);
|
|
140
|
+
const peer3 = PublicKey.random();
|
|
141
|
+
await api3.join(topic, peer3);
|
|
142
|
+
|
|
143
|
+
// sending signal from peer1 to peer3.
|
|
144
|
+
const msg1to3: Message = {
|
|
145
|
+
id: peer1,
|
|
146
|
+
remoteId: peer3,
|
|
147
|
+
sessionId: PublicKey.random(),
|
|
148
|
+
topic,
|
|
149
|
+
data: { signal: { json: '1to3' } }
|
|
150
|
+
};
|
|
151
|
+
await router1.signal(msg1to3);
|
|
152
|
+
await waitForExpect(() => {
|
|
153
|
+
expect(signalMock3).toHaveBeenCalledWith([msg1to3]);
|
|
154
|
+
}, 4_000);
|
|
155
|
+
|
|
156
|
+
// sending signal from peer2 to peer3.
|
|
157
|
+
const msg2to3: Message = {
|
|
158
|
+
id: peer2,
|
|
159
|
+
remoteId: peer3,
|
|
160
|
+
sessionId: PublicKey.random(),
|
|
161
|
+
topic,
|
|
162
|
+
data: { signal: { json: '2to3' } }
|
|
163
|
+
};
|
|
164
|
+
await router2.signal(msg2to3);
|
|
165
|
+
await waitForExpect(() => {
|
|
166
|
+
expect(signalMock3).toHaveBeenCalledWith([msg2to3]);
|
|
167
|
+
}, 4_000);
|
|
168
|
+
|
|
169
|
+
// sending signal from peer3 to peer1.
|
|
170
|
+
const msg3to1: Message = {
|
|
171
|
+
id: peer3,
|
|
172
|
+
remoteId: peer1,
|
|
173
|
+
sessionId: PublicKey.random(),
|
|
174
|
+
topic,
|
|
175
|
+
data: { signal: { json: '3to1' } }
|
|
176
|
+
};
|
|
177
|
+
await router3.signal(msg3to1);
|
|
178
|
+
await waitForExpect(() => {
|
|
179
|
+
expect(signalMock1).toHaveBeenCalledWith([msg3to1]);
|
|
180
|
+
}, 4_000);
|
|
181
|
+
}).timeout(5_000);
|
|
182
|
+
|
|
183
|
+
test('two offers', async () => {
|
|
184
|
+
const { api: api1, router: router1 } = await createSignalClientAndMessageRouter(
|
|
185
|
+
signalApiUrl1,
|
|
186
|
+
(async () => {}) as any,
|
|
187
|
+
async () => ({ accept: true })
|
|
188
|
+
);
|
|
189
|
+
const { api: api2, router: router2 } = await createSignalClientAndMessageRouter(
|
|
190
|
+
signalApiUrl1,
|
|
191
|
+
(async () => {}) as any,
|
|
192
|
+
async () => ({ accept: true })
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
await api1.join(topic, peer1);
|
|
196
|
+
await api2.join(topic, peer2);
|
|
197
|
+
|
|
198
|
+
// sending offer from peer1 to peer2.
|
|
199
|
+
const answer1 = await router1.offer({
|
|
200
|
+
id: peer1,
|
|
201
|
+
remoteId: peer2,
|
|
202
|
+
sessionId: PublicKey.random(),
|
|
203
|
+
topic,
|
|
204
|
+
data: { offer: {} }
|
|
205
|
+
});
|
|
206
|
+
expect(answer1).toEqual({ accept: true });
|
|
207
|
+
|
|
208
|
+
// sending offer from peer2 to peer1.
|
|
209
|
+
const answer2 = await router2.offer({
|
|
210
|
+
id: peer2,
|
|
211
|
+
remoteId: peer1,
|
|
212
|
+
sessionId: PublicKey.random(),
|
|
213
|
+
topic,
|
|
214
|
+
data: { offer: {} }
|
|
215
|
+
});
|
|
216
|
+
expect(answer2).toEqual({ accept: true });
|
|
217
|
+
}).timeout(5_000);
|
|
218
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2020 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import assert from 'assert';
|
|
6
|
+
|
|
7
|
+
import { PublicKey } from '@dxos/protocols';
|
|
8
|
+
import { ComplexMap } from '@dxos/util';
|
|
9
|
+
|
|
10
|
+
import { Answer, Message } from '../proto/gen/dxos/mesh/signal';
|
|
11
|
+
import { SignalMessaging } from './signal-manager';
|
|
12
|
+
|
|
13
|
+
interface OfferRecord {
|
|
14
|
+
resolve: (answer: Answer) => void;
|
|
15
|
+
reject: (error?: Error) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface MessageRouterOptions {
|
|
19
|
+
onSignal: (message: Message) => Promise<void>;
|
|
20
|
+
sendMessage: (message: Message) => Promise<void>;
|
|
21
|
+
onOffer: (message: Message) => Promise<Answer>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Adds offer/answer RPC and reliable messaging.
|
|
26
|
+
*/
|
|
27
|
+
// TODO(mykola): https://github.com/dxos/protocols/issues/1316
|
|
28
|
+
export class MessageRouter implements SignalMessaging {
|
|
29
|
+
private readonly _offerRecords: ComplexMap<PublicKey, OfferRecord> = new ComplexMap(key => key.toHex());
|
|
30
|
+
private readonly _onSignal: (message: Message) => Promise<void>;
|
|
31
|
+
private readonly _sendMessage: (message: Message) => Promise<void>;
|
|
32
|
+
private readonly _onOffer: (message: Message) => Promise<Answer>;
|
|
33
|
+
|
|
34
|
+
constructor ({
|
|
35
|
+
sendMessage,
|
|
36
|
+
onSignal,
|
|
37
|
+
onOffer
|
|
38
|
+
}: MessageRouterOptions) {
|
|
39
|
+
this._sendMessage = sendMessage;
|
|
40
|
+
this._onSignal = onSignal;
|
|
41
|
+
this._onOffer = onOffer;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async receiveMessage (message: Message): Promise<void> {
|
|
45
|
+
if (message.data?.offer) {
|
|
46
|
+
await this._handleOffer(message);
|
|
47
|
+
} else if (message.data?.answer) {
|
|
48
|
+
await this._resolveAnswers(message);
|
|
49
|
+
} else if (message.data?.signal) {
|
|
50
|
+
await this._onSignal(message);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async signal (message: Message): Promise<void> {
|
|
55
|
+
assert(message.data?.signal);
|
|
56
|
+
await this._sendMessage(message);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async offer (message: Message): Promise<Answer> {
|
|
60
|
+
const promise = new Promise<Answer>((resolve, reject) => {
|
|
61
|
+
assert(message.sessionId);
|
|
62
|
+
this._offerRecords.set(message.sessionId, { resolve, reject });
|
|
63
|
+
});
|
|
64
|
+
await this._sendMessage(message);
|
|
65
|
+
return promise;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private async _resolveAnswers (message: Message): Promise<void> {
|
|
69
|
+
assert(message.sessionId);
|
|
70
|
+
const offerRecord = this._offerRecords.get(message.sessionId);
|
|
71
|
+
if (offerRecord) {
|
|
72
|
+
this._offerRecords.delete(message.sessionId);
|
|
73
|
+
assert(message.data?.answer);
|
|
74
|
+
offerRecord.resolve(message.data.answer);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private async _handleOffer (message: Message): Promise<void> {
|
|
79
|
+
const answer = await this._onOffer(message);
|
|
80
|
+
const answerMessage = {
|
|
81
|
+
id: message.remoteId,
|
|
82
|
+
remoteId: message.id,
|
|
83
|
+
topic: message.topic,
|
|
84
|
+
sessionId: message.sessionId,
|
|
85
|
+
data: { answer: answer }
|
|
86
|
+
};
|
|
87
|
+
await this._sendMessage(answerMessage);
|
|
88
|
+
}
|
|
89
|
+
}
|
package/src/signal/signal-api.ts
CHANGED