@dxos/network-manager 2.33.5-dev.ea3876ba → 2.33.5-dev.fee8f5fe

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.
Files changed (112) hide show
  1. package/dist/browser-mocha/bundle.js +754 -573
  2. package/dist/src/index.js +5 -1
  3. package/dist/src/index.js.map +1 -1
  4. package/dist/src/network-manager.blueprint-test.d.ts +1 -1
  5. package/dist/src/network-manager.blueprint-test.d.ts.map +1 -1
  6. package/dist/src/network-manager.blueprint-test.js +11 -7
  7. package/dist/src/network-manager.blueprint-test.js.map +1 -1
  8. package/dist/src/network-manager.d.ts +1 -1
  9. package/dist/src/network-manager.d.ts.map +1 -1
  10. package/dist/src/network-manager.js +19 -17
  11. package/dist/src/network-manager.js.map +1 -1
  12. package/dist/src/network-manager.test.js +2 -2
  13. package/dist/src/network-manager.test.js.map +1 -1
  14. package/dist/src/protocol-factory.d.ts +1 -1
  15. package/dist/src/protocol-factory.d.ts.map +1 -1
  16. package/dist/src/protocol-factory.js +10 -18
  17. package/dist/src/protocol-factory.js.map +1 -1
  18. package/dist/src/signal/in-memory-signal-manager.d.ts +2 -2
  19. package/dist/src/signal/in-memory-signal-manager.d.ts.map +1 -1
  20. package/dist/src/signal/in-memory-signal-manager.js +2 -1
  21. package/dist/src/signal/in-memory-signal-manager.js.map +1 -1
  22. package/dist/src/signal/index.d.ts +2 -1
  23. package/dist/src/signal/index.d.ts.map +1 -1
  24. package/dist/src/signal/index.js +7 -2
  25. package/dist/src/signal/index.js.map +1 -1
  26. package/dist/src/signal/signal-api.d.ts +8 -66
  27. package/dist/src/signal/signal-api.d.ts.map +1 -1
  28. package/dist/src/signal/signal-api.js +2 -190
  29. package/dist/src/signal/signal-api.js.map +1 -1
  30. package/dist/src/signal/signal-client.d.ts +54 -0
  31. package/dist/src/signal/signal-client.d.ts.map +1 -0
  32. package/dist/src/signal/signal-client.js +198 -0
  33. package/dist/src/signal/signal-client.js.map +1 -0
  34. package/dist/src/signal/signal-client.test.d.ts +2 -0
  35. package/dist/src/signal/signal-client.test.d.ts.map +1 -0
  36. package/dist/src/signal/{signal-api.test.js → signal-client.test.js} +28 -28
  37. package/dist/src/signal/signal-client.test.js.map +1 -0
  38. package/dist/src/signal/{interface.d.ts → signal-manager.d.ts} +16 -5
  39. package/dist/src/signal/signal-manager.d.ts.map +1 -0
  40. package/dist/src/signal/{interface.js → signal-manager.js} +1 -1
  41. package/dist/src/signal/signal-manager.js.map +1 -0
  42. package/dist/src/signal/websocket-rpc.d.ts.map +1 -1
  43. package/dist/src/signal/websocket-rpc.js.map +1 -1
  44. package/dist/src/signal/websocket-signal-manager.d.ts +4 -4
  45. package/dist/src/signal/websocket-signal-manager.d.ts.map +1 -1
  46. package/dist/src/signal/websocket-signal-manager.js +4 -6
  47. package/dist/src/signal/websocket-signal-manager.js.map +1 -1
  48. package/dist/src/swarm/connection.d.ts +4 -1
  49. package/dist/src/swarm/connection.d.ts.map +1 -1
  50. package/dist/src/swarm/connection.js +6 -3
  51. package/dist/src/swarm/connection.js.map +1 -1
  52. package/dist/src/swarm/index.js +5 -1
  53. package/dist/src/swarm/index.js.map +1 -1
  54. package/dist/src/swarm/swarm.d.ts +4 -6
  55. package/dist/src/swarm/swarm.d.ts.map +1 -1
  56. package/dist/src/swarm/swarm.js +11 -12
  57. package/dist/src/swarm/swarm.js.map +1 -1
  58. package/dist/src/swarm/swarm.test.js +17 -14
  59. package/dist/src/swarm/swarm.test.js.map +1 -1
  60. package/dist/src/testing/test-protocol.d.ts +1 -0
  61. package/dist/src/testing/test-protocol.d.ts.map +1 -1
  62. package/dist/src/testing/test-protocol.js +5 -9
  63. package/dist/src/testing/test-protocol.js.map +1 -1
  64. package/dist/src/topology/index.js +5 -1
  65. package/dist/src/topology/index.js.map +1 -1
  66. package/dist/src/topology/mmst-topology.js +1 -3
  67. package/dist/src/topology/mmst-topology.js.map +1 -1
  68. package/dist/src/transport/in-memory-transport.d.ts +1 -1
  69. package/dist/src/transport/in-memory-transport.d.ts.map +1 -1
  70. package/dist/src/transport/in-memory-transport.js +8 -10
  71. package/dist/src/transport/in-memory-transport.js.map +1 -1
  72. package/dist/src/transport/in-memory-transport.test.js +2 -2
  73. package/dist/src/transport/in-memory-transport.test.js.map +1 -1
  74. package/dist/src/transport/index.js +5 -1
  75. package/dist/src/transport/index.js.map +1 -1
  76. package/dist/src/transport/transport.d.ts +1 -1
  77. package/dist/src/transport/transport.d.ts.map +1 -1
  78. package/dist/src/transport/webrtc-transport.d.ts +4 -4
  79. package/dist/src/transport/webrtc-transport.d.ts.map +1 -1
  80. package/dist/src/transport/webrtc-transport.js +8 -9
  81. package/dist/src/transport/webrtc-transport.js.map +1 -1
  82. package/dist/src/transport/webrtc-transport.test.js +4 -4
  83. package/dist/tsconfig.tsbuildinfo +1 -1
  84. package/package.json +11 -11
  85. package/src/network-manager.blueprint-test.ts +6 -6
  86. package/src/network-manager.test.ts +2 -2
  87. package/src/network-manager.ts +22 -23
  88. package/src/protocol-factory.ts +10 -18
  89. package/src/signal/in-memory-signal-manager.ts +6 -5
  90. package/src/signal/index.ts +2 -1
  91. package/src/signal/signal-api.ts +13 -246
  92. package/src/signal/{signal-api.test.ts → signal-client.test.ts} +28 -27
  93. package/src/signal/signal-client.ts +235 -0
  94. package/src/signal/signal-manager.ts +39 -0
  95. package/src/signal/websocket-rpc.ts +3 -6
  96. package/src/signal/websocket-signal-manager.ts +11 -15
  97. package/src/swarm/connection.ts +7 -6
  98. package/src/swarm/swarm.test.ts +32 -25
  99. package/src/swarm/swarm.ts +16 -15
  100. package/src/testing/test-protocol.ts +5 -9
  101. package/src/topology/mmst-topology.ts +1 -3
  102. package/src/transport/in-memory-transport.test.ts +2 -2
  103. package/src/transport/in-memory-transport.ts +8 -10
  104. package/src/transport/transport.ts +13 -17
  105. package/src/transport/webrtc-transport.test.ts +5 -5
  106. package/src/transport/webrtc-transport.ts +15 -16
  107. package/dist/src/signal/interface.d.ts.map +0 -1
  108. package/dist/src/signal/interface.js.map +0 -1
  109. package/dist/src/signal/signal-api.test.d.ts +0 -2
  110. package/dist/src/signal/signal-api.test.d.ts.map +0 -1
  111. package/dist/src/signal/signal-api.test.js.map +0 -1
  112. package/src/signal/interface.ts +0 -32
