@dxos/network-manager 0.6.12 → 0.6.13-main.09887cd

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 (163) hide show
  1. package/dist/lib/browser/chunk-GW3YM55A.mjs +14 -0
  2. package/dist/lib/browser/chunk-GW3YM55A.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-XYSYUN63.mjs → chunk-MKIVP7G3.mjs} +1249 -1065
  4. package/dist/lib/browser/chunk-MKIVP7G3.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +10 -19
  6. package/dist/lib/browser/meta.json +1 -1
  7. package/dist/lib/browser/testing/index.mjs +22 -32
  8. package/dist/lib/browser/testing/index.mjs.map +3 -3
  9. package/dist/lib/browser/transport/tcp/index.mjs +39 -0
  10. package/dist/lib/browser/transport/tcp/index.mjs.map +7 -0
  11. package/dist/lib/node/{chunk-4YAYC7WN.cjs → chunk-D6P7ACEM.cjs} +1262 -1205
  12. package/dist/lib/node/chunk-D6P7ACEM.cjs.map +7 -0
  13. package/dist/lib/node/index.cjs +27 -37
  14. package/dist/lib/node/index.cjs.map +2 -2
  15. package/dist/lib/node/meta.json +1 -1
  16. package/dist/lib/node/testing/index.cjs +24 -34
  17. package/dist/lib/node/testing/index.cjs.map +3 -3
  18. package/dist/lib/node/transport/tcp/index.cjs +191 -0
  19. package/dist/lib/node/transport/tcp/index.cjs.map +7 -0
  20. package/dist/lib/node-esm/chunk-22DA2US6.mjs +4373 -0
  21. package/dist/lib/node-esm/chunk-22DA2US6.mjs.map +7 -0
  22. package/dist/lib/node-esm/index.mjs +50 -0
  23. package/dist/lib/node-esm/index.mjs.map +7 -0
  24. package/dist/lib/node-esm/meta.json +1 -0
  25. package/dist/lib/node-esm/testing/index.mjs +279 -0
  26. package/dist/lib/node-esm/testing/index.mjs.map +7 -0
  27. package/dist/lib/node-esm/transport/tcp/index.mjs +159 -0
  28. package/dist/lib/node-esm/transport/tcp/index.mjs.map +7 -0
  29. package/dist/types/src/network-manager.d.ts +2 -1
  30. package/dist/types/src/network-manager.d.ts.map +1 -1
  31. package/dist/types/src/signal/ice.d.ts.map +1 -1
  32. package/dist/types/src/signal/integration.node.test.d.ts +2 -0
  33. package/dist/types/src/signal/integration.node.test.d.ts.map +1 -0
  34. package/dist/types/src/signal/swarm-messenger.node.test.d.ts +2 -0
  35. package/dist/types/src/signal/swarm-messenger.node.test.d.ts.map +1 -0
  36. package/dist/types/src/swarm/connection.d.ts.map +1 -1
  37. package/dist/types/src/swarm/swarm.d.ts +1 -1
  38. package/dist/types/src/testing/test-builder.d.ts +2 -2
  39. package/dist/types/src/testing/test-builder.d.ts.map +1 -1
  40. package/dist/types/src/testing/test-wire-protocol.d.ts +1 -2
  41. package/dist/types/src/testing/test-wire-protocol.d.ts.map +1 -1
  42. package/dist/types/src/tests/basic-test-suite.d.ts.map +1 -1
  43. package/dist/types/src/tests/property-test-suite.d.ts.map +1 -1
  44. package/dist/types/src/tests/tcp-transport.node.test.d.ts +2 -0
  45. package/dist/types/src/tests/tcp-transport.node.test.d.ts.map +1 -0
  46. package/dist/types/src/tests/utils.d.ts.map +1 -1
  47. package/dist/types/src/transport/index.d.ts +1 -5
  48. package/dist/types/src/transport/index.d.ts.map +1 -1
  49. package/dist/types/src/transport/memory-transport.d.ts +2 -2
  50. package/dist/types/src/transport/memory-transport.d.ts.map +1 -1
  51. package/dist/types/src/transport/tcp/index.d.ts +2 -0
  52. package/dist/types/src/transport/tcp/index.d.ts.map +1 -0
  53. package/dist/types/src/transport/{tcp-transport.browser.d.ts → tcp/tcp-transport.browser.d.ts} +3 -3
  54. package/dist/types/src/transport/tcp/tcp-transport.browser.d.ts.map +1 -0
  55. package/dist/types/src/transport/{tcp-transport.d.ts → tcp/tcp-transport.d.ts} +3 -3
  56. package/dist/types/src/transport/tcp/tcp-transport.d.ts.map +1 -0
  57. package/dist/types/src/transport/transport.d.ts +7 -6
  58. package/dist/types/src/transport/transport.d.ts.map +1 -1
  59. package/dist/types/src/transport/webrtc/index.d.ts +4 -0
  60. package/dist/types/src/transport/webrtc/index.d.ts.map +1 -0
  61. package/dist/types/src/transport/webrtc/rtc-connection-factory.d.ts +14 -0
  62. package/dist/types/src/transport/webrtc/rtc-connection-factory.d.ts.map +1 -0
  63. package/dist/types/src/transport/webrtc/rtc-peer-connection.d.ts +68 -0
  64. package/dist/types/src/transport/webrtc/rtc-peer-connection.d.ts.map +1 -0
  65. package/dist/types/src/transport/webrtc/rtc-transport-channel.d.ts +33 -0
  66. package/dist/types/src/transport/webrtc/rtc-transport-channel.d.ts.map +1 -0
  67. package/dist/types/src/transport/webrtc/rtc-transport-channel.test.d.ts +2 -0
  68. package/dist/types/src/transport/webrtc/rtc-transport-channel.test.d.ts.map +1 -0
  69. package/dist/types/src/transport/webrtc/rtc-transport-factory.d.ts +4 -0
  70. package/dist/types/src/transport/webrtc/rtc-transport-factory.d.ts.map +1 -0
  71. package/dist/types/src/transport/{simplepeer-transport-proxy.d.ts → webrtc/rtc-transport-proxy.d.ts} +10 -12
  72. package/dist/types/src/transport/webrtc/rtc-transport-proxy.d.ts.map +1 -0
  73. package/dist/types/src/transport/webrtc/rtc-transport-proxy.test.d.ts +2 -0
  74. package/dist/types/src/transport/webrtc/rtc-transport-proxy.test.d.ts.map +1 -0
  75. package/dist/types/src/transport/{simplepeer-transport-service.d.ts → webrtc/rtc-transport-service.d.ts} +9 -7
  76. package/dist/types/src/transport/webrtc/rtc-transport-service.d.ts.map +1 -0
  77. package/dist/types/src/transport/webrtc/rtc-transport-stats.d.ts +4 -0
  78. package/dist/types/src/transport/webrtc/rtc-transport-stats.d.ts.map +1 -0
  79. package/dist/types/src/transport/webrtc/rtc-transport.test.d.ts +2 -0
  80. package/dist/types/src/transport/webrtc/rtc-transport.test.d.ts.map +1 -0
  81. package/dist/types/src/transport/webrtc/test-utils.d.ts +5 -0
  82. package/dist/types/src/transport/webrtc/test-utils.d.ts.map +1 -0
  83. package/dist/types/src/transport/webrtc/utils.d.ts +3 -0
  84. package/dist/types/src/transport/webrtc/utils.d.ts.map +1 -0
  85. package/package.json +55 -30
  86. package/src/network-manager.ts +5 -13
  87. package/src/signal/ice.test.ts +1 -3
  88. package/src/signal/ice.ts +6 -1
  89. package/src/signal/{integration.test.ts → integration.node.test.ts} +9 -15
  90. package/src/signal/{swarm-messenger.test.ts → swarm-messenger.node.test.ts} +13 -23
  91. package/src/swarm/connection-limiter.test.ts +3 -6
  92. package/src/swarm/connection.test.ts +63 -38
  93. package/src/swarm/connection.ts +5 -5
  94. package/src/swarm/swarm.test.ts +10 -12
  95. package/src/swarm/swarm.ts +1 -1
  96. package/src/testing/test-builder.ts +13 -29
  97. package/src/testing/test-wire-protocol.ts +1 -4
  98. package/src/tests/basic-test-suite.ts +34 -33
  99. package/src/tests/memory-transport.test.ts +40 -42
  100. package/src/tests/property-test-suite.ts +21 -22
  101. package/src/tests/tcp-transport.node.test.ts +65 -0
  102. package/src/tests/utils.ts +3 -2
  103. package/src/tests/webrtc-transport.test.ts +9 -9
  104. package/src/transport/index.ts +1 -5
  105. package/src/transport/memory-transport.ts +2 -0
  106. package/src/transport/tcp/index.ts +5 -0
  107. package/src/transport/{tcp-transport.browser.ts → tcp/tcp-transport.browser.ts} +7 -3
  108. package/src/transport/{tcp-transport.ts → tcp/tcp-transport.ts} +3 -1
  109. package/src/transport/transport.ts +8 -7
  110. package/src/transport/webrtc/index.ts +7 -0
  111. package/src/transport/webrtc/rtc-connection-factory.ts +82 -0
  112. package/src/transport/webrtc/rtc-peer-connection.ts +472 -0
  113. package/src/transport/webrtc/rtc-transport-channel.test.ts +176 -0
  114. package/src/transport/webrtc/rtc-transport-channel.ts +195 -0
  115. package/src/transport/webrtc/rtc-transport-factory.ts +28 -0
  116. package/src/transport/webrtc/rtc-transport-proxy.test.ts +413 -0
  117. package/src/transport/webrtc/rtc-transport-proxy.ts +264 -0
  118. package/src/transport/webrtc/rtc-transport-service.ts +192 -0
  119. package/src/transport/webrtc/rtc-transport-stats.ts +67 -0
  120. package/src/transport/webrtc/rtc-transport.test.ts +198 -0
  121. package/src/transport/webrtc/test-utils.ts +22 -0
  122. package/src/transport/webrtc/utils.ts +36 -0
  123. package/src/typings.d.ts +8 -2
  124. package/dist/lib/browser/chunk-XYSYUN63.mjs.map +0 -7
  125. package/dist/lib/node/chunk-4YAYC7WN.cjs.map +0 -7
  126. package/dist/types/src/signal/integration.test.d.ts +0 -2
  127. package/dist/types/src/signal/integration.test.d.ts.map +0 -1
  128. package/dist/types/src/signal/swarm-messenger.test.d.ts +0 -2
  129. package/dist/types/src/signal/swarm-messenger.test.d.ts.map +0 -1
  130. package/dist/types/src/tests/tcp-transport.test.d.ts +0 -2
  131. package/dist/types/src/tests/tcp-transport.test.d.ts.map +0 -1
  132. package/dist/types/src/transport/libdatachannel-transport.d.ts +0 -42
  133. package/dist/types/src/transport/libdatachannel-transport.d.ts.map +0 -1
  134. package/dist/types/src/transport/libdatachannel-transport.test.d.ts +0 -2
  135. package/dist/types/src/transport/libdatachannel-transport.test.d.ts.map +0 -1
  136. package/dist/types/src/transport/memory-transport.test.d.ts +0 -2
  137. package/dist/types/src/transport/memory-transport.test.d.ts.map +0 -1
  138. package/dist/types/src/transport/simplepeer-simple-peer.d.ts +0 -2
  139. package/dist/types/src/transport/simplepeer-simple-peer.d.ts.map +0 -1
  140. package/dist/types/src/transport/simplepeer-transport-proxy-test.d.ts +0 -2
  141. package/dist/types/src/transport/simplepeer-transport-proxy-test.d.ts.map +0 -1
  142. package/dist/types/src/transport/simplepeer-transport-proxy.d.ts.map +0 -1
  143. package/dist/types/src/transport/simplepeer-transport-service.d.ts.map +0 -1
  144. package/dist/types/src/transport/simplepeer-transport.d.ts +0 -36
  145. package/dist/types/src/transport/simplepeer-transport.d.ts.map +0 -1
  146. package/dist/types/src/transport/simplepeer-transport.test.d.ts +0 -2
  147. package/dist/types/src/transport/simplepeer-transport.test.d.ts.map +0 -1
  148. package/dist/types/src/transport/tcp-transport.browser.d.ts.map +0 -1
  149. package/dist/types/src/transport/tcp-transport.d.ts.map +0 -1
  150. package/dist/types/src/transport/webrtc.d.ts +0 -6
  151. package/dist/types/src/transport/webrtc.d.ts.map +0 -1
  152. package/src/globals.d.ts +0 -7
  153. package/src/tests/tcp-transport.test.ts +0 -67
  154. package/src/transport/libdatachannel-transport.test.ts +0 -100
  155. package/src/transport/libdatachannel-transport.ts +0 -376
  156. package/src/transport/memory-transport.test.ts +0 -74
  157. package/src/transport/simplepeer-simple-peer.ts +0 -26
  158. package/src/transport/simplepeer-transport-proxy-test.ts +0 -181
  159. package/src/transport/simplepeer-transport-proxy.ts +0 -246
  160. package/src/transport/simplepeer-transport-service.ts +0 -160
  161. package/src/transport/simplepeer-transport.test.ts +0 -61
  162. package/src/transport/simplepeer-transport.ts +0 -250
  163. package/src/transport/webrtc.ts +0 -15
@@ -0,0 +1,413 @@
1
+ //
2
+ // Copyright 2020 DXOS.org
3
+ //
4
+
5
+ import { Duplex } from 'stream';
6
+ import { onTestFinished, describe, test, expect } from 'vitest';
7
+
8
+ import { Event as AsyncEvent, TestStream, Trigger, sleep } from '@dxos/async';
9
+ import { ErrorStream } from '@dxos/debug';
10
+ import { PublicKey } from '@dxos/keys';
11
+ import { schema } from '@dxos/protocols/proto';
12
+ import { type BridgeService } from '@dxos/protocols/proto/dxos/mesh/bridge';
13
+ import { createLinkedPorts, createProtoRpcPeer, type RpcPort } from '@dxos/rpc';
14
+
15
+ import { RtcTransportProxy } from './rtc-transport-proxy';
16
+ import { RtcTransportService } from './rtc-transport-service';
17
+ import { handleChannelErrors } from './test-utils';
18
+ import { type Transport, type TransportFactory, type TransportOptions, type TransportStats } from '../transport';
19
+
20
+ describe('RtcPeerTransportProxy', () => {
21
+ test('open and close', async () => {
22
+ const { proxy } = await setupProxy();
23
+ await proxy.open();
24
+ const wait = proxy.closed.waitForCount(1);
25
+ expect(proxy.isOpen).toBeTruthy();
26
+ await proxy.close();
27
+ await wait;
28
+ });
29
+
30
+ test('transport open failure closes proxy', async () => {
31
+ const mockTransport = createMockTransport({
32
+ open: async () => {
33
+ await sleep(20);
34
+ throw new Error();
35
+ },
36
+ });
37
+ const peer = await setupProxy({}, mockTransport.factory);
38
+ const errors = handleChannelErrors(peer.proxy);
39
+ await peer.proxy.open();
40
+ await sleep(20);
41
+ expect(peer.proxy.isOpen).toBeFalsy();
42
+ await errors.expectErrorRaised();
43
+ });
44
+
45
+ test('transport error before connected closes proxy', async () => {
46
+ const mockTransport = createMockTransport();
47
+ const peer = await setupProxy({}, mockTransport.factory);
48
+ const errors = handleChannelErrors(peer.proxy);
49
+ await peer.proxy.open();
50
+ mockTransport.transport.errors.raise(new Error());
51
+ await sleep(20);
52
+ expect(peer.proxy.isOpen).toBeFalsy();
53
+ await errors.expectErrorRaised();
54
+ });
55
+
56
+ test('transport close closes proxy', async () => {
57
+ const mockTransport = createMockTransport();
58
+ const peer = await setupProxy({}, mockTransport.factory);
59
+ await peer.proxy.open();
60
+ expect(peer.proxy.isOpen).toBeTruthy();
61
+ await connectAndWaitProxy(peer, mockTransport);
62
+ await closeAndWaitProxy(peer, mockTransport);
63
+ expect(peer.proxy.isOpen).toBeFalsy();
64
+ });
65
+
66
+ test('transport connected after proxy is closed is ignored', async () => {
67
+ const mockTransport = createMockTransport();
68
+ const peer = await setupProxy({}, mockTransport.factory);
69
+ await peer.proxy.open();
70
+ expect(peer.proxy.isOpen).toBeTruthy();
71
+ await peer.proxy.close();
72
+
73
+ let connected = false;
74
+ peer.proxy.connected.on(() => {
75
+ connected = true;
76
+ });
77
+ mockTransport.transport.connected.emit();
78
+ await sleep(20);
79
+
80
+ expect(connected).toBeFalsy();
81
+ peer.proxy.errors.assertNoUnhandledErrors();
82
+ });
83
+
84
+ test('transport error after connection passed to proxy raises an error', async () => {
85
+ const mockTransport = createMockTransport();
86
+ const peer = await setupProxy({}, mockTransport.factory);
87
+ const errors = handleChannelErrors(peer.proxy);
88
+ await peer.proxy.open();
89
+ await connectAndWaitProxy(peer, mockTransport);
90
+ mockTransport.transport.errors.raise(new Error());
91
+ await sleep(20);
92
+ expect(peer.proxy.isOpen).toBeFalsy();
93
+ await errors.expectErrorRaised();
94
+ });
95
+
96
+ test('transport error raised after close is ignored', async () => {
97
+ const mockTransport = createMockTransport();
98
+ const peer = await setupProxy({}, mockTransport.factory);
99
+ await peer.proxy.open();
100
+ await connectAndWaitProxy(peer, mockTransport);
101
+ await sleep(20);
102
+ await closeAndWaitProxy(peer, mockTransport);
103
+ mockTransport.transport.errors.raise(new Error());
104
+ await sleep(20);
105
+ expect(peer.proxy.isOpen).toBeFalsy();
106
+ peer.proxy.errors.assertNoUnhandledErrors();
107
+ });
108
+
109
+ test('ice candidate signal failure tolerated', async () => {
110
+ const mockTransport = createMockTransport();
111
+ const failed = new Trigger();
112
+ const peer = await setupProxy(
113
+ {
114
+ sendSignal: () => {
115
+ failed.wake();
116
+ throw new Error();
117
+ },
118
+ },
119
+ mockTransport.factory,
120
+ );
121
+ await peer.proxy.open();
122
+ await connectAndWaitProxy(peer, mockTransport);
123
+ await mockTransport.sendSignalFromTransport({ payload: { data: { type: 'candidate' } } });
124
+ await failed.wait();
125
+
126
+ await sleep(20);
127
+ expect(peer.proxy.isOpen).toBeTruthy();
128
+ peer.proxy.errors.assertNoUnhandledErrors();
129
+ });
130
+
131
+ test('error raised when fails to send an offer/answer', async () => {
132
+ for (const type of ['offer', 'answer']) {
133
+ const mockTransport = createMockTransport();
134
+ const failed = new Trigger();
135
+ const peer = await setupProxy(
136
+ {
137
+ sendSignal: () => {
138
+ failed.wake();
139
+ throw new Error();
140
+ },
141
+ },
142
+ mockTransport.factory,
143
+ );
144
+ await peer.proxy.open();
145
+ const errors = handleChannelErrors(peer.proxy);
146
+ await connectAndWaitProxy(peer, mockTransport);
147
+ await mockTransport.sendSignalFromTransport({ payload: { data: { type } } });
148
+ await failed.wait();
149
+
150
+ await sleep(20);
151
+ await errors.expectErrorRaised();
152
+ }
153
+ });
154
+
155
+ test('error raised when transport fails to handle a signal', async () => {
156
+ const failed = new Trigger();
157
+ const mockTransport = createMockTransport({
158
+ onSignal: async (): Promise<void> => {
159
+ await sleep(20);
160
+ failed.wake();
161
+ throw new Error();
162
+ },
163
+ });
164
+ const peer = await setupProxy({}, mockTransport.factory);
165
+ await peer.proxy.open();
166
+ const errors = handleChannelErrors(peer.proxy);
167
+ await connectAndWaitProxy(peer, mockTransport);
168
+ await peer.proxy.onSignal({ payload: { data: { type: 'offer' } } });
169
+ await failed.wait();
170
+
171
+ await sleep(20);
172
+ await errors.expectErrorRaised();
173
+ });
174
+
175
+ test('transport close error tolerated', async () => {
176
+ const failed = new Trigger();
177
+ const mockTransport = createMockTransport({
178
+ close: async () => {
179
+ await sleep(20);
180
+ failed.wake();
181
+ throw new Error();
182
+ },
183
+ });
184
+ const peer = await setupProxy({}, mockTransport.factory);
185
+ await peer.proxy.open();
186
+ await connectAndWaitProxy(peer, mockTransport);
187
+ await sleep(20);
188
+ await peer.proxy.close();
189
+ await failed.wait();
190
+
191
+ peer.proxy.errors.assertNoUnhandledErrors();
192
+ });
193
+
194
+ test('transport data push after proxy close is ignored', async () => {
195
+ const mockTransport = createMockTransport();
196
+ const peer = await setupProxy({}, mockTransport.factory);
197
+ await peer.proxy.open();
198
+ const messageParts = [Buffer.from('hello,'), Buffer.from('world!')];
199
+ await connectAndWaitProxy(peer, mockTransport);
200
+ mockTransport.stream.push(messageParts[0]);
201
+ mockTransport.stream.push(messageParts[1]);
202
+ await sleep(20);
203
+ await peer.stream.assertReceivedAsync(Buffer.concat(messageParts));
204
+ await peer.proxy.close();
205
+ await sleep(20);
206
+ mockTransport.stream.push(Buffer.from('!!!'));
207
+ await sleep(20);
208
+
209
+ await peer.stream.assertReceivedAsync(Buffer.concat(messageParts));
210
+ peer.proxy.errors.assertNoUnhandledErrors();
211
+ });
212
+
213
+ describe('RtcPeerConnection', () => {
214
+ test('establish connection and send data through with protocol', async () => {
215
+ const peer1 = await setupProxy({
216
+ sendSignal: async (signal) => {
217
+ await peer2.proxy.onSignal(signal);
218
+ },
219
+ });
220
+ await peer1.proxy.open();
221
+ assertNoErrorsAfterTest(peer1);
222
+
223
+ const peer2 = await setupProxy({
224
+ ownPeerKey: peer1.options.remotePeerKey,
225
+ remotePeerKey: peer1.options.ownPeerKey,
226
+ topic: peer1.options.topic,
227
+ sendSignal: async (signal) => {
228
+ await peer1.proxy.onSignal(signal);
229
+ },
230
+ });
231
+ await peer2.proxy.open();
232
+ assertNoErrorsAfterTest(peer2);
233
+
234
+ await TestStream.assertConnectivity(peer1.stream, peer2.stream, { timeout: 1500 });
235
+ });
236
+
237
+ test('establish connection and send data through with protocol with multiplexing', async () => {
238
+ const [port1, port2] = createLinkedPorts();
239
+ await createService(port1);
240
+ const rpcClient = await createClient(port2);
241
+
242
+ const proxy1 = await createProxy(rpcClient, {
243
+ sendSignal: async (signal) => {
244
+ await proxy2.proxy.onSignal(signal);
245
+ },
246
+ });
247
+ assertNoErrorsAfterTest(proxy1);
248
+
249
+ const proxy2 = await createProxy(rpcClient, {
250
+ ownPeerKey: proxy1.options.remotePeerKey,
251
+ remotePeerKey: proxy1.options.ownPeerKey,
252
+ topic: proxy1.options.topic,
253
+ sendSignal: async (signal) => {
254
+ await proxy1.proxy.onSignal(signal);
255
+ },
256
+ });
257
+ assertNoErrorsAfterTest(proxy2);
258
+
259
+ await proxy1.proxy.open();
260
+ await proxy2.proxy.open();
261
+
262
+ await TestStream.assertConnectivity(proxy1.stream, proxy2.stream, { timeout: 1500 });
263
+ });
264
+ });
265
+
266
+ const setupProxy = async (overrides?: Partial<TransportOptions>, transportFactory?: TransportFactory) => {
267
+ const [port1, port2] = createLinkedPorts();
268
+ const service = await createService(port1, transportFactory);
269
+ const rpcClient = await createClient(port2);
270
+ return { service, ...(await createProxy(rpcClient, overrides)) };
271
+ };
272
+
273
+ const createProxy = async (
274
+ client: { rpc: { BridgeService: BridgeService } },
275
+ overrides?: Partial<TransportOptions>,
276
+ ) => {
277
+ const stream = (overrides?.stream as TestStream) ?? new TestStream();
278
+ const options = createTransportOptions({ stream, ...overrides });
279
+ const proxy = new RtcTransportProxy({ ...options, bridgeService: client.rpc.BridgeService });
280
+ onTestFinished(async () => {
281
+ await proxy.close();
282
+ });
283
+ return { proxy, options, stream };
284
+ };
285
+
286
+ const createService = async (port: RpcPort, transportFactory?: TransportFactory) => {
287
+ const rtcTransportService = new RtcTransportService(undefined, undefined, transportFactory);
288
+ const service = createProtoRpcPeer({
289
+ requested: {},
290
+ exposed: {
291
+ BridgeService: schema.getService('dxos.mesh.bridge.BridgeService'),
292
+ },
293
+ handlers: { BridgeService: rtcTransportService },
294
+ port,
295
+ noHandshake: true,
296
+ encodingOptions: {
297
+ preserveAny: true,
298
+ },
299
+ });
300
+ await service.open();
301
+ onTestFinished(async () => {
302
+ expect(rtcTransportService.hasOpenTransports()).toBeFalsy();
303
+ await service.close();
304
+ });
305
+ return service;
306
+ };
307
+
308
+ const createClient = async (port: RpcPort) => {
309
+ const rpcClient = createProtoRpcPeer({
310
+ requested: {
311
+ BridgeService: schema.getService('dxos.mesh.bridge.BridgeService'),
312
+ },
313
+ port,
314
+ noHandshake: true,
315
+ encodingOptions: {
316
+ preserveAny: true,
317
+ },
318
+ });
319
+ await rpcClient.open();
320
+ onTestFinished(async () => {
321
+ await rpcClient.close();
322
+ });
323
+ return rpcClient;
324
+ };
325
+
326
+ const assertNoErrorsAfterTest = (args: { proxy: Transport }) => {
327
+ onTestFinished(() => {
328
+ args.proxy.errors.assertNoUnhandledErrors();
329
+ });
330
+ };
331
+
332
+ const closeAndWaitProxy = (peer: { proxy: RtcTransportProxy }, mockTransport: { transport: MockTransport }) => {
333
+ const waitForConnected = peer.proxy.closed.waitForCount(1);
334
+ mockTransport.transport.closed.emit();
335
+ return waitForConnected;
336
+ };
337
+
338
+ const connectAndWaitProxy = (peer: { proxy: RtcTransportProxy }, mockTransport: { transport: MockTransport }) => {
339
+ const waitForConnected = peer.proxy.connected.waitForCount(1);
340
+ mockTransport.transport.connected.emit();
341
+ return waitForConnected;
342
+ };
343
+ });
344
+
345
+ const createTransportOptions = (options: Partial<TransportOptions>): TransportOptions => {
346
+ return {
347
+ initiator: false,
348
+ stream: new Duplex(),
349
+ sendSignal: async () => {},
350
+ remotePeerKey: PublicKey.random().toHex(),
351
+ ownPeerKey: PublicKey.random().toHex(),
352
+ topic: PublicKey.random().toHex(),
353
+ ...options,
354
+ };
355
+ };
356
+
357
+ const createMockTransport = (delegate?: Partial<Transport>) => {
358
+ const transport = new MockTransport(delegate);
359
+ const receivedMessages: string[] = [];
360
+ const stream = new Duplex({
361
+ read: () => {},
362
+ write: (chunk: any, _: BufferEncoding, callback: () => void) => {
363
+ receivedMessages.push(Buffer.from(chunk).toString());
364
+ callback();
365
+ },
366
+ });
367
+ let sendSignal: any | undefined;
368
+ return {
369
+ receivedMessages,
370
+ transport,
371
+ stream,
372
+ sendSignalFromTransport: async (signal: any) => {
373
+ sendSignal(signal);
374
+ },
375
+ factory: {
376
+ createTransport: (options: TransportOptions): Transport => {
377
+ sendSignal = options.sendSignal;
378
+ stream.pipe(options.stream).pipe(stream);
379
+ return transport;
380
+ },
381
+ },
382
+ };
383
+ };
384
+
385
+ class MockTransport implements Transport {
386
+ public readonly closed = new AsyncEvent();
387
+ public readonly connected = new AsyncEvent();
388
+ public readonly errors = new ErrorStream();
389
+
390
+ constructor(private readonly _delegate: Partial<Transport> = {}) {}
391
+
392
+ public async open(): Promise<this> {
393
+ await this._delegate.open?.();
394
+ return this;
395
+ }
396
+
397
+ public async close(): Promise<this> {
398
+ await this._delegate.close?.();
399
+ return this;
400
+ }
401
+
402
+ public async onSignal(signal: any): Promise<void> {
403
+ return this._delegate.onSignal?.(signal);
404
+ }
405
+
406
+ public async getStats(): Promise<TransportStats> {
407
+ return {} as any;
408
+ }
409
+
410
+ public async getDetails(): Promise<string> {
411
+ return '';
412
+ }
413
+ }
@@ -0,0 +1,264 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import { Writable } from 'node:stream';
6
+
7
+ import { Event, scheduleTask } from '@dxos/async';
8
+ import { type Stream } from '@dxos/codec-protobuf';
9
+ import { Resource } from '@dxos/context';
10
+ import { ErrorStream } from '@dxos/debug';
11
+ import { invariant } from '@dxos/invariant';
12
+ import { PublicKey } from '@dxos/keys';
13
+ import { log } from '@dxos/log';
14
+ import { ConnectionResetError, ConnectivityError, TimeoutError } from '@dxos/protocols';
15
+ import { type BridgeEvent, type BridgeService, ConnectionState } from '@dxos/protocols/proto/dxos/mesh/bridge';
16
+ import { type Signal } from '@dxos/protocols/proto/dxos/mesh/swarm';
17
+ import { arrayToBuffer } from '@dxos/util';
18
+
19
+ import { type Transport, type TransportFactory, type TransportOptions, type TransportStats } from '../transport';
20
+
21
+ const RPC_TIMEOUT = 10_000;
22
+ const CLOSE_RPC_TIMEOUT = 3000;
23
+ const RESP_MIN_THRESHOLD = 500;
24
+
25
+ export type RtcTransportProxyOptions = TransportOptions & {
26
+ bridgeService: BridgeService;
27
+ };
28
+
29
+ export class RtcTransportProxy extends Resource implements Transport {
30
+ private readonly _proxyId = PublicKey.random();
31
+
32
+ readonly closed = new Event();
33
+ readonly connected = new Event();
34
+ readonly errors = new ErrorStream();
35
+
36
+ private _serviceStream: Stream<BridgeEvent> | undefined;
37
+
38
+ constructor(private readonly _options: RtcTransportProxyOptions) {
39
+ super();
40
+ }
41
+
42
+ protected override async _open() {
43
+ let stream: Stream<BridgeEvent>;
44
+ try {
45
+ stream = this._options.bridgeService.open(
46
+ {
47
+ proxyId: this._proxyId,
48
+ remotePeerKey: this._options.remotePeerKey,
49
+ ownPeerKey: this._options.ownPeerKey,
50
+ topic: this._options.topic,
51
+ initiator: this._options.initiator ?? false,
52
+ },
53
+ { timeout: RPC_TIMEOUT },
54
+ );
55
+ } catch (error: any) {
56
+ this.errors.raise(error);
57
+ return;
58
+ }
59
+
60
+ this._serviceStream = stream;
61
+
62
+ stream.waitUntilReady().then(
63
+ () => {
64
+ stream.subscribe(
65
+ async (event: BridgeEvent) => {
66
+ log('rtc transport proxy event', event);
67
+ if (event.connection) {
68
+ await this._handleConnection(event.connection);
69
+ } else if (event.data) {
70
+ this._handleData(event.data);
71
+ } else if (event.signal) {
72
+ await this._handleSignal(event.signal);
73
+ }
74
+ },
75
+ (err) => {
76
+ log('rtc bridge stream closed', { err });
77
+ if (err) {
78
+ this._raiseIfOpen(err);
79
+ } else {
80
+ void this.close();
81
+ }
82
+ },
83
+ );
84
+
85
+ const connectorStream = new Writable({
86
+ write: (chunk, _, callback) => {
87
+ const sendStartMs = Date.now();
88
+ this._options.bridgeService
89
+ .sendData({ proxyId: this._proxyId, payload: chunk }, { timeout: RPC_TIMEOUT })
90
+ .then(
91
+ () => {
92
+ if (Date.now() - sendStartMs > RESP_MIN_THRESHOLD) {
93
+ log('slow response, delaying callback');
94
+ scheduleTask(this._ctx, () => callback(), RESP_MIN_THRESHOLD);
95
+ } else {
96
+ callback();
97
+ }
98
+ },
99
+ (err: any) => {
100
+ callback();
101
+ this._raiseIfOpen(err);
102
+ },
103
+ );
104
+ },
105
+ });
106
+
107
+ connectorStream.on('error', (err) => {
108
+ this._raiseIfOpen(err);
109
+ });
110
+
111
+ this._options.stream.pipe(connectorStream);
112
+ },
113
+ (error) => {
114
+ if (error) {
115
+ this._raiseIfOpen(error);
116
+ } else {
117
+ void this.close();
118
+ }
119
+ },
120
+ );
121
+ }
122
+
123
+ protected override async _close() {
124
+ try {
125
+ await this._serviceStream?.close();
126
+ this._serviceStream = undefined;
127
+ } catch (err: any) {
128
+ log.catch(err);
129
+ }
130
+
131
+ try {
132
+ await this._options.bridgeService.close({ proxyId: this._proxyId }, { timeout: CLOSE_RPC_TIMEOUT });
133
+ } catch (err: any) {
134
+ log.catch(err);
135
+ }
136
+
137
+ this.closed.emit();
138
+ }
139
+
140
+ async onSignal(signal: Signal) {
141
+ this._options.bridgeService
142
+ .sendSignal({ proxyId: this._proxyId, signal }, { timeout: RPC_TIMEOUT })
143
+ .catch((err) => this._raiseIfOpen(decodeError(err)));
144
+ }
145
+
146
+ private async _handleConnection(connectionEvent: BridgeEvent.ConnectionEvent): Promise<void> {
147
+ if (connectionEvent.error) {
148
+ this.errors.raise(decodeError(connectionEvent.error));
149
+ return;
150
+ }
151
+
152
+ switch (connectionEvent.state) {
153
+ case ConnectionState.CONNECTED: {
154
+ this.connected.emit();
155
+ break;
156
+ }
157
+ case ConnectionState.CLOSED: {
158
+ await this.close();
159
+ break;
160
+ }
161
+ }
162
+ }
163
+
164
+ private _handleData(dataEvent: BridgeEvent.DataEvent) {
165
+ try {
166
+ // NOTE: This must be a Buffer otherwise hypercore-protocol breaks.
167
+ this._options.stream.write(arrayToBuffer(dataEvent.payload));
168
+ } catch (error: any) {
169
+ this._raiseIfOpen(error);
170
+ }
171
+ }
172
+
173
+ private async _handleSignal(signalEvent: BridgeEvent.SignalEvent) {
174
+ try {
175
+ await this._options.sendSignal(signalEvent.payload);
176
+ } catch (error) {
177
+ const type = signalEvent.payload.payload.data?.type;
178
+ if (type === 'offer' || type === 'answer') {
179
+ this._raiseIfOpen(new ConnectivityError(`Session establishment failed: ${type} couldn't be sent.`));
180
+ }
181
+ }
182
+ }
183
+
184
+ async getDetails(): Promise<string> {
185
+ try {
186
+ const response = await this._options.bridgeService.getDetails(
187
+ { proxyId: this._proxyId },
188
+ { timeout: RPC_TIMEOUT },
189
+ );
190
+ return response.details;
191
+ } catch (err) {
192
+ return 'bridge-svc unreachable';
193
+ }
194
+ }
195
+
196
+ async getStats(): Promise<TransportStats> {
197
+ try {
198
+ const response = await this._options.bridgeService.getStats({ proxyId: this._proxyId }, { timeout: RPC_TIMEOUT });
199
+ return response.stats as TransportStats;
200
+ } catch (err) {
201
+ return {
202
+ bytesSent: 0,
203
+ bytesReceived: 0,
204
+ packetsSent: 0,
205
+ packetsReceived: 0,
206
+ rawStats: 'bridge-svc unreachable',
207
+ };
208
+ }
209
+ }
210
+
211
+ private _raiseIfOpen(error: any) {
212
+ if (this.isOpen) {
213
+ this.errors.raise(error);
214
+ } else {
215
+ log.info('error swallowed because transport was closed', { message: error.message });
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Called when underlying proxy service becomes unavailable.
221
+ */
222
+ forceClose() {
223
+ void this._serviceStream?.close();
224
+ this.closed.emit();
225
+ }
226
+ }
227
+
228
+ export class RtcTransportProxyFactory implements TransportFactory {
229
+ private _bridgeService: BridgeService | undefined;
230
+ private _connections = new Set<RtcTransportProxy>();
231
+
232
+ /**
233
+ * Sets the current BridgeService to be used to open connections.
234
+ * Calling this method will close any existing connections.
235
+ */
236
+ setBridgeService(bridgeService: BridgeService | undefined): this {
237
+ this._bridgeService = bridgeService;
238
+ for (const connection of this._connections) {
239
+ connection.forceClose();
240
+ }
241
+ return this;
242
+ }
243
+
244
+ createTransport(options: TransportOptions): Transport {
245
+ invariant(this._bridgeService, 'RtcTransportProxyFactory is not ready to open connections');
246
+ const transport = new RtcTransportProxy({ ...options, bridgeService: this._bridgeService });
247
+ this._connections.add(transport);
248
+ transport.closed.on(() => this._connections.delete(transport));
249
+ return transport;
250
+ }
251
+ }
252
+
253
+ const decodeError = (err: Error | string) => {
254
+ const message = typeof err === 'string' ? err : err.message;
255
+ if (message.includes('CONNECTION_RESET')) {
256
+ return new ConnectionResetError(message);
257
+ } else if (message.includes('TIMEOUT')) {
258
+ return new TimeoutError(message);
259
+ } else if (message.includes('CONNECTIVITY_ERROR')) {
260
+ return new ConnectivityError(message);
261
+ } else {
262
+ return typeof err === 'string' ? new Error(err) : err;
263
+ }
264
+ };