@dxos/network-manager 2.33.5-dev.fa6b779b → 2.33.6-dev.21302cba
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 +413 -279
- package/dist/src/index.js +5 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/network-manager.blueprint-test.js +5 -1
- package/dist/src/network-manager.blueprint-test.js.map +1 -1
- package/dist/src/network-manager.d.ts +1 -1
- package/dist/src/network-manager.d.ts.map +1 -1
- package/dist/src/network-manager.js +19 -17
- package/dist/src/network-manager.js.map +1 -1
- package/dist/src/signal/in-memory-signal-manager.d.ts +2 -2
- package/dist/src/signal/in-memory-signal-manager.d.ts.map +1 -1
- package/dist/src/signal/in-memory-signal-manager.js +2 -1
- package/dist/src/signal/in-memory-signal-manager.js.map +1 -1
- package/dist/src/signal/index.d.ts +2 -1
- package/dist/src/signal/index.d.ts.map +1 -1
- package/dist/src/signal/index.js +7 -2
- package/dist/src/signal/index.js.map +1 -1
- package/dist/src/signal/signal-api.d.ts +8 -66
- package/dist/src/signal/signal-api.d.ts.map +1 -1
- package/dist/src/signal/signal-api.js +2 -190
- package/dist/src/signal/signal-api.js.map +1 -1
- package/dist/src/signal/signal-client.d.ts +54 -0
- package/dist/src/signal/signal-client.d.ts.map +1 -0
- package/dist/src/signal/signal-client.js +198 -0
- package/dist/src/signal/signal-client.js.map +1 -0
- package/dist/src/signal/signal-client.test.d.ts +2 -0
- package/dist/src/signal/signal-client.test.d.ts.map +1 -0
- package/dist/src/signal/{signal-api.test.js → signal-client.test.js} +27 -27
- package/dist/src/signal/signal-client.test.js.map +1 -0
- package/dist/src/signal/{interface.d.ts → signal-manager.d.ts} +16 -5
- package/dist/src/signal/signal-manager.d.ts.map +1 -0
- package/dist/src/signal/{interface.js → signal-manager.js} +1 -1
- package/dist/src/signal/signal-manager.js.map +1 -0
- package/dist/src/signal/websocket-rpc.d.ts.map +1 -1
- package/dist/src/signal/websocket-rpc.js.map +1 -1
- package/dist/src/signal/websocket-signal-manager.d.ts +4 -4
- package/dist/src/signal/websocket-signal-manager.d.ts.map +1 -1
- package/dist/src/signal/websocket-signal-manager.js +4 -6
- package/dist/src/signal/websocket-signal-manager.js.map +1 -1
- package/dist/src/swarm/connection.d.ts +4 -1
- package/dist/src/swarm/connection.d.ts.map +1 -1
- package/dist/src/swarm/connection.js +6 -3
- package/dist/src/swarm/connection.js.map +1 -1
- package/dist/src/swarm/index.js +5 -1
- package/dist/src/swarm/index.js.map +1 -1
- package/dist/src/swarm/swarm.d.ts +4 -6
- package/dist/src/swarm/swarm.d.ts.map +1 -1
- package/dist/src/swarm/swarm.js +11 -12
- package/dist/src/swarm/swarm.js.map +1 -1
- package/dist/src/swarm/swarm.test.js +17 -14
- package/dist/src/swarm/swarm.test.js.map +1 -1
- package/dist/src/testing/test-protocol.d.ts +1 -0
- package/dist/src/testing/test-protocol.d.ts.map +1 -1
- package/dist/src/topology/index.js +5 -1
- package/dist/src/topology/index.js.map +1 -1
- package/dist/src/transport/in-memory-transport.d.ts +1 -1
- 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/index.js +5 -1
- package/dist/src/transport/index.js.map +1 -1
- package/dist/src/transport/transport.d.ts +1 -1
- package/dist/src/transport/transport.d.ts.map +1 -1
- package/dist/src/transport/webrtc-transport.d.ts +4 -4
- package/dist/src/transport/webrtc-transport.d.ts.map +1 -1
- package/dist/src/transport/webrtc-transport.js +8 -7
- package/dist/src/transport/webrtc-transport.js.map +1 -1
- package/dist/src/transport/webrtc-transport.test.js +4 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +13 -13
- package/src/network-manager.ts +23 -23
- package/src/signal/in-memory-signal-manager.ts +6 -5
- package/src/signal/index.ts +2 -1
- package/src/signal/signal-api.ts +13 -246
- package/src/signal/{signal-api.test.ts → signal-client.test.ts} +27 -26
- package/src/signal/signal-client.ts +235 -0
- package/src/signal/signal-manager.ts +39 -0
- package/src/signal/websocket-rpc.ts +3 -6
- package/src/signal/websocket-signal-manager.ts +11 -15
- package/src/swarm/connection.ts +7 -6
- package/src/swarm/swarm.test.ts +32 -25
- package/src/swarm/swarm.ts +16 -15
- package/src/transport/in-memory-transport.ts +2 -2
- package/src/transport/transport.ts +13 -17
- package/src/transport/webrtc-transport.test.ts +5 -5
- package/src/transport/webrtc-transport.ts +6 -5
- package/dist/src/signal/interface.d.ts.map +0 -1
- package/dist/src/signal/interface.js.map +0 -1
- package/dist/src/signal/signal-api.test.d.ts +0 -2
- package/dist/src/signal/signal-api.test.d.ts.map +0 -1
- package/dist/src/signal/signal-api.test.js.map +0 -1
- package/src/signal/interface.ts +0 -32
package/src/network-manager.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { ConnectionLog } from './connection-log';
|
|
|
15
15
|
import { InMemorySignalManager, SignalManager, SignalApi, WebsocketSignalManager } from './signal';
|
|
16
16
|
import { Swarm, SwarmMapper } from './swarm';
|
|
17
17
|
import { Topology } from './topology';
|
|
18
|
-
import {
|
|
18
|
+
import { createWebRTCTransportFactory, inMemoryTransportFactory } from './transport';
|
|
19
19
|
|
|
20
20
|
export type ProtocolProvider = (opts: { channel: Buffer, initiator: boolean}) => Protocol;
|
|
21
21
|
|
|
@@ -37,7 +37,7 @@ export class NetworkManager {
|
|
|
37
37
|
private readonly _ice?: any[];
|
|
38
38
|
private readonly _swarms = new ComplexMap<PublicKey, Swarm>(key => key.toHex());
|
|
39
39
|
private readonly _maps = new ComplexMap<PublicKey, SwarmMapper>(key => key.toHex());
|
|
40
|
-
private readonly
|
|
40
|
+
private readonly _signalManager: SignalManager;
|
|
41
41
|
private readonly _connectionLog?: ConnectionLog;
|
|
42
42
|
|
|
43
43
|
public readonly topicsUpdated = new Event<void>();
|
|
@@ -47,13 +47,15 @@ export class NetworkManager {
|
|
|
47
47
|
|
|
48
48
|
const onOffer = async (message: SignalApi.SignalMessage) =>
|
|
49
49
|
await this._swarms.get(message.topic)?.onOffer(message) ?? { accept: false };
|
|
50
|
-
|
|
50
|
+
|
|
51
|
+
this._signalManager = options.signal
|
|
51
52
|
? new WebsocketSignalManager(options.signal, onOffer)
|
|
52
53
|
: new InMemorySignalManager(onOffer);
|
|
53
54
|
|
|
54
|
-
this.
|
|
55
|
-
this._swarms.get(topic)?.onPeerCandidatesChanged(candidates));
|
|
56
|
-
this.
|
|
55
|
+
this._signalManager.peerCandidatesChanged
|
|
56
|
+
.on(([topic, candidates]) => this._swarms.get(topic)?.onPeerCandidatesChanged(candidates));
|
|
57
|
+
this._signalManager.onSignal
|
|
58
|
+
.on(msg => this._swarms.get(msg.topic)?.onSignal(msg));
|
|
57
59
|
|
|
58
60
|
if (options.log) {
|
|
59
61
|
this._connectionLog = new ConnectionLog();
|
|
@@ -61,9 +63,10 @@ export class NetworkManager {
|
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
get signal () {
|
|
64
|
-
return this.
|
|
66
|
+
return this._signalManager;
|
|
65
67
|
}
|
|
66
68
|
|
|
69
|
+
// TODO(burdon): Reconcile with "discoveryKey".
|
|
67
70
|
get topics () {
|
|
68
71
|
return Array.from(this._swarms.keys());
|
|
69
72
|
}
|
|
@@ -82,12 +85,12 @@ export class NetworkManager {
|
|
|
82
85
|
|
|
83
86
|
joinProtocolSwarm (options: SwarmOptions) {
|
|
84
87
|
// TODO(burdon): Use TS to constrain properties.
|
|
85
|
-
assert(typeof options === 'object'
|
|
88
|
+
assert(typeof options === 'object');
|
|
86
89
|
const { topic, peerId, topology, protocol, presence } = options;
|
|
87
|
-
assert(PublicKey.isPublicKey(topic)
|
|
88
|
-
assert(PublicKey.isPublicKey(peerId)
|
|
89
|
-
assert(topology
|
|
90
|
-
assert(typeof protocol === 'function'
|
|
90
|
+
assert(PublicKey.isPublicKey(topic));
|
|
91
|
+
assert(PublicKey.isPublicKey(peerId));
|
|
92
|
+
assert(topology);
|
|
93
|
+
assert(typeof protocol === 'function');
|
|
91
94
|
|
|
92
95
|
log(`Join ${options.topic} as ${options.peerId} with ${options.topology.toString()} topology.`);
|
|
93
96
|
if (this._swarms.has(topic)) {
|
|
@@ -95,21 +98,17 @@ export class NetworkManager {
|
|
|
95
98
|
GreetingCommandPlugin.EXTENSION_NAME, ERR_GREET_ALREADY_CONNECTED_TO_SWARM, `Already connected to swarm ${topic}`);
|
|
96
99
|
}
|
|
97
100
|
|
|
101
|
+
// TODO(burdon): Require factory (i.e., don't make InMemorySignalManager by default).
|
|
98
102
|
// TODO(burdon): Bundle common transport related classes.
|
|
99
|
-
const transportFactory = this.
|
|
100
|
-
? inMemoryTransportFactory :
|
|
103
|
+
const transportFactory = this._signalManager instanceof InMemorySignalManager
|
|
104
|
+
? inMemoryTransportFactory : createWebRTCTransportFactory({ iceServers: this._ice });
|
|
101
105
|
|
|
102
106
|
const swarm = new Swarm(
|
|
103
107
|
topic,
|
|
104
108
|
peerId,
|
|
105
109
|
topology,
|
|
106
110
|
protocol,
|
|
107
|
-
|
|
108
|
-
// TODO(burdon): Merge.
|
|
109
|
-
async offer => this._signal.offer(offer),
|
|
110
|
-
async msg => this._signal.signal(msg),
|
|
111
|
-
() => this._signal.lookup(topic),
|
|
112
|
-
|
|
111
|
+
this._signalManager,
|
|
113
112
|
transportFactory,
|
|
114
113
|
options.label
|
|
115
114
|
);
|
|
@@ -119,7 +118,7 @@ export class NetworkManager {
|
|
|
119
118
|
});
|
|
120
119
|
|
|
121
120
|
this._swarms.set(topic, swarm);
|
|
122
|
-
this.
|
|
121
|
+
this._signalManager.join(topic, peerId);
|
|
123
122
|
this._maps.set(topic, new SwarmMapper(swarm, presence));
|
|
124
123
|
|
|
125
124
|
this.topicsUpdated.emit();
|
|
@@ -139,7 +138,7 @@ export class NetworkManager {
|
|
|
139
138
|
const map = this._maps.get(topic)!;
|
|
140
139
|
const swarm = this._swarms.get(topic)!;
|
|
141
140
|
|
|
142
|
-
this.
|
|
141
|
+
this._signalManager.leave(topic, swarm.ownPeerId);
|
|
143
142
|
|
|
144
143
|
map.destroy();
|
|
145
144
|
this._maps.delete(topic);
|
|
@@ -168,7 +167,7 @@ export class NetworkManager {
|
|
|
168
167
|
});
|
|
169
168
|
}
|
|
170
169
|
|
|
171
|
-
await this.
|
|
170
|
+
await this._signalManager.destroy();
|
|
172
171
|
}
|
|
173
172
|
}
|
|
174
173
|
|
|
@@ -177,6 +176,7 @@ export interface SwarmOptions {
|
|
|
177
176
|
* Swarm topic.
|
|
178
177
|
*/
|
|
179
178
|
topic: PublicKey,
|
|
179
|
+
|
|
180
180
|
/**
|
|
181
181
|
* This node's peer id.
|
|
182
182
|
*/
|
|
@@ -8,16 +8,13 @@ import { Event } from '@dxos/async';
|
|
|
8
8
|
import { PublicKey } from '@dxos/crypto';
|
|
9
9
|
import { ComplexMap, ComplexSet } from '@dxos/util';
|
|
10
10
|
|
|
11
|
-
import { SignalManager } from './interface';
|
|
12
11
|
import { SignalApi } from './signal-api';
|
|
12
|
+
import { SignalManager } from './signal-manager';
|
|
13
13
|
|
|
14
14
|
export class InMemorySignalManager implements SignalManager {
|
|
15
15
|
readonly statusChanged = new Event<SignalApi.Status[]>();
|
|
16
|
-
|
|
17
16
|
readonly commandTrace = new Event<SignalApi.CommandTrace>();
|
|
18
|
-
|
|
19
17
|
readonly peerCandidatesChanged = new Event<[topic: PublicKey, candidates: PublicKey[]]>()
|
|
20
|
-
|
|
21
18
|
readonly onSignal = new Event<SignalApi.SignalMessage>();
|
|
22
19
|
|
|
23
20
|
constructor (
|
|
@@ -32,6 +29,7 @@ export class InMemorySignalManager implements SignalManager {
|
|
|
32
29
|
if (!state.swarms.has(topic)) {
|
|
33
30
|
state.swarms.set(topic, new ComplexSet(x => x.toHex()));
|
|
34
31
|
}
|
|
32
|
+
|
|
35
33
|
state.swarms.get(topic)!.add(peerId);
|
|
36
34
|
state.connections.set(peerId, this);
|
|
37
35
|
|
|
@@ -42,6 +40,7 @@ export class InMemorySignalManager implements SignalManager {
|
|
|
42
40
|
if (!state.swarms.has(topic)) {
|
|
43
41
|
state.swarms.set(topic, new ComplexSet(x => x.toHex()));
|
|
44
42
|
}
|
|
43
|
+
|
|
45
44
|
state.swarms.get(topic)!.delete(peerId);
|
|
46
45
|
}
|
|
47
46
|
|
|
@@ -54,7 +53,7 @@ export class InMemorySignalManager implements SignalManager {
|
|
|
54
53
|
return state.connections.get(msg.remoteId)!._onOffer(msg);
|
|
55
54
|
}
|
|
56
55
|
|
|
57
|
-
signal (msg: SignalApi.SignalMessage) {
|
|
56
|
+
async signal (msg: SignalApi.SignalMessage) {
|
|
58
57
|
assert(state.connections.get(msg.remoteId), 'Peer not connected');
|
|
59
58
|
state.connections.get(msg.remoteId)!.onSignal.emit(msg);
|
|
60
59
|
}
|
|
@@ -62,10 +61,12 @@ export class InMemorySignalManager implements SignalManager {
|
|
|
62
61
|
async destroy () {}
|
|
63
62
|
}
|
|
64
63
|
|
|
64
|
+
// TODO(burdon): Remove global singleton.
|
|
65
65
|
// This is global state for the in-memory signal manager.
|
|
66
66
|
const state = {
|
|
67
67
|
// Mapping from topic to set of peers.
|
|
68
68
|
swarms: new ComplexMap<PublicKey, ComplexSet<PublicKey>>(x => x.toHex()),
|
|
69
|
+
|
|
69
70
|
// Map of connections for each peer for signaling.
|
|
70
71
|
connections: new ComplexMap<PublicKey, InMemorySignalManager>(x => x.toHex())
|
|
71
72
|
};
|
package/src/signal/index.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
export * from './in-memory-signal-manager';
|
|
6
|
-
export * from './interface';
|
|
7
6
|
export * from './signal-api';
|
|
7
|
+
export * from './signal-client';
|
|
8
|
+
export * from './signal-manager';
|
|
8
9
|
export * from './websocket-rpc';
|
|
9
10
|
export * from './websocket-signal-manager';
|
package/src/signal/signal-api.ts
CHANGED
|
@@ -2,235 +2,11 @@
|
|
|
2
2
|
// Copyright 2020 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import debug from 'debug';
|
|
6
5
|
import { SignalData } from 'simple-peer';
|
|
7
6
|
|
|
8
|
-
import { Event } from '@dxos/async';
|
|
9
7
|
import { PublicKey } from '@dxos/crypto';
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const log = debug('dxos:network-manager:signal-api');
|
|
14
|
-
|
|
15
|
-
const DEFAULT_RECONNECT_TIMEOUT = 1000;
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Establishes a websocket connection to signal server and provides RPC methods.
|
|
19
|
-
*/
|
|
20
|
-
export class SignalApi {
|
|
21
|
-
private _state = SignalApi.State.CONNECTING;
|
|
22
|
-
|
|
23
|
-
private _lastError?: Error;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Number of milliseconds after which the connection will be attempted again in case of error.
|
|
27
|
-
*/
|
|
28
|
-
private _reconnectAfter = DEFAULT_RECONNECT_TIMEOUT;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Timestamp of when the connection attempt was began.
|
|
32
|
-
*/
|
|
33
|
-
private _connectionStarted = Date.now();
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Timestamp of last state change.
|
|
37
|
-
*/
|
|
38
|
-
private _lastStateChange = Date.now();
|
|
39
|
-
|
|
40
|
-
private _reconnectIntervalId?: NodeJS.Timeout;
|
|
41
|
-
|
|
42
|
-
private _client!: WebsocketRpc;
|
|
43
|
-
|
|
44
|
-
private _clientCleanup: (() => void)[] = [];
|
|
45
|
-
|
|
46
|
-
readonly statusChanged = new Event<SignalApi.Status>();
|
|
47
|
-
|
|
48
|
-
readonly commandTrace = new Event<SignalApi.CommandTrace>();
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* @param _host Signal server websocket URL.
|
|
52
|
-
* @param _onOffer See `SignalApi.offer`.
|
|
53
|
-
* @param _onSignal See `SignalApi.signal`.
|
|
54
|
-
*/
|
|
55
|
-
constructor (
|
|
56
|
-
private readonly _host: string,
|
|
57
|
-
private readonly _onOffer: (message: SignalApi.SignalMessage) => Promise<SignalApi.Answer>,
|
|
58
|
-
private readonly _onSignal: (message: SignalApi.SignalMessage) => Promise<void>
|
|
59
|
-
) {
|
|
60
|
-
this._setState(SignalApi.State.CONNECTING);
|
|
61
|
-
this._createClient();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
private _setState (newState: SignalApi.State) {
|
|
65
|
-
this._state = newState;
|
|
66
|
-
this._lastStateChange = Date.now();
|
|
67
|
-
log(`Signal state changed ${JSON.stringify(this.getStatus())}`);
|
|
68
|
-
this.statusChanged.emit(this.getStatus());
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
private _createClient () {
|
|
72
|
-
this._connectionStarted = Date.now();
|
|
73
|
-
try {
|
|
74
|
-
this._client = new WebsocketRpc(this._host);
|
|
75
|
-
} catch (error: any) {
|
|
76
|
-
if (this._state === SignalApi.State.RE_CONNECTING) {
|
|
77
|
-
this._reconnectAfter *= 2;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
this._lastError = error;
|
|
81
|
-
this._setState(SignalApi.State.DISCONNECTED);
|
|
82
|
-
|
|
83
|
-
this._reconnect();
|
|
84
|
-
}
|
|
85
|
-
this._client.addHandler('offer', (message: any) => this._onOffer({
|
|
86
|
-
id: PublicKey.from(message.id),
|
|
87
|
-
remoteId: PublicKey.from(message.remoteId),
|
|
88
|
-
topic: PublicKey.from(message.topic),
|
|
89
|
-
sessionId: PublicKey.from(message.sessionId),
|
|
90
|
-
data: message.data
|
|
91
|
-
}));
|
|
92
|
-
this._client.subscribe('signal', (msg: SignalApi.SignalMessage) => this._onSignal({
|
|
93
|
-
id: PublicKey.from(msg.id),
|
|
94
|
-
remoteId: PublicKey.from(msg.remoteId),
|
|
95
|
-
topic: PublicKey.from(msg.topic),
|
|
96
|
-
sessionId: PublicKey.from(msg.sessionId),
|
|
97
|
-
data: msg.data
|
|
98
|
-
}));
|
|
99
|
-
|
|
100
|
-
this._clientCleanup.push(this._client.connected.on(() => {
|
|
101
|
-
log('Socket connected');
|
|
102
|
-
this._lastError = undefined;
|
|
103
|
-
this._reconnectAfter = DEFAULT_RECONNECT_TIMEOUT;
|
|
104
|
-
this._setState(SignalApi.State.CONNECTED);
|
|
105
|
-
}));
|
|
106
|
-
this._clientCleanup.push(this._client.error.on(error => {
|
|
107
|
-
log(`Socket error: ${error.message}`);
|
|
108
|
-
if (this._state === SignalApi.State.CLOSED) {
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (this._state === SignalApi.State.RE_CONNECTING) {
|
|
113
|
-
this._reconnectAfter *= 2;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
this._lastError = error;
|
|
117
|
-
this._setState(SignalApi.State.DISCONNECTED);
|
|
118
|
-
|
|
119
|
-
this._reconnect();
|
|
120
|
-
}));
|
|
121
|
-
this._clientCleanup.push(this._client.disconnected.on(() => {
|
|
122
|
-
log('Socket disconnected');
|
|
123
|
-
// This is also called in case of error, but we already have disconnected the socket on error, so no need to do anything here.
|
|
124
|
-
if (this._state !== SignalApi.State.CONNECTING && this._state !== SignalApi.State.RE_CONNECTING) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (this._state === SignalApi.State.RE_CONNECTING) {
|
|
129
|
-
this._reconnectAfter *= 2;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
this._setState(SignalApi.State.DISCONNECTED);
|
|
133
|
-
|
|
134
|
-
this._reconnect();
|
|
135
|
-
}));
|
|
136
|
-
this._clientCleanup.push(this._client.commandTrace.on(trace => this.commandTrace.emit(trace)));
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
private _reconnect () {
|
|
140
|
-
if (this._reconnectIntervalId !== undefined) {
|
|
141
|
-
console.error('Signal api already reconnecting.');
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
if (this._state === SignalApi.State.CLOSED) {
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
this._reconnectIntervalId = setTimeout(() => {
|
|
149
|
-
this._reconnectIntervalId = undefined;
|
|
150
|
-
|
|
151
|
-
this._clientCleanup.forEach(cb => cb());
|
|
152
|
-
this._clientCleanup = [];
|
|
153
|
-
|
|
154
|
-
// Close client if it wasn't already closed.
|
|
155
|
-
this._client.close().catch(() => {});
|
|
156
|
-
|
|
157
|
-
this._setState(SignalApi.State.RE_CONNECTING);
|
|
158
|
-
this._createClient();
|
|
159
|
-
}, this._reconnectAfter);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
async close () {
|
|
163
|
-
this._clientCleanup.forEach(cb => cb());
|
|
164
|
-
this._clientCleanup = [];
|
|
165
|
-
|
|
166
|
-
if (this._reconnectIntervalId !== undefined) {
|
|
167
|
-
clearTimeout(this._reconnectIntervalId);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
await this._client.close();
|
|
171
|
-
this._setState(SignalApi.State.CLOSED);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
getStatus (): SignalApi.Status {
|
|
175
|
-
return {
|
|
176
|
-
host: this._host,
|
|
177
|
-
state: this._state,
|
|
178
|
-
error: this._lastError?.message,
|
|
179
|
-
reconnectIn: this._reconnectAfter,
|
|
180
|
-
connectionStarted: this._connectionStarted,
|
|
181
|
-
lastStateChange: this._lastStateChange
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
async join (topic: PublicKey, peerId: PublicKey): Promise<PublicKey[]> {
|
|
186
|
-
const peers: Buffer[] = await this._client.call('join', {
|
|
187
|
-
id: peerId.asBuffer(),
|
|
188
|
-
topic: topic.asBuffer()
|
|
189
|
-
});
|
|
190
|
-
return peers.map(id => PublicKey.from(id));
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async leave (topic: PublicKey, peerId: PublicKey): Promise<void> {
|
|
194
|
-
await this._client.call('leave', {
|
|
195
|
-
id: peerId.asBuffer(),
|
|
196
|
-
topic: topic.asBuffer()
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
async lookup (topic: PublicKey): Promise<PublicKey[]> {
|
|
201
|
-
const peers: Buffer[] = await this._client.call('lookup', {
|
|
202
|
-
topic: topic.asBuffer()
|
|
203
|
-
});
|
|
204
|
-
return peers.map(id => PublicKey.from(id));
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Routes an offer to the other peer's _onOffer callback.
|
|
209
|
-
* @returns Other peer's _onOffer callback return value.
|
|
210
|
-
*/
|
|
211
|
-
async offer (payload: SignalApi.SignalMessage): Promise<SignalApi.Answer> {
|
|
212
|
-
return this._client.call('offer', {
|
|
213
|
-
id: payload.id.asBuffer(),
|
|
214
|
-
remoteId: payload.remoteId.asBuffer(),
|
|
215
|
-
topic: payload.topic.asBuffer(),
|
|
216
|
-
sessionId: payload.sessionId.asBuffer(),
|
|
217
|
-
data: payload.data
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Routes an offer to the other peer's _onSignal callback.
|
|
223
|
-
*/
|
|
224
|
-
async signal (payload: SignalApi.SignalMessage): Promise<void> {
|
|
225
|
-
return this._client.emit('signal', {
|
|
226
|
-
id: payload.id.asBuffer(),
|
|
227
|
-
remoteId: payload.remoteId.asBuffer(),
|
|
228
|
-
topic: payload.topic.asBuffer(),
|
|
229
|
-
sessionId: payload.sessionId.asBuffer(),
|
|
230
|
-
data: payload.data
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
}
|
|
9
|
+
// TODO(burdon): Define message types as protobuf.
|
|
234
10
|
|
|
235
11
|
export namespace SignalApi {
|
|
236
12
|
export enum State {
|
|
@@ -247,19 +23,19 @@ export namespace SignalApi {
|
|
|
247
23
|
DISCONNECTED = 'DISCONNECTED',
|
|
248
24
|
|
|
249
25
|
/** Socket was closed. */
|
|
250
|
-
CLOSED = 'CLOSED'
|
|
26
|
+
CLOSED = 'CLOSED'
|
|
251
27
|
}
|
|
252
28
|
|
|
253
|
-
export
|
|
254
|
-
host: string
|
|
255
|
-
state: State
|
|
29
|
+
export type Status = {
|
|
30
|
+
host: string
|
|
31
|
+
state: State
|
|
256
32
|
error?: string
|
|
257
|
-
reconnectIn: number
|
|
33
|
+
reconnectIn: number
|
|
258
34
|
connectionStarted: number
|
|
259
35
|
lastStateChange: number
|
|
260
36
|
}
|
|
261
37
|
|
|
262
|
-
export
|
|
38
|
+
export type CommandTrace = {
|
|
263
39
|
messageId: string
|
|
264
40
|
host: string
|
|
265
41
|
incoming: boolean
|
|
@@ -270,24 +46,15 @@ export namespace SignalApi {
|
|
|
270
46
|
error?: string
|
|
271
47
|
}
|
|
272
48
|
|
|
273
|
-
|
|
274
|
-
export interface SignalMessage {
|
|
275
|
-
id: PublicKey
|
|
276
|
-
remoteId: PublicKey,
|
|
277
|
-
topic: PublicKey,
|
|
278
|
-
sessionId: PublicKey,
|
|
279
|
-
data: SignalData,
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
export interface OfferMessage {
|
|
49
|
+
export type SignalMessage = {
|
|
283
50
|
id: PublicKey
|
|
284
|
-
remoteId: PublicKey
|
|
285
|
-
topic: PublicKey
|
|
286
|
-
sessionId: PublicKey
|
|
287
|
-
data: SignalData
|
|
51
|
+
remoteId: PublicKey
|
|
52
|
+
topic: PublicKey
|
|
53
|
+
sessionId: PublicKey
|
|
54
|
+
data: SignalData
|
|
288
55
|
}
|
|
289
56
|
|
|
290
|
-
export
|
|
57
|
+
export type Answer = {
|
|
291
58
|
accept: boolean
|
|
292
59
|
}
|
|
293
60
|
}
|
|
@@ -12,25 +12,26 @@ import { createTestBroker } from '@dxos/signal';
|
|
|
12
12
|
import { randomInt } from '@dxos/util';
|
|
13
13
|
|
|
14
14
|
import { SignalApi } from './signal-api';
|
|
15
|
+
import { SignalClient } from './signal-client';
|
|
15
16
|
|
|
16
17
|
describe('SignalApi', () => {
|
|
17
18
|
let topic: PublicKey;
|
|
18
19
|
let peer1: PublicKey;
|
|
19
20
|
let peer2: PublicKey;
|
|
20
|
-
let
|
|
21
|
-
let api2:
|
|
21
|
+
let api1: SignalClient;
|
|
22
|
+
let api2: SignalClient;
|
|
22
23
|
|
|
23
|
-
let
|
|
24
|
-
const
|
|
25
|
-
const
|
|
24
|
+
let broker1: Awaited<ReturnType<typeof createTestBroker>>;
|
|
25
|
+
const signalApiPort1 = randomInt(10000, 50000);
|
|
26
|
+
const signalApiUrl1 = 'http://0.0.0.0:' + signalApiPort1;
|
|
26
27
|
|
|
27
28
|
// code let broker2: ReturnType<typeof createBroker>;
|
|
28
29
|
const signalApiPort2 = randomInt(10000, 50000);
|
|
29
30
|
const signalApiUrl2 = 'http://0.0.0.0:' + signalApiPort2;
|
|
30
31
|
|
|
31
32
|
before(async () => {
|
|
32
|
-
|
|
33
|
-
//
|
|
33
|
+
broker1 = await createTestBroker(signalApiPort1);
|
|
34
|
+
// broker2 = await createTestBroker(signalApiPort2);
|
|
34
35
|
});
|
|
35
36
|
|
|
36
37
|
beforeEach(() => {
|
|
@@ -41,27 +42,27 @@ describe('SignalApi', () => {
|
|
|
41
42
|
|
|
42
43
|
after(async function () {
|
|
43
44
|
this.timeout(0);
|
|
44
|
-
await
|
|
45
|
-
await
|
|
45
|
+
await api1.close();
|
|
46
|
+
await broker1.stop();
|
|
46
47
|
// code await broker2.stop();
|
|
47
48
|
});
|
|
48
49
|
|
|
49
50
|
test('join', async () => {
|
|
50
|
-
|
|
51
|
+
api1 = new SignalClient(signalApiUrl1, (async () => {}) as any, async () => {});
|
|
51
52
|
|
|
52
|
-
const join = await
|
|
53
|
+
const join = await api1.join(topic, peer1);
|
|
53
54
|
expect(join).toEqual([peer1]);
|
|
54
55
|
|
|
55
|
-
const join2 = await
|
|
56
|
+
const join2 = await api1.join(topic, peer2);
|
|
56
57
|
expect(join2).toEqual([peer1, peer2]);
|
|
57
58
|
}).timeout(1_000);
|
|
58
59
|
|
|
59
60
|
test('offer', async () => {
|
|
60
61
|
const offerMock = mockFn<(msg: SignalApi.SignalMessage) => Promise<SignalApi.Answer>>()
|
|
61
62
|
.resolvesTo({ accept: true });
|
|
62
|
-
|
|
63
|
+
api1 = new SignalClient(signalApiUrl1, offerMock, async () => {});
|
|
63
64
|
|
|
64
|
-
await
|
|
65
|
+
await api1.join(topic, peer1);
|
|
65
66
|
|
|
66
67
|
const offer: SignalApi.SignalMessage = {
|
|
67
68
|
data: { foo: 'bar' } as any,
|
|
@@ -70,7 +71,7 @@ describe('SignalApi', () => {
|
|
|
70
71
|
sessionId: PublicKey.random(),
|
|
71
72
|
topic
|
|
72
73
|
};
|
|
73
|
-
const offerResult = await
|
|
74
|
+
const offerResult = await api1.offer(offer);
|
|
74
75
|
expect(offerResult).toEqual({ accept: true });
|
|
75
76
|
expect(offerMock).toHaveBeenCalledWith([offer]);
|
|
76
77
|
}).timeout(5_000);
|
|
@@ -78,9 +79,9 @@ describe('SignalApi', () => {
|
|
|
78
79
|
test('signal', async () => {
|
|
79
80
|
const signalMock = mockFn<(msg: SignalApi.SignalMessage) => Promise<void>>()
|
|
80
81
|
.resolvesTo();
|
|
81
|
-
|
|
82
|
+
api1 = new SignalClient(signalApiUrl1, (async () => {}) as any, signalMock);
|
|
82
83
|
|
|
83
|
-
await
|
|
84
|
+
await api1.join(topic, peer1);
|
|
84
85
|
|
|
85
86
|
const msg: SignalApi.SignalMessage = {
|
|
86
87
|
id: peer2,
|
|
@@ -89,7 +90,7 @@ describe('SignalApi', () => {
|
|
|
89
90
|
topic,
|
|
90
91
|
data: { foo: 'bar' } as any
|
|
91
92
|
};
|
|
92
|
-
await
|
|
93
|
+
await api1.signal(msg);
|
|
93
94
|
|
|
94
95
|
await waitForExpect(() => {
|
|
95
96
|
expect(signalMock).toHaveBeenCalledWith([msg]);
|
|
@@ -98,10 +99,10 @@ describe('SignalApi', () => {
|
|
|
98
99
|
|
|
99
100
|
test.skip('join across multiple signal servers', async () => {
|
|
100
101
|
// This feature is not implemented yet.
|
|
101
|
-
|
|
102
|
-
api2 = new
|
|
102
|
+
api1 = new SignalClient(signalApiUrl1, (async () => {}) as any, async () => {});
|
|
103
|
+
api2 = new SignalClient(signalApiUrl2, (async () => {}) as any, async () => {});
|
|
103
104
|
|
|
104
|
-
await
|
|
105
|
+
await api1.join(topic, peer1);
|
|
105
106
|
await api2.join(topic, peer2);
|
|
106
107
|
|
|
107
108
|
await waitForExpect(async () => {
|
|
@@ -110,7 +111,7 @@ describe('SignalApi', () => {
|
|
|
110
111
|
}, 4_000);
|
|
111
112
|
|
|
112
113
|
await waitForExpect(async () => {
|
|
113
|
-
const peers = await
|
|
114
|
+
const peers = await api1.lookup(topic);
|
|
114
115
|
expect(peers.length).toEqual(2);
|
|
115
116
|
}, 4_000);
|
|
116
117
|
}).timeout(5_000);
|
|
@@ -122,10 +123,10 @@ describe('SignalApi', () => {
|
|
|
122
123
|
const signalMock = mockFn<(msg: SignalApi.SignalMessage) => Promise<void>>()
|
|
123
124
|
.resolvesTo();
|
|
124
125
|
|
|
125
|
-
|
|
126
|
-
api2 = new
|
|
126
|
+
api1 = new SignalClient(signalApiUrl1, offerMock, async () => {});
|
|
127
|
+
api2 = new SignalClient(signalApiUrl2, (async () => {}) as any, signalMock);
|
|
127
128
|
|
|
128
|
-
await
|
|
129
|
+
await api1.join(topic, peer1);
|
|
129
130
|
await sleep(3000);
|
|
130
131
|
await api2.join(topic, peer2);
|
|
131
132
|
|
|
@@ -146,7 +147,7 @@ describe('SignalApi', () => {
|
|
|
146
147
|
topic,
|
|
147
148
|
data: { foo: 'bar' } as any
|
|
148
149
|
};
|
|
149
|
-
await
|
|
150
|
+
await api1.signal(msg);
|
|
150
151
|
|
|
151
152
|
await waitForExpect(() => {
|
|
152
153
|
expect(signalMock).toHaveBeenCalledWith([msg]);
|