@dxos/network-manager 0.3.9-main.3a0f16f → 0.3.9-main.4e30508

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 (58) hide show
  1. package/dist/lib/browser/{chunk-4HITD6WI.mjs → chunk-J7KS7JMZ.mjs} +325 -87
  2. package/dist/lib/browser/chunk-J7KS7JMZ.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 +1 -1
  6. package/dist/lib/node/{chunk-6AD7QA4X.cjs → chunk-55WTPQV5.cjs} +334 -86
  7. package/dist/lib/node/chunk-55WTPQV5.cjs.map +7 -0
  8. package/dist/lib/node/{chunk-XE5JOIGD.cjs → chunk-JDKM4UPJ.cjs} +21 -13
  9. package/dist/lib/node/chunk-JDKM4UPJ.cjs.map +7 -0
  10. package/dist/lib/node/{datachannel-53JZPGFA.cjs → datachannel-3VTTIMR5.cjs} +9 -9
  11. package/dist/lib/node/{datachannel-53JZPGFA.cjs.map → datachannel-3VTTIMR5.cjs.map} +2 -2
  12. package/dist/lib/node/index.cjs +32 -32
  13. package/dist/lib/node/index.cjs.map +1 -1
  14. package/dist/lib/node/meta.json +1 -1
  15. package/dist/lib/node/testing/index.cjs +19 -19
  16. package/dist/types/src/connection-log.d.ts.map +1 -1
  17. package/dist/types/src/swarm/connection.d.ts +4 -1
  18. package/dist/types/src/swarm/connection.d.ts.map +1 -1
  19. package/dist/types/src/topology/mmst-topology.d.ts +1 -0
  20. package/dist/types/src/topology/mmst-topology.d.ts.map +1 -1
  21. package/dist/types/src/transport/datachannel/rtc-peer-connection.d.ts +7 -0
  22. package/dist/types/src/transport/datachannel/rtc-peer-connection.d.ts.map +1 -1
  23. package/dist/types/src/transport/libdatachannel-transport.d.ts +3 -1
  24. package/dist/types/src/transport/libdatachannel-transport.d.ts.map +1 -1
  25. package/dist/types/src/transport/memory-transport.d.ts +3 -1
  26. package/dist/types/src/transport/memory-transport.d.ts.map +1 -1
  27. package/dist/types/src/transport/simplepeer-transport-proxy.d.ts +3 -1
  28. package/dist/types/src/transport/simplepeer-transport-proxy.d.ts.map +1 -1
  29. package/dist/types/src/transport/simplepeer-transport-service.d.ts +3 -1
  30. package/dist/types/src/transport/simplepeer-transport-service.d.ts.map +1 -1
  31. package/dist/types/src/transport/simplepeer-transport.d.ts +7 -1
  32. package/dist/types/src/transport/simplepeer-transport.d.ts.map +1 -1
  33. package/dist/types/src/transport/tcp-transport.browser.d.ts +3 -1
  34. package/dist/types/src/transport/tcp-transport.browser.d.ts.map +1 -1
  35. package/dist/types/src/transport/tcp-transport.d.ts +3 -1
  36. package/dist/types/src/transport/tcp-transport.d.ts.map +1 -1
  37. package/dist/types/src/transport/transport.d.ts +17 -0
  38. package/dist/types/src/transport/transport.d.ts.map +1 -1
  39. package/dist/types/src/wire-protocol.d.ts +1 -1
  40. package/dist/types/src/wire-protocol.d.ts.map +1 -1
  41. package/package.json +17 -17
  42. package/src/connection-log.ts +14 -1
  43. package/src/swarm/connection.ts +23 -4
  44. package/src/topology/mmst-topology.ts +35 -7
  45. package/src/transport/datachannel/rtc-peer-connection.ts +20 -12
  46. package/src/transport/libdatachannel-transport.test.ts +0 -4
  47. package/src/transport/libdatachannel-transport.ts +37 -1
  48. package/src/transport/memory-transport.ts +14 -1
  49. package/src/transport/simplepeer-transport-proxy.ts +9 -1
  50. package/src/transport/simplepeer-transport-service.ts +14 -0
  51. package/src/transport/simplepeer-transport.ts +66 -2
  52. package/src/transport/tcp-transport.browser.ts +9 -1
  53. package/src/transport/tcp-transport.ts +19 -1
  54. package/src/transport/transport.ts +19 -0
  55. package/src/wire-protocol.ts +3 -3
  56. package/dist/lib/browser/chunk-4HITD6WI.mjs.map +0 -7
  57. package/dist/lib/node/chunk-6AD7QA4X.cjs.map +0 -7
  58. package/dist/lib/node/chunk-XE5JOIGD.cjs.map +0 -7