@@ -0,0 +1,235 @@
1
+ //
2
+ // Copyright 2020 DXOS.org
3
+ //
4
+
5
+ import debug from 'debug';
6
+
7
+ import { Event } from '@dxos/async';
8
+ import { PublicKey } from '@dxos/crypto';
9
+
10
+ import { SignalApi } from './signal-api';
11
+ import { WebsocketRpc } from './websocket-rpc';
12
+
13
+ const log = debug('dxos:network-manager:signal-client');
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 SignalClient {
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
+ readonly commandTrace = new Event<SignalApi.CommandTrace>();
48
+
49
+ /**
50
+ * @param _host Signal server websocket URL.
51
+ * @param _onOffer See `SignalApi.offer`.
52
+ * @param _onSignal See `SignalApi.signal`.
53
+ */
54
+ constructor (
55
+ private readonly _host: string,
56
+ private readonly _onOffer: (message: SignalApi.SignalMessage) => Promise<SignalApi.Answer>,
57
+ private readonly _onSignal: (message: SignalApi.SignalMessage) => Promise<void>
58
+ ) {
59
+ this._setState(SignalApi.State.CONNECTING);
60
+ this._createClient();
61
+ }
62
+
63
+ private _setState (newState: SignalApi.State) {
64
+ this._state = newState;
65
+ this._lastStateChange = Date.now();
66
+ log(`Signal state changed ${JSON.stringify(this.getStatus())}`);
67
+ this.statusChanged.emit(this.getStatus());
68
+ }
69
+
70
+ private _createClient () {
71
+ this._connectionStarted = Date.now();
72
+ try {
73
+ this._client = new WebsocketRpc(this._host);
74
+ } catch (error: any) {
75
+ if (this._state === SignalApi.State.RE_CONNECTING) {
76
+ this._reconnectAfter *= 2;
77
+ }
78
+
79
+ this._lastError = error;
80
+ this._setState(SignalApi.State.DISCONNECTED);
81
+ this._reconnect();
82
+ }
83
+
84
+ this._client.addHandler('offer', (message: any) => this._onOffer({
85
+ id: PublicKey.from(message.id),
86
+ remoteId: PublicKey.from(message.remoteId),
87
+ topic: PublicKey.from(message.topic),
88
+ sessionId: PublicKey.from(message.sessionId),
89
+ data: message.data
90
+ }));
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
+
107
+ this._clientCleanup.push(this._client.error.on(error => {
108
+ log(`Socket error: ${error.message}`);
109
+ if (this._state === SignalApi.State.CLOSED) {
110
+ return;
111
+ }
112
+
113
+ if (this._state === SignalApi.State.RE_CONNECTING) {
114
+ this._reconnectAfter *= 2;
115
+ }
116
+
117
+ this._lastError = error;
118
+ this._setState(SignalApi.State.DISCONNECTED);
119
+
120
+ this._reconnect();
121
+ }));
122
+
123
+ this._clientCleanup.push(this._client.disconnected.on(() => {
124
+ log('Socket disconnected');
125
+ // This is also called in case of error, but we already have disconnected the socket on error, so no need to do anything here.
126
+ if (this._state !== SignalApi.State.CONNECTING && this._state !== SignalApi.State.RE_CONNECTING) {
127
+ return;
128
+ }
129
+
130
+ if (this._state === SignalApi.State.RE_CONNECTING) {
131
+ this._reconnectAfter *= 2;
132
+ }
133
+
134
+ this._setState(SignalApi.State.DISCONNECTED);
135
+ this._reconnect();
136
+ }));
137
+
138
+ this._clientCleanup.push(this._client.commandTrace.on(trace => this.commandTrace.emit(trace)));
139
+ }
140
+
141
+ private _reconnect () {
142
+ if (this._reconnectIntervalId !== undefined) {
143
+ console.error('Signal api already reconnecting.');
144
+ return;
145
+ }
146
+ if (this._state === SignalApi.State.CLOSED) {
147
+ return;
148
+ }
149
+
150
+ this._reconnectIntervalId = setTimeout(() => {
151
+ this._reconnectIntervalId = undefined;
152
+
153
+ this._clientCleanup.forEach(cb => cb());
154
+ this._clientCleanup = [];
155
+
156
+ // Close client if it wasn't already closed.
157
+ this._client.close().catch(() => {});
158
+
159
+ this._setState(SignalApi.State.RE_CONNECTING);
160
+ this._createClient();
161
+ }, this._reconnectAfter);
162
+ }
163
+
164
+ async close () {
165
+ this._clientCleanup.forEach(cb => cb());
166
+ this._clientCleanup = [];
167
+
168
+ if (this._reconnectIntervalId !== undefined) {
169
+ clearTimeout(this._reconnectIntervalId);
170
+ }
171
+
172
+ await this._client.close();
173
+ this._setState(SignalApi.State.CLOSED);
174
+ }
175
+
176
+ getStatus (): SignalApi.Status {
177
+ return {
178
+ host: this._host,
179
+ state: this._state,
180
+ error: this._lastError?.message,
181
+ reconnectIn: this._reconnectAfter,
182
+ connectionStarted: this._connectionStarted,
183
+ lastStateChange: this._lastStateChange
184
+ };
185
+ }
186
+
187
+ async join (topic: PublicKey, peerId: PublicKey): Promise<PublicKey[]> {
188
+ const peers: Buffer[] = await this._client.call('join', {
189
+ id: peerId.asBuffer(),
190
+ topic: topic.asBuffer()
191
+ });
192
+ return peers.map(id => PublicKey.from(id));
193
+ }
194
+
195
+ async leave (topic: PublicKey, peerId: PublicKey): Promise<void> {
196
+ await this._client.call('leave', {
197
+ id: peerId.asBuffer(),
198
+ topic: topic.asBuffer()
199
+ });
200
+ }
201
+
202
+ async lookup (topic: PublicKey): Promise<PublicKey[]> {
203
+ const peers: Buffer[] = await this._client.call('lookup', {
204
+ topic: topic.asBuffer()
205
+ });
206
+ return peers.map(id => PublicKey.from(id));
207
+ }
208
+
209
+ /**
210
+ * Routes an offer to the other peer's _onOffer callback.
211
+ * @returns Other peer's _onOffer callback return value.
212
+ */
213
+ async offer (payload: SignalApi.SignalMessage): Promise<SignalApi.Answer> {
214
+ return this._client.call('offer', {
215
+ id: payload.id.asBuffer(),
216
+ remoteId: payload.remoteId.asBuffer(),
217
+ topic: payload.topic.asBuffer(),
218
+ sessionId: payload.sessionId.asBuffer(),
219
+ data: payload.data
220
+ });
221
+ }
222
+
223
+ /**
224
+ * Routes an offer to the other peer's _onSignal callback.
225
+ */
226
+ async signal (payload: SignalApi.SignalMessage): Promise<void> {
227
+ return this._client.emit('signal', {
228
+ id: payload.id.asBuffer(),
229
+ remoteId: payload.remoteId.asBuffer(),
230
+ topic: payload.topic.asBuffer(),
231
+ sessionId: payload.sessionId.asBuffer(),
232
+ data: payload.data
233
+ });
234
+ }
235
+ }
@@ -0,0 +1,39 @@
1
+ //
2
+ // Copyright 2020 DXOS.org
3
+ //
4
+
5
+ import { Event } from '@dxos/async';
6
+ import { PublicKey } from '@dxos/crypto';
7
+
8
+ import { SignalApi } from './signal-api';
9
+
10
+ // TODO(burdon): Document methods.
11
+ export interface SignalConnection {
12
+ /**
13
+ * Find peers (triggers async event).
14
+ */
15
+ lookup (topic: PublicKey): void
16
+
17
+ /**
18
+ *
19
+ */
20
+ // TODO(burdon): Document.
21
+ offer (msg: SignalApi.SignalMessage): Promise<SignalApi.Answer>
22
+
23
+ /**
24
+ * Send message to peer.
25
+ */
26
+ signal (msg: SignalApi.SignalMessage): Promise<void>
27
+ }
28
+
29
+ export interface SignalManager extends SignalConnection {
30
+ statusChanged: Event<SignalApi.Status[]>
31
+ commandTrace: Event<SignalApi.CommandTrace>
32
+ peerCandidatesChanged: Event<[topic: PublicKey, candidates: PublicKey[]]>
33
+ onSignal: Event<SignalApi.SignalMessage>
34
+
35
+ getStatus (): SignalApi.Status[]
36
+ join (topic: PublicKey, peerId: PublicKey): void
37
+ leave (topic: PublicKey, peerId: PublicKey): void
38
+ destroy(): Promise<void>
39
+ }
@@ -25,19 +25,13 @@ const RPC_TIMEOUT = 3_000;
25
25
  */
