@dxos/network-manager 2.33.9-dev.7d11f506 → 2.33.9-dev.9bbef4e2

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 (158) hide show
  1. package/dist/src/network-manager.blueprint-test.d.ts +3 -1
  2. package/dist/src/network-manager.blueprint-test.d.ts.map +1 -1
  3. package/dist/src/network-manager.blueprint-test.js +46 -17
  4. package/dist/src/network-manager.blueprint-test.js.map +1 -1
  5. package/dist/src/network-manager.browser-test.js +1 -1
  6. package/dist/src/network-manager.browser-test.js.map +1 -1
  7. package/dist/src/network-manager.d.ts.map +1 -1
  8. package/dist/src/network-manager.js +13 -12
  9. package/dist/src/network-manager.js.map +1 -1
  10. package/dist/src/network-manager.test.js +5 -4
  11. package/dist/src/network-manager.test.js.map +1 -1
  12. package/dist/src/proto/gen/dxos/credentials.d.ts +39 -0
  13. package/dist/src/proto/gen/dxos/credentials.d.ts.map +1 -1
  14. package/dist/src/proto/gen/dxos/halo/keys.d.ts +44 -2
  15. package/dist/src/proto/gen/dxos/halo/keys.d.ts.map +1 -1
  16. package/dist/src/proto/gen/dxos/halo/keys.js +4 -0
  17. package/dist/src/proto/gen/dxos/halo/keys.js.map +1 -1
  18. package/dist/src/proto/gen/dxos/mesh/signal.d.ts +74 -16
  19. package/dist/src/proto/gen/dxos/mesh/signal.d.ts.map +1 -1
  20. package/dist/src/proto/gen/dxos/mesh/signalMessage.d.ts +79 -0
  21. package/dist/src/proto/gen/dxos/mesh/signalMessage.d.ts.map +1 -0
  22. package/dist/src/proto/gen/dxos/mesh/signalMessage.js +3 -0
  23. package/dist/src/proto/gen/dxos/mesh/signalMessage.js.map +1 -0
  24. package/dist/src/proto/gen/google/protobuf.d.ts +8 -2
  25. package/dist/src/proto/gen/google/protobuf.d.ts.map +1 -1
  26. package/dist/src/proto/gen/index.d.ts +17 -4
  27. package/dist/src/proto/gen/index.d.ts.map +1 -1
  28. package/dist/src/proto/gen/index.js +1 -1
  29. package/dist/src/proto/gen/index.js.map +1 -1
  30. package/dist/src/proto/substitutions.d.ts +4 -0
  31. package/dist/src/proto/substitutions.d.ts.map +1 -1
  32. package/dist/src/proto/substitutions.js +3 -1
  33. package/dist/src/proto/substitutions.js.map +1 -1
  34. package/dist/src/protocol-factory.js +3 -3
  35. package/dist/src/protocol-factory.js.map +1 -1
  36. package/dist/src/signal/in-memory-signal-manager.d.ts +7 -7
  37. package/dist/src/signal/in-memory-signal-manager.d.ts.map +1 -1
  38. package/dist/src/signal/in-memory-signal-manager.js +34 -13
  39. package/dist/src/signal/in-memory-signal-manager.js.map +1 -1
  40. package/dist/src/signal/index.d.ts +1 -2
  41. package/dist/src/signal/index.d.ts.map +1 -1
  42. package/dist/src/signal/index.js +1 -2
  43. package/dist/src/signal/index.js.map +1 -1
  44. package/dist/src/signal/integration.test.d.ts +2 -0
  45. package/dist/src/signal/integration.test.d.ts.map +1 -0
  46. package/dist/src/signal/integration.test.js +102 -0
  47. package/dist/src/signal/integration.test.js.map +1 -0
  48. package/dist/src/signal/message-router.d.ts +20 -8
  49. package/dist/src/signal/message-router.d.ts.map +1 -1
  50. package/dist/src/signal/message-router.js +96 -17
  51. package/dist/src/signal/message-router.js.map +1 -1
  52. package/dist/src/signal/message-router.test.js +125 -22
  53. package/dist/src/signal/message-router.test.js.map +1 -1
  54. package/dist/src/signal/signal-client.d.ts +33 -17
  55. package/dist/src/signal/signal-client.d.ts.map +1 -1
  56. package/dist/src/signal/signal-client.js +102 -82
  57. package/dist/src/signal/signal-client.js.map +1 -1
  58. package/dist/src/signal/signal-client.test.js +60 -75
  59. package/dist/src/signal/signal-client.test.js.map +1 -1
  60. package/dist/src/signal/{websocket-signal-manager.d.ts → signal-manager-impl.d.ts} +13 -11
  61. package/dist/src/signal/signal-manager-impl.d.ts.map +1 -0
  62. package/dist/src/signal/signal-manager-impl.js +151 -0
  63. package/dist/src/signal/signal-manager-impl.js.map +1 -0
  64. package/dist/src/signal/signal-manager.d.ts +12 -11
  65. package/dist/src/signal/signal-manager.d.ts.map +1 -1
  66. package/dist/src/signal/signal-rpc-client.d.ts +19 -0
  67. package/dist/src/signal/signal-rpc-client.d.ts.map +1 -0
  68. package/dist/src/signal/signal-rpc-client.js +108 -0
  69. package/dist/src/signal/signal-rpc-client.js.map +1 -0
  70. package/dist/src/signal/signal-rpc-client.test.d.ts +2 -0
  71. package/dist/src/signal/signal-rpc-client.test.d.ts.map +1 -0
  72. package/dist/src/signal/signal-rpc-client.test.js +74 -0
  73. package/dist/src/signal/signal-rpc-client.test.js.map +1 -0
  74. package/dist/src/swarm/connection.d.ts +3 -3
  75. package/dist/src/swarm/connection.d.ts.map +1 -1
  76. package/dist/src/swarm/connection.js +8 -11
  77. package/dist/src/swarm/connection.js.map +1 -1
  78. package/dist/src/swarm/swarm.d.ts +6 -7
  79. package/dist/src/swarm/swarm.d.ts.map +1 -1
  80. package/dist/src/swarm/swarm.js +29 -25
  81. package/dist/src/swarm/swarm.js.map +1 -1
  82. package/dist/src/swarm/swarm.test.js +156 -115
  83. package/dist/src/swarm/swarm.test.js.map +1 -1
  84. package/dist/src/testing/test-protocol.d.ts.map +1 -1
  85. package/dist/src/testing/test-protocol.js +3 -3
  86. package/dist/src/testing/test-protocol.js.map +1 -1
  87. package/dist/src/topology/fully-connected-topology.d.ts +0 -1
  88. package/dist/src/topology/fully-connected-topology.d.ts.map +1 -1
  89. package/dist/src/topology/fully-connected-topology.js +4 -9
  90. package/dist/src/topology/fully-connected-topology.js.map +1 -1
  91. package/dist/src/topology/mmst-topology.d.ts +0 -1
  92. package/dist/src/topology/mmst-topology.d.ts.map +1 -1
  93. package/dist/src/topology/mmst-topology.js +6 -11
  94. package/dist/src/topology/mmst-topology.js.map +1 -1
  95. package/dist/src/topology/star-topology.d.ts +0 -1
  96. package/dist/src/topology/star-topology.d.ts.map +1 -1
  97. package/dist/src/topology/star-topology.js +5 -10
  98. package/dist/src/topology/star-topology.js.map +1 -1
  99. package/dist/src/topology/topology.d.ts +0 -6
  100. package/dist/src/topology/topology.d.ts.map +1 -1
  101. package/dist/src/transport/in-memory-transport.d.ts +2 -2
  102. package/dist/src/transport/in-memory-transport.d.ts.map +1 -1
  103. package/dist/src/transport/in-memory-transport.js +2 -2
  104. package/dist/src/transport/in-memory-transport.js.map +1 -1
  105. package/dist/src/transport/transport.d.ts +3 -3
  106. package/dist/src/transport/transport.d.ts.map +1 -1
  107. package/dist/src/transport/webrtc-transport.d.ts +3 -3
  108. package/dist/src/transport/webrtc-transport.d.ts.map +1 -1
  109. package/dist/src/transport/webrtc-transport.js +3 -3
  110. package/dist/src/transport/webrtc-transport.js.map +1 -1
  111. package/dist/tests-setup.js +1 -1
  112. package/dist/tsconfig.tsbuildinfo +1 -1
  113. package/package.json +17 -12
  114. package/src/network-manager.blueprint-test.ts +57 -22
  115. package/src/network-manager.browser-test.ts +1 -1
  116. package/src/network-manager.test.ts +8 -7
  117. package/src/network-manager.ts +10 -10
  118. package/src/proto/defs/dxos/mesh/signal.proto +54 -23
  119. package/src/proto/defs/dxos/mesh/signalMessage.proto +51 -0
  120. package/src/proto/gen/dxos/credentials.ts +40 -0
  121. package/src/proto/gen/dxos/halo/keys.ts +45 -2
  122. package/src/proto/gen/dxos/mesh/signal.ts +73 -16
  123. package/src/proto/gen/dxos/mesh/signalMessage.ts +83 -0
  124. package/src/proto/gen/google/protobuf.ts +9 -2
  125. package/src/proto/gen/index.ts +18 -5
  126. package/src/proto/substitutions.ts +3 -1
  127. package/src/protocol-factory.ts +1 -1
  128. package/src/signal/in-memory-signal-manager.ts +38 -13
  129. package/src/signal/index.ts +1 -2
  130. package/src/signal/integration.test.ts +117 -0
  131. package/src/signal/message-router.test.ts +169 -58
  132. package/src/signal/message-router.ts +120 -27
  133. package/src/signal/signal-client.test.ts +70 -90
  134. package/src/signal/signal-client.ts +120 -87
  135. package/src/signal/signal-manager-impl.ts +166 -0
  136. package/src/signal/signal-manager.ts +12 -12
  137. package/src/signal/signal-rpc-client.test.ts +86 -0
  138. package/src/signal/signal-rpc-client.ts +121 -0
  139. package/src/swarm/connection.ts +6 -9
  140. package/src/swarm/swarm.test.ts +208 -167
  141. package/src/swarm/swarm.ts +26 -22
  142. package/src/testing/test-protocol.ts +1 -1
  143. package/src/topology/fully-connected-topology.ts +2 -10
  144. package/src/topology/mmst-topology.ts +2 -10
  145. package/src/topology/star-topology.ts +2 -8
  146. package/src/topology/topology.ts +0 -7
  147. package/src/transport/in-memory-transport.ts +3 -3
  148. package/src/transport/transport.ts +3 -3
  149. package/src/transport/webrtc-transport.ts +4 -4
  150. package/dist/src/signal/websocket-rpc.d.ts +0 -30
  151. package/dist/src/signal/websocket-rpc.d.ts.map +0 -1
  152. package/dist/src/signal/websocket-rpc.js +0 -203
  153. package/dist/src/signal/websocket-rpc.js.map +0 -1
  154. package/dist/src/signal/websocket-signal-manager.d.ts.map +0 -1
  155. package/dist/src/signal/websocket-signal-manager.js +0 -134
  156. package/dist/src/signal/websocket-signal-manager.js.map +0 -1
  157. package/src/signal/websocket-rpc.ts +0 -208
  158. package/src/signal/websocket-signal-manager.ts +0 -158