@@ -2,7 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import node from 'node-datachannel';
5
+ import node, { type SelectedCandidateInfo } from 'node-datachannel';
6
6
  import defer, { type DeferredPromise } from 'p-defer';
7
7
 
8
8
  import { DataChannel } from './rtc-data-channel';
@@ -45,21 +45,17 @@ export class PeerConnection extends EventTarget implements RTCPeerConnection {
45
45
  const iceServers = init.iceServers ?? [];
46
46
 
47
47
  this.#peerConnection = new node.PeerConnection(`peer-${Math.random()}`, {
48
+ // convert https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/RTCPeerConnection#iceservers to the format expected by https://github.com/murat-dogan/node-datachannel/blob/master/src/peer-connection-wrapper.cpp#L101
48
49
  iceServers: iceServers
49
50
  .map((server) => {
50
- const urls = (Array.isArray(server.urls) ? server.urls : [server.urls]).map((str) => new URL(str));
51
+ const urls = Array.isArray(server.urls) ? server.urls : [server.urls];
51
52
 
52
53
  return urls.map((url) => {
53
- /** @type {import('../lib/index.js').IceServer} */
54
- const iceServer = {
55
- hostname: url.hostname,
56
- port: parseInt(url.port, 10),
57
- username: server.username,
58
- password: server.credential,
59
- // relayType - how to specify?
60
- };
61
-
62
- return iceServer;
54
+ if (server.username && server.credential) {
55
+ const [protocol, rest] = url.split(/:(.*)/);
56
+ return `${protocol}:${server.username}:${server.credential}@${rest}`;
57
+ }
58
+ return url;
63
59
  });
64
60
  })
65
61
  .flat(),
@@ -188,6 +184,10 @@ export class PeerConnection extends EventTarget implements RTCPeerConnection {
188
184
  this.#peerConnection.addRemoteCandidate(candidate.candidate, candidate.sdpMid ?? '0');
189
185
  }
190
186
 
187
+ getSelectedCandidatePair(): { local: SelectedCandidateInfo; remote: SelectedCandidateInfo } | null {
188
+ return this.#peerConnection.getSelectedCandidatePair();
189
+ }
190
+
191
191
  addTrack(track: MediaStreamTrack, ...streams: MediaStream[]): RTCRtpSender {
192
192
  throw new Error('addTrack Not implemented');
193
193
  }
@@ -206,6 +206,14 @@ export class PeerConnection extends EventTarget implements RTCPeerConnection {
206
206
  this.#peerConnection.destroy();
207
207
  }
208
208
 
209
+ bytesSent(): number {
210
+ return this.#peerConnection.bytesSent();
211
+ }
212
+
213
+ bytesReceived(): number {
214
+ return this.#peerConnection.bytesReceived();
215
+ }
216
+
209
217
  createDataChannel(label: string, dataChannelDict: RTCDataChannelInit = {}): RTCDataChannel {
210
218
  const channel = this.#peerConnection.createDataChannel(label, dataChannelDict);
211
219
  const dataChannel = new DataChannel(channel, dataChannelDict);
@@ -2,10 +2,6 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- //
6
- // Copyright 2020 DXOS.org
7
- //
8
-
9
5
  import { Duplex } from 'stream';
10
6
 
11
7
  import { sleep, TestStream } from '@dxos/async';
@@ -10,7 +10,7 @@ import { log } from '@dxos/log';
10
10
  import { type Signal } from '@dxos/protocols/proto/dxos/mesh/swarm';
11
11
 
12
12
  import { type PeerConnection } from './datachannel/rtc-peer-connection';
13
- import { type Transport, type TransportFactory } from './transport';
13
+ import { type Transport, type TransportFactory, type TransportStats } from './transport';
14
14
 
15
15
  export type LibDataChannelTransportParams = {
16
16
  initiator: boolean;
@@ -237,6 +237,42 @@ export class LibDataChannelTransport implements Transport {
237
237
  });
238
238
  }
239
239
 
240
+ async getDetails(): Promise<string> {
241
+ return this._peer
242
+ .then(async (peer) => {
243
+ const cp = await peer.getSelectedCandidatePair();
244
+ if (!cp) {
245
+ return 'unavailable';
246
+ }
247
+ return `${cp.remote.address}:${cp.remote.port}/${cp.remote.transportType} ${cp.remote.type}`;
248
+ })
249
+ .catch((err) => {
250
+ log.catch(err);
251
+ return 'unavailable';
252
+ });
253
+ }
254
+
255
+ async getStats(): Promise<TransportStats> {
256
+ return this._peer
257
+ .then((peer) => {
258
+ return {
259
+ bytesSent: peer.bytesSent(),
260
+ bytesReceived: peer.bytesReceived(),
261
+ packetsSent: 0,
262
+ packetsReceived: 0,
263
+ };
264
+ })
265
+ .catch((err) => {
266
+ log.catch(err);
267
+ return {
268
+ bytesSent: 0,
269
+ bytesReceived: 0,
270
+ packetsSent: 0,
271
+ packetsReceived: 0,
272
+ };
273
+ });
274
+ }
275
+
240
276
  // TODO(nf): add classmethod to call node-datachannel.cleanup() when all instances have been destroyed?
241
277
  async destroy(): Promise<void> {
242
278
  await this._close();
@@ -12,7 +12,7 @@ import { log, logInfo } from '@dxos/log';
12
12
  import { type Signal } from '@dxos/protocols/proto/dxos/mesh/swarm';
13
13
  import { ComplexMap } from '@dxos/util';
14
14
 
15
- import { type Transport, type TransportFactory, type TransportOptions } from './transport';
15
+ import { type Transport, type TransportFactory, type TransportOptions, type TransportStats } from './transport';
16
16
 
17
17
  // TODO(burdon): Make configurable.
18
18
  // Delay (in milliseconds) for data being sent through in-memory connections to simulate network latency.
@@ -166,4 +166,17 @@ export class MemoryTransport implements Transport {
166
166
  this._remote.wake(remoteId);
167
167
  }
168
168
  }
169
+
170
+ async getDetails(): Promise<string> {
171
+ return this._instanceId.toHex();
172
+ }
173
+
174
+ async getStats(): Promise<TransportStats> {
175
+ return {
176
+ bytesSent: 0,
177
+ bytesReceived: 0,
178
+ packetsSent: 0,
179
+ packetsReceived: 0,
180
+ };
181
+ }
169
182
  }