26
26
  export class WebsocketRpc {
27
27
  private readonly _connectTrigger = new Trigger();
28
-
29
28
  private readonly _socket: WebSocket;
30
-
31
29
  private readonly _rpc: any;
32
-
33
30
  private _messageId = Date.now();
34
31
 
35
32
  readonly commandTrace = new Event<SignalApi.CommandTrace>();
36
-
37
33
  readonly connected = new Event();
38
-
39
34
  readonly disconnected = new Event();
40
-
41
35
  readonly error = new Event<Error>();
42
36
 
43
37
  /**
@@ -58,6 +52,7 @@ export class WebsocketRpc {
58
52
  this.error.emit(err);
59
53
  }
60
54
  };
55
+
61
56
  this._socket.onclose = async () => {
62
57
  log(`Disconnected ${this._host}`);
63
58
  this.disconnected.emit();
@@ -67,10 +62,12 @@ export class WebsocketRpc {
67
62
  this.error.emit(err);
68
63
  }
69
64
  };
65
+
70
66
  this._socket.onerror = e => {
71
67
  log(`Signal socket error ${this._host} ${e.message}`);
72
68
  this.error.emit(e.error ?? new Error(e.message));
73
69
  };
70
+
74
71
  this._rpc = nanomessagerpc({
75
72
  send: async (data: Uint8Array) => {
76
73
  await this._connectTrigger.wait();
@@ -9,24 +9,25 @@ import { Event, synchronized } from '@dxos/async';
9
9
  import { PublicKey } from '@dxos/crypto';
10
10
  import { ComplexMap } from '@dxos/util';
11
11
 
12
- import { SignalManager } from './interface';
13
12
  import { SignalApi } from './signal-api';
13
+ import { SignalClient } from './signal-client';
14
+ import { SignalManager } from './signal-manager';
14
15
 
15
16
  const log = debug('dxos:network-manager:websocket-signal-manager');
16
17
 
17
18
  export class WebsocketSignalManager implements SignalManager {
18
- private readonly _servers = new Map<string, SignalApi>();
19
+ private readonly _servers = new Map<string, SignalClient>();
19
20
 
20
21
  /** Topics joined: topic => peerId */
21
- private readonly _topicsJoined = new ComplexMap<PublicKey, PublicKey>(x => x.toHex());
22
-
22
+ private readonly _topicsJoined = new ComplexMap<PublicKey, PublicKey>(topic => topic.toHex());
23
23
  private readonly _topicsJoinedPerSignal = new Map<string, ComplexMap<PublicKey, PublicKey>>();
24
24
 
25
25
  private _reconcileTimeoutId?: NodeJS.Timeout;
26
26
 
27
27
  readonly statusChanged = new Event<SignalApi.Status[]>();
28
-
29
28
  readonly commandTrace = new Event<SignalApi.CommandTrace>();
29
+ readonly peerCandidatesChanged = new Event<[topic: PublicKey, candidates: PublicKey[]]>()
30
+ readonly onSignal = new Event<SignalApi.SignalMessage>();
30
31
 
31
32
  constructor (
32
33
  private readonly _hosts: string[],
@@ -35,13 +36,12 @@ export class WebsocketSignalManager implements SignalManager {
35
36
  log(`Created WebsocketSignalManager with signal servers: ${_hosts}`);
36
37
  assert(_hosts.length === 1, 'Only a single signaling server connection is supported');
37
38
  for (const host of this._hosts) {
38
- const server = new SignalApi(
39
+ const server = new SignalClient(
39
40
  host,
40
- async (msg) => this._onOffer(msg),
41
- async msg => {
42
- this.onSignal.emit(msg);
43
- }
41
+ async msg => this._onOffer(msg),
42
+ async msg => this.onSignal.emit(msg)
44
43
  );
44
+
45
45
  this._servers.set(host, server);
46
46
  server.statusChanged.on(() => this.statusChanged.emit(this.getStatus()));
47
47
  server.commandTrace.on(trace => this.commandTrace.emit(trace));
@@ -143,7 +143,7 @@ export class WebsocketSignalManager implements SignalManager {
143
143
  return Array.from(this._servers.values())[0].offer(msg);
144
144
  }
145
145
 
146
- signal (msg: SignalApi.SignalMessage) {
146
+ async signal (msg: SignalApi.SignalMessage) {
147
147
  log(`Signal ${msg.remoteId}`);
148
148
  for (const server of this._servers.values()) {
149
149
  void server.signal(msg);
@@ -154,8 +154,4 @@ export class WebsocketSignalManager implements SignalManager {
154
154
  async destroy () {
155
155
  await Promise.all(Array.from(this._servers.values()).map(server => server.close()));
156
156
  }
157
-
158
- peerCandidatesChanged = new Event<[topic: PublicKey, candidates: PublicKey[]]>()
159
-
160
- onSignal = new Event<SignalApi.SignalMessage>();
161
157
  }
@@ -45,15 +45,15 @@ export enum ConnectionState {
45
45
  CLOSED = 'CLOSED',
46
46
  }
47
47
 
48
+ /**
49
+ * Represents a connection to a remote peer.
50
+ */
48
51
  export class Connection {
49
52
  private _state: ConnectionState = ConnectionState.INITIAL;
50
-
51
53
  private _transport: Transport | undefined;
52
-
53
54
  private _bufferedSignals: SignalApi.SignalMessage[] = [];
54
55
 
55
56
  readonly stateChanged = new Event<ConnectionState>();
56
-
57
57
  readonly errors = new ErrorStream();
58
58
 
59
59
  constructor (
@@ -110,12 +110,13 @@ export class Connection {
110
110
 
111
111
  // Replay signals that were received before transport was created.
112
112
  for (const signal of this._bufferedSignals) {
113
- this._transport.signal(signal);
113
+ void this._transport.signal(signal); // TODO(burdon): Remove async?
114
114
  }
115
+
115
116
  this._bufferedSignals = [];
116
117
  }
117
118
 
118
- signal (msg: SignalApi.SignalMessage) {
119
+ async signal (msg: SignalApi.SignalMessage) {
119
120
  if (!msg.sessionId.equals(this.sessionId)) {
120
121
  log('Dropping signal for incorrect session id.');
121
122
  return;
@@ -134,7 +135,7 @@ export class Connection {
134
135
 
135
136
  assert(this._transport, 'Connection not ready to accept signals.');
136
137
  log(`${this.ownId} received signal from ${this.remoteId}: ${msg.data.type}`);
137
- this._transport.signal(msg);
138
+ await this._transport.signal(msg);
138
139
  }
139
140
 
140
141
  @synchronized
@@ -12,50 +12,57 @@ import { PublicKey } from '@dxos/crypto';
12
12
  import { Protocol } from '@dxos/mesh-protocol';
13
13
  import { afterTest } from '@dxos/testutils';
14
14
 
15
+ import { SignalApi, SignalConnection } from '../signal';
15
16
  import { FullyConnectedTopology } from '../topology';
16
- import { createWebRtcTransportFactory, WebrtcTransport } from '../transport';
17
+ import { createWebRTCTransportFactory, WebRTCTransport } from '../transport';
17
18
  import { Swarm } from './swarm';
18
19
 
19
20
  const log = debug('dxos:network-manager:swarm:test');
20
21
 
22
+ class MockSignalConnection implements SignalConnection {
23
+ constructor (
24
+ readonly _swarm: () => Swarm,
25
+ readonly _delay = 10
26
+ ) {}
27
+
28
+ lookup (topic: PublicKey) {}
29
+
30
+ async offer (msg: SignalApi.SignalMessage) {
31
+ await sleep(this._delay);
32
+ return this._swarm().onOffer(msg);
33
+ }
34
+
35
+ async signal (msg: SignalApi.SignalMessage) {
36
+ await sleep(this._delay);
37
+ await this._swarm().onSignal(msg);
38
+ }
39
+ }
40
+
21
41
  const setup = () => {
22
42
  const topic = PublicKey.random();
23
43
  const peerId1 = PublicKey.random();
24
44
  const peerId2 = PublicKey.random();
45
+
25
46
  const swarm1: Swarm = new Swarm(
26
47
  topic,
27
48
  peerId1,
28
49
  new FullyConnectedTopology(),
29
50
  () => new Protocol(),
30
- async msg => {
31
- await sleep(10); // Simulating network delay.
32
- return swarm2.onOffer(msg);
33
- },
34
- async msg => {
35
- await sleep(10); // Simulating network delay.
36
- await swarm2.onSignal(msg);
37
- },
38
- () => {},
39
- createWebRtcTransportFactory(),
51
+ new MockSignalConnection(() => swarm2),
52
+ createWebRTCTransportFactory(),
40
53
  undefined
41
54
  );
55
+
42
56
  const swarm2: Swarm = new Swarm(
43
57
  topic,
44
58
  peerId2,
45
59
  new FullyConnectedTopology(),
46
60
  () => new Protocol(),
47
- async msg => {
48
- await sleep(10); // Simulating network delay.
49
- return swarm1.onOffer(msg);
50
- },
51
- async msg => {
52
- await sleep(10); // Simulating network delay.
53
- await swarm1.onSignal(msg);
54
- },
55
- () => {},
56
- createWebRtcTransportFactory(),
61
+ new MockSignalConnection(() => swarm1),
62
+ createWebRTCTransportFactory(),
57
63
  undefined
58
64
  );
65
+
59
66
  afterTest(async () => {
60
67
  await swarm1.destroy();
61
68
  await swarm2.destroy();
@@ -84,10 +91,10 @@ test('connects two peers in a swarm', async () => {
84
91
  const swarm1Connection = swarm1.connections[0];
85
92
  const swarm2Connection = swarm2.connections[0];
86
93
  const onData = mockFn<(data: Buffer) => void>().returns(undefined);
87
- (swarm2Connection.transport as WebrtcTransport).peer!.on('data', onData);
94
+ (swarm2Connection.transport as WebRTCTransport).peer!.on('data', onData);
88
95
 
89
96
  const data = Buffer.from('1234');
90
- (swarm1Connection.transport as WebrtcTransport).peer!.send(data);
97
+ (swarm1Connection.transport as WebRTCTransport).peer!.send(data);
91
98
  await waitForExpect(() => {
92
99
  expect(onData).toHaveBeenCalledWith([data]);
93
100
  });
@@ -126,10 +133,10 @@ test('second peer discovered after delay', async () => {
126
133
  const swarm1Connection = swarm1.connections[0];
127
134
  const swarm2Connection = swarm2.connections[0];
128
135
  const onData = mockFn<(data: Buffer) => void>().returns(undefined);
129
- (swarm2Connection.transport as WebrtcTransport).peer!.on('data', onData);
136
+ (swarm2Connection.transport as WebRTCTransport).peer!.on('data', onData);
130
137
 
131
138
  const data = Buffer.from('1234');
132
- (swarm1Connection.transport as WebrtcTransport).peer!.send(data);
139
+ (swarm1Connection.transport as WebRTCTransport).peer!.send(data);
133
140
  await waitForExpect(() => {
134
141
  expect(onData).toHaveBeenCalledWith([data]);
135
142
  });
@@ -11,7 +11,7 @@ import { ErrorStream } from '@dxos/debug';
11
11
  import { ComplexMap, ComplexSet } from '@dxos/util';
12
12
 
13
13
  import { ProtocolProvider } from '../network-manager';
14
- import { SignalApi } from '../signal';
14
+ import { SignalApi, SignalConnection } from '../signal';
15
15
  import { SwarmController, Topology } from '../topology';
16
16
  import { TransportFactory } from '../transport';
17
17
  import { Topic } from '../types';
@@ -31,9 +31,7 @@ export class Swarm {
31
31
  readonly id = PublicKey.random();
32
32
 
33
33
  private readonly _connections = new ComplexMap<PublicKey, Connection>(x => x.toHex());
34
-
35
34
  private readonly _discoveredPeers = new ComplexSet<PublicKey>(x => x.toHex());
36
-
37
35
  private readonly _peerCandidatesUpdated = new Event();
38
36
 
39
37
  get connections () {
@@ -62,10 +60,8 @@ export class Swarm {
62
60
  private readonly _topic: PublicKey,
63
61
  private readonly _ownPeerId: PublicKey,
64
62
  private _topology: Topology,
65
- private readonly _protocol: ProtocolProvider,
66
- private readonly _sendOffer: (message: SignalApi.SignalMessage) => Promise<SignalApi.Answer>,
67
- private readonly _sendSignal: (message: SignalApi.SignalMessage) => Promise<void>,
68
- private readonly _lookup: () => void,
63
+ private readonly _protocolProvider: ProtocolProvider,
64
+ private readonly _signalConnection: SignalConnection,
69
65
  private readonly _transportFactory: TransportFactory,
70
66
  private readonly _label: string | undefined
71
67
  ) {
@@ -148,7 +144,8 @@ export class Swarm {
148
144
  log(`Dropping signal message for non-existent connection: topic=${this._topic}, peerId=${message.id}`);
149
145
  return;
150
146
  }
151
- connection.signal(message);
147
+
148
+ await connection.signal(message);
152
149
  }
153
150
 
154
151
  async setTopology (newTopology: Topology) {
@@ -185,7 +182,7 @@ export class Swarm {
185
182
  this._topology.update();
186
183
  },
187
184
  lookup: () => {
188
- this._lookup();
185
+ this._signalConnection.lookup(this._topic);
189
186
  }
190
187
  };
191
188
  }
@@ -199,7 +196,7 @@ export class Swarm {
199
196
  const sessionId = PublicKey.random();
200
197
 
201
198
  const connection = this._createConnection(true, remoteId, sessionId);
202
- this._sendOffer({
199
+ this._signalConnection.offer({
203
200
  id: this._ownPeerId,
204
201
  remoteId,
205
202
  sessionId,
@@ -209,7 +206,7 @@ export class Swarm {
209
206
  .then(answer => {
210
207
  log(`Received answer: ${JSON.stringify(answer)} topic=${this._topic} ownId=${this._ownPeerId} remoteId=${remoteId}`);
211
208
  if (connection.state !== ConnectionState.INITIAL) {
212
- log('Ignoring answer');
209
+ log('Ignoring answer.');
213
210
  return;
214
211
  }
215
212
 
@@ -228,12 +225,13 @@ export class Swarm {
228
225
  .catch(err => {
229
226
  this.errors.raise(err);
230
227
  });
228
+
231
229
  this._topology.update();
232
230
  }
233
231
 
234
232
  private _createConnection (initiator: boolean, remoteId: PublicKey, sessionId: PublicKey) {
235
233
  log(`Create connection topic=${this._topic} remoteId=${remoteId} initiator=${initiator}`);
236
- assert(!this._connections.has(remoteId), 'Peer already connected');
234
+ assert(!this._connections.has(remoteId), 'Peer already connected.');
237
235
 
238
236
  const connection = new Connection(
239
237
  this._topic,
@@ -241,8 +239,8 @@ export class Swarm {
241
239
  remoteId,
242
240
  sessionId,
243
241
  initiator,
244
- this._sendSignal,
245
- this._protocol({ channel: discoveryKey(this._topic), initiator }),
242
+ (msg: SignalApi.SignalMessage) => this._signalConnection.signal(msg),
243
+ this._protocolProvider({ channel: discoveryKey(this._topic), initiator }),
246
244
  this._transportFactory
247
245
  );
248
246
 
@@ -258,12 +256,14 @@ export class Swarm {
258
256
 
259
257
  void connection.stateChanged.waitFor(s => s === ConnectionState.CLOSED).then(() => {
260
258
  log(`Connection closed topic=${this._topic} remoteId=${remoteId} initiator=${initiator}`);
261
- // Connection might have been already closed or replace by a different one. Only remove the connection if it has the same session id.
259
+ // Connection might have been already closed or replace by a different one.
260
+ // Only remove the connection if it has the same session id.
262
261
  if (this._connections.get(remoteId)?.sessionId.equals(sessionId)) {
263
262
  this._connections.delete(remoteId);
264
263
  this.connectionRemoved.emit(connection);
265
264
  }
266
265
  });
266
+
267
267
  return connection;
268
268
  }
269
269
 
@@ -273,6 +273,7 @@ export class Swarm {
273
273
  if (!connection) {
274
274
  return;
275
275
  }
276
+
276
277
  this._connections.delete(peerId);
277
278
  await connection.close();
278
279
  }
@@ -149,12 +149,8 @@ export class TestProtocolPlugin extends EventEmitter {
149
149
  * @return {ProtocolProvider}
150
150
  */
151
151
  // TODO(dboreham): Try to encapsulate swarmKey, nodeId.
152
- export const testProtocolProvider = (swarmKey: Buffer, nodeId: Buffer, protocolPlugin: any) => {
153
- return protocolFactory({
154
- getTopics: () => {
155
- return [swarmKey];
156
- },
157
- session: { peerId: keyToString(nodeId) },
158
- plugins: [protocolPlugin]
159
- });
160
- };
152
+ export const testProtocolProvider = (swarmKey: Buffer, nodeId: Buffer, protocolPlugin: any) => protocolFactory({
153
+ getTopics: () => [swarmKey],
154
+ session: { peerId: keyToString(nodeId) },
155
+ plugins: [protocolPlugin]
156
+ });
@@ -111,6 +111,4 @@ export class MMSTTopology implements Topology {
111
111
  }
112
112
  }
113
113
 
114
- function sortByXorDistance (keys: PublicKey[], reference: PublicKey): PublicKey[] {
115
- return keys.sort((a, b) => distance.gt(distance(a.asBuffer(), reference.asBuffer()), distance(b.asBuffer(), reference.asBuffer())));
116
- }
114
+ const sortByXorDistance = (keys: PublicKey[], reference: PublicKey): PublicKey[] => keys.sort((a, b) => distance.gt(distance(a.asBuffer(), reference.asBuffer()), distance(b.asBuffer(), reference.asBuffer())));
@@ -17,7 +17,7 @@ import { InMemoryTransport } from './in-memory-transport';
17
17
  // Cannot log after tests are done. Did you forget to wait for something async in your test?
18
18
  // Attempted to log "Ignoring unsupported ICE candidate.".
19
19
 
20
- function createPair () {
20
+ const createPair = () => {
21
21
  const topic = PublicKey.random();
22
22
  const peer1Id = PublicKey.random();
23
23
  const peer2Id = PublicKey.random();
@@ -50,7 +50,7 @@ function createPair () {
50
50
  afterTest(() => connection2.errors.assertNoUnhandledErrors());
51
51
 
52
52
  return { connection1, connection2, plugin1, plugin2, peer1Id, peer2Id, topic };
53
- }
53
+ };
54
54
 
55
55
  describe('InMemoryTransport', () => {
56
56
  it('establish connection and send data through with protocol', async () => {