@@ -6,164 +6,144 @@ import { expect, mockFn } from 'earljs';
6
6
  import { it as test, describe } from 'mocha';
7
7
  import waitForExpect from 'wait-for-expect';
8
8
 
9
- import { Awaited, sleep } from '@dxos/async';
9
+ import { sleep } from '@dxos/async';
10
10
  import { PublicKey } from '@dxos/protocols';
11
- import { createTestBroker } from '@dxos/signal';
12
- import { randomInt } from '@dxos/util';
11
+ import { createTestBroker, TestBroker } from '@dxos/signal';
12
+ import { afterTest } from '@dxos/testutils';
13
13
 
14
- import { Answer, Message } from '../proto/gen/dxos/mesh/signal';
14
+ import { SignalMessage } from '../proto/gen/dxos/mesh/signalMessage';
15
15
  import { SignalClient } from './signal-client';
16
16
 
17
- describe('SignalApi', () => {
18
- let topic: PublicKey;
19
- let peer1: PublicKey;
20
- let peer2: PublicKey;
21
- let api1: SignalClient;
22
- let api2: SignalClient;
17
+ describe('SignalClient', () => {
18
+ let broker1: TestBroker;
23
19
 
24
- let broker1: Awaited<ReturnType<typeof createTestBroker>>;
25
- const signalApiPort1 = randomInt(10000, 50000);
26
- const signalApiUrl1 = 'http://0.0.0.0:' + signalApiPort1;
27
-
28
- // code let broker2: ReturnType<typeof createBroker>;
29
- const signalApiPort2 = randomInt(10000, 50000);
30
- const signalApiUrl2 = 'http://0.0.0.0:' + signalApiPort2;
20
+ let broker2: TestBroker;
31
21
 
32
22
  before(async () => {
33
- broker1 = await createTestBroker(signalApiPort1);
34
- // broker2 = await createTestBroker(signalApiPort2);
35
- });
36
-
37
- beforeEach(() => {
38
- topic = PublicKey.random();
39
- peer1 = PublicKey.random();
40
- peer2 = PublicKey.random();
23
+ broker1 = await createTestBroker();
24
+ // broker2 = await await createTestBroker(signalApiPort2);
41
25
  });
42
26
 
43
- after(async function () {
44
- this.timeout(0);
45
- await api1.close();
46
- await broker1.stop();
27
+ after(() => {
28
+ broker1.stop();
47
29
  // code await broker2.stop();
48
30
  });
49
31
 
50
32
  test('message between 2 clients', async () => {
51
- const signalMock1 = mockFn<(msg: Message) => Promise<void>>()
33
+ const topic = PublicKey.random();
34
+ const peer1 = PublicKey.random();
35
+ const peer2 = PublicKey.random();
36
+ const signalMock1 = mockFn<(msg: SignalMessage) => Promise<void>>()
52
37
  .resolvesTo();
53
- api1 = new SignalClient(signalApiUrl1, (async () => {}) as any, signalMock1);
54
- api2 = new SignalClient(signalApiUrl1, (async () => {}) as any, (async () => {}) as any);
38
+ const api1 = new SignalClient(broker1.url(), signalMock1);
39
+ afterTest(() => api1.close());
40
+ const api2 = new SignalClient(broker1.url(), (async () => {}) as any);
41
+ afterTest(() => api2.close());
55
42
 
56
43
  await api1.join(topic, peer1);
57
44
  await api2.join(topic, peer2);
58
45
 
59
- const msg: Message = {
46
+ const msg: SignalMessage = {
60
47
  id: peer2,
61
48
  remoteId: peer1,
62
49
  sessionId: PublicKey.random(),
63
50
  topic,
64
- data: { signal: { json: "foo: 'bar'" } }
51
+ data: { signal: { json: JSON.stringify({ 'asd': 'asd' }) } }
65
52
  };
66
53
  await api2.signal(msg);
67
-
68
54
  await waitForExpect(() => {
69
55
  expect(signalMock1).toHaveBeenCalledWith([msg]);
70
56
  }, 4_000);
71
- }).timeout(5_000);
57
+ }).timeout(500);
72
58
 
73
59
  test('join', async () => {
74
- api1 = new SignalClient(signalApiUrl1, (async () => {}) as any, async () => {});
60
+ const topic = PublicKey.random();
61
+ const peer1 = PublicKey.random();
62
+ const peer2 = PublicKey.random();
63
+ const api1 = new SignalClient(broker1.url(), async () => {});
64
+ afterTest(() => api1.close());
65
+ const api2 = new SignalClient(broker1.url(), async () => {});
66
+ afterTest(() => api2.close());
75
67
 
76
- const join = await api1.join(topic, peer1);
77
- expect(join).toEqual([peer1]);
78
-
79
- const join2 = await api1.join(topic, peer2);
80
- expect(join2).toEqual([peer1, peer2]);
81
- }).timeout(1_000);
82
-
83
- test('offer', async () => {
84
- const offerMock = mockFn<(msg: Message) => Promise<Answer>>()
85
- .resolvesTo({ accept: true });
86
- api1 = new SignalClient(signalApiUrl1, offerMock, async () => {});
68
+ const promise1 = api1.swarmEvent.waitFor(([, swarmEvent]) => !!swarmEvent.peerAvailable && peer2.equals(swarmEvent.peerAvailable.peer));
69
+ const promise2 = api2.swarmEvent.waitFor(([, swarmEvent]) => !!swarmEvent.peerAvailable && peer1.equals(swarmEvent.peerAvailable.peer));
87
70
 
88
71
  await api1.join(topic, peer1);
72
+ await api2.join(topic, peer2);
89
73
 
90
- const offer: Message = {
91
- data: { offer: {} },
92
- id: peer2,
93
- remoteId: peer1,
94
- sessionId: PublicKey.random(),
95
- topic
96
- };
97
- const offerResult = await api1.offer(offer);
98
- expect(offerResult).toEqual({ accept: true });
99
- expect(offerMock).toHaveBeenCalledWith([offer]);
100
- }).timeout(5_000);
74
+ await promise1;
75
+ await promise2;
76
+ }).timeout(500);
101
77
 
102
- test('signal', async () => {
103
- const signalMock = mockFn<(msg: Message) => Promise<void>>()
78
+ test('signal to self', async () => {
79
+ const topic = PublicKey.random();
80
+ const peer1 = PublicKey.random();
81
+ const peer2 = PublicKey.random();
82
+ const signalMock = mockFn<(msg: SignalMessage) => Promise<void>>()
104
83
  .resolvesTo();
105
- api1 = new SignalClient(signalApiUrl1, (async () => {}) as any, signalMock);
84
+ const api1 = new SignalClient(broker1.url(), signalMock);
85
+ afterTest(() => api1.close());
106
86
 
107
87
  await api1.join(topic, peer1);
108
88
 
109
- const msg: Message = {
89
+ const msg: SignalMessage = {
110
90
  id: peer2,
111
91
  remoteId: peer1,
112
92
  sessionId: PublicKey.random(),
113
93
  topic,
114
- data: { signal: { json: 'bar' } }
94
+ data: { signal: { json: JSON.stringify({ 'asd': 'asd' }) } }
115
95
  };
116
96
  await api1.signal(msg);
117
97
 
118
98
  await waitForExpect(() => {
119
99
  expect(signalMock).toHaveBeenCalledWith([msg]);
120
100
  }, 4_000);
121
- }).timeout(5_000);
101
+ }).timeout(500);
122
102
 
123
103
  test.skip('join across multiple signal servers', async () => {
104
+ const topic = PublicKey.random();
105
+ const peer1 = PublicKey.random();
106
+ const peer2 = PublicKey.random();
124
107
  // This feature is not implemented yet.
125
- api1 = new SignalClient(signalApiUrl1, (async () => {}) as any, async () => {});
126
- api2 = new SignalClient(signalApiUrl2, (async () => {}) as any, async () => {});
108
+ const api1 = new SignalClient(broker1.url(), async () => {});
109
+ afterTest(() => api1.close());
110
+ const api2 = new SignalClient(broker2.url(), async () => {});
111
+ afterTest(() => api2.close());
127
112
 
128
113
  await api1.join(topic, peer1);
129
114
  await api2.join(topic, peer2);
130
115
 
131
- await waitForExpect(async () => {
132
- const peers = await api2.lookup(topic);
133
- expect(peers.length).toEqual(2);
134
- }, 4_000);
116
+ // await waitForExpect(async () => {
117
+ // const peers = await api2.lookup(topic);
118
+ // expect(peers.length).toEqual(2);
119
+ // }, 4_000);
135
120
 
136
- await waitForExpect(async () => {
137
- const peers = await api1.lookup(topic);
138
- expect(peers.length).toEqual(2);
139
- }, 4_000);
121
+ // await waitForExpect(async () => {
122
+ // const peers = await api1.lookup(topic);
123
+ // expect(peers.length).toEqual(2);
124
+ // }, 4_000);
140
125
  }).timeout(5_000);
141
126
 
142
127
  // Skip because communication between signal servers is not yet implemented.
143
128
  test.skip('newly joined peer can receive signals from other signal servers', async () => {
144
- const offerMock = mockFn<(msg: Message) => Promise<Answer>>()
145
- .resolvesTo({ accept: true });
146
- const signalMock = mockFn<(msg: Message) => Promise<void>>()
129
+ const topic = PublicKey.random();
130
+ const peer1 = PublicKey.random();
131
+ const peer2 = PublicKey.random();
132
+ const signalMock = mockFn<(msg: SignalMessage) => Promise<void>>()
147
133
  .resolvesTo();
148
134
 
149
- api1 = new SignalClient(signalApiUrl1, offerMock, async () => {});
150
- api2 = new SignalClient(signalApiUrl2, (async () => {}) as any, signalMock);
135
+ const api1 = new SignalClient(broker1.url(), async () => {});
136
+ afterTest(() => api1.close());
137
+ const api2 = new SignalClient(broker2.url(), signalMock);
138
+ afterTest(() => api2.close());
151
139
 
152
140
  await api1.join(topic, peer1);
153
141
  await sleep(3000);
154
142
  await api2.join(topic, peer2);
155
143
 
156
144
  const sessionId = PublicKey.random();
157
- const answer = await api2.offer({
158
- remoteId: peer1,
159
- id: peer2,
160
- topic,
161
- sessionId,
162
- data: { offer: {} }
163
- });
164
- expect(answer).toEqual({ accept: true });
165
145
 
166
- const msg: Message = {
146
+ const msg: SignalMessage = {
167
147
  id: peer2,
168
148
  remoteId: peer1,
169
149
  sessionId,
@@ -176,4 +156,4 @@ describe('SignalApi', () => {
176
156
  expect(signalMock).toHaveBeenCalledWith([msg]);
177
157
  }, 4_000);
178
158
  }).timeout(5_000);
179
- }).timeout(10_000);
159
+ });
@@ -2,24 +2,55 @@
2
2
  // Copyright 2020 DXOS.org
3
3
  //
4
4
 
5
+ import assert from 'assert';
5
6
  import debug from 'debug';
6
7
 
7
- import { Event } from '@dxos/async';
8
+ import { Event, synchronized } from '@dxos/async';
9
+ import { Any, Stream } from '@dxos/codec-protobuf';
8
10
  import { PublicKey } from '@dxos/protocols';
11
+ import { ComplexMap, SubscriptionGroup } from '@dxos/util';
9
12
 
10
- import { Answer, Message } from '../proto/gen/dxos/mesh/signal';
13
+ import { schema } from '../proto/gen';
14
+ import { Message, SwarmEvent } from '../proto/gen/dxos/mesh/signal';
15
+ import { SignalMessage } from '../proto/gen/dxos/mesh/signalMessage';
11
16
  import { SignalApi } from './signal-api';
12
- import { WebsocketRpc } from './websocket-rpc';
17
+ import { SignalRPCClient } from './signal-rpc-client';
13
18
 
14
19
  const log = debug('dxos:network-manager:signal-client');
15
20
 
16
21
  const DEFAULT_RECONNECT_TIMEOUT = 1000;
17
22
 
23
+ enum State {
24
+ /** Connection is being established. */
25
+ CONNECTING = 'CONNECTING',
26
+
27
+ /** Connection is being re-established. */
28
+ RE_CONNECTING = 'RE_CONNECTING',
29
+
30
+ /** Connected. */
31
+ CONNECTED = 'CONNECTED',
32
+
33
+ /** Server terminated the connection. Socket will be reconnected. */
34
+ DISCONNECTED = 'DISCONNECTED',
35
+
36
+ /** Socket was closed. */
37
+ CLOSED = 'CLOSED'
38
+ }
39
+
40
+ export type Status = {
41
+ host: string
42
+ state: State
43
+ error?: string
44
+ reconnectIn: number
45
+ connectionStarted: number
46
+ lastStateChange: number
47
+ }
48
+
18
49
  /**
19
50
  * Establishes a websocket connection to signal server and provides RPC methods.
20
51
  */
21
52
  export class SignalClient {
22
- private _state = SignalApi.State.CONNECTING;
53
+ private _state = State.CONNECTING;
23
54
 
24
55
  private _lastError?: Error;
25
56
 
@@ -40,28 +71,29 @@ export class SignalClient {
40
71
 
41
72
  private _reconnectIntervalId?: NodeJS.Timeout;
42
73
 
43
- private _client!: WebsocketRpc;
74
+ private _client!: SignalRPCClient;
44
75
 
45
- private _clientCleanup: (() => void)[] = [];
76
+ private _cleanupSubscriptions = new SubscriptionGroup();
77
+ readonly statusChanged = new Event<Status>();
46
78
 
47
- readonly statusChanged = new Event<SignalApi.Status>();
48
79
  readonly commandTrace = new Event<SignalApi.CommandTrace>();
80
+ readonly swarmEvent = new Event<[topic: PublicKey, swarmEvent: SwarmEvent]>();
49
81
 
82
+ private readonly _swarmStreams = new ComplexMap<PublicKey, Stream<SwarmEvent>>(key => key.toHex());
83
+ private readonly _messageStreams = new ComplexMap<PublicKey, Stream<Message>>(key => key.toHex());
50
84
  /**
51
85
  * @param _host Signal server websocket URL.
52
- * @param _onOffer See `SignalApi.offer`.
53
86
  * @param _onSignal See `SignalApi.signal`.
54
87
  */
55
88
  constructor (
56
89
  private readonly _host: string,
57
- private readonly _onOffer: (message: Message) => Promise<Answer>,
58
- private readonly _onSignal: (message: Message) => Promise<void>
90
+ private readonly _onSignal: (message: SignalMessage) => Promise<void>
59
91
  ) {
60
- this._setState(SignalApi.State.CONNECTING);
92
+ this._setState(State.CONNECTING);
61
93
  this._createClient();
62
94
  }
63
95
 
64
- private _setState (newState: SignalApi.State) {
96
+ private _setState (newState: State) {
65
97
  this._state = newState;
66
98
  this._lastStateChange = Date.now();
67
99
  log(`Signal state changed ${JSON.stringify(this.getStatus())}`);
@@ -71,107 +103,88 @@ export class SignalClient {
71
103
  private _createClient () {
72
104
  this._connectionStarted = Date.now();
73
105
  try {
74
- this._client = new WebsocketRpc(this._host);
106
+ this._client = new SignalRPCClient(this._host);
75
107
  } catch (error: any) {
76
- if (this._state === SignalApi.State.RE_CONNECTING) {
108
+ if (this._state === State.RE_CONNECTING) {
77
109
  this._reconnectAfter *= 2;
78
110
  }
79
111
 
80
112
  this._lastError = error;
81
- this._setState(SignalApi.State.DISCONNECTED);
113
+ this._setState(State.DISCONNECTED);
82
114
  this._reconnect();
83
115
  }
84
116
 
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
-
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!),
98
- data: msg.data
99
- }));
100
-
101
- this._clientCleanup.push(this._client.connected.on(() => {
102
- log('Socket connected');
117
+ this._cleanupSubscriptions.push(this._client.connected.on(() => {
103
118
  this._lastError = undefined;
104
119
  this._reconnectAfter = DEFAULT_RECONNECT_TIMEOUT;
105
- this._setState(SignalApi.State.CONNECTED);
120
+ this._setState(State.CONNECTED);
106
121
  }));
107
122
 
108
- this._clientCleanup.push(this._client.error.on(error => {
123
+ this._cleanupSubscriptions.push(this._client.error.on(error => {
109
124
  log(`Socket error: ${error.message}`);
110
- if (this._state === SignalApi.State.CLOSED) {
125
+ if (this._state === State.CLOSED) {
111
126
  return;
112
127
  }
113
128
 
114
- if (this._state === SignalApi.State.RE_CONNECTING) {
129
+ if (this._state === State.RE_CONNECTING) {
115
130
  this._reconnectAfter *= 2;
116
131
  }
117
132
 
118
133
  this._lastError = error;
119
- this._setState(SignalApi.State.DISCONNECTED);
134
+ this._setState(State.DISCONNECTED);
120
135
 
121
136
  this._reconnect();
122
137
  }));
123
138
 
124
- this._clientCleanup.push(this._client.disconnected.on(() => {
139
+ this._cleanupSubscriptions.push(this._client.disconnected.on(() => {
125
140
  log('Socket disconnected');
126
141
  // This is also called in case of error, but we already have disconnected the socket on error, so no need to do anything here.
127
- if (this._state !== SignalApi.State.CONNECTING && this._state !== SignalApi.State.RE_CONNECTING) {
142
+ if (this._state !== State.CONNECTING && this._state !== State.RE_CONNECTING) {
128
143
  return;
129
144
  }
130
145
 
131
- if (this._state === SignalApi.State.RE_CONNECTING) {
146
+ if (this._state === State.RE_CONNECTING) {
132
147
  this._reconnectAfter *= 2;
133
148
  }
134
149
 
135
- this._setState(SignalApi.State.DISCONNECTED);
150
+ this._setState(State.DISCONNECTED);
136
151
  this._reconnect();
137
152
  }));
138
-
139
- this._clientCleanup.push(this._client.commandTrace.on(trace => this.commandTrace.emit(trace)));
140
153
  }
141
154
 
142
155
  private _reconnect () {
156
+ log(`Reconnecting in ${this._reconnectAfter}ms`);
143
157
  if (this._reconnectIntervalId !== undefined) {
144
158
  console.error('Signal api already reconnecting.');
145
159
  return;
146
160
  }
147
- if (this._state === SignalApi.State.CLOSED) {
161
+ if (this._state === State.CLOSED) {
148
162
  return;
149
163
  }
150
164
 
151
165
  this._reconnectIntervalId = setTimeout(() => {
152
166
  this._reconnectIntervalId = undefined;
153
167
 
154
- this._clientCleanup.forEach(cb => cb());
155
- this._clientCleanup = [];
168
+ this._cleanupSubscriptions.unsubscribe();
156
169
 
157
170
  // Close client if it wasn't already closed.
158
171
  this._client.close().catch(() => {});
159
172
 
160
- this._setState(SignalApi.State.RE_CONNECTING);
173
+ this._setState(State.RE_CONNECTING);
161
174
  this._createClient();
162
175
  }, this._reconnectAfter);
163
176
  }
164
177
 
165
178
  async close () {
166
- this._clientCleanup.forEach(cb => cb());
167
- this._clientCleanup = [];
179
+ this._cleanupSubscriptions.unsubscribe();
168
180
 
169
181
  if (this._reconnectIntervalId !== undefined) {
170
182
  clearTimeout(this._reconnectIntervalId);
171
183
  }
172
184
 
173
185
  await this._client.close();
174
- this._setState(SignalApi.State.CLOSED);
186
+ this._setState(State.CLOSED);
187
+ log('Closed.');
175
188
  }
176
189
 
177
190
  getStatus (): SignalApi.Status {
@@ -185,52 +198,72 @@ export class SignalClient {
185
198
  };
186
199
  }
187
200
 
188
- async join (topic: PublicKey, peerId: PublicKey): Promise<PublicKey[]> {
189
- const peers: Buffer[] = await this._client.call('join', {
190
- id: peerId.asBuffer(),
191
- topic: topic.asBuffer()
192
- });
193
- return peers.map(id => PublicKey.from(id));
201
+ async join (topic: PublicKey, peerId: PublicKey): Promise<void> {
202
+ log(`Join: topic=${topic} peerId=${peerId}`);
203
+ await this._subscribeMessages(peerId);
204
+ await this._subscribeSwarmEvents(topic, peerId);
194
205
  }
195
206
 
196
207
  async leave (topic: PublicKey, peerId: PublicKey): Promise<void> {
197
- await this._client.call('leave', {
198
- id: peerId.asBuffer(),
199
- topic: topic.asBuffer()
200
- });
208
+ log(`Leave: topic=${topic} peerId=${peerId}`);
209
+
210
+ this._swarmStreams.get(topic)?.close();
211
+ this._swarmStreams.delete(topic);
212
+
213
+ this._messageStreams.get(topic)?.close();
214
+ this._messageStreams.delete(topic);
201
215
  }
202
216
 
203
- async lookup (topic: PublicKey): Promise<PublicKey[]> {
204
- const peers: Buffer[] = await this._client.call('lookup', {
205
- topic: topic.asBuffer()
206
- });
207
- return peers.map(id => PublicKey.from(id));
217
+ async signal (message: SignalMessage): Promise<void> {
218
+ const payload: Any = {
219
+ type_url: 'dxos.mesh.signalMessage.SignalMessage',
220
+ value: schema.getCodecForType('dxos.mesh.signalMessage.SignalMessage').encode(message)
221
+ };
222
+ return this._client.sendMessage(message.id, message.remoteId, payload);
208
223
  }
209
224
 
210
- /**
211
- * Routes an offer to the other peer's _onOffer callback.
212
- * @returns Other peer's _onOffer callback return value.
213
- */
214
- async offer (msg: Message): Promise<Answer> {
215
- return this._client.call('offer', {
216
- id: msg.id?.asBuffer(),
217
- remoteId: msg.remoteId?.asBuffer(),
218
- topic: msg.topic?.asBuffer(),
219
- sessionId: msg.sessionId?.asBuffer(),
220
- data: msg.data
225
+ @synchronized
226
+ private async _subscribeSwarmEvents (topic: PublicKey, peerId: PublicKey): Promise<void> {
227
+ assert(!this._swarmStreams.has(topic));
228
+ const swarmStream = await this._client.join(topic, peerId);
229
+ // Subscribing to swarm events.
230
+ // TODO(mykola): What happens when the swarm stream is closed? Maybe send leave event for each peer?
231
+ swarmStream.subscribe((swarmEvent: SwarmEvent) => {
232
+ this.swarmEvent.emit([topic, swarmEvent]);
233
+ });
234
+
235
+ // Saving swarm stream.
236
+ this._swarmStreams.set(topic, swarmStream);
237
+
238
+ this._cleanupSubscriptions.push(() => {
239
+ swarmStream.close();
240
+ this._swarmStreams.delete(topic);
221
241
  });
222
242
  }
223
243
 
224
- /**
225
- * Routes an offer to the other peer's _onSignal callback.
226
- */
227
- async signal (payload: Message): Promise<void> {
228
- return this._client.emit('signal', {
229
- id: payload.id?.asBuffer(),
230
- remoteId: payload.remoteId?.asBuffer(),
231
- topic: payload.topic?.asBuffer(),
232
- sessionId: payload.sessionId?.asBuffer(),
233
- data: payload.data
244
+ private async _subscribeMessages (peerId: PublicKey) {
245
+ // Subscribing to messages.
246
+ const messageStream = await this._client.receiveMessages(peerId);
247
+ messageStream.subscribe(async (message: Message) => {
248
+ if (message.payload.type_url === 'dxos.mesh.signalMessage.SignalMessage') {
249
+ const signalMessage = schema.getCodecForType('dxos.mesh.signalMessage.SignalMessage').decode(message.payload.value);
250
+ log('Message received: ' + JSON.stringify(signalMessage));
251
+ assert(signalMessage.remoteId.equals(peerId));
252
+ await this._onSignal(signalMessage);
253
+ } else {
254
+ log('Unknown message type: ' + message.payload.type_url);
255
+ }
256
+ });
257
+
258
+ // Saving message stream.
259
+ if (!this._messageStreams.has(peerId)) {
260
+ this._messageStreams.set(peerId, messageStream);
261
+ }
262
+
263
+ this._cleanupSubscriptions.push(() => {
264
+ messageStream.close();
265
+ this._messageStreams.delete(peerId);
234
266
  });
235
267
  }
268
+
236
269
  }