@dxos/network-manager 0.4.10-main.fa5a270 → 0.4.10-main.fe71b4c

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 (47) hide show
  1. package/dist/lib/browser/{chunk-KEN4UCZF.mjs → chunk-OSMVZUEK.mjs} +503 -453
  2. package/dist/lib/browser/chunk-OSMVZUEK.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-5D3NBEP2.cjs → chunk-M4CGCR2I.cjs} +519 -468
  7. package/dist/lib/node/chunk-M4CGCR2I.cjs.map +7 -0
  8. package/dist/lib/node/index.cjs +27 -27
  9. package/dist/lib/node/index.cjs.map +1 -1
  10. package/dist/lib/node/meta.json +1 -1
  11. package/dist/lib/node/testing/index.cjs +18 -18
  12. package/dist/types/src/swarm/connection.d.ts.map +1 -1
  13. package/dist/types/src/swarm/peer.d.ts.map +1 -1
  14. package/dist/types/src/swarm/swarm.d.ts.map +1 -1
  15. package/dist/types/src/transport/libdatachannel-transport.d.ts +20 -19
  16. package/dist/types/src/transport/libdatachannel-transport.d.ts.map +1 -1
  17. package/dist/types/src/transport/memory-transport.d.ts +17 -10
  18. package/dist/types/src/transport/memory-transport.d.ts.map +1 -1
  19. package/dist/types/src/transport/simplepeer-transport-proxy.d.ts +7 -9
  20. package/dist/types/src/transport/simplepeer-transport-proxy.d.ts.map +1 -1
  21. package/dist/types/src/transport/simplepeer-transport-service.d.ts.map +1 -1
  22. package/dist/types/src/transport/simplepeer-transport.d.ts +9 -11
  23. package/dist/types/src/transport/simplepeer-transport.d.ts.map +1 -1
  24. package/dist/types/src/transport/tcp-transport.browser.d.ts +7 -2
  25. package/dist/types/src/transport/tcp-transport.browser.d.ts.map +1 -1
  26. package/dist/types/src/transport/tcp-transport.d.ts +6 -4
  27. package/dist/types/src/transport/tcp-transport.d.ts.map +1 -1
  28. package/dist/types/src/transport/transport.d.ts +18 -12
  29. package/dist/types/src/transport/transport.d.ts.map +1 -1
  30. package/package.json +17 -17
  31. package/src/swarm/connection.ts +7 -5
  32. package/src/swarm/peer.ts +0 -1
  33. package/src/swarm/swarm.ts +1 -0
  34. package/src/transport/libdatachannel-transport.test.ts +14 -9
  35. package/src/transport/libdatachannel-transport.ts +226 -204
  36. package/src/transport/memory-transport.test.ts +10 -7
  37. package/src/transport/memory-transport.ts +49 -42
  38. package/src/transport/simplepeer-transport-proxy-test.ts +13 -8
  39. package/src/transport/simplepeer-transport-proxy.ts +57 -50
  40. package/src/transport/simplepeer-transport-service.ts +5 -2
  41. package/src/transport/simplepeer-transport.test.ts +9 -5
  42. package/src/transport/simplepeer-transport.ts +25 -25
  43. package/src/transport/tcp-transport.browser.ts +10 -5
  44. package/src/transport/tcp-transport.ts +31 -22
  45. package/src/transport/transport.ts +25 -14
  46. package/dist/lib/browser/chunk-KEN4UCZF.mjs.map +0 -7
  47. package/dist/lib/node/chunk-5D3NBEP2.cjs.map +0 -7
@@ -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, type TransportStats } from './transport';
15
+ import { type Transport, type TransportFactory, type TransportOptions } 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.
@@ -31,7 +31,7 @@ const createStreamDelay = (delay: number): NodeJS.ReadWriteStream => {
31
31
  };
32
32
 
33
33
  export const MemoryTransportFactory: TransportFactory = {
34
- createTransport: (params) => new MemoryTransport(params),
34
+ createTransport: (options) => new MemoryTransport(options),
35
35
  };
36
36
 
37
37
  /**
@@ -41,10 +41,6 @@ export class MemoryTransport implements Transport {
41
41
  // TODO(burdon): Remove static properties (inject context into constructor).
42
42
  private static readonly _connections = new ComplexMap<PublicKey, MemoryTransport>(PublicKey.hash);
43
43
 
44
- public readonly closed = new Event<void>();
45
- public readonly connected = new Event<void>();
46
- public readonly errors = new ErrorStream();
47
-
48
44
  @logInfo
49
45
  private readonly _instanceId = PublicKey.random(); // TODO(burdon): Rename peerId? (Use local/remote labels in logs).
50
46
 
@@ -53,37 +49,46 @@ export class MemoryTransport implements Transport {
53
49
  private readonly _outgoingDelay = createStreamDelay(MEMORY_TRANSPORT_DELAY);
54
50
  private readonly _incomingDelay = createStreamDelay(MEMORY_TRANSPORT_DELAY);
55
51
 
56
- private _destroyed = false;
52
+ private _closed = false;
57
53
 
58
54
  @logInfo
59
55
  private _remoteInstanceId!: PublicKey;
60
56
 
61
57
  private _remoteConnection?: MemoryTransport;
62
58
 
63
- constructor(private readonly options: TransportOptions) {
64
- log('creating');
59
+ public readonly closed = new Event<void>();
60
+ public readonly connected = new Event<void>();
61
+ public readonly errors = new ErrorStream();
65
62
 
63
+ constructor(private readonly _options: TransportOptions) {
66
64
  invariant(!MemoryTransport._connections.has(this._instanceId), 'Duplicate memory connection');
67
65
  MemoryTransport._connections.set(this._instanceId, this);
66
+ }
67
+
68
+ get isOpen() {
69
+ // TODO(burdon): Open state?
70
+ return !this._closed;
71
+ }
72
+
73
+ async open() {
74
+ log('opening...');
68
75
 
69
76
  // Initiator will send a signal, the receiver will receive the unique ID and connect the streams.
70
- if (this.options.initiator) {
71
- // prettier-ignore
72
- setTimeout(async () => {
73
- log('sending signal');
74
- void this.options.sendSignal({
75
- payload: { transportId: this._instanceId.toHex() }
76
- }).catch(err => {
77
- if (!this._destroyed) {
78
- this.errors.raise(err);
79
- }
80
- });
81
- });
77
+ if (this._options.initiator) {
78
+ log('sending signal');
79
+ try {
80
+ await this._options.sendSignal({ payload: { transportId: this._instanceId.toHex() } });
81
+ } catch (err) {
82
+ if (!this._closed) {
83
+ this.errors.raise(toError(err));
84
+ }
85
+ }
82
86
  } else {
87
+ // Don't block the open method.
83
88
  this._remote
84
- .wait({ timeout: this.options.timeout ?? 1000 })
89
+ .wait({ timeout: this._options.timeout ?? 1_000 })
85
90
  .then((remoteId) => {
86
- if (this._destroyed) {
91
+ if (this._closed) {
87
92
  return;
88
93
  }
89
94
 
@@ -91,7 +96,7 @@ export class MemoryTransport implements Transport {
91
96
  this._remoteConnection = MemoryTransport._connections.get(this._remoteInstanceId);
92
97
  if (!this._remoteConnection) {
93
98
  // Remote connection was destroyed before we could connect.
94
- this._destroyed = true;
99
+ this._closed = true;
95
100
  this.closed.emit();
96
101
  return;
97
102
  }
@@ -101,17 +106,17 @@ export class MemoryTransport implements Transport {
101
106
  this._remoteConnection._remoteInstanceId = this._instanceId;
102
107
 
103
108
  log('connected');
104
- this.options.stream
109
+ this._options.stream
105
110
  .pipe(this._outgoingDelay)
106
- .pipe(this._remoteConnection.options.stream)
111
+ .pipe(this._remoteConnection._options.stream)
107
112
  .pipe(this._incomingDelay)
108
- .pipe(this.options.stream);
113
+ .pipe(this._options.stream);
109
114
 
110
115
  this.connected.emit();
111
116
  this._remoteConnection.connected.emit();
112
117
  })
113
118
  .catch((err) => {
114
- if (this._destroyed) {
119
+ if (this._closed) {
115
120
  return;
116
121
  }
117
122
 
@@ -120,14 +125,13 @@ export class MemoryTransport implements Transport {
120
125
  }
121
126
  }
122
127
 
123
- async destroy(): Promise<void> {
124
- log('closing');
125
- this._destroyed = true;
128
+ async close() {
129
+ log('closing...');
130
+ this._closed = true;
126
131
 
127
132
  MemoryTransport._connections.delete(this._instanceId);
128
133
  if (this._remoteConnection) {
129
- log('closing');
130
- this._remoteConnection._destroyed = true;
134
+ this._remoteConnection._closed = true;
131
135
  MemoryTransport._connections.delete(this._remoteInstanceId);
132
136
 
133
137
  // TODO(dmaretskyi): Hypercore streams do not seem to have the unpipe method.
@@ -138,27 +142,27 @@ export class MemoryTransport implements Transport {
138
142
  // code .unpipe(this._incomingDelay)
139
143
  // code .unpipe(this._stream);
140
144
 
141
- this.options.stream.unpipe(this._incomingDelay);
142
- this._incomingDelay.unpipe(this._remoteConnection.options.stream);
143
- this._remoteConnection.options.stream.unpipe(this._outgoingDelay);
144
- this._outgoingDelay.unpipe(this.options.stream);
145
- this.options.stream.unpipe(this._outgoingDelay);
145
+ this._options.stream.unpipe(this._incomingDelay);
146
+ this._incomingDelay.unpipe(this._remoteConnection._options.stream);
147
+ this._remoteConnection._options.stream.unpipe(this._outgoingDelay);
148
+ this._outgoingDelay.unpipe(this._options.stream);
149
+ this._options.stream.unpipe(this._outgoingDelay);
146
150
 
147
151
  this._remoteConnection.closed.emit();
148
152
  this._remoteConnection._remoteConnection = undefined;
149
153
  this._remoteConnection = undefined;
150
- log('closed');
151
154
  }
152
155
 
153
156
  this.closed.emit();
154
157
  log('closed');
155
158
  }
156
159
 
157
- signal({ payload }: Signal) {
160
+ async onSignal({ payload }: Signal) {
158
161
  log('received signal', { payload });
159
162
  if (!payload?.transportId) {
160
163
  return;
161
164
  }
165
+
162
166
  // TODO(burdon): Check open?
163
167
  const transportId = payload.transportId as string;
164
168
  if (transportId) {
@@ -167,11 +171,11 @@ export class MemoryTransport implements Transport {
167
171
  }
168
172
  }
169
173
 
170
- async getDetails(): Promise<string> {
174
+ async getDetails() {
171
175
  return this._instanceId.toHex();
172
176
  }
173
177
 
174
- async getStats(): Promise<TransportStats> {
178
+ async getStats() {
175
179
  return {
176
180
  bytesSent: 0,
177
181
  bytesReceived: 0,
@@ -180,3 +184,6 @@ export class MemoryTransport implements Transport {
180
184
  };
181
185
  }
182
186
  }
187
+
188
+ // TODO(burdon): Factor out.
189
+ const toError = (err: any): Error => (err instanceof Error ? err : new Error(String(err)));
@@ -65,7 +65,8 @@ describe('SimplePeerTransportProxy', () => {
65
65
  sendSignal,
66
66
  bridgeService: rpcClient.rpc.BridgeService,
67
67
  });
68
- afterTest(async () => await simplePeerTransportProxy.destroy());
68
+ await simplePeerTransportProxy.open();
69
+ afterTest(async () => await simplePeerTransportProxy.close());
69
70
 
70
71
  return { simplePeerService: rpcService, SimplePeerTransportProxy: simplePeerTransportProxy };
71
72
  };
@@ -75,7 +76,7 @@ describe('SimplePeerTransportProxy', () => {
75
76
  const { SimplePeerTransportProxy: connection } = await setupProxy();
76
77
 
77
78
  const wait = connection.closed.waitForCount(1);
78
- await connection.destroy();
79
+ await connection.close();
79
80
  await wait;
80
81
  }).timeout(1_000);
81
82
 
@@ -85,7 +86,7 @@ describe('SimplePeerTransportProxy', () => {
85
86
  initiator: true,
86
87
  stream: stream1,
87
88
  sendSignal: async (signal) => {
88
- connection2.signal(signal);
89
+ await connection2.onSignal(signal);
89
90
  },
90
91
  });
91
92
  afterTest(() => connection1.errors.assertNoUnhandledErrors());
@@ -95,10 +96,11 @@ describe('SimplePeerTransportProxy', () => {
95
96
  initiator: false,
96
97
  stream: stream2,
97
98
  sendSignal: async (signal) => {
98
- connection1.signal(signal);
99
+ await connection1.onSignal(signal);
99
100
  },
100
101
  });
101
102
  afterTest(() => connection2.errors.assertNoUnhandledErrors());
103
+
102
104
  await TestStream.assertConnectivity(stream1, stream2);
103
105
  }).timeout(2_000);
104
106
 
@@ -147,13 +149,13 @@ describe('SimplePeerTransportProxy', () => {
147
149
  initiator: true,
148
150
  stream: stream1,
149
151
  sendSignal: async (signal) => {
150
- proxy2.signal(signal);
152
+ await proxy2.onSignal(signal);
151
153
  },
152
154
  bridgeService: rpcClient.rpc.BridgeService,
153
155
  });
154
156
  afterTest(async () => {
155
157
  proxy1.errors.assertNoUnhandledErrors();
156
- await proxy1.destroy();
158
+ await proxy1.close();
157
159
  });
158
160
 
159
161
  const stream2 = new TestStream();
@@ -161,15 +163,18 @@ describe('SimplePeerTransportProxy', () => {
161
163
  initiator: false,
162
164
  stream: stream2,
163
165
  sendSignal: async (signal) => {
164
- proxy1.signal(signal);
166
+ await proxy1.onSignal(signal);
165
167
  },
166
168
  bridgeService: rpcClient.rpc.BridgeService,
167
169
  });
168
170
  afterTest(async () => {
169
171
  proxy2.errors.assertNoUnhandledErrors();
170
- await proxy2.destroy();
172
+ await proxy2.close();
171
173
  });
172
174
 
175
+ await proxy1.open();
176
+ await proxy2.open();
177
+
173
178
  await TestStream.assertConnectivity(stream1, stream2);
174
179
  }).timeout(3_000);
175
180
  });
@@ -24,15 +24,12 @@ import { arrayToBuffer } from '@dxos/util';
24
24
 
25
25
  import { type Transport, type TransportFactory, type TransportOptions, type TransportStats } from './transport';
26
26
 
27
+ const RPC_TIMEOUT = 10_000;
27
28
  const RESP_MIN_THRESHOLD = 500;
28
29
  const TIMEOUT_THRESHOLD = 10;
29
30
 
30
- export type SimplePeerTransportProxyParams = {
31
- initiator: boolean;
32
- stream: NodeJS.ReadWriteStream;
31
+ export type SimplePeerTransportProxyOptions = TransportOptions & {
33
32
  bridgeService: BridgeService;
34
- // TODO(burdon): Rename onSignal.
35
- sendSignal: (signal: Signal) => Promise<void>;
36
33
  };
37
34
 
38
35
  export class SimplePeerTransportProxy implements Transport {
@@ -47,11 +44,21 @@ export class SimplePeerTransportProxy implements Transport {
47
44
  private _closed = false;
48
45
  private _serviceStream!: Stream<BridgeEvent>;
49
46
 
50
- constructor(private readonly _params: SimplePeerTransportProxyParams) {
51
- this._serviceStream = this._params.bridgeService.open({
52
- proxyId: this._proxyId,
53
- initiator: this._params.initiator,
54
- });
47
+ constructor(private readonly _options: SimplePeerTransportProxyOptions) {}
48
+
49
+ get isOpen() {
50
+ // TODO(burdon): Open state?
51
+ return !this._closed;
52
+ }
53
+
54
+ async open() {
55
+ this._serviceStream = this._options.bridgeService.open(
56
+ {
57
+ proxyId: this._proxyId,
58
+ initiator: this._options.initiator,
59
+ },
60
+ { timeout: RPC_TIMEOUT },
61
+ );
55
62
 
56
63
  this._serviceStream.waitUntilReady().then(
57
64
  () => {
@@ -69,13 +76,13 @@ export class SimplePeerTransportProxy implements Transport {
69
76
  const proxyStream = new Writable({
70
77
  write: (chunk, _, callback) => {
71
78
  const then = performance.now();
72
- this._params.bridgeService
79
+ this._options.bridgeService
73
80
  .sendData(
74
81
  {
75
82
  proxyId: this._proxyId,
76
83
  payload: chunk,
77
84
  },
78
- { timeout: 10_000 },
85
+ { timeout: RPC_TIMEOUT },
79
86
  )
80
87
  .then(
81
88
  () => {
@@ -90,7 +97,7 @@ export class SimplePeerTransportProxy implements Transport {
90
97
  (err: any) => {
91
98
  if (err instanceof TimeoutError || err.constructor.name === 'TimeoutError') {
92
99
  if (this._timeoutCount++ > TIMEOUT_THRESHOLD) {
93
- throw new TimeoutError(`too many timeoutes (${this._timeoutCount} > ${TIMEOUT_THRESHOLD}`);
100
+ throw new TimeoutError(`too many timeouts (${this._timeoutCount} > ${TIMEOUT_THRESHOLD}`);
94
101
  } else {
95
102
  log('timeout error, but still invoking callback');
96
103
  callback();
@@ -107,12 +114,42 @@ export class SimplePeerTransportProxy implements Transport {
107
114
  log('proxystream error', { err });
108
115
  });
109
116
 
110
- this._params.stream.pipe(proxyStream);
117
+ this._options.stream.pipe(proxyStream);
111
118
  },
112
119
  (error) => log.catch(error),
113
120
  );
114
121
  }
115
122
 
123
+ async close() {
124
+ await this._ctx.dispose();
125
+ if (this._closed) {
126
+ return;
127
+ }
128
+
129
+ await this._serviceStream.close();
130
+
131
+ try {
132
+ await this._options.bridgeService.close({ proxyId: this._proxyId }, { timeout: RPC_TIMEOUT });
133
+ } catch (err: any) {
134
+ log.catch(err);
135
+ }
136
+
137
+ this.closed.emit();
138
+ this._closed = true;
139
+ }
140
+
141
+ async onSignal(signal: Signal) {
142
+ this._options.bridgeService
143
+ .sendSignal(
144
+ {
145
+ proxyId: this._proxyId,
146
+ signal,
147
+ },
148
+ { timeout: RPC_TIMEOUT },
149
+ )
150
+ .catch((err) => this.errors.raise(decodeError(err)));
151
+ }
152
+
116
153
  private async _handleConnection(connectionEvent: BridgeEvent.ConnectionEvent): Promise<void> {
117
154
  if (connectionEvent.error) {
118
155
  this.errors.raise(decodeError(connectionEvent.error));
@@ -124,7 +161,7 @@ export class SimplePeerTransportProxy implements Transport {
124
161
  break;
125
162
  }
126
163
  case ConnectionState.CLOSED: {
127
- await this.destroy();
164
+ await this.close();
128
165
  break;
129
166
  }
130
167
  }
@@ -132,47 +169,20 @@ export class SimplePeerTransportProxy implements Transport {
132
169
 
133
170
  private _handleData(dataEvent: BridgeEvent.DataEvent) {
134
171
  // NOTE: This must be a Buffer otherwise hypercore-protocol breaks.
135
- this._params.stream.write(arrayToBuffer(dataEvent.payload));
172
+ this._options.stream.write(arrayToBuffer(dataEvent.payload));
136
173
  }
137
174
 
138
175
  private async _handleSignal(signalEvent: BridgeEvent.SignalEvent) {
139
- await this._params.sendSignal(signalEvent.payload);
140
- }
141
-
142
- signal(signal: Signal): void {
143
- this._params.bridgeService
144
- .sendSignal({
145
- proxyId: this._proxyId,
146
- signal,
147
- })
148
- .catch((err) => this.errors.raise(decodeError(err)));
176
+ await this._options.sendSignal(signalEvent.payload);
149
177
  }
150
178
 
151
179
  async getDetails(): Promise<string> {
152
- return (await this._params.bridgeService.getDetails({ proxyId: this._proxyId })).details;
180
+ return (await this._options.bridgeService.getDetails({ proxyId: this._proxyId }, { timeout: RPC_TIMEOUT })).details;
153
181
  }
154
182
 
155
183
  async getStats(): Promise<TransportStats> {
156
- return (await this._params.bridgeService.getStats({ proxyId: this._proxyId })).stats as TransportStats;
157
- }
158
-
159
- // TODO(burdon): Move open from constructor.
160
- async destroy(): Promise<void> {
161
- await this._ctx.dispose();
162
- if (this._closed) {
163
- return;
164
- }
165
-
166
- await this._serviceStream.close();
167
-
168
- try {
169
- await this._params.bridgeService.close({ proxyId: this._proxyId });
170
- } catch (err: any) {
171
- log.catch(err);
172
- }
173
-
174
- this.closed.emit();
175
- this._closed = true;
184
+ return (await this._options.bridgeService.getStats({ proxyId: this._proxyId }, { timeout: RPC_TIMEOUT }))
185
+ .stats as TransportStats;
176
186
  }
177
187
 
178
188
  /**
@@ -206,7 +216,6 @@ export class SimplePeerTransportProxyFactory implements TransportFactory {
206
216
 
207
217
  createTransport(options: TransportOptions): Transport {
208
218
  invariant(this._bridgeService, 'SimplePeerTransportProxyFactory is not ready to open connections');
209
-
210
219
  const transport = new SimplePeerTransportProxy({
211
220
  ...options,
212
221
  bridgeService: this._bridgeService,
@@ -214,7 +223,6 @@ export class SimplePeerTransportProxyFactory implements TransportFactory {
214
223
 
215
224
  this._connections.add(transport);
216
225
  transport.closed.on(() => this._connections.delete(transport));
217
-
218
226
  return transport;
219
227
  }
220
228
  }
@@ -222,7 +230,6 @@ export class SimplePeerTransportProxyFactory implements TransportFactory {
222
230
  // TODO(nf): fix so Errors crossing RPC boundary preserve class
223
231
  const decodeError = (err: Error | string) => {
224
232
  const message = typeof err === 'string' ? err : err.message;
225
-
226
233
  if (message.includes('CONNECTION_RESET')) {
227
234
  return new ConnectionResetError(message);
228
235
  } else if (message.includes('TIMEOUT')) {
@@ -64,6 +64,9 @@ export class SimplePeerTransportService implements BridgeService {
64
64
  },
65
65
  });
66
66
 
67
+ // TODO(burdon): Async.
68
+ void transport.open();
69
+
67
70
  next({
68
71
  connection: {
69
72
  state: ConnectionState.CONNECTING,
@@ -114,7 +117,7 @@ export class SimplePeerTransportService implements BridgeService {
114
117
 
115
118
  async sendSignal({ proxyId, signal }: SignalRequest): Promise<void> {
116
119
  invariant(this.transports.has(proxyId));
117
- await this.transports.get(proxyId)!.transport.signal(signal);
120
+ await this.transports.get(proxyId)!.transport.onSignal(signal);
118
121
  }
119
122
 
120
123
  async getDetails({ proxyId }: DetailsRequest): Promise<DetailsResponse> {
@@ -142,7 +145,7 @@ export class SimplePeerTransportService implements BridgeService {
142
145
  }
143
146
 
144
147
  async close({ proxyId }: CloseRequest) {
145
- await this.transports.get(proxyId)?.transport.destroy();
148
+ await this.transports.get(proxyId)?.transport.close();
146
149
  await this.transports.get(proxyId)?.stream.end();
147
150
  if (this.transports.get(proxyId)) {
148
151
  this.transports.get(proxyId)!.state = 'CLOSED';
@@ -18,8 +18,9 @@ describe('SimplePeerTransport', () => {
18
18
  sendSignal: async () => {},
19
19
  });
20
20
 
21
+ await connection.open();
21
22
  const wait = connection.closed.waitForCount(1);
22
- await connection.destroy();
23
+ await connection.close();
23
24
  await wait;
24
25
  })
25
26
  .timeout(1_000)
@@ -32,10 +33,10 @@ describe('SimplePeerTransport', () => {
32
33
  stream: stream1,
33
34
  sendSignal: async (signal) => {
34
35
  await sleep(10);
35
- await connection2.signal(signal);
36
+ await connection2.onSignal(signal);
36
37
  },
37
38
  });
38
- afterTest(() => connection1.destroy());
39
+ afterTest(() => connection1.close());
39
40
  afterTest(() => connection1.errors.assertNoUnhandledErrors());
40
41
 
41
42
  const stream2 = new TestStream();
@@ -44,12 +45,15 @@ describe('SimplePeerTransport', () => {
44
45
  stream: stream2,
45
46
  sendSignal: async (signal) => {
46
47
  await sleep(10);
47
- await connection1.signal(signal);
48
+ await connection1.onSignal(signal);
48
49
  },
49
50
  });
50
- afterTest(() => connection2.destroy());
51
+ afterTest(() => connection2.close());
51
52
  afterTest(() => connection2.errors.assertNoUnhandledErrors());
52
53
 
54
+ await connection1.open();
55
+ await connection2.open();
56
+
53
57
  await TestStream.assertConnectivity(stream1, stream2);
54
58
  })
55
59
  .timeout(2_000)
@@ -13,16 +13,17 @@ 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, type TransportStats } from './transport';
16
+ import { type Transport, type TransportFactory, type TransportOptions, type TransportStats } from './transport';
17
17
  import { wrtc } from './webrtc';
18
18
 
19
- export type SimplePeerTransportParams = {
20
- initiator: boolean;
21
- stream: NodeJS.ReadWriteStream;
19
+ export type SimplePeerTransportParams = TransportOptions & {
22
20
  webrtcConfig?: any;
23
- sendSignal: (signal: Signal) => Promise<void>;
24
21
  };
25
22
 
23
+ export const createSimplePeerTransportFactory = (webrtcConfig?: any): TransportFactory => ({
24
+ createTransport: (options) => new SimplePeerTransport({ ...options, webrtcConfig }),
25
+ });
26
+
26
27
  /**
27
28
  * Implements Transport for WebRTC. Uses simple-peer under the hood.
28
29
  */
@@ -37,34 +38,39 @@ export class SimplePeerTransport implements Transport {
37
38
 
38
39
  private readonly _instanceId = PublicKey.random().toHex();
39
40
 
41
+ get isOpen() {
42
+ // TODO(burdon): Open state?
43
+ return this._piped && !this._closed;
44
+ }
45
+
40
46
  /**
41
47
  * @params opts.config formatted as per https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/RTCPeerConnection
42
48
  */
43
- constructor(private readonly params: SimplePeerTransportParams) {
49
+ constructor(private readonly _params: SimplePeerTransportParams) {
44
50
  log.trace('dxos.mesh.webrtc-transport.constructor', trace.begin({ id: this._instanceId }));
45
- log('created connection', params);
51
+ log('created connection', _params);
46
52
  this._peer = new SimplePeerConstructor({
47
53
  channelName: 'dxos.mesh.transport',
48
- initiator: this.params.initiator,
54
+ initiator: this._params.initiator,
49
55
  wrtc: SimplePeerConstructor.WEBRTC_SUPPORT ? undefined : wrtc ?? raise(new Error('wrtc not available')),
50
- config: this.params.webrtcConfig,
56
+ config: this._params.webrtcConfig,
51
57
  });
52
58
 
53
59
  this._peer.on('signal', async (data) => {
54
60
  log('signal', data);
55
- await this.params.sendSignal({ payload: { data } });
61
+ await this._params.sendSignal({ payload: { data } });
56
62
  });
57
63
 
58
64
  this._peer.on('connect', () => {
59
65
  log('connected');
60
- this.params.stream.pipe(this._peer!).pipe(this.params.stream);
66
+ this._params.stream.pipe(this._peer!).pipe(this._params.stream);
61
67
  this._piped = true;
62
68
  this.connected.emit();
63
69
  });
64
70
 
65
71
  this._peer.on('close', async () => {
66
72
  log('closed');
67
- await this.destroy();
73
+ await this.close();
68
74
  });
69
75
 
70
76
  this._peer.on('error', async (err) => {
@@ -114,7 +120,7 @@ export class SimplePeerTransport implements Transport {
114
120
  if (typeof (this._peer as any)?._pc?.getStats === 'function') {
115
121
  (this._peer as any)._pc.getStats().then((stats: any) => {
116
122
  log.info('report after webrtc error', {
117
- config: this.params.webrtcConfig,
123
+ config: this._params.webrtcConfig,
118
124
  stats: Object.fromEntries((stats as any).entries()),
119
125
  });
120
126
  });
@@ -123,7 +129,7 @@ export class SimplePeerTransport implements Transport {
123
129
  log.catch(err);
124
130
  }
125
131
 
126
- await this.destroy();
132
+ await this.close();
127
133
  });
128
134
 
129
135
  log.trace('dxos.mesh.webrtc-transport.constructor', trace.end({ id: this._instanceId }));
@@ -188,7 +194,9 @@ export class SimplePeerTransport implements Transport {
188
194
  return `${rc.ip}:${rc.port}/${rc.protocol} ${rc.candidateType}`;
189
195
  }
190
196
 
191
- async destroy() {
197
+ async open() {}
198
+
199
+ async close() {
192
200
  log('closing...');
193
201
  if (this._closed) {
194
202
  return;
@@ -200,7 +208,7 @@ export class SimplePeerTransport implements Transport {
200
208
  log('closed');
201
209
  }
202
210
 
203
- signal(signal: Signal) {
211
+ async onSignal(signal: Signal) {
204
212
  if (this._closed) {
205
213
  return; // Ignore signals after close.
206
214
  }
@@ -212,15 +220,7 @@ export class SimplePeerTransport implements Transport {
212
220
  private _disconnectStreams() {
213
221
  // TODO(rzadp): Find a way of unpiping this?
214
222
  if (this._piped) {
215
- this.params.stream.unpipe?.(this._peer)?.unpipe?.(this.params.stream);
223
+ this._params.stream.unpipe?.(this._peer)?.unpipe?.(this._params.stream);
216
224
  }
217
225
  }
218
226
  }
219
-
220
- export const createSimplePeerTransportFactory = (webrtcConfig?: any): TransportFactory => ({
221
- createTransport: (params) =>
222
- new SimplePeerTransport({
223
- ...params,
224
- webrtcConfig,
225
- }),
226
- });