@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.
Files changed (98) hide show
  1. package/dist/browser-mocha/bundle.js +15046 -15255
  2. package/dist/src/network-manager.d.ts +1 -0
  3. package/dist/src/network-manager.d.ts.map +1 -1
  4. package/dist/src/network-manager.js +8 -3
  5. package/dist/src/network-manager.js.map +1 -1
  6. package/dist/src/proto/gen/dxos/credentials.d.ts +26 -0
  7. package/dist/src/proto/gen/dxos/credentials.d.ts.map +1 -0
  8. package/dist/src/proto/gen/dxos/credentials.js +3 -0
  9. package/dist/src/proto/gen/dxos/credentials.js.map +1 -0
  10. package/dist/src/proto/gen/dxos/halo/keys.d.ts +60 -0
  11. package/dist/src/proto/gen/dxos/halo/keys.d.ts.map +1 -0
  12. package/dist/src/proto/gen/dxos/halo/keys.js +13 -0
  13. package/dist/src/proto/gen/dxos/halo/keys.js.map +1 -0
  14. package/dist/src/proto/gen/dxos/mesh/signal.d.ts +28 -0
  15. package/dist/src/proto/gen/dxos/mesh/signal.d.ts.map +1 -0
  16. package/dist/src/proto/gen/dxos/mesh/signal.js +3 -0
  17. package/dist/src/proto/gen/dxos/mesh/signal.js.map +1 -0
  18. package/dist/src/proto/gen/google/protobuf.d.ts +5 -0
  19. package/dist/src/proto/gen/google/protobuf.d.ts.map +1 -0
  20. package/dist/src/proto/gen/google/protobuf.js +3 -0
  21. package/dist/src/proto/gen/google/protobuf.js.map +1 -0
  22. package/dist/src/proto/gen/index.d.ts +28 -0
  23. package/dist/src/proto/gen/index.d.ts.map +1 -0
  24. package/dist/src/proto/gen/index.js +11 -0
  25. package/dist/src/proto/gen/index.js.map +1 -0
  26. package/dist/src/proto/substitutions.d.ts +17 -0
  27. package/dist/src/proto/substitutions.d.ts.map +1 -0
  28. package/dist/src/proto/substitutions.js +10 -0
  29. package/dist/src/proto/substitutions.js.map +1 -0
  30. package/dist/src/signal/in-memory-signal-manager.d.ts +5 -4
  31. package/dist/src/signal/in-memory-signal-manager.d.ts.map +1 -1
  32. package/dist/src/signal/in-memory-signal-manager.js +2 -0
  33. package/dist/src/signal/in-memory-signal-manager.js.map +1 -1
  34. package/dist/src/signal/message-router.d.ts +24 -0
  35. package/dist/src/signal/message-router.d.ts.map +1 -0
  36. package/dist/src/signal/message-router.js +71 -0
  37. package/dist/src/signal/message-router.js.map +1 -0
  38. package/dist/src/signal/message-router.test.d.ts +2 -0
  39. package/dist/src/signal/message-router.test.d.ts.map +1 -0
  40. package/dist/src/signal/message-router.test.js +158 -0
  41. package/dist/src/signal/message-router.test.js.map +1 -0
  42. package/dist/src/signal/signal-api.d.ts +6 -0
  43. package/dist/src/signal/signal-api.d.ts.map +1 -1
  44. package/dist/src/signal/signal-api.js.map +1 -1
  45. package/dist/src/signal/signal-client.d.ts +4 -3
  46. package/dist/src/signal/signal-client.d.ts.map +1 -1
  47. package/dist/src/signal/signal-client.js +12 -10
  48. package/dist/src/signal/signal-client.js.map +1 -1
  49. package/dist/src/signal/signal-client.test.js +23 -4
  50. package/dist/src/signal/signal-client.test.js.map +1 -1
  51. package/dist/src/signal/signal-manager.d.ts +22 -7
  52. package/dist/src/signal/signal-manager.d.ts.map +1 -1
  53. package/dist/src/signal/websocket-signal-manager.d.ts +5 -4
  54. package/dist/src/signal/websocket-signal-manager.d.ts.map +1 -1
  55. package/dist/src/signal/websocket-signal-manager.js.map +1 -1
  56. package/dist/src/swarm/connection.d.ts +3 -3
  57. package/dist/src/swarm/connection.d.ts.map +1 -1
  58. package/dist/src/swarm/connection.js +8 -5
  59. package/dist/src/swarm/connection.js.map +1 -1
  60. package/dist/src/swarm/swarm.d.ts +7 -5
  61. package/dist/src/swarm/swarm.d.ts.map +1 -1
  62. package/dist/src/swarm/swarm.js +16 -10
  63. package/dist/src/swarm/swarm.js.map +1 -1
  64. package/dist/src/swarm/swarm.test.js +42 -4
  65. package/dist/src/swarm/swarm.test.js.map +1 -1
  66. package/dist/src/transport/in-memory-transport.d.ts +2 -2
  67. package/dist/src/transport/in-memory-transport.d.ts.map +1 -1
  68. package/dist/src/transport/in-memory-transport.js.map +1 -1
  69. package/dist/src/transport/transport.d.ts +3 -3
  70. package/dist/src/transport/transport.d.ts.map +1 -1
  71. package/dist/src/transport/webrtc-transport.d.ts +3 -3
  72. package/dist/src/transport/webrtc-transport.d.ts.map +1 -1
  73. package/dist/src/transport/webrtc-transport.js +4 -2
  74. package/dist/src/transport/webrtc-transport.js.map +1 -1
  75. package/dist/tsconfig.tsbuildinfo +1 -1
  76. package/package.json +14 -13
  77. package/src/network-manager.ts +16 -6
  78. package/src/proto/defs/dxos/mesh/signal.proto +38 -0
  79. package/src/proto/gen/dxos/credentials.ts +27 -0
  80. package/src/proto/gen/dxos/halo/keys.ts +62 -0
  81. package/src/proto/gen/dxos/mesh/signal.ts +31 -0
  82. package/src/proto/gen/google/protobuf.ts +9 -0
  83. package/src/proto/gen/index.ts +28 -0
  84. package/src/proto/substitutions.ts +9 -0
  85. package/src/signal/in-memory-signal-manager.ts +7 -4
  86. package/src/signal/message-router.test.ts +218 -0
  87. package/src/signal/message-router.ts +89 -0
  88. package/src/signal/signal-api.ts +6 -0
  89. package/src/signal/signal-client.test.ts +35 -12
  90. package/src/signal/signal-client.ts +19 -18
  91. package/src/signal/signal-manager.ts +24 -9
  92. package/src/signal/websocket-signal-manager.ts +5 -4
  93. package/src/swarm/connection.ts +11 -9
  94. package/src/swarm/swarm.test.ts +61 -11
  95. package/src/swarm/swarm.ts +17 -12
  96. package/src/transport/in-memory-transport.ts +2 -2
  97. package/src/transport/transport.ts +3 -3
  98. package/src/transport/webrtc-transport.ts +6 -5
@@ -11,7 +11,7 @@ import { PublicKey } from '@dxos/protocols';
11
11
  import { createTestBroker } from '@dxos/signal';
12
12
  import { randomInt } from '@dxos/util';
13
13
 
14
- import { SignalApi } from './signal-api';
14
+ import { Answer, Message } from '../proto/gen/dxos/mesh/signal';
15
15
  import { SignalClient } from './signal-client';
16
16
 
17
17
  describe('SignalApi', () => {
@@ -47,6 +47,29 @@ describe('SignalApi', () => {
47
47
  // code await broker2.stop();
48
48
  });
49
49
 
50
+ test('message between 2 clients', async () => {
51
+ const signalMock1 = mockFn<(msg: Message) => Promise<void>>()
52
+ .resolvesTo();
53
+ api1 = new SignalClient(signalApiUrl1, (async () => {}) as any, signalMock1);
54
+ api2 = new SignalClient(signalApiUrl1, (async () => {}) as any, (async () => {}) as any);
55
+
56
+ await api1.join(topic, peer1);
57
+ await api2.join(topic, peer2);
58
+
59
+ const msg: Message = {
60
+ id: peer2,
61
+ remoteId: peer1,
62
+ sessionId: PublicKey.random(),
63
+ topic,
64
+ data: { signal: { json: "foo: 'bar'" } }
65
+ };
66
+ await api2.signal(msg);
67
+
68
+ await waitForExpect(() => {
69
+ expect(signalMock1).toHaveBeenCalledWith([msg]);
70
+ }, 4_000);
71
+ }).timeout(5_000);
72
+
50
73
  test('join', async () => {
51
74
  api1 = new SignalClient(signalApiUrl1, (async () => {}) as any, async () => {});
52
75
 
@@ -58,14 +81,14 @@ describe('SignalApi', () => {
58
81
  }).timeout(1_000);
59
82
 
60
83
  test('offer', async () => {
61
- const offerMock = mockFn<(msg: SignalApi.SignalMessage) => Promise<SignalApi.Answer>>()
84
+ const offerMock = mockFn<(msg: Message) => Promise<Answer>>()
62
85
  .resolvesTo({ accept: true });
63
86
  api1 = new SignalClient(signalApiUrl1, offerMock, async () => {});
64
87
 
65
88
  await api1.join(topic, peer1);
66
89
 
67
- const offer: SignalApi.SignalMessage = {
68
- data: { foo: 'bar' } as any,
90
+ const offer: Message = {
91
+ data: { offer: {} },
69
92
  id: peer2,
70
93
  remoteId: peer1,
71
94
  sessionId: PublicKey.random(),
@@ -77,18 +100,18 @@ describe('SignalApi', () => {
77
100
  }).timeout(5_000);
78
101
 
79
102
  test('signal', async () => {
80
- const signalMock = mockFn<(msg: SignalApi.SignalMessage) => Promise<void>>()
103
+ const signalMock = mockFn<(msg: Message) => Promise<void>>()
81
104
  .resolvesTo();
82
105
  api1 = new SignalClient(signalApiUrl1, (async () => {}) as any, signalMock);
83
106
 
84
107
  await api1.join(topic, peer1);
85
108
 
86
- const msg: SignalApi.SignalMessage = {
109
+ const msg: Message = {
87
110
  id: peer2,
88
111
  remoteId: peer1,
89
112
  sessionId: PublicKey.random(),
90
113
  topic,
91
- data: { foo: 'bar' } as any
114
+ data: { signal: { json: 'bar' } }
92
115
  };
93
116
  await api1.signal(msg);
94
117
 
@@ -118,9 +141,9 @@ describe('SignalApi', () => {
118
141
 
119
142
  // Skip because communication between signal servers is not yet implemented.
120
143
  test.skip('newly joined peer can receive signals from other signal servers', async () => {
121
- const offerMock = mockFn<(msg: SignalApi.SignalMessage) => Promise<SignalApi.Answer>>()
144
+ const offerMock = mockFn<(msg: Message) => Promise<Answer>>()
122
145
  .resolvesTo({ accept: true });
123
- const signalMock = mockFn<(msg: SignalApi.SignalMessage) => Promise<void>>()
146
+ const signalMock = mockFn<(msg: Message) => Promise<void>>()
124
147
  .resolvesTo();
125
148
 
126
149
  api1 = new SignalClient(signalApiUrl1, offerMock, async () => {});
@@ -136,16 +159,16 @@ describe('SignalApi', () => {
136
159
  id: peer2,
137
160
  topic,
138
161
  sessionId,
139
- data: {}
162
+ data: { offer: {} }
140
163
  });
141
164
  expect(answer).toEqual({ accept: true });
142
165
 
143
- const msg: SignalApi.SignalMessage = {
166
+ const msg: Message = {
144
167
  id: peer2,
145
168
  remoteId: peer1,
146
169
  sessionId,
147
170
  topic,
148
- data: { foo: 'bar' } as any
171
+ data: { offer: { json: 'bar' } }
149
172
  };
150
173
  await api1.signal(msg);
151
174
 
@@ -7,6 +7,7 @@ import debug from 'debug';
7
7
  import { Event } from '@dxos/async';
8
8
  import { PublicKey } from '@dxos/protocols';
9
9
 
10
+ import { Answer, Message } from '../proto/gen/dxos/mesh/signal';
10
11
  import { SignalApi } from './signal-api';
11
12
  import { WebsocketRpc } from './websocket-rpc';
12
13
 
@@ -53,8 +54,8 @@ export class SignalClient {
53
54
  */
54
55
  constructor (
55
56
  private readonly _host: string,
56
- private readonly _onOffer: (message: SignalApi.SignalMessage) => Promise<SignalApi.Answer>,
57
- private readonly _onSignal: (message: SignalApi.SignalMessage) => Promise<void>
57
+ private readonly _onOffer: (message: Message) => Promise<Answer>,
58
+ private readonly _onSignal: (message: Message) => Promise<void>
58
59
  ) {
59
60
  this._setState(SignalApi.State.CONNECTING);
60
61
  this._createClient();
@@ -89,11 +90,11 @@ export class SignalClient {
89
90
  data: message.data
90
91
  }));
91
92
 
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),
93
+ this._client.subscribe('signal', (msg: Message) => this._onSignal({
94
+ id: PublicKey.from(msg.id!),
95
+ remoteId: PublicKey.from(msg.remoteId!),
96
+ topic: PublicKey.from(msg.topic!),
97
+ sessionId: PublicKey.from(msg.sessionId!),
97
98
  data: msg.data
98
99
  }));
99
100
 
@@ -210,25 +211,25 @@ export class SignalClient {
210
211
  * Routes an offer to the other peer's _onOffer callback.
211
212
  * @returns Other peer's _onOffer callback return value.
212
213
  */
213
- async offer (payload: SignalApi.SignalMessage): Promise<SignalApi.Answer> {
214
+ async offer (msg: Message): Promise<Answer> {
214
215
  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
216
+ id: msg.id?.asBuffer(),
217
+ remoteId: msg.remoteId?.asBuffer(),
218
+ topic: msg.topic?.asBuffer(),
219
+ sessionId: msg.sessionId?.asBuffer(),
220
+ data: msg.data
220
221
  });
221
222
  }
222
223
 
223
224
  /**
224
225
  * Routes an offer to the other peer's _onSignal callback.
225
226
  */
226
- async signal (payload: SignalApi.SignalMessage): Promise<void> {
227
+ async signal (payload: Message): Promise<void> {
227
228
  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(),
229
+ id: payload.id?.asBuffer(),
230
+ remoteId: payload.remoteId?.asBuffer(),
231
+ topic: payload.topic?.asBuffer(),
232
+ sessionId: payload.sessionId?.asBuffer(),
232
233
  data: payload.data
233
234
  });
234
235
  }
@@ -5,9 +5,12 @@
5
5
  import { Event } from '@dxos/async';
6
6
  import { PublicKey } from '@dxos/protocols';
7
7
 
8
+ import { Answer, Message } from '../proto/gen/dxos/mesh/signal';
8
9
  import { SignalApi } from './signal-api';
9
10
 
10
- // TODO(burdon): Document methods.
11
+ /**
12
+ * Signal peer discovery interface.
13
+ */
11
14
  export interface SignalConnection {
12
15
  /**
13
16
  * Find peers (triggers async event).
@@ -15,25 +18,37 @@ export interface SignalConnection {
15
18
  lookup (topic: PublicKey): void
16
19
 
17
20
  /**
18
- *
21
+ * Join topic on signal network, to be discoverable by other peers.
19
22
  */
20
- // TODO(burdon): Document.
21
- offer (msg: SignalApi.SignalMessage): Promise<SignalApi.Answer>
23
+ join (topic: PublicKey, peerId: PublicKey): void
24
+
25
+ /**
26
+ * Leave topic on signal network, to stop being discoverable by other peers.
27
+ */
28
+ leave (topic: PublicKey, peerId: PublicKey): void
29
+ }
30
+
31
+ /**
32
+ * Signal peer messaging interface.
33
+ */
34
+ export interface SignalMessaging {
35
+ /**
36
+ * Offer/answer RPC.
37
+ */
38
+ offer (msg: Message): Promise<Answer>
22
39
 
23
40
  /**
24
41
  * Send message to peer.
25
42
  */
26
- signal (msg: SignalApi.SignalMessage): Promise<void>
43
+ signal (msg: Message): Promise<void>
27
44
  }
28
45
 
29
- export interface SignalManager extends SignalConnection {
46
+ export interface SignalManager extends SignalConnection, SignalMessaging {
30
47
  statusChanged: Event<SignalApi.Status[]>
31
48
  commandTrace: Event<SignalApi.CommandTrace>
32
49
  peerCandidatesChanged: Event<[topic: PublicKey, candidates: PublicKey[]]>
33
- onSignal: Event<SignalApi.SignalMessage>
50
+ onSignal: Event<Message>
34
51
 
35
52
  getStatus (): SignalApi.Status[]
36
- join (topic: PublicKey, peerId: PublicKey): void
37
- leave (topic: PublicKey, peerId: PublicKey): void
38
53
  destroy(): Promise<void>
39
54
  }
@@ -9,6 +9,7 @@ import { Event, synchronized } from '@dxos/async';
9
9
  import { PublicKey } from '@dxos/protocols';
10
10
  import { ComplexMap } from '@dxos/util';
11
11
 
12
+ import { Answer, Message } from '../proto/gen/dxos/mesh/signal';
12
13
  import { SignalApi } from './signal-api';
13
14
  import { SignalClient } from './signal-client';
14
15
  import { SignalManager } from './signal-manager';
@@ -27,11 +28,11 @@ export class WebsocketSignalManager implements SignalManager {
27
28
  readonly statusChanged = new Event<SignalApi.Status[]>();
28
29
  readonly commandTrace = new Event<SignalApi.CommandTrace>();
29
30
  readonly peerCandidatesChanged = new Event<[topic: PublicKey, candidates: PublicKey[]]>()
30
- readonly onSignal = new Event<SignalApi.SignalMessage>();
31
+ readonly onSignal = new Event<Message>();
31
32
 
32
33
  constructor (
33
34
  private readonly _hosts: string[],
34
- private readonly _onOffer: (message: SignalApi.SignalMessage) => Promise<SignalApi.Answer>
35
+ private readonly _onOffer: (message: Message) => Promise<Answer>
35
36
  ) {
36
37
  log(`Created WebsocketSignalManager with signal servers: ${_hosts}`);
37
38
  assert(_hosts.length === 1, 'Only a single signaling server connection is supported');
@@ -137,13 +138,13 @@ export class WebsocketSignalManager implements SignalManager {
137
138
  }
138
139
  }
139
140
 
140
- offer (msg: SignalApi.SignalMessage) {
141
+ offer (msg: Message) {
141
142
  log(`Offer ${msg.remoteId}`);
142
143
  // TODO(marik-d): Broadcast to all signal servers.
143
144
  return Array.from(this._servers.values())[0].offer(msg);
144
145
  }
145
146
 
146
- async signal (msg: SignalApi.SignalMessage) {
147
+ async signal (msg: Message) {
147
148
  log(`Signal ${msg.remoteId}`);
148
149
  for (const server of this._servers.values()) {
149
150
  void server.signal(msg);
@@ -10,7 +10,7 @@ import { ErrorStream } from '@dxos/debug';
10
10
  import { Protocol } from '@dxos/mesh-protocol';
11
11
  import { PublicKey } from '@dxos/protocols';
12
12
 
13
- import { SignalApi } from '../signal';
13
+ import { Message } from '../proto/gen/dxos/mesh/signal';
14
14
  import { Transport, TransportFactory } from '../transport';
15
15
 
16
16
  const log = debug('dxos:network-manager:swarm:connection');
@@ -51,7 +51,7 @@ export enum ConnectionState {
51
51
  export class Connection {
52
52
  private _state: ConnectionState = ConnectionState.INITIAL;
53
53
  private _transport: Transport | undefined;
54
- private _bufferedSignals: SignalApi.SignalMessage[] = [];
54
+ private _bufferedSignals: Message[] = [];
55
55
 
56
56
  readonly stateChanged = new Event<ConnectionState>();
57
57
  readonly errors = new ErrorStream();
@@ -62,7 +62,7 @@ export class Connection {
62
62
  public readonly remoteId: PublicKey,
63
63
  public readonly sessionId: PublicKey,
64
64
  public readonly initiator: boolean,
65
- private readonly _sendSignal: (msg: SignalApi.SignalMessage) => Promise<void>,
65
+ private readonly _sendSignal: (msg: Message) => Promise<void>,
66
66
  private readonly _protocol: Protocol,
67
67
  private readonly _transportFactory: TransportFactory
68
68
  ) {}
@@ -116,25 +116,27 @@ export class Connection {
116
116
  this._bufferedSignals = [];
117
117
  }
118
118
 
119
- async signal (msg: SignalApi.SignalMessage) {
119
+ async signal (msg: Message) {
120
+ assert(msg.sessionId);
120
121
  if (!msg.sessionId.equals(this.sessionId)) {
121
122
  log('Dropping signal for incorrect session id.');
122
123
  return;
123
124
  }
124
- if (msg.data.type === 'offer' && this._state === ConnectionState.INITIATING_CONNECTION) {
125
+ assert(msg.data);
126
+ if (msg.data.offer && this._state === ConnectionState.INITIATING_CONNECTION) {
125
127
  throw new Error('Invalid state: Cannot send offer to an initiating peer.');
126
128
  }
127
- assert(msg.id.equals(this.remoteId));
128
- assert(msg.remoteId.equals(this.ownId));
129
+ assert(msg.id?.equals(this.remoteId));
130
+ assert(msg.remoteId?.equals(this.ownId));
129
131
 
130
132
  if (this._state === ConnectionState.INITIAL) {
131
- log(`${this.ownId} buffered signal from ${this.remoteId}: ${msg.data.type}`);
133
+ log(`${this.ownId} buffered signal from ${this.remoteId}: ${msg.data}`);
132
134
  this._bufferedSignals.push(msg);
133
135
  return;
134
136
  }
135
137
 
136
138
  assert(this._transport, 'Connection not ready to accept signals.');
137
- log(`${this.ownId} received signal from ${this.remoteId}: ${msg.data.type}`);
139
+ log(`${this.ownId} received signal from ${this.remoteId}: ${msg.data}`);
138
140
  await this._transport.signal(msg);
139
141
  }
140
142
 
@@ -12,53 +12,75 @@ import { Protocol } from '@dxos/mesh-protocol';
12
12
  import { PublicKey } from '@dxos/protocols';
13
13
  import { afterTest } from '@dxos/testutils';
14
14
 
15
- import { SignalApi, SignalConnection } from '../signal';
15
+ import { Message } from '../proto/gen/dxos/mesh/signal';
16
+ import { SignalMessaging } from '../signal';
17
+ import { MessageRouter } from '../signal/message-router';
16
18
  import { FullyConnectedTopology } from '../topology';
17
19
  import { createWebRTCTransportFactory, WebRTCTransport } from '../transport';
18
20
  import { Swarm } from './swarm';
19
21
 
20
22
  const log = debug('dxos:network-manager:swarm:test');
21
23
 
22
- class MockSignalConnection implements SignalConnection {
24
+ class MockSignalConnection implements SignalMessaging {
23
25
  constructor (
24
26
  readonly _swarm: () => Swarm,
25
27
  readonly _delay = 10
26
28
  ) {}
27
29
 
28
- lookup (topic: PublicKey) {}
29
-
30
- async offer (msg: SignalApi.SignalMessage) {
30
+ async offer (msg: Message) {
31
31
  await sleep(this._delay);
32
32
  return this._swarm().onOffer(msg);
33
33
  }
34
34
 
35
- async signal (msg: SignalApi.SignalMessage) {
35
+ async signal (msg: Message) {
36
36
  await sleep(this._delay);
37
37
  await this._swarm().onSignal(msg);
38
38
  }
39
39
  }
40
40
 
41
- const setup = () => {
41
+ const setup = ({ router = false } = {}) => {
42
42
  const topic = PublicKey.random();
43
43
  const peerId1 = PublicKey.random();
44
44
  const peerId2 = PublicKey.random();
45
+ // eslint-disable-next-line prefer-const
46
+ let swarm1: Swarm;
47
+ // eslint-disable-next-line prefer-const
48
+ let swarm2: Swarm;
49
+
50
+ const mr1: MessageRouter = new MessageRouter({
51
+ sendMessage: msg => mr2.receiveMessage(msg),
52
+ onSignal: msg => swarm1.onSignal(msg),
53
+ onOffer: msg => swarm1.onOffer(msg)
54
+ });
55
+
56
+ const mr2: MessageRouter = new MessageRouter({
57
+ sendMessage: msg => mr1.receiveMessage(msg),
58
+ onSignal: msg => swarm2.onSignal(msg),
59
+ onOffer: msg => swarm2.onOffer(msg)
60
+ });
45
61
 
46
- const swarm1: Swarm = new Swarm(
62
+ const sm1: SignalMessaging = router ? mr1 : new MockSignalConnection(() => swarm2);
63
+
64
+ const sm2: SignalMessaging = router ? mr2 : new MockSignalConnection(() => swarm1);
65
+
66
+ swarm1 = new Swarm(
47
67
  topic,
48
68
  peerId1,
49
69
  new FullyConnectedTopology(),
50
70
  () => new Protocol(),
51
- new MockSignalConnection(() => swarm2),
71
+ sm1,
72
+ () => {},
52
73
  createWebRTCTransportFactory(),
53
74
  undefined
54
75
  );
55
76
 
56
- const swarm2: Swarm = new Swarm(
77
+ swarm2 = new Swarm(
57
78
  topic,
58
79
  peerId2,
59
80
  new FullyConnectedTopology(),
60
81
  () => new Protocol(),
61
- new MockSignalConnection(() => swarm1),
82
+ sm2,
83
+ () => {},
62
84
  createWebRTCTransportFactory(),
63
85
  undefined
64
86
  );
@@ -141,3 +163,31 @@ test('second peer discovered after delay', async () => {
141
163
  expect(onData).toHaveBeenCalledWith([data]);
142
164
  });
143
165
  }).timeout(5_000);
166
+
167
+ test('swarming with message router', async () => {
168
+ const { swarm1, swarm2, peerId2 } = setup({ router: true });
169
+
170
+ const promise = Promise.all([
171
+ promiseTimeout(swarm1.connected.waitForCount(1), 3000, new Error('Swarm1 connect timeout.')),
172
+ promiseTimeout(swarm2.connected.waitForCount(1), 3000, new Error('Swarm2 connect timeout.'))
173
+ ]);
174
+
175
+ swarm1.onPeerCandidatesChanged([peerId2]);
176
+
177
+ log('Candidates changed');
178
+ await promise;
179
+ log('Swarms connected');
180
+
181
+ const swarm1Connection = swarm1.connections[0];
182
+ const swarm2Connection = swarm2.connections[0];
183
+ const onData = mockFn<(data: Buffer) => void>().returns(undefined);
184
+ (swarm2Connection.transport as WebRTCTransport).peer!.on('data', onData);
185
+
186
+ const data = Buffer.from('1234');
187
+ (swarm1Connection.transport as WebRTCTransport).peer!.send(data);
188
+ await waitForExpect(() => {
189
+ expect(onData).toHaveBeenCalledWith([data]);
190
+ });
191
+ await swarm1.destroy();
192
+ await swarm2.destroy();
193
+ }).timeout(5_000);
@@ -12,7 +12,8 @@ import { PublicKey } from '@dxos/protocols';
12
12
  import { ComplexMap, ComplexSet } from '@dxos/util';
13
13
 
14
14
  import { ProtocolProvider } from '../network-manager';
15
- import { SignalApi, SignalConnection } from '../signal';
15
+ import { Answer, Message } from '../proto/gen/dxos/mesh/signal';
16
+ import { SignalMessaging } from '../signal';
16
17
  import { SwarmController, Topology } from '../topology';
17
18
  import { TransportFactory } from '../transport';
18
19
  import { Topic } from '../types';
@@ -62,7 +63,8 @@ export class Swarm {
62
63
  private readonly _ownPeerId: PublicKey,
63
64
  private _topology: Topology,
64
65
  private readonly _protocolProvider: ProtocolProvider,
65
- private readonly _signalConnection: SignalConnection,
66
+ private readonly _signalMessaging: SignalMessaging,
67
+ private readonly _lookupPeers: (topic: PublicKey) => void,
66
68
  private readonly _transportFactory: TransportFactory,
67
69
  private readonly _label: string | undefined
68
70
  ) {
@@ -98,12 +100,13 @@ export class Swarm {
98
100
  this._topology.update();
99
101
  }
100
102
 
101
- async onOffer (message: SignalApi.SignalMessage): Promise<SignalApi.Answer> {
103
+ async onOffer (message: Message): Promise<Answer> {
102
104
  log(`Offer from ${message.id} topic=${this._topic}`);
103
105
  // Id of the peer offering us the connection.
106
+ assert(message.id);
104
107
  const remoteId = message.id;
105
- assert(message.remoteId.equals(this._ownPeerId));
106
- assert(message.topic.equals(this._topic));
108
+ assert(message.remoteId?.equals(this._ownPeerId));
109
+ assert(message.topic?.equals(this._topic));
107
110
 
108
111
  // Check if we are already trying to connect to that peer.
109
112
  if (this._connections.has(remoteId)) {
@@ -123,6 +126,7 @@ export class Swarm {
123
126
  let accept = false;
124
127
  if (await this._topology.onOffer(remoteId)) {
125
128
  if (!this._connections.has(remoteId)) { // Connection might have been already established.
129
+ assert(message.sessionId);
126
130
  const connection = this._createConnection(false, message.id, message.sessionId);
127
131
  try {
128
132
  connection.connect();
@@ -136,10 +140,11 @@ export class Swarm {
136
140
  return { accept };
137
141
  }
138
142
 
139
- async onSignal (message: SignalApi.SignalMessage): Promise<void> {
143
+ async onSignal (message: Message): Promise<void> {
140
144
  log(`Signal ${this._topic} ${JSON.stringify(message)}`);
141
- assert(message.remoteId.equals(this._ownPeerId), `Invalid signal peer id expected=${this.ownPeerId}, actual=${message.remoteId}`);
142
- assert(message.topic.equals(this._topic));
145
+ assert(message.remoteId?.equals(this._ownPeerId), `Invalid signal peer id expected=${this.ownPeerId}, actual=${message.remoteId}`);
146
+ assert(message.topic?.equals(this._topic));
147
+ assert(message.id);
143
148
  const connection = this._connections.get(message.id);
144
149
  if (!connection) {
145
150
  log(`Dropping signal message for non-existent connection: topic=${this._topic}, peerId=${message.id}`);
@@ -183,7 +188,7 @@ export class Swarm {
183
188
  this._topology.update();
184
189
  },
185
190
  lookup: () => {
186
- this._signalConnection.lookup(this._topic);
191
+ this._lookupPeers(this._topic);
187
192
  }
188
193
  };
189
194
  }
@@ -197,12 +202,12 @@ export class Swarm {
197
202
  const sessionId = PublicKey.random();
198
203
 
199
204
  const connection = this._createConnection(true, remoteId, sessionId);
200
- this._signalConnection.offer({
205
+ this._signalMessaging.offer({
201
206
  id: this._ownPeerId,
202
207
  remoteId,
203
208
  sessionId,
204
209
  topic: this._topic,
205
- data: {}
210
+ data: { offer: {} }
206
211
  })
207
212
  .then(answer => {
208
213
  log(`Received answer: ${JSON.stringify(answer)} topic=${this._topic} ownId=${this._ownPeerId} remoteId=${remoteId}`);
@@ -240,7 +245,7 @@ export class Swarm {
240
245
  remoteId,
241
246
  sessionId,
242
247
  initiator,
243
- (msg: SignalApi.SignalMessage) => this._signalConnection.signal(msg),
248
+ (msg: Message) => this._signalMessaging.signal(msg),
244
249
  this._protocolProvider({ channel: discoveryKey(this._topic), initiator }),
245
250
  this._transportFactory
246
251
  );
@@ -11,7 +11,7 @@ import { ErrorStream } from '@dxos/debug';
11
11
  import { PublicKey } from '@dxos/protocols';
12
12
  import { ComplexMap } from '@dxos/util';
13
13
 
14
- import { SignalApi } from '../signal';
14
+ import { Message } from '../proto/gen/dxos/mesh/signal';
15
15
  import { Transport, TransportFactory } from './transport';
16
16
 
17
17
  const log = debug('dxos:network-manager:swarm:transport:in-memory-transport');
@@ -79,7 +79,7 @@ export class InMemoryTransport implements Transport {
79
79
  return this._sessionId;
80
80
  }
81
81
 
82
- async signal (msg: SignalApi.SignalMessage) {
82
+ async signal (msg: Message) {
83
83
  // No-op.
84
84
  }
85
85
 
@@ -6,7 +6,7 @@ import { Event } from '@dxos/async';
6
6
  import { ErrorStream } from '@dxos/debug';
7
7
  import { PublicKey } from '@dxos/protocols';
8
8
 
9
- import { SignalApi } from '../signal';
9
+ import { Message } from '../proto/gen/dxos/mesh/signal';
10
10
 
11
11
  /**
12
12
  * Abstraction over a P2P connection transport. Currently either WebRTC or in-memory.
@@ -15,7 +15,7 @@ export interface Transport {
15
15
  closed: Event
16
16
  connected: Event
17
17
  errors: ErrorStream
18
- signal (msg: SignalApi.SignalMessage): Promise<void> // TODO(burdon): Remove async?
18
+ signal (msg: Message): Promise<void> // TODO(burdon): Remove async?
19
19
  close (): Promise<void>
20
20
  }
21
21
 
@@ -38,7 +38,7 @@ export interface TransportOptions {
38
38
  /**
39
39
  * Send a signal message to remote peer.
40
40
  */
41
- sendSignal: (msg: SignalApi.SignalMessage) => Promise<void> // TODO(burdon): Remove async?
41
+ sendSignal: (msg: Message) => Promise<void> // TODO(burdon): Remove async?
42
42
  }
43
43
 
44
44
  export type TransportFactory = (options: TransportOptions) => Transport
@@ -11,7 +11,7 @@ import { Event } from '@dxos/async';
11
11
  import { ErrorStream } from '@dxos/debug';
12
12
  import { PublicKey } from '@dxos/protocols';
13
13
 
14
- import { SignalApi } from '../signal';
14
+ import { Message } from '../proto/gen/dxos/mesh/signal';
15
15
  import { Transport, TransportFactory } from './transport';
16
16
 
17
17
  const log = debug('dxos:network-manager:swarm:transport:webrtc');
@@ -35,7 +35,7 @@ export class WebRTCTransport implements Transport {
35
35
  private readonly _remoteId: PublicKey,
36
36
  private readonly _sessionId: PublicKey,
37
37
  private readonly _topic: PublicKey,
38
- private readonly _sendSignal: (msg: SignalApi.SignalMessage) => void,
38
+ private readonly _sendSignal: (msg: Message) => void,
39
39
  private readonly _webrtcConfig?: any
40
40
  ) {
41
41
  log(`Created WebRTC connection ${this._ownId} -> ${this._remoteId} initiator=${this._initiator}`);
@@ -53,7 +53,7 @@ export class WebRTCTransport implements Transport {
53
53
  remoteId: this._remoteId,
54
54
  sessionId: this._sessionId,
55
55
  topic: this._topic,
56
- data
56
+ data: { signal: { json: JSON.stringify(data) } }
57
57
  });
58
58
  } catch (err: any) {
59
59
  this.errors.raise(err);
@@ -89,9 +89,10 @@ export class WebRTCTransport implements Transport {
89
89
  return this._peer;
90
90
  }
91
91
 
92
- async signal (msg: SignalApi.SignalMessage) {
92
+ async signal (msg: Message) {
93
93
  assert(this._peer, 'Connection not ready to accept signals.');
94
- this._peer.signal(msg.data);
94
+ assert(msg.data?.signal?.json, 'Signal message must contain signal data.');
95
+ this._peer.signal(JSON.parse(msg.data.signal.json));
95
96
  }
96
97
 
97
98
  async close () {