@dxos/network-manager 0.1.56-main.e47dfd1 → 0.1.56-main.e79d64a

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 (53) hide show
  1. package/dist/lib/browser/{chunk-5R7TDXUU.mjs → chunk-WQ3YXGWP.mjs} +702 -276
  2. package/dist/lib/browser/chunk-WQ3YXGWP.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +1 -1
  4. package/dist/lib/browser/meta.json +1 -1
  5. package/dist/lib/browser/testing/index.mjs +23 -16
  6. package/dist/lib/browser/testing/index.mjs.map +3 -3
  7. package/dist/lib/node/index.cjs +700 -274
  8. package/dist/lib/node/index.cjs.map +3 -3
  9. package/dist/lib/node/meta.json +1 -1
  10. package/dist/lib/node/testing/index.cjs +648 -271
  11. package/dist/lib/node/testing/index.cjs.map +3 -3
  12. package/dist/types/src/connection-log.d.ts.map +1 -1
  13. package/dist/types/src/network-manager.d.ts.map +1 -1
  14. package/dist/types/src/signal/swarm-messenger.d.ts +1 -1
  15. package/dist/types/src/signal/swarm-messenger.d.ts.map +1 -1
  16. package/dist/types/src/swarm/connection-limiter.d.ts +1 -1
  17. package/dist/types/src/swarm/connection-limiter.d.ts.map +1 -1
  18. package/dist/types/src/swarm/connection.d.ts +2 -1
  19. package/dist/types/src/swarm/connection.d.ts.map +1 -1
  20. package/dist/types/src/swarm/peer.d.ts +1 -1
  21. package/dist/types/src/swarm/peer.d.ts.map +1 -1
  22. package/dist/types/src/swarm/swarm.d.ts.map +1 -1
  23. package/dist/types/src/testing/test-builder.d.ts +4 -3
  24. package/dist/types/src/testing/test-builder.d.ts.map +1 -1
  25. package/dist/types/src/testing/test-wire-protocol.d.ts +13 -2
  26. package/dist/types/src/testing/test-wire-protocol.d.ts.map +1 -1
  27. package/dist/types/src/topology/fully-connected-topology.d.ts.map +1 -1
  28. package/dist/types/src/topology/star-topology.d.ts.map +1 -1
  29. package/dist/types/src/transport/memory-transport.d.ts.map +1 -1
  30. package/dist/types/src/transport/webrtc-transport-proxy.d.ts +1 -1
  31. package/dist/types/src/transport/webrtc-transport-proxy.d.ts.map +1 -1
  32. package/dist/types/src/transport/webrtc-transport-service.d.ts.map +1 -1
  33. package/dist/types/src/transport/webrtc-transport.d.ts +3 -3
  34. package/dist/types/src/transport/webrtc-transport.d.ts.map +1 -1
  35. package/package.json +18 -18
  36. package/src/connection-log.ts +2 -0
  37. package/src/network-manager.ts +1 -2
  38. package/src/signal/swarm-messenger.ts +3 -4
  39. package/src/swarm/connection-limiter.ts +3 -2
  40. package/src/swarm/connection.ts +29 -6
  41. package/src/swarm/peer.ts +32 -8
  42. package/src/swarm/swarm.ts +6 -7
  43. package/src/testing/test-builder.ts +9 -5
  44. package/src/testing/test-wire-protocol.ts +22 -2
  45. package/src/tests/memory-transport.test.ts +1 -1
  46. package/src/topology/fully-connected-topology.ts +1 -2
  47. package/src/topology/mmst-topology.ts +1 -1
  48. package/src/topology/star-topology.ts +1 -2
  49. package/src/transport/memory-transport.ts +1 -1
  50. package/src/transport/webrtc-transport-proxy.ts +18 -16
  51. package/src/transport/webrtc-transport-service.ts +31 -8
  52. package/src/transport/webrtc-transport.ts +21 -7
  53. package/dist/lib/browser/chunk-5R7TDXUU.mjs.map +0 -7
@@ -2,11 +2,10 @@
2
2
  // Copyright 2020 DXOS.org
3
3
  //
4
4
 
5
- import invariant from 'tiny-invariant';
6
-
7
5
  import { Event, scheduleTask, sleep, synchronized } from '@dxos/async';
8
6
  import { Context } from '@dxos/context';
9
7
  import { ErrorStream } from '@dxos/debug';
8
+ import { invariant } from '@dxos/invariant';
10
9
  import { PublicKey } from '@dxos/keys';
11
10
  import { log, logInfo } from '@dxos/log';
12
11
  import { ListeningHandle, Messenger } from '@dxos/messaging';
@@ -35,17 +34,17 @@ const getClassName = (obj: any) => Object.getPrototypeOf(obj).constructor.name;
35
34
  * Routes signal events and maintains swarm topology.
36
35
  */
37
36
  export class Swarm {
38
- /**
39
- * @internal
40
- */
41
- readonly _peers = new ComplexMap<PublicKey, Peer>(PublicKey.hash);
42
-
43
37
  private readonly _swarmMessenger: SwarmMessenger;
44
38
 
45
39
  private _ctx = new Context();
46
40
 
47
41
  private _listeningHandle?: ListeningHandle = undefined;
48
42
 
43
+ /**
44
+ * @internal
45
+ */
46
+ readonly _peers = new ComplexMap<PublicKey, Peer>(PublicKey.hash);
47
+
49
48
  /**
50
49
  * Unique id of the swarm, local to the current peer, generated when swarm is joined.
51
50
  */
@@ -24,7 +24,7 @@ import {
24
24
  WebRTCTransportProxyFactory,
25
25
  WebRTCTransportService,
26
26
  } from '../transport';
27
- import { TestWireProtocol } from './test-wire-protocol';
27
+ import { TestWireProtocol, type TestTeleportExtensionFactory } from './test-wire-protocol';
28
28
 
29
29
  // Signal server will be started by the setup script.
30
30
  const port = process.env.SIGNAL_PORT ?? 4000;
@@ -152,13 +152,13 @@ export class TestPeer {
152
152
  return swarm;
153
153
  }
154
154
 
155
- createSwarm(topic: PublicKey): TestSwarmConnection {
155
+ createSwarm(topic: PublicKey, extensionFactory: TestTeleportExtensionFactory = () => []): TestSwarmConnection {
156
156
  // TODO(burdon): Multiple.
157
157
  // if (this._swarms.get(topic)) {
158
158
  // throw new Error(`Swarm already exists for topic: ${topic.truncate()}`);
159
159
  // }
160
160
 
161
- const swarm = new TestSwarmConnection(this, topic);
161
+ const swarm = new TestSwarmConnection(this, topic, extensionFactory);
162
162
  this._swarms.set(topic, swarm);
163
163
  return swarm;
164
164
  }
@@ -176,10 +176,14 @@ export class TestPeer {
176
176
  export class TestSwarmConnection {
177
177
  protocol: TestWireProtocol;
178
178
 
179
- constructor(readonly peer: TestPeer, readonly topic: PublicKey) {
179
+ constructor(
180
+ readonly peer: TestPeer,
181
+ readonly topic: PublicKey,
182
+ readonly extensionFactory: TestTeleportExtensionFactory,
183
+ ) {
180
184
  // TODO(burdon): Configure plugins.
181
185
  // TODO(burdon): Prevent reuse?
182
- this.protocol = new TestWireProtocol(this.peer.peerId);
186
+ this.protocol = new TestWireProtocol(this.peer.peerId, this.extensionFactory);
183
187
  }
184
188
 
185
189
  // TODO(burdon): Need to create new plugin instance per swarm?
@@ -5,11 +5,19 @@
5
5
  import { asyncTimeout, Event } from '@dxos/async';
6
6
  import { PublicKey } from '@dxos/keys';
7
7
  import { log } from '@dxos/log';
8
- import { TestExtension, TestExtensionWithStreams, type TestStreamStats } from '@dxos/teleport';
8
+ import { TestExtension, TestExtensionWithStreams } from '@dxos/teleport';
9
+ import type { TestStreamStats, TeleportExtension } from '@dxos/teleport';
9
10
  import { ComplexMap } from '@dxos/util';
10
11
 
11
12
  import { createTeleportProtocolFactory } from '../wire-protocol';
12
13
 
14
+ export type TestTeleportExtension = {
15
+ name: string;
16
+ extension: TeleportExtension;
17
+ };
18
+
19
+ export type TestTeleportExtensionFactory = () => TestTeleportExtension[];
20
+
13
21
  export class TestWireProtocol {
14
22
  public readonly connections = new ComplexMap<PublicKey, TestExtension>(PublicKey.hash);
15
23
  public readonly streamConnections = new ComplexMap<PublicKey, TestExtensionWithStreams>(PublicKey.hash);
@@ -17,7 +25,14 @@ export class TestWireProtocol {
17
25
  public readonly connected = new Event<PublicKey>();
18
26
  public readonly disconnected = new Event<PublicKey>();
19
27
 
20
- constructor(public readonly peerId: PublicKey) {}
28
+ public readonly otherConnections = new ComplexMap<{ remotePeerId: PublicKey; extension: string }, TeleportExtension>(
29
+ ({ remotePeerId, extension }) => remotePeerId.toHex() + extension,
30
+ );
31
+
32
+ constructor(
33
+ public readonly peerId: PublicKey,
34
+ private readonly _extensionFactory: TestTeleportExtensionFactory = () => [],
35
+ ) {}
21
36
 
22
37
  readonly factory = createTeleportProtocolFactory(async (teleport) => {
23
38
  log('create', { remotePeerId: teleport.remotePeerId });
@@ -38,6 +53,11 @@ export class TestWireProtocol {
38
53
  });
39
54
  this.streamConnections.set(teleport.remotePeerId, streamExtension);
40
55
  teleport.addExtension('test-stream', streamExtension);
56
+
57
+ for (const { name, extension } of this._extensionFactory()) {
58
+ this.otherConnections.set({ remotePeerId: teleport.remotePeerId, extension: name }, extension);
59
+ teleport.addExtension(name, extension);
60
+ }
41
61
  });
42
62
 
43
63
  async waitForConnection(peerId: PublicKey) {
@@ -45,7 +45,7 @@ describe('Memory transport', () => {
45
45
  const message = randomBytes(PACKET_SIZE / 2).toString('hex');
46
46
 
47
47
  for (let i = 0; i < NUM_ROUNDS; i++) {
48
- console.log(`Round ${i}/${NUM_ROUNDS}`);
48
+ // console.log(`Round ${i}/${NUM_ROUNDS}`);
49
49
  await Promise.all(
50
50
  pairs.map(async ({ swarm1, swarm2 }) => {
51
51
  await exchangeMessages(swarm1, swarm2, message);
@@ -2,8 +2,7 @@
2
2
  // Copyright 2020 DXOS.org
3
3
  //
4
4
 
5
- import invariant from 'tiny-invariant';
6
-
5
+ import { invariant } from '@dxos/invariant';
7
6
  import { PublicKey } from '@dxos/keys';
8
7
 
9
8
  import { SwarmController, Topology } from './topology';
@@ -2,9 +2,9 @@
2
2
  // Copyright 2020 DXOS.org
3
3
  //
4
4
 
5
- import invariant from 'tiny-invariant';
6
5
  import distance from 'xor-distance';
7
6
 
7
+ import { invariant } from '@dxos/invariant';
8
8
  import { PublicKey } from '@dxos/keys';
9
9
  import { log } from '@dxos/log';
10
10
 
@@ -2,8 +2,7 @@
2
2
  // Copyright 2020 DXOS.org
3
3
  //
4
4
 
5
- import invariant from 'tiny-invariant';
6
-
5
+ import { invariant } from '@dxos/invariant';
7
6
  import { PublicKey } from '@dxos/keys';
8
7
  import { log } from '@dxos/log';
9
8
 
@@ -3,10 +3,10 @@
3
3
  //
4
4
 
5
5
  import { Transform } from 'node:stream';
6
- import invariant from 'tiny-invariant';
7
6
 
8
7
  import { Event, Trigger } from '@dxos/async';
9
8
  import { ErrorStream } from '@dxos/debug';
9
+ import { invariant } from '@dxos/invariant';
10
10
  import { PublicKey } from '@dxos/keys';
11
11
  import { log, logInfo } from '@dxos/log';
12
12
  import { Signal } from '@dxos/protocols/proto/dxos/mesh/swarm';
@@ -2,12 +2,13 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import invariant from 'tiny-invariant';
5
+ import { Writable } from 'node:stream';
6
6
 
7
7
  import { Event } from '@dxos/async';
8
8
  import { Stream } from '@dxos/codec-protobuf';
9
9
  import { Context } from '@dxos/context';
10
10
  import { ErrorStream } from '@dxos/debug';
11
+ import { invariant } from '@dxos/invariant';
11
12
  import { PublicKey } from '@dxos/keys';
12
13
  import { log } from '@dxos/log';
13
14
  import { ConnectionState, BridgeEvent, BridgeService } from '@dxos/protocols/proto/dxos/mesh/bridge';
@@ -20,6 +21,7 @@ export type WebRTCTransportProxyParams = {
20
21
  initiator: boolean;
21
22
  stream: NodeJS.ReadWriteStream;
22
23
  bridgeService: BridgeService;
24
+ // TODO(burdon): Rename onSignal.
23
25
  sendSignal: (signal: Signal) => Promise<void>;
24
26
  };
25
27
 
@@ -28,11 +30,10 @@ export class WebRTCTransportProxy implements Transport {
28
30
  private readonly _ctx = new Context();
29
31
 
30
32
  readonly closed = new Event();
31
- private _closed = false;
32
33
  readonly connected = new Event();
33
-
34
34
  readonly errors = new ErrorStream();
35
35
 
36
+ private _closed = false;
36
37
  private _serviceStream!: Stream<BridgeEvent>;
37
38
 
38
39
  // prettier-ignore
@@ -57,18 +58,19 @@ export class WebRTCTransportProxy implements Transport {
57
58
  }
58
59
  });
59
60
 
60
- const dataListener = async (data: Uint8Array) => {
61
- try {
62
- await this._params.bridgeService.sendData({
61
+ this._params.stream.pipe(new Writable({
62
+ write: (chunk, _, callback) => {
63
+ this._params.bridgeService.sendData({
63
64
  proxyId: this._proxyId,
64
- payload: data
65
- });
66
- } catch (err: any) {
67
- log.catch(err);
68
- }
69
- };
70
- this._params.stream.on('data', dataListener);
71
- this._ctx.onDispose(() => { this._params.stream.off('data', dataListener); });
65
+ payload: chunk
66
+ }).then(
67
+ () => callback(),
68
+ (err: any) => {
69
+ log.catch(err);
70
+ }
71
+ );
72
+ },
73
+ }));
72
74
  },
73
75
  (error) => log.catch(error)
74
76
  );
@@ -116,7 +118,7 @@ export class WebRTCTransportProxy implements Transport {
116
118
  return;
117
119
  }
118
120
 
119
- this._serviceStream.close();
121
+ await this._serviceStream.close();
120
122
 
121
123
  try {
122
124
  await this._params.bridgeService.close({ proxyId: this._proxyId });
@@ -133,7 +135,7 @@ export class WebRTCTransportProxy implements Transport {
133
135
  */
134
136
  // TODO(burdon): Option on close method.
135
137
  forceClose() {
136
- this._serviceStream.close();
138
+ void this._serviceStream.close();
137
139
  this.closed.emit();
138
140
  this._closed = true;
139
141
  }
@@ -3,9 +3,9 @@
3
3
  //
4
4
 
5
5
  import { Duplex } from 'node:stream';
6
- import invariant from 'tiny-invariant';
7
6
 
8
7
  import { Stream } from '@dxos/codec-protobuf';
8
+ import { invariant } from '@dxos/invariant';
9
9
  import { PublicKey } from '@dxos/keys';
10
10
  import { log } from '@dxos/log';
11
11
  import {
@@ -21,20 +21,30 @@ import { ComplexMap } from '@dxos/util';
21
21
 
22
22
  import { WebRTCTransport } from './webrtc-transport';
23
23
 
24
+ type TransportState = {
25
+ transport: WebRTCTransport;
26
+ stream: Duplex;
27
+ writeCallbacks: (() => void)[];
28
+ };
29
+
24
30
  export class WebRTCTransportService implements BridgeService {
25
- private readonly transports = new ComplexMap<PublicKey, { transport: WebRTCTransport; stream: Duplex }>(
26
- PublicKey.hash,
27
- );
31
+ private readonly transports = new ComplexMap<PublicKey, TransportState>(PublicKey.hash);
28
32
 
29
33
  // prettier-ignore
30
34
  constructor(
31
35
  private readonly _webrtcConfig?: any
32
- ) {}
36
+ ) { }
33
37
 
34
38
  open(request: ConnectionRequest): Stream<BridgeEvent> {
35
39
  const rpcStream: Stream<BridgeEvent> = new Stream(({ ready, next, close }) => {
36
40
  const duplex: Duplex = new Duplex({
37
- read: () => {},
41
+ read: () => {
42
+ const callbacks = [...transportState.writeCallbacks];
43
+ transportState.writeCallbacks.length = 0;
44
+ for (const cb of callbacks) {
45
+ cb();
46
+ }
47
+ },
38
48
  write: function (chunk, _, callback) {
39
49
  next({ data: { payload: chunk } });
40
50
  callback();
@@ -85,8 +95,15 @@ export class WebRTCTransportService implements BridgeService {
85
95
  close();
86
96
  });
87
97
 
98
+ const transportState: TransportState = {
99
+ transport,
100
+ stream: duplex,
101
+ writeCallbacks: [],
102
+ };
103
+
88
104
  ready();
89
- this.transports.set(request.proxyId, { transport, stream: duplex });
105
+
106
+ this.transports.set(request.proxyId, transportState);
90
107
  });
91
108
 
92
109
  return rpcStream;
@@ -99,7 +116,13 @@ export class WebRTCTransportService implements BridgeService {
99
116
 
100
117
  async sendData({ proxyId, payload }: DataRequest): Promise<void> {
101
118
  invariant(this.transports.has(proxyId));
102
- await this.transports.get(proxyId)!.stream.push(payload);
119
+ const state = this.transports.get(proxyId)!;
120
+ const bufferHasSpace = state.stream.push(payload);
121
+ if (!bufferHasSpace) {
122
+ await new Promise<void>((resolve) => {
123
+ state.writeCallbacks.push(resolve);
124
+ });
125
+ }
103
126
  }
104
127
 
105
128
  async close({ proxyId }: CloseRequest) {
@@ -3,10 +3,10 @@
3
3
  //
4
4
 
5
5
  import SimplePeerConstructor, { Instance as SimplePeer } from 'simple-peer';
6
- import invariant from 'tiny-invariant';
7
6
 
8
7
  import { Event } from '@dxos/async';
9
8
  import { ErrorStream, raise } from '@dxos/debug';
9
+ import { invariant } from '@dxos/invariant';
10
10
  import { PublicKey } from '@dxos/keys';
11
11
  import { log } from '@dxos/log';
12
12
  import { trace } from '@dxos/protocols';
@@ -17,31 +17,45 @@ import { wrtc } from './webrtc';
17
17
 
18
18
  export type WebRTCTransportParams = {
19
19
  initiator: boolean;
20
- stream: NodeJS.ReadWriteStream;
20
+ // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/RTCPeerConnection
21
21
  webrtcConfig?: any;
22
+ stream: NodeJS.ReadWriteStream;
23
+ // TODO(burdon): Rename onSignal.
22
24
  sendSignal: (signal: Signal) => Promise<void>;
23
25
  };
24
26
 
27
+ // TODO(burdon): Refs:
28
+ // https://webrtc.github.io/samples
29
+ // Simple peer discord.
30
+
25
31
  /**
26
- * Implements Transport for WebRTC. Uses simple-peer under the hood.
32
+ * Implements WebRTC transport using simple-peer (used by WebTorrent).
27
33
  */
28
34
  export class WebRTCTransport implements Transport {
35
+ private readonly _instanceId = PublicKey.random().toHex();
29
36
  private readonly _peer: SimplePeer;
30
- private _closed = false;
31
37
 
38
+ private _closed = false;
32
39
  readonly closed = new Event();
33
40
  readonly connected = new Event();
34
41
  readonly errors = new ErrorStream();
35
42
 
36
- private readonly _instanceId = PublicKey.random().toHex();
37
-
38
43
  constructor(private readonly params: WebRTCTransportParams) {
39
44
  log.trace('dxos.mesh.webrtc-transport.constructor', trace.begin({ id: this._instanceId }));
40
45
  log('created connection', params);
46
+
47
+ // https://www.npmjs.com/package/simple-peer#api
48
+ // NOTE: Set localStorage.debug = 'simple-peer*' to enable debug logging. (Or show verbose in Chrome).
49
+ // View throughput via: chrome://webrtc-internals (data-channel)
41
50
  this._peer = new SimplePeerConstructor({
42
51
  initiator: this.params.initiator,
43
- wrtc: SimplePeerConstructor.WEBRTC_SUPPORT ? undefined : wrtc ?? raise(new Error('wrtc not available')),
44
52
  config: this.params.webrtcConfig,
53
+ // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createDataChannel
54
+ channelConfig: {
55
+ protocol: 'dxos.mesh',
56
+ },
57
+ // Custom WebRTC implementation (for Node); otherwise get-browser-rtc.
58
+ wrtc: SimplePeerConstructor.WEBRTC_SUPPORT ? undefined : wrtc ?? raise(new Error('wrtc not available')),
45
59
  });
46
60
 
47
61
  this._peer.on('signal', async (data) => {