@@ -22,7 +22,7 @@ import { ConnectionState, type BridgeEvent, type BridgeService } from '@dxos/pro
22
22
  import { type Signal } from '@dxos/protocols/proto/dxos/mesh/swarm';
23
23
  import { arrayToBuffer } from '@dxos/util';
24
24
 
25
- import { type Transport, type TransportFactory, type TransportOptions } from './transport';
25
+ import { type Transport, type TransportFactory, type TransportOptions, type TransportStats } from './transport';
26
26
 
27
27
  const RESP_MIN_THRESHOLD = 500;
28
28
  const TIMEOUT_THRESHOLD = 10;
@@ -145,6 +145,14 @@ export class SimplePeerTransportProxy implements Transport {
145
145
  .catch((err) => this.errors.raise(decodeError(err)));
146
146
  }
147
147
 
148
+ async getDetails(): Promise<string> {
149
+ return (await this._params.bridgeService.getDetails({ proxyId: this._proxyId })).details;
150
+ }
151
+
152
+ async getStats(): Promise<TransportStats> {
153
+ return (await this._params.bridgeService.getStats({ proxyId: this._proxyId })).stats as TransportStats;
154
+ }
155
+
148
156
  // TODO(burdon): Move open from constructor.
149
157
  async destroy(): Promise<void> {
150
158
  await this._ctx.dispose();
@@ -16,6 +16,10 @@ import {
16
16
  type BridgeEvent,
17
17
  ConnectionState,
18
18
  type CloseRequest,
19
+ type DetailsRequest,
20
+ type DetailsResponse,
21
+ type StatsRequest,
22
+ type StatsResponse,
19
23
  } from '@dxos/protocols/proto/dxos/mesh/bridge';
20
24
  import { ComplexMap } from '@dxos/util';
21
25
 
@@ -113,6 +117,16 @@ export class SimplePeerTransportService implements BridgeService {
113
117
  await this.transports.get(proxyId)!.transport.signal(signal);
114
118
  }
115
119
 
120
+ async getDetails({ proxyId }: DetailsRequest): Promise<DetailsResponse> {
121
+ invariant(this.transports.has(proxyId));
122
+ return { details: await this.transports.get(proxyId)!.transport.getDetails() };
123
+ }
124
+
125
+ async getStats({ proxyId }: StatsRequest): Promise<StatsResponse> {
126
+ invariant(this.transports.has(proxyId));
127
+ return { stats: await this.transports.get(proxyId)!.transport.getStats() };
128
+ }
129
+
116
130
  async sendData({ proxyId, payload }: DataRequest): Promise<void> {
117
131
  if (this.transports.get(proxyId)?.state !== 'OPEN') {
118
132
  log.debug('transport is closed');
@@ -13,7 +13,7 @@ import { log } from '@dxos/log';
13
13
  import { ConnectionResetError, ConnectivityError, ProtocolError, UnknownProtocolError, trace } from '@dxos/protocols';
14
14
  import { type Signal } from '@dxos/protocols/proto/dxos/mesh/swarm';
15
15
 
16
- import { type Transport, type TransportFactory } from './transport';
16
+ import { type Transport, type TransportFactory, type TransportStats } from './transport';
17
17
  import { wrtc } from './webrtc';
18
18
 
19
19
  export type SimplePeerTransportParams = {
@@ -37,6 +37,9 @@ export class SimplePeerTransport implements Transport {
37
37
 
38
38
  private readonly _instanceId = PublicKey.random().toHex();
39
39
 
40
+ /**
41
+ * @params opts.config formatted as per https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/RTCPeerConnection
42
+ */
40
43
  constructor(private readonly params: SimplePeerTransportParams) {
41
44
  log.trace('dxos.mesh.webrtc-transport.constructor', trace.begin({ id: this._instanceId }));
42
45
  log('created connection', params);
@@ -112,7 +115,7 @@ export class SimplePeerTransport implements Transport {
112
115
  (this._peer as any)._pc.getStats().then((stats: any) => {
113
116
  log.info('report after webrtc error', {
114
117
  config: this.params.webrtcConfig,
115
- stats,
118
+ stats: Object.fromEntries((stats as any).entries()),
116
119
  });
117
120
  });
118
121
  }
@@ -122,9 +125,69 @@ export class SimplePeerTransport implements Transport {
122
125
 
123
126
  await this.destroy();
124
127
  });
128
+
125
129
  log.trace('dxos.mesh.webrtc-transport.constructor', trace.end({ id: this._instanceId }));
126
130
  }
127
131
 
132
+ async getStats(): Promise<TransportStats> {
133
+ const stats = await this._getStats();
134
+ if (!stats) {
135
+ return {
136
+ bytesSent: 0,
137
+ bytesReceived: 0,
138
+ packetsSent: 0,
139
+ packetsReceived: 0,
140
+ rawStats: {},
141
+ };
142
+ }
143
+
144
+ // TODO(nf): transport or candidatePair?
145
+ return {
146
+ bytesSent: stats.transport.bytesSent,
147
+ bytesReceived: stats.transport.bytesReceived,
148
+ packetsSent: stats.transport.messagesSent,
149
+ packetsReceived: stats.transport.messagesReceived,
150
+ rawStats: stats.raw,
151
+ };
152
+ }
153
+
154
+ async _getStats(): Promise<any> {
155
+ if (typeof (this._peer as any)?._pc?.getStats !== 'function') {
156
+ return null;
157
+ }
158
+ return await (this._peer as any)._pc.getStats().then((stats: any) => {
159
+ const statsEntries = Array.from(stats.entries() as any[]);
160
+ const transport = statsEntries.filter((s: any) => s[1].type === 'transport')[0][1];
161
+ const candidatePair = statsEntries.filter((s: any) => s[0] === transport.selectedCandidatePairId);
162
+ let selectedCandidatePair: any;
163
+ let remoteCandidate: any;
164
+ if (candidatePair.length > 0) {
165
+ selectedCandidatePair = candidatePair[0][1];
166
+ remoteCandidate = statsEntries.filter((s: any) => s[0] === selectedCandidatePair.remoteCandidateId)[0][1];
167
+ }
168
+ return {
169
+ datachannel: statsEntries.filter((s: any) => s[1].type === 'data-channel')[0][1],
170
+ transport,
171
+ selectedCandidatePair,
172
+ remoteCandidate,
173
+ raw: Object.fromEntries(stats.entries()),
174
+ };
175
+ });
176
+ }
177
+
178
+ async getDetails(): Promise<string> {
179
+ const stats = await this._getStats();
180
+ const rc = stats?.remoteCandidate;
181
+ if (!rc) {
182
+ return 'unavailable';
183
+ }
184
+
185
+ if (rc.relay) {
186
+ return `${rc.ip}:${rc.port}/${rc.protocol} relay for XXX ${rc.candidateType}`;
187
+ }
188
+ return `${rc.ip}:${rc.port}/${rc.protocol} ${rc.candidateType}`;
189
+ }
190
+
128
191
  async destroy() {
129
192
  log('closing...');
130
193
  if (this._closed) {
@@ -133,6 +196,7 @@ export class SimplePeerTransport implements Transport {
133
196
  this._closed = true;
134
197
  this._disconnectStreams();
135
198
  this._peer!.destroy();
199
+ this._closed = true;
136
200
  this.closed.emit();
137
201
  log('closed');
138
202
  }
@@ -5,7 +5,7 @@
5
5
  import { Event } from '@dxos/async';
6
6
  import { ErrorStream } from '@dxos/debug';
7
7
 
8
- import { type Transport, type TransportFactory } from './transport';
8
+ import { type Transport, type TransportFactory, type TransportStats } from './transport';
9
9
 
10
10
  // NOTE: Browser stub.
11
11
 
@@ -25,4 +25,12 @@ export class TcpTransport implements Transport {
25
25
  signal() {
26
26
  throw new Error('Method not implemented.');
27
27
  }
28
+
29
+ async getStats(): Promise<TransportStats> {
30
+ throw new Error('Method not implemented.');
31
+ }
32
+
33
+ async getDetails(): Promise<string> {
34
+ throw new Error('Method not implemented.');
35
+ }
28
36
  }
@@ -9,7 +9,7 @@ import { ErrorStream } from '@dxos/debug';
9
9
  import { log } from '@dxos/log';
10
10
  import { type Signal } from '@dxos/protocols/proto/dxos/mesh/swarm';
11
11
 
12
- import { type Transport, type TransportFactory, type TransportOptions } from './transport';
12
+ import { type Transport, type TransportFactory, type TransportOptions, type TransportStats } from './transport';
13
13
 
14
14
  export const TcpTransportFactory: TransportFactory = {
15
15
  createTransport: (params) => new TcpTransport(params),
@@ -87,6 +87,24 @@ export class TcpTransport implements Transport {
87
87
  socket.connect({ port: payload.port, host: 'localhost' });
88
88
  }
89
89
 
90
+ async getDetails(): Promise<string> {
91
+ if (this.options.initiator) {
92
+ const { port, address } = this._server?.address() as AddressInfo;
93
+ return `LISTEN ${address}:${port}`;
94
+ }
95
+ const { port, address } = this._socket?.address() as AddressInfo;
96
+ return `ACCEPT ${address}:${port}`;
97
+ }
98
+
99
+ async getStats(): Promise<TransportStats> {
100
+ return {
101
+ bytesSent: 0,
102
+ bytesReceived: 0,
103
+ packetsSent: 0,
104
+ packetsReceived: 0,
105
+ };
106
+ }
107
+
90
108
  private _handleSocket(socket: Socket) {
91
109
  log('handling socket', { remotePort: socket.remotePort, localPort: socket.localPort });
92
110
  this._socket = socket;
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { type Event } from '@dxos/async';
6
6
  import { type ErrorStream } from '@dxos/debug';
7
+ import { type PublicKey } from '@dxos/keys';
7
8
  import { type Signal } from '@dxos/protocols/proto/dxos/mesh/swarm';
8
9
 
9
10
  export enum TransportKind {
@@ -22,6 +23,14 @@ export interface Transport {
22
23
  connected: Event;
23
24
  errors: ErrorStream;
24
25
 
26
+ /**
27
+ * Transport-specfic stats.
28
+ */
29
+ getStats(): Promise<TransportStats>;
30
+ /**
31
+ * Transport-specfic connection details.
32
+ */
33
+ getDetails(): Promise<string>;
25
34
  destroy(): Promise<void>;
26
35
  signal(signal: Signal): void;
27
36
  }
@@ -43,6 +52,8 @@ export type TransportOptions = {
43
52
  sendSignal: (signal: Signal) => Promise<void>; // TODO(burdon): Remove async?
44
53
 
45
54
  timeout?: number;
55
+
56
+ sessionId?: PublicKey;
46
57
  };
47
58
 
48
59
  /**
@@ -51,3 +62,11 @@ export type TransportOptions = {
51
62
  export interface TransportFactory {
52
63
  createTransport(options: TransportOptions): Transport;
53
64
  }
65
+
66
+ export type TransportStats = {
67
+ bytesSent: number;
68
+ bytesReceived: number;
69
+ packetsSent: number;
70
+ packetsReceived: number;
71
+ rawStats?: any;
72
+ };
@@ -23,7 +23,7 @@ export type WireProtocolProvider = (params: WireProtocolParams) => WireProtocol;
23
23
  export interface WireProtocol {
24
24
  stream: Duplex;
25
25
 
26
- open(): Promise<void>;
26
+ open(sessionId?: PublicKey): Promise<void>;
27
27
  close(): Promise<void>;
28
28
  abort(): Promise<void>;
29
29
  }
@@ -40,8 +40,8 @@ export const createTeleportProtocolFactory = (
40
40
  const teleport = new Teleport(params);
41
41
  return {
42
42
  stream: teleport.stream,
43
- open: async () => {
44
- await teleport.open();
43
+ open: async (sessionId?: PublicKey) => {
44
+ await teleport.open(sessionId);
45
45
  await onConnection(teleport);
46
46
  },
47
47
  close: async () => {