@dxos/network-manager 0.1.57-main.e87098f → 0.1.57

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 (95) hide show
  1. package/dist/lib/browser/{chunk-UAB4ONRL.mjs → chunk-UKMTAO4L.mjs} +553 -121
  2. package/dist/lib/browser/chunk-UKMTAO4L.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-YERMWDRB.mjs +24 -0
  4. package/dist/lib/browser/chunk-YERMWDRB.mjs.map +7 -0
  5. package/dist/lib/browser/index.browser-O53CPVK3.mjs +14 -0
  6. package/dist/lib/browser/index.browser-O53CPVK3.mjs.map +7 -0
  7. package/dist/lib/browser/index.mjs +29 -13
  8. package/dist/lib/browser/meta.json +1 -1
  9. package/dist/lib/browser/testing/index.mjs +83 -38
  10. package/dist/lib/browser/testing/index.mjs.map +3 -3
  11. package/dist/lib/node/index.cjs +1048 -124
  12. package/dist/lib/node/index.cjs.map +4 -4
  13. package/dist/lib/node/meta.json +1 -1
  14. package/dist/lib/node/testing/index.cjs +1109 -151
  15. package/dist/lib/node/testing/index.cjs.map +4 -4
  16. package/dist/types/src/swarm/connection.d.ts +6 -0
  17. package/dist/types/src/swarm/connection.d.ts.map +1 -1
  18. package/dist/types/src/swarm/peer.d.ts.map +1 -1
  19. package/dist/types/src/testing/test-builder.d.ts +5 -2
  20. package/dist/types/src/testing/test-builder.d.ts.map +1 -1
  21. package/dist/types/src/transport/datachannel/index.browser.d.ts +15 -0
  22. package/dist/types/src/transport/datachannel/index.browser.d.ts.map +1 -0
  23. package/dist/types/src/transport/datachannel/index.d.ts +8 -0
  24. package/dist/types/src/transport/datachannel/index.d.ts.map +1 -0
  25. package/dist/types/src/transport/datachannel/rtc-data-channel.d.ts +29 -0
  26. package/dist/types/src/transport/datachannel/rtc-data-channel.d.ts.map +1 -0
  27. package/dist/types/src/transport/datachannel/rtc-events.d.ts +9 -0
  28. package/dist/types/src/transport/datachannel/rtc-events.d.ts.map +1 -0
  29. package/dist/types/src/transport/datachannel/rtc-ice-candidate.d.ts +22 -0
  30. package/dist/types/src/transport/datachannel/rtc-ice-candidate.d.ts.map +1 -0
  31. package/dist/types/src/transport/datachannel/rtc-peer-connection.d.ts +47 -0
  32. package/dist/types/src/transport/datachannel/rtc-peer-connection.d.ts.map +1 -0
  33. package/dist/types/src/transport/datachannel/rtc-peer-connection.test.d.ts +2 -0
  34. package/dist/types/src/transport/datachannel/rtc-peer-connection.test.d.ts.map +1 -0
  35. package/dist/types/src/transport/datachannel/rtc-session-description.d.ts +10 -0
  36. package/dist/types/src/transport/datachannel/rtc-session-description.d.ts.map +1 -0
  37. package/dist/types/src/transport/index.d.ts +5 -3
  38. package/dist/types/src/transport/index.d.ts.map +1 -1
  39. package/dist/types/src/transport/libdatachannel-transport.d.ts +35 -0
  40. package/dist/types/src/transport/libdatachannel-transport.d.ts.map +1 -0
  41. package/dist/types/src/transport/libdatachannel-transport.test.d.ts +2 -0
  42. package/dist/types/src/transport/libdatachannel-transport.test.d.ts.map +1 -0
  43. package/dist/types/src/transport/simplepeer-simple-peer.d.ts +2 -0
  44. package/dist/types/src/transport/simplepeer-simple-peer.d.ts.map +1 -0
  45. package/dist/types/src/transport/simplepeer-transport-proxy-test.d.ts +2 -0
  46. package/dist/types/src/transport/simplepeer-transport-proxy-test.d.ts.map +1 -0
  47. package/dist/types/src/transport/{webrtc-transport-proxy.d.ts → simplepeer-transport-proxy.d.ts} +5 -5
  48. package/dist/types/src/transport/simplepeer-transport-proxy.d.ts.map +1 -0
  49. package/dist/types/src/transport/{webrtc-transport-service.d.ts → simplepeer-transport-service.d.ts} +2 -2
  50. package/dist/types/src/transport/simplepeer-transport-service.d.ts.map +1 -0
  51. package/dist/types/src/transport/{webrtc-transport.d.ts → simplepeer-transport.d.ts} +8 -8
  52. package/dist/types/src/transport/simplepeer-transport.d.ts.map +1 -0
  53. package/dist/types/src/transport/simplepeer-transport.test.d.ts +2 -0
  54. package/dist/types/src/transport/simplepeer-transport.test.d.ts.map +1 -0
  55. package/dist/types/src/transport/transport.d.ts +6 -0
  56. package/dist/types/src/transport/transport.d.ts.map +1 -1
  57. package/dist/types/src/wire-protocol.d.ts +3 -2
  58. package/dist/types/src/wire-protocol.d.ts.map +1 -1
  59. package/package.json +25 -20
  60. package/src/connection-log.ts +1 -1
  61. package/src/swarm/connection.test.ts +8 -3
  62. package/src/swarm/connection.ts +58 -6
  63. package/src/swarm/peer.ts +4 -0
  64. package/src/swarm/swarm.test.ts +3 -2
  65. package/src/testing/test-builder.ts +72 -41
  66. package/src/transport/datachannel/README.md +2 -0
  67. package/src/transport/datachannel/index.browser.ts +8 -0
  68. package/src/transport/datachannel/index.ts +17 -0
  69. package/src/transport/datachannel/rtc-data-channel.ts +148 -0
  70. package/src/transport/datachannel/rtc-events.ts +23 -0
  71. package/src/transport/datachannel/rtc-ice-candidate.ts +54 -0
  72. package/src/transport/datachannel/rtc-peer-connection.test.ts +112 -0
  73. package/src/transport/datachannel/rtc-peer-connection.ts +347 -0
  74. package/src/transport/datachannel/rtc-session-description.ts +23 -0
  75. package/src/transport/index.ts +5 -3
  76. package/src/transport/libdatachannel-transport.test.ts +99 -0
  77. package/src/transport/libdatachannel-transport.ts +252 -0
  78. package/src/transport/{webrtc-transport-proxy.test.ts → simplepeer-transport-proxy-test.ts} +15 -15
  79. package/src/transport/{webrtc-transport-proxy.ts → simplepeer-transport-proxy.ts} +8 -8
  80. package/src/transport/{webrtc-transport-service.ts → simplepeer-transport-service.ts} +4 -4
  81. package/src/transport/{webrtc-transport.test.ts → simplepeer-transport.test.ts} +5 -5
  82. package/src/transport/{webrtc-transport.ts → simplepeer-transport.ts} +13 -27
  83. package/src/transport/transport.ts +7 -0
  84. package/src/wire-protocol.ts +8 -4
  85. package/dist/lib/browser/chunk-UAB4ONRL.mjs.map +0 -7
  86. package/dist/types/src/transport/webrtc-simple-peer.test.d.ts +0 -2
  87. package/dist/types/src/transport/webrtc-simple-peer.test.d.ts.map +0 -1
  88. package/dist/types/src/transport/webrtc-transport-proxy.d.ts.map +0 -1
  89. package/dist/types/src/transport/webrtc-transport-proxy.test.d.ts +0 -2
  90. package/dist/types/src/transport/webrtc-transport-proxy.test.d.ts.map +0 -1
  91. package/dist/types/src/transport/webrtc-transport-service.d.ts.map +0 -1
  92. package/dist/types/src/transport/webrtc-transport.d.ts.map +0 -1
  93. package/dist/types/src/transport/webrtc-transport.test.d.ts +0 -2
  94. package/dist/types/src/transport/webrtc-transport.test.d.ts.map +0 -1
  95. /package/src/transport/{webrtc-simple-peer.test.ts → simplepeer-simple-peer.ts} +0 -0
@@ -8,7 +8,7 @@ import { log } from '@dxos/log';
8
8
  import { describe, test } from '@dxos/test';
9
9
 
10
10
  import { TestWireProtocol } from '../testing/test-wire-protocol';
11
- import { createWebRTCTransportFactory } from '../transport';
11
+ import { createSimplePeerTransportFactory } from '../transport';
12
12
  import { Connection } from './connection';
13
13
 
14
14
  describe('Connection', () => {
@@ -37,7 +37,8 @@ describe('Connection', () => {
37
37
  remotePeerId: peerId2,
38
38
  topic,
39
39
  }),
40
- createWebRTCTransportFactory(),
40
+ // TODO(nf): configure?
41
+ createSimplePeerTransportFactory(),
41
42
  );
42
43
 
43
44
  const protocol2 = new TestWireProtocol(peerId2);
@@ -58,11 +59,15 @@ describe('Connection', () => {
58
59
  },
59
60
  },
60
61
  protocol2.factory({ initiator: false, localPeerId: peerId2, remotePeerId: peerId1, topic }),
61
- createWebRTCTransportFactory(),
62
+ // TODO(nf): configure?
63
+ createSimplePeerTransportFactory(),
62
64
  );
63
65
 
66
+ connection2.initiate();
64
67
  await connection2.openConnection();
65
68
  await sleep(100);
69
+
70
+ connection1.initiate();
66
71
  await connection1.openConnection();
67
72
  await Promise.all([
68
73
  protocol1.testConnection(peerId2, 'test message 1'),
@@ -43,6 +43,11 @@ interface ConnectionCallbacks {
43
43
  * State machine for each connection.
44
44
  */
45
45
  export enum ConnectionState {
46
+ /**
47
+ * Connection is created, but not yet passed through the connection limiter.
48
+ */
49
+ CREATED = 'CREATED',
50
+
46
51
  /**
47
52
  * Initial state. Connection is registered but no attempt to connect to the remote peer has been performed.
48
53
  * Might mean that we are waiting for the answer signal from the remote peer.
@@ -77,7 +82,7 @@ export enum ConnectionState {
77
82
  export class Connection {
78
83
  private readonly _ctx = new Context();
79
84
 
80
- private _state: ConnectionState = ConnectionState.INITIAL;
85
+ private _state: ConnectionState = ConnectionState.CREATED;
81
86
  private _transport: Transport | undefined;
82
87
  closeReason?: string;
83
88
 
@@ -144,7 +149,7 @@ export class Connection {
144
149
  this._changeState(ConnectionState.CONNECTING);
145
150
 
146
151
  // TODO(dmaretskyi): Initialize only after the transport has established connection.
147
- this._protocol.initialize().catch((err) => {
152
+ this._protocol.open().catch((err) => {
148
153
  this.errors.raise(err);
149
154
  });
150
155
 
@@ -168,13 +173,21 @@ export class Connection {
168
173
 
169
174
  this._transport.closed.once(() => {
170
175
  this._transport = undefined;
171
- this.close().catch((err) => this.errors.raise(err));
176
+ log('abort triggered by transport close');
177
+ this.abort().catch((err) => this.errors.raise(err));
172
178
  });
173
179
 
174
180
  this._transport.errors.handle((err) => {
181
+ log('transport error:', { err });
182
+ // TODO(nf): collect a list of errors?
175
183
  if (!this.closeReason) {
176
184
  this.closeReason = err?.message;
177
185
  }
186
+ if (err?.message.includes('OperationError')) {
187
+ log('aborting due to transport OperationError');
188
+ this.abort().catch((err) => this.errors.raise(err));
189
+ }
190
+
178
191
  if (this._state !== ConnectionState.CLOSED && this._state !== ConnectionState.CLOSING) {
179
192
  this.errors.raise(err);
180
193
  }
@@ -190,6 +203,41 @@ export class Connection {
190
203
  log.trace('dxos.mesh.connection.open-connection', trace.end({ id: this._instanceId }));
191
204
  }
192
205
 
206
+ @synchronized
207
+ // TODO(nf): make the caller responsible for recording the reason and determining flow control.
208
+ async abort(err?: Error) {
209
+ log('aborting...', { err });
210
+ if (!this.closeReason) {
211
+ this.closeReason = err?.message;
212
+ }
213
+ if (this._state === ConnectionState.CLOSED) {
214
+ log('abort ignored: already closed', this.closeReason);
215
+ return;
216
+ }
217
+ // TODO(nf): different state?
218
+ this._changeState(ConnectionState.CLOSING);
219
+
220
+ await this._ctx.dispose();
221
+
222
+ try {
223
+ // Forcefully close the stream flushing any unsent data packets.
224
+ log('aborting protocol... ', { proto: this._protocol });
225
+ await this._protocol.abort();
226
+ } catch (err: any) {
227
+ log.catch(err);
228
+ }
229
+
230
+ try {
231
+ // After the transport is closed streams are disconnected.
232
+ await this._transport?.destroy();
233
+ } catch (err: any) {
234
+ log.catch(err);
235
+ }
236
+
237
+ this._changeState(ConnectionState.CLOSED);
238
+ this._callbacks?.onClosed?.(err);
239
+ }
240
+
193
241
  @synchronized
194
242
  async close(err?: Error) {
195
243
  if (!this.closeReason) {
@@ -206,7 +254,7 @@ export class Connection {
206
254
 
207
255
  try {
208
256
  // Gracefully close the stream flushing any unsent data packets.
209
- await this._protocol.destroy();
257
+ await this._protocol.close();
210
258
  } catch (err: any) {
211
259
  log.catch(err);
212
260
  }
@@ -255,7 +303,7 @@ export class Connection {
255
303
  }
256
304
 
257
305
  // If signal fails treat connection as failed
258
- log.warn('Signal failed', { err });
306
+ log.warn('signal failed', { err });
259
307
  await this.close();
260
308
  }
261
309
  }
@@ -279,7 +327,7 @@ export class Connection {
279
327
  continue;
280
328
  }
281
329
 
282
- if (this._state === ConnectionState.INITIAL) {
330
+ if ([ConnectionState.CREATED, ConnectionState.INITIAL].includes(this.state)) {
283
331
  log('buffered signal', { peerId: this.ownId, remoteId: this.remoteId, msg: msg.data });
284
332
  this._incomingSignalBuffer.push(signal);
285
333
  } else {
@@ -290,6 +338,10 @@ export class Connection {
290
338
  }
291
339
  }
292
340
 
341
+ initiate() {
342
+ this._changeState(ConnectionState.INITIAL);
343
+ }
344
+
293
345
  private _changeState(state: ConnectionState): void {
294
346
  log('stateChanged', { from: this._state, too: state, peerId: this.ownId });
295
347
  invariant(state !== this._state, 'Already in this state.');
package/src/swarm/peer.ts CHANGED
@@ -113,6 +113,7 @@ export class Peer {
113
113
  // Peer with the highest Id closes its connection, and accepts remote peer's offer.
114
114
  if (remoteId.toHex() < this.localPeerId.toHex()) {
115
115
  // TODO(burdon): Too verbose.
116
+ // TODO(nf): gets stuck when remote connection is aborted (i.e. closed tab)
116
117
  log('close local connection', {
117
118
  localPeerId: this.id,
118
119
  topic: this.topic,
@@ -138,6 +139,8 @@ export class Peer {
138
139
 
139
140
  try {
140
141
  await this._connectionLimiter.connecting(message.sessionId);
142
+ connection.initiate();
143
+
141
144
  await connection.openConnection();
142
145
  } catch (err: any) {
143
146
  if (!(err instanceof CancelledError)) {
@@ -168,6 +171,7 @@ export class Peer {
168
171
 
169
172
  try {
170
173
  await this._connectionLimiter.connecting(sessionId);
174
+ connection.initiate();
171
175
 
172
176
  const answer = await this._signalMessaging.offer({
173
177
  author: this.localPeerId,
@@ -12,7 +12,7 @@ import { ComplexSet } from '@dxos/util';
12
12
 
13
13
  import { TestWireProtocol } from '../testing/test-wire-protocol';
14
14
  import { FullyConnectedTopology } from '../topology';
15
- import { createWebRTCTransportFactory } from '../transport';
15
+ import { createSimplePeerTransportFactory } from '../transport';
16
16
  import { ConnectionState } from './connection';
17
17
  import { ConnectionLimiter } from './connection-limiter';
18
18
  import { Swarm } from './swarm';
@@ -48,7 +48,8 @@ describe('Swarm', () => {
48
48
  new FullyConnectedTopology(),
49
49
  protocol.factory,
50
50
  new Messenger({ signalManager }),
51
- createWebRTCTransportFactory(),
51
+ // TODO(nf): configure?
52
+ createSimplePeerTransportFactory(),
52
53
  undefined,
53
54
  connectionLimiter,
54
55
  initiationDelay,
@@ -3,6 +3,7 @@
3
3
  //
4
4
 
5
5
  import { PublicKey } from '@dxos/keys';
6
+ import { log } from '@dxos/log';
6
7
  import {
7
8
  MemorySignalManager,
8
9
  MemorySignalManagerContext,
@@ -20,9 +21,11 @@ import { FullyConnectedTopology } from '../topology';
20
21
  import {
21
22
  MemoryTransportFactory,
22
23
  TransportFactory,
23
- WebRTCTransport,
24
- WebRTCTransportProxyFactory,
25
- WebRTCTransportService,
24
+ SimplePeerTransportProxyFactory,
25
+ SimplePeerTransportService,
26
+ createSimplePeerTransportFactory,
27
+ createLibDataChannelTransportFactory,
28
+ TransportKind,
26
29
  } from '../transport';
27
30
  import { TestWireProtocol, type TestTeleportExtensionFactory } from './test-wire-protocol';
28
31
 
@@ -35,6 +38,7 @@ export const TEST_SIGNAL_HOSTS: Runtime.Services.Signal[] = [
35
38
  export type TestBuilderOptions = {
36
39
  signalHosts?: Runtime.Services.Signal[];
37
40
  bridge?: boolean;
41
+ transport?: TransportKind;
38
42
  };
39
43
 
40
44
  /**
@@ -54,7 +58,7 @@ export class TestBuilder {
54
58
  }
55
59
 
56
60
  createPeer(peerId: PublicKey = PublicKey.random()) {
57
- return new TestPeer(this, peerId);
61
+ return new TestPeer(this, peerId, this.options.transport);
58
62
  }
59
63
  }
60
64
 
@@ -77,49 +81,76 @@ export class TestPeer {
77
81
  private _proxy?: ProtoRpcPeer<any>;
78
82
  private _service?: ProtoRpcPeer<any>;
79
83
 
80
- constructor(private readonly testBuilder: TestBuilder, public readonly peerId: PublicKey) {
84
+ constructor(
85
+ private readonly testBuilder: TestBuilder,
86
+ public readonly peerId: PublicKey,
87
+ public readonly transport?: TransportKind,
88
+ ) {
81
89
  this._signalManager = this.testBuilder.createSignalManager();
82
- this._networkManager = this.createNetworkManager();
90
+ if (!transport) {
91
+ if (this.testBuilder.options.signalHosts) {
92
+ transport = TransportKind.SIMPLE_PEER;
93
+ } else {
94
+ transport = TransportKind.MEMORY;
95
+ }
96
+ }
97
+ this._networkManager = this.createNetworkManager(transport);
83
98
  }
84
99
 
85
100
  // TODO(burdon): Move to TestBuilder.
86
- createNetworkManager() {
87
- let transportFactory: TransportFactory = MemoryTransportFactory;
88
-
101
+ createNetworkManager(transport: TransportKind) {
102
+ let transportFactory: TransportFactory;
89
103
  if (this.testBuilder.options.signalHosts) {
90
- if (this.testBuilder.options.bridge) {
91
- // Simulates bridge to shared worker.
92
- const [proxyPort, servicePort] = createLinkedPorts();
93
-
94
- this._proxy = createProtoRpcPeer({
95
- port: proxyPort,
96
- requested: {
97
- BridgeService: schema.getService('dxos.mesh.bridge.BridgeService'),
98
- },
99
- noHandshake: true,
100
- encodingOptions: {
101
- preserveAny: true,
102
- },
103
- });
104
-
105
- this._service = createProtoRpcPeer({
106
- port: servicePort,
107
- exposed: {
108
- BridgeService: schema.getService('dxos.mesh.bridge.BridgeService'),
109
- },
110
- handlers: { BridgeService: new WebRTCTransportService() },
111
- noHandshake: true,
112
- encodingOptions: {
113
- preserveAny: true,
114
- },
115
- });
116
-
117
- transportFactory = new WebRTCTransportProxyFactory().setBridgeService(this._proxy.rpc.BridgeService);
118
- } else {
119
- transportFactory = {
120
- createTransport: (params) => new WebRTCTransport(params),
121
- };
104
+ log.info(`using ${transport} transport with signal server.`);
105
+ switch (transport) {
106
+ case TransportKind.MEMORY:
107
+ throw new Error('Memory transport not supported with signal server.');
108
+ case TransportKind.SIMPLE_PEER:
109
+ transportFactory = createSimplePeerTransportFactory();
110
+ break;
111
+ case TransportKind.LIBDATACHANNEL:
112
+ transportFactory = createLibDataChannelTransportFactory();
113
+ break;
114
+ case TransportKind.SIMPLE_PEER_PROXY:
115
+ {
116
+ // Simulates bridge to shared worker.
117
+ const [proxyPort, servicePort] = createLinkedPorts();
118
+
119
+ this._proxy = createProtoRpcPeer({
120
+ port: proxyPort,
121
+ requested: {
122
+ BridgeService: schema.getService('dxos.mesh.bridge.BridgeService'),
123
+ },
124
+ noHandshake: true,
125
+ encodingOptions: {
126
+ preserveAny: true,
127
+ },
128
+ });
129
+
130
+ this._service = createProtoRpcPeer({
131
+ port: servicePort,
132
+ exposed: {
133
+ BridgeService: schema.getService('dxos.mesh.bridge.BridgeService'),
134
+ },
135
+ handlers: { BridgeService: new SimplePeerTransportService() },
136
+ noHandshake: true,
137
+ encodingOptions: {
138
+ preserveAny: true,
139
+ },
140
+ });
141
+
142
+ transportFactory = new SimplePeerTransportProxyFactory().setBridgeService(this._proxy.rpc.BridgeService);
143
+ }
144
+ break;
145
+ default:
146
+ throw new Error(`Unsupported transport: ${transport}`);
147
+ }
148
+ } else {
149
+ if (transport !== TransportKind.MEMORY) {
150
+ log.warn(`specified transport ${transport} but no signalling configured, using memory transport instead`);
122
151
  }
152
+ log.info(`using ${transport} transport without signal server.`);
153
+ transportFactory = MemoryTransportFactory;
123
154
  }
124
155
 
125
156
  return new NetworkManager({
@@ -0,0 +1,2 @@
1
+ Code in this directory was adopted from https://github.com/libp2p/js-libp2p/tree/master/packages/transport-webrtc/src/webrtc
2
+ at of commit e664d14f
@@ -0,0 +1,8 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export const RTCPeerConnection = globalThis.RTCPeerConnection;
6
+ export const RTCSessionDescription = globalThis.RTCSessionDescription;
7
+ export const RTCIceCandidate = globalThis.RTCIceCandidate;
8
+ export const cleanup = (): void => {};
@@ -0,0 +1,17 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import node from 'node-datachannel';
6
+
7
+ import { IceCandidate } from './rtc-ice-candidate';
8
+ import { PeerConnection } from './rtc-peer-connection';
9
+ import { SessionDescription } from './rtc-session-description';
10
+
11
+ export { SessionDescription as RTCSessionDescription };
12
+ export { IceCandidate as RTCIceCandidate };
13
+ export { PeerConnection as RTCPeerConnection };
14
+
15
+ export const cleanup = (): void => {
16
+ node.cleanup();
17
+ };
@@ -0,0 +1,148 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import type node from 'node-datachannel';
6
+
7
+ export class DataChannel extends EventTarget implements RTCDataChannel {
8
+ binaryType: BinaryType;
9
+
10
+ readonly maxPacketLifeTime: number | null;
11
+ readonly maxRetransmits: number | null;
12
+ readonly negotiated: boolean;
13
+ readonly ordered: boolean;
14
+
15
+ onbufferedamountlow: ((this: RTCDataChannel, ev: Event) => any) | null;
16
+ onclose: ((this: RTCDataChannel, ev: Event) => any) | null;
17
+ onclosing: ((this: RTCDataChannel, ev: Event) => any) | null;
18
+ onerror: ((this: RTCDataChannel, ev: Event) => any) | null;
19
+ onmessage: ((this: RTCDataChannel, ev: MessageEvent) => any) | null;
20
+ onopen: ((this: RTCDataChannel, ev: Event) => any) | null;
21
+
22
+ #dataChannel: node.DataChannel;
23
+ #bufferedAmountLowThreshold: number;
24
+ #readyState: RTCDataChannelState;
25
+
26
+ constructor(dataChannel: node.DataChannel, dataChannelDict: RTCDataChannelInit = {}) {
27
+ super();
28
+
29
+ this.#dataChannel = dataChannel;
30
+ this.#readyState = 'connecting';
31
+ this.#bufferedAmountLowThreshold = 0;
32
+
33
+ this.binaryType = 'arraybuffer';
34
+
35
+ this.#dataChannel.onOpen(() => {
36
+ this.#readyState = 'open';
37
+ this.dispatchEvent(new Event('open'));
38
+ });
39
+ this.#dataChannel.onClosed(() => {
40
+ this.#readyState = 'closed';
41
+ this.dispatchEvent(new Event('close'));
42
+ });
43
+ this.#dataChannel.onError((msg) => {
44
+ this.#readyState = 'closed';
45
+ this.dispatchEvent(
46
+ new RTCErrorEvent('error', {
47
+ error: new RTCError(
48
+ {
49
+ errorDetail: 'data-channel-failure',
50
+ },
51
+ msg,
52
+ ),
53
+ }),
54
+ );
55
+ });
56
+ this.#dataChannel.onBufferedAmountLow(() => {
57
+ this.dispatchEvent(new Event('bufferedamountlow'));
58
+ });
59
+ this.#dataChannel.onMessage((data: string | Uint8Array) => {
60
+ if (typeof data === 'string') {
61
+ data = Buffer.from(data);
62
+ }
63
+
64
+ this.dispatchEvent(new MessageEvent('message', { data }));
65
+ });
66
+
67
+ // forward events to properties
68
+ this.addEventListener('message', (event) => {
69
+ this.onmessage?.(event as MessageEvent<ArrayBuffer>);
70
+ });
71
+ this.addEventListener('bufferedamountlow', (event) => {
72
+ this.onbufferedamountlow?.(event);
73
+ });
74
+ this.addEventListener('error', (event) => {
75
+ this.onerror?.(event);
76
+ });
77
+ this.addEventListener('close', (event) => {
78
+ this.onclose?.(event);
79
+ });
80
+ this.addEventListener('closing', (event) => {
81
+ this.onclosing?.(event);
82
+ });
83
+ this.addEventListener('open', (event) => {
84
+ this.onopen?.(event);
85
+ });
86
+
87
+ this.onbufferedamountlow = null;
88
+ this.onclose = null;
89
+ this.onclosing = null;
90
+ this.onerror = null;
91
+ this.onmessage = null;
92
+ this.onopen = null;
93
+
94
+ this.maxPacketLifeTime = dataChannelDict.maxPacketLifeTime ?? null;
95
+ this.maxRetransmits = dataChannelDict.maxRetransmits ?? null;
96
+ this.negotiated = dataChannelDict.negotiated ?? false;
97
+ this.ordered = dataChannelDict.ordered ?? true;
98
+ }
99
+
100
+ get id(): number {
101
+ return this.#dataChannel.getId();
102
+ }
103
+
104
+ get label(): string {
105
+ return this.#dataChannel.getLabel();
106
+ }
107
+
108
+ get protocol(): string {
109
+ return this.#dataChannel.getProtocol();
110
+ }
111
+
112
+ get bufferedAmount(): number {
113
+ return this.#dataChannel.bufferedAmount();
114
+ }
115
+
116
+ set bufferedAmountLowThreshold(threshold: number) {
117
+ this.#bufferedAmountLowThreshold = threshold;
118
+ this.#dataChannel.setBufferedAmountLowThreshold(threshold);
119
+ }
120
+
121
+ get bufferedAmountLowThreshold(): number {
122
+ return this.#bufferedAmountLowThreshold;
123
+ }
124
+
125
+ get readyState(): RTCDataChannelState {
126
+ return this.#readyState;
127
+ }
128
+
129
+ close(): void {
130
+ this.#readyState = 'closing';
131
+ this.dispatchEvent(new Event('closing'));
132
+
133
+ this.#dataChannel.close();
134
+ }
135
+
136
+ send(data: string): void;
137
+ send(data: Blob): void;
138
+ send(data: ArrayBuffer): void;
139
+ send(data: ArrayBufferView): void;
140
+ send(data: any): void {
141
+ // TODO: sending Blobs
142
+ if (typeof data === 'string') {
143
+ this.#dataChannel.sendMessage(data);
144
+ } else {
145
+ this.#dataChannel.sendMessageBinary(data);
146
+ }
147
+ }
148
+ }
@@ -0,0 +1,23 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export class PeerConnectionIceEvent extends Event implements RTCPeerConnectionIceEvent {
6
+ readonly candidate: RTCIceCandidate | null;
7
+
8
+ constructor(candidate: RTCIceCandidate) {
9
+ super('icecandidate');
10
+
11
+ this.candidate = candidate;
12
+ }
13
+ }
14
+
15
+ export class DataChannelEvent extends Event implements RTCDataChannelEvent {
16
+ readonly channel: RTCDataChannel;
17
+
18
+ constructor(channel: RTCDataChannel) {
19
+ super('datachannel');
20
+
21
+ this.channel = channel;
22
+ }
23
+ }
@@ -0,0 +1,54 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ /**
6
+ * @see https://developer.mozilla.org/docs/Web/API/RTCIceCandidate
7
+ */
8
+ export class IceCandidate implements RTCIceCandidate {
9
+ readonly address: string | null;
10
+ readonly candidate: string;
11
+ readonly component: RTCIceComponent | null;
12
+ readonly foundation: string | null;
13
+ readonly port: number | null;
14
+ readonly priority: number | null;
15
+ readonly protocol: RTCIceProtocol | null;
16
+ readonly relatedAddress: string | null;
17
+ readonly relatedPort: number | null;
18
+ readonly sdpMLineIndex: number | null;
19
+ readonly sdpMid: string | null;
20
+ readonly tcpType: RTCIceTcpCandidateType | null;
21
+ readonly type: RTCIceCandidateType | null;
22
+ readonly usernameFragment: string | null;
23
+
24
+ constructor(init: RTCIceCandidateInit) {
25
+ if (init.candidate == null) {
26
+ throw new DOMException('candidate must be specified');
27
+ }
28
+
29
+ this.candidate = init.candidate;
30
+ this.sdpMLineIndex = init.sdpMLineIndex ?? null;
31
+ this.sdpMid = init.sdpMid ?? null;
32
+ this.usernameFragment = init.usernameFragment ?? null;
33
+
34
+ this.address = null;
35
+ this.component = null;
36
+ this.foundation = null;
37
+ this.port = null;
38
+ this.priority = null;
39
+ this.protocol = null;
40
+ this.relatedAddress = null;
41
+ this.relatedPort = null;
42
+ this.tcpType = null;
43
+ this.type = null;
44
+ }
45
+
46
+ toJSON(): RTCIceCandidateInit {
47
+ return {
48
+ candidate: this.candidate,
49
+ sdpMLineIndex: this.sdpMLineIndex,
50
+ sdpMid: this.sdpMid,
51
+ usernameFragment: this.usernameFragment,
52
+ };
53
+ }
54
+ }