@libp2p/webrtc 5.2.23-cf9aab5c8 → 5.2.24-a02cb0461

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 (87) hide show
  1. package/README.md +10 -20
  2. package/dist/index.min.js +14 -14
  3. package/dist/index.min.js.map +4 -4
  4. package/dist/src/constants.d.ts +0 -15
  5. package/dist/src/constants.d.ts.map +1 -1
  6. package/dist/src/constants.js +0 -15
  7. package/dist/src/constants.js.map +1 -1
  8. package/dist/src/index.d.ts +12 -22
  9. package/dist/src/index.d.ts.map +1 -1
  10. package/dist/src/index.js +12 -22
  11. package/dist/src/index.js.map +1 -1
  12. package/dist/src/muxer.d.ts +14 -46
  13. package/dist/src/muxer.d.ts.map +1 -1
  14. package/dist/src/muxer.js +32 -135
  15. package/dist/src/muxer.js.map +1 -1
  16. package/dist/src/private-to-private/initiate-connection.d.ts +3 -4
  17. package/dist/src/private-to-private/initiate-connection.d.ts.map +1 -1
  18. package/dist/src/private-to-private/initiate-connection.js +23 -5
  19. package/dist/src/private-to-private/initiate-connection.js.map +1 -1
  20. package/dist/src/private-to-private/signaling-stream-handler.d.ts +4 -4
  21. package/dist/src/private-to-private/signaling-stream-handler.d.ts.map +1 -1
  22. package/dist/src/private-to-private/signaling-stream-handler.js +10 -6
  23. package/dist/src/private-to-private/signaling-stream-handler.js.map +1 -1
  24. package/dist/src/private-to-private/transport.d.ts +2 -2
  25. package/dist/src/private-to-private/transport.d.ts.map +1 -1
  26. package/dist/src/private-to-private/transport.js +30 -15
  27. package/dist/src/private-to-private/transport.js.map +1 -1
  28. package/dist/src/private-to-private/util.d.ts +3 -1
  29. package/dist/src/private-to-private/util.d.ts.map +1 -1
  30. package/dist/src/private-to-private/util.js +15 -3
  31. package/dist/src/private-to-private/util.js.map +1 -1
  32. package/dist/src/private-to-public/listener.js +2 -2
  33. package/dist/src/private-to-public/listener.js.map +1 -1
  34. package/dist/src/private-to-public/transport.js +1 -1
  35. package/dist/src/private-to-public/transport.js.map +1 -1
  36. package/dist/src/private-to-public/utils/connect.d.ts +1 -1
  37. package/dist/src/private-to-public/utils/connect.d.ts.map +1 -1
  38. package/dist/src/private-to-public/utils/connect.js +17 -14
  39. package/dist/src/private-to-public/utils/connect.js.map +1 -1
  40. package/dist/src/private-to-public/utils/get-rtcpeerconnection.d.ts +4 -4
  41. package/dist/src/private-to-public/utils/get-rtcpeerconnection.d.ts.map +1 -1
  42. package/dist/src/private-to-public/utils/get-rtcpeerconnection.js +13 -2
  43. package/dist/src/private-to-public/utils/get-rtcpeerconnection.js.map +1 -1
  44. package/dist/src/private-to-public/utils/sdp.d.ts.map +1 -1
  45. package/dist/src/private-to-public/utils/sdp.js +8 -3
  46. package/dist/src/private-to-public/utils/sdp.js.map +1 -1
  47. package/dist/src/private-to-public/utils/stun-listener.js +1 -1
  48. package/dist/src/private-to-public/utils/stun-listener.js.map +1 -1
  49. package/dist/src/rtcpeerconnection-to-conn.d.ts +12 -0
  50. package/dist/src/rtcpeerconnection-to-conn.d.ts.map +1 -0
  51. package/dist/src/rtcpeerconnection-to-conn.js +43 -0
  52. package/dist/src/rtcpeerconnection-to-conn.js.map +1 -0
  53. package/dist/src/stream.d.ts +13 -26
  54. package/dist/src/stream.d.ts.map +1 -1
  55. package/dist/src/stream.js +64 -166
  56. package/dist/src/stream.js.map +1 -1
  57. package/dist/src/util.d.ts +3 -1
  58. package/dist/src/util.d.ts.map +1 -1
  59. package/dist/src/util.js +19 -0
  60. package/dist/src/util.js.map +1 -1
  61. package/dist/src/webrtc/index.d.ts +1 -1
  62. package/dist/src/webrtc/index.d.ts.map +1 -1
  63. package/dist/src/webrtc/index.js +1 -1
  64. package/dist/src/webrtc/index.js.map +1 -1
  65. package/package.json +26 -29
  66. package/src/constants.ts +0 -18
  67. package/src/index.ts +12 -22
  68. package/src/muxer.ts +43 -166
  69. package/src/private-to-private/initiate-connection.ts +31 -9
  70. package/src/private-to-private/signaling-stream-handler.ts +12 -9
  71. package/src/private-to-private/transport.ts +33 -17
  72. package/src/private-to-private/util.ts +21 -4
  73. package/src/private-to-public/listener.ts +2 -2
  74. package/src/private-to-public/transport.ts +1 -1
  75. package/src/private-to-public/utils/connect.ts +18 -15
  76. package/src/private-to-public/utils/get-rtcpeerconnection.ts +16 -4
  77. package/src/private-to-public/utils/sdp.ts +8 -3
  78. package/src/private-to-public/utils/stun-listener.ts +1 -1
  79. package/src/rtcpeerconnection-to-conn.ts +62 -0
  80. package/src/stream.ts +74 -194
  81. package/src/util.ts +22 -1
  82. package/src/webrtc/index.ts +1 -1
  83. package/dist/src/maconn.d.ts +0 -58
  84. package/dist/src/maconn.d.ts.map +0 -1
  85. package/dist/src/maconn.js +0 -56
  86. package/dist/src/maconn.js.map +0 -1
  87. package/src/maconn.ts +0 -101
@@ -186,7 +186,7 @@ export class WebRTCDirectTransport implements Transport, Startable {
186
186
  dataChannel: this.init.dataChannel,
187
187
  upgrader: options.upgrader,
188
188
  peerId: this.components.peerId,
189
- remotePeerId: theirPeerId,
189
+ remotePeer: theirPeerId,
190
190
  privateKey: this.components.privateKey
191
191
  })
192
192
  } catch (err) {
@@ -1,8 +1,8 @@
1
- import { noise } from '@chainsafe/libp2p-noise'
2
- import { raceEvent } from 'race-event'
1
+ import { noise } from '@libp2p/noise'
2
+ import { pEvent } from 'p-event'
3
3
  import { WebRTCTransportError } from '../../error.js'
4
- import { WebRTCMultiaddrConnection } from '../../maconn.js'
5
4
  import { DataChannelMuxerFactory } from '../../muxer.js'
5
+ import { toMultiaddrConnection } from '../../rtcpeerconnection-to-conn.ts'
6
6
  import { createStream } from '../../stream.js'
7
7
  import { isFirefox } from '../../util.js'
8
8
  import { generateNoisePrologue } from './generate-noise-prologue.js'
@@ -22,7 +22,7 @@ export interface ConnectOptions {
22
22
  dataChannel?: DataChannelOptions
23
23
  upgrader: Upgrader
24
24
  peerId: PeerId
25
- remotePeerId?: PeerId
25
+ remotePeer?: PeerId
26
26
  signal: AbortSignal
27
27
  privateKey: PrivateKey
28
28
  }
@@ -83,7 +83,7 @@ export async function connect (peerConnection: DirectRTCPeerConnection, ufrag: s
83
83
 
84
84
  if (handshakeDataChannel.readyState !== 'open') {
85
85
  options.log.trace('%s wait for handshake channel to open, starting status %s', options.role, handshakeDataChannel.readyState)
86
- await raceEvent(handshakeDataChannel, 'open', options.signal)
86
+ await pEvent(handshakeDataChannel, 'open', options)
87
87
  }
88
88
 
89
89
  options.log.trace('%s handshake channel opened', options.role)
@@ -116,20 +116,19 @@ export async function connect (peerConnection: DirectRTCPeerConnection, ufrag: s
116
116
  const handshakeStream = createStream({
117
117
  channel: handshakeDataChannel,
118
118
  direction: 'outbound',
119
- handshake: true,
119
+ isHandshake: true,
120
120
  log: options.log,
121
121
  ...(options.dataChannel ?? {})
122
122
  })
123
123
 
124
124
  // Creating the connection before completion of the noise
125
125
  // handshake ensures that the stream opening callback is set up
126
- const maConn = new WebRTCMultiaddrConnection(options, {
126
+ const maConn = toMultiaddrConnection({
127
127
  peerConnection,
128
128
  remoteAddr: options.remoteAddr,
129
- timeline: {
130
- open: Date.now()
131
- },
132
- metrics: options.events
129
+ metrics: options.events,
130
+ direction: options.role === 'client' ? 'outbound' : 'inbound',
131
+ log: options.logger.forComponent('libp2p:webrtc-direct:connection')
133
132
  })
134
133
 
135
134
  peerConnection.addEventListener(CONNECTION_STATE_CHANGE_EVENT, () => {
@@ -150,7 +149,8 @@ export async function connect (peerConnection: DirectRTCPeerConnection, ufrag: s
150
149
  // Track opened peer connection
151
150
  options.events?.increment({ peer_connection: true })
152
151
 
153
- const muxerFactory = new DataChannelMuxerFactory(options, {
152
+ const muxerFactory = new DataChannelMuxerFactory({
153
+ // @ts-expect-error https://github.com/murat-dogan/node-datachannel/pull/370
154
154
  peerConnection,
155
155
  metrics: options.events,
156
156
  dataChannelOptions: options.dataChannel
@@ -161,8 +161,8 @@ export async function connect (peerConnection: DirectRTCPeerConnection, ufrag: s
161
161
  // handshake. Therefore, we need to secure an inbound noise connection
162
162
  // from the server.
163
163
  options.log.trace('%s secure inbound', options.role)
164
- await connectionEncrypter.secureInbound(handshakeStream, {
165
- remotePeer: options.remotePeerId,
164
+ const result = await connectionEncrypter.secureInbound(handshakeStream, {
165
+ remotePeer: options.remotePeer,
166
166
  signal: options.signal,
167
167
  skipStreamMuxerNegotiation: true
168
168
  })
@@ -171,6 +171,7 @@ export async function connect (peerConnection: DirectRTCPeerConnection, ufrag: s
171
171
  return await options.upgrader.upgradeOutbound(maConn, {
172
172
  skipProtection: true,
173
173
  skipEncryption: true,
174
+ remotePeer: result.remotePeer,
174
175
  muxerFactory,
175
176
  signal: options.signal
176
177
  })
@@ -181,7 +182,7 @@ export async function connect (peerConnection: DirectRTCPeerConnection, ufrag: s
181
182
  // the client.
182
183
  options.log.trace('%s secure outbound', options.role)
183
184
  const result = await connectionEncrypter.secureOutbound(handshakeStream, {
184
- remotePeer: options.remotePeerId,
185
+ remotePeer: options.remotePeer,
185
186
  signal: options.signal,
186
187
  skipStreamMuxerNegotiation: true
187
188
  })
@@ -193,11 +194,13 @@ export async function connect (peerConnection: DirectRTCPeerConnection, ufrag: s
193
194
  await options.upgrader.upgradeInbound(maConn, {
194
195
  skipProtection: true,
195
196
  skipEncryption: true,
197
+ remotePeer: result.remotePeer,
196
198
  muxerFactory,
197
199
  signal: options.signal
198
200
  })
199
201
  } catch (err) {
200
202
  handshakeDataChannel.close()
203
+ peerConnection.close()
201
204
 
202
205
  throw err
203
206
  }
@@ -1,10 +1,10 @@
1
- import { PeerConnection } from '@ipshipyard/node-datachannel'
2
- import { RTCPeerConnection } from '@ipshipyard/node-datachannel/polyfill'
3
1
  import { Crypto } from '@peculiar/webcrypto'
2
+ import { PeerConnection } from 'node-datachannel'
3
+ import { RTCPeerConnection } from 'node-datachannel/polyfill'
4
4
  import { DEFAULT_ICE_SERVERS, MAX_MESSAGE_SIZE } from '../../constants.js'
5
5
  import { generateTransportCertificate } from './generate-certificates.js'
6
6
  import type { TransportCertificate } from '../../index.js'
7
- import type { CertificateFingerprint } from '@ipshipyard/node-datachannel'
7
+ import type { CertificateFingerprint } from 'node-datachannel'
8
8
 
9
9
  const crypto = new Crypto()
10
10
 
@@ -14,7 +14,7 @@ interface DirectRTCPeerConnectionInit extends RTCConfiguration {
14
14
  }
15
15
 
16
16
  export class DirectRTCPeerConnection extends RTCPeerConnection {
17
- private readonly peerConnection: PeerConnection
17
+ private peerConnection: PeerConnection
18
18
  private readonly ufrag: string
19
19
 
20
20
  constructor (init: DirectRTCPeerConnectionInit) {
@@ -22,6 +22,18 @@ export class DirectRTCPeerConnection extends RTCPeerConnection {
22
22
 
23
23
  this.peerConnection = init.peerConnection
24
24
  this.ufrag = init.ufrag
25
+
26
+ // make sure C++ peer connection is garbage collected
27
+ // https://github.com/murat-dogan/node-datachannel/issues/366#issuecomment-3228453155
28
+ this.addEventListener('connectionstatechange', () => {
29
+ switch (this.connectionState) {
30
+ case 'closed':
31
+ this.peerConnection.close()
32
+ break
33
+ default:
34
+ break
35
+ }
36
+ })
25
37
  }
26
38
 
27
39
  async createOffer (): Promise<globalThis.RTCSessionDescriptionInit | any> {
@@ -166,8 +166,13 @@ export function munge (desc: RTCSessionDescriptionInit, ufrag: string): RTCSessi
166
166
 
167
167
  const lineBreak = desc.sdp.includes('\r\n') ? '\r\n' : '\n'
168
168
 
169
- desc.sdp = desc.sdp
170
- .replace(/\na=ice-ufrag:[^\n]*\n/, '\na=ice-ufrag:' + ufrag + lineBreak)
171
- .replace(/\na=ice-pwd:[^\n]*\n/, '\na=ice-pwd:' + ufrag + lineBreak)
169
+ try {
170
+ desc.sdp = desc.sdp
171
+ .replace(/\na=ice-ufrag:[^\n]*\n/, '\na=ice-ufrag:' + ufrag + lineBreak)
172
+ .replace(/\na=ice-pwd:[^\n]*\n/, '\na=ice-pwd:' + ufrag + lineBreak)
173
+ } catch {
174
+ // fails under Node.js
175
+ }
176
+
172
177
  return desc
173
178
  }
@@ -1,5 +1,5 @@
1
1
  import { isIPv4 } from '@chainsafe/is-ip'
2
- import { IceUdpMuxListener } from '@ipshipyard/node-datachannel'
2
+ import { IceUdpMuxListener } from 'node-datachannel'
3
3
  import type { Logger } from '@libp2p/interface'
4
4
  import type { AddressInfo } from 'node:net'
5
5
 
@@ -0,0 +1,62 @@
1
+ import { AbstractMultiaddrConnection } from '@libp2p/utils'
2
+ import type { RTCPeerConnection } from './webrtc/index.js'
3
+ import type { AbortOptions, MultiaddrConnection } from '@libp2p/interface'
4
+ import type { AbstractMultiaddrConnectionInit, SendResult } from '@libp2p/utils'
5
+ import type { Uint8ArrayList } from 'uint8arraylist'
6
+
7
+ export interface RTCPeerConnectionMultiaddrConnectionInit extends Omit<AbstractMultiaddrConnectionInit, 'stream'> {
8
+ peerConnection: RTCPeerConnection
9
+ }
10
+
11
+ class RTCPeerConnectionMultiaddrConnection extends AbstractMultiaddrConnection {
12
+ private peerConnection: RTCPeerConnection
13
+
14
+ constructor (init: RTCPeerConnectionMultiaddrConnectionInit) {
15
+ super(init)
16
+
17
+ this.peerConnection = init.peerConnection
18
+
19
+ const initialState = init.peerConnection.connectionState
20
+
21
+ this.peerConnection.onconnectionstatechange = () => {
22
+ this.log.trace('peer connection state change %s initial state %s', this.peerConnection.connectionState, initialState)
23
+
24
+ if (this.peerConnection.connectionState === 'disconnected' || this.peerConnection.connectionState === 'failed' || this.peerConnection.connectionState === 'closed') {
25
+ // nothing else to do but close the connection
26
+ this.onTransportClosed()
27
+ }
28
+ }
29
+ }
30
+
31
+ sendData (data: Uint8ArrayList): SendResult {
32
+ return {
33
+ sentBytes: data.byteLength,
34
+ canSendMore: true
35
+ }
36
+ }
37
+
38
+ async sendClose (options?: AbortOptions): Promise<void> {
39
+ this.peerConnection.close()
40
+ options?.signal?.throwIfAborted()
41
+ }
42
+
43
+ sendReset (): void {
44
+ this.peerConnection.close()
45
+ }
46
+
47
+ sendPause (): void {
48
+ // TODO: readable backpressure?
49
+ }
50
+
51
+ sendResume (): void {
52
+ // TODO: readable backpressure?
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Convert a RTCPeerConnection into a MultiaddrConnection
58
+ * https://github.com/libp2p/interface-transport#multiaddrconnection
59
+ */
60
+ export const toMultiaddrConnection = (init: RTCPeerConnectionMultiaddrConnectionInit): MultiaddrConnection => {
61
+ return new RTCPeerConnectionMultiaddrConnection(init)
62
+ }
package/src/stream.ts CHANGED
@@ -1,20 +1,15 @@
1
- import { StreamStateError, TimeoutError } from '@libp2p/interface'
2
- import { AbstractStream } from '@libp2p/utils/abstract-stream'
3
- import { anySignal } from 'any-signal'
1
+ import { StreamStateError } from '@libp2p/interface'
2
+ import { AbstractStream } from '@libp2p/utils'
4
3
  import * as lengthPrefixed from 'it-length-prefixed'
5
4
  import { pushable } from 'it-pushable'
6
- import pDefer from 'p-defer'
7
- import pTimeout from 'p-timeout'
8
- import { raceEvent } from 'race-event'
9
5
  import { raceSignal } from 'race-signal'
10
6
  import { Uint8ArrayList } from 'uint8arraylist'
11
- import { BUFFERED_AMOUNT_LOW_TIMEOUT, FIN_ACK_TIMEOUT, MAX_BUFFERED_AMOUNT, MAX_MESSAGE_SIZE, OPEN_TIMEOUT, PROTOBUF_OVERHEAD } from './constants.js'
7
+ import { MAX_BUFFERED_AMOUNT, MAX_MESSAGE_SIZE, PROTOBUF_OVERHEAD } from './constants.js'
12
8
  import { Message } from './private-to-public/pb/message.js'
13
9
  import type { DataChannelOptions } from './index.js'
14
- import type { AbortOptions, Direction, Logger } from '@libp2p/interface'
15
- import type { AbstractStreamInit } from '@libp2p/utils/abstract-stream'
10
+ import type { AbortOptions, MessageStreamDirection, Logger } from '@libp2p/interface'
11
+ import type { AbstractStreamInit, SendResult } from '@libp2p/utils'
16
12
  import type { Pushable } from 'it-pushable'
17
- import type { DeferredPromise } from 'p-defer'
18
13
 
19
14
  export interface WebRTCStreamInit extends AbstractStreamInit, DataChannelOptions {
20
15
  /**
@@ -39,76 +34,24 @@ export class WebRTCStream extends AbstractStream {
39
34
  * and then the protobuf decoder.
40
35
  */
41
36
  private readonly incomingData: Pushable<Uint8Array>
42
-
43
37
  private readonly maxBufferedAmount: number
44
-
45
- private readonly bufferedAmountLowEventTimeout: number
46
-
47
- /**
48
- * The maximum size of a message in bytes
49
- */
50
- private readonly maxMessageSize: number
51
-
52
- /**
53
- * When this promise is resolved, the remote has sent us a FIN flag
54
- */
55
- private readonly receiveFinAck: DeferredPromise<void>
56
- private readonly finAckTimeout: number
57
- private readonly openTimeout: number
58
- private readonly closeController: AbortController
38
+ private readonly receivedFinAck: PromiseWithResolvers<void>
59
39
 
60
40
  constructor (init: WebRTCStreamInit) {
61
- // override onEnd to send/receive FIN_ACK before closing the stream
62
- const originalOnEnd = init.onEnd
63
- init.onEnd = (err?: Error): void => {
64
- this.log.trace('readable and writeable ends closed with status "%s"', this.status)
65
-
66
- void Promise.resolve(async () => {
67
- if (this.timeline.abort != null || this.timeline.reset !== null) {
68
- return
69
- }
70
-
71
- // wait for FIN_ACK if we haven't received it already
72
- try {
73
- await pTimeout(this.receiveFinAck.promise, {
74
- milliseconds: this.finAckTimeout
75
- })
76
- } catch (err) {
77
- this.log.error('error receiving FIN_ACK', err)
78
- }
79
- })
80
- .then(() => {
81
- // stop processing incoming messages
82
- this.incomingData.end()
83
-
84
- // final cleanup
85
- originalOnEnd?.(err)
86
- })
87
- .catch(err => {
88
- this.log.error('error ending stream', err)
89
- })
90
- .finally(() => {
91
- this.channel.close()
92
- })
93
- }
94
-
95
- super(init)
41
+ super({
42
+ ...init,
43
+ maxMessageSize: (init.maxMessageSize ?? MAX_MESSAGE_SIZE) - PROTOBUF_OVERHEAD
44
+ })
96
45
 
97
46
  this.channel = init.channel
98
47
  this.channel.binaryType = 'arraybuffer'
99
48
  this.incomingData = pushable<Uint8Array>()
100
- this.bufferedAmountLowEventTimeout = init.bufferedAmountLowEventTimeout ?? BUFFERED_AMOUNT_LOW_TIMEOUT
101
49
  this.maxBufferedAmount = init.maxBufferedAmount ?? MAX_BUFFERED_AMOUNT
102
- this.maxMessageSize = (init.maxMessageSize ?? MAX_MESSAGE_SIZE) - PROTOBUF_OVERHEAD
103
- this.receiveFinAck = pDefer()
104
- this.finAckTimeout = init.closeTimeout ?? FIN_ACK_TIMEOUT
105
- this.openTimeout = init.openTimeout ?? OPEN_TIMEOUT
106
- this.closeController = new AbortController()
50
+ this.receivedFinAck = Promise.withResolvers()
107
51
 
108
52
  // set up initial state
109
53
  switch (this.channel.readyState) {
110
54
  case 'open':
111
- this.timeline.open = new Date().getTime()
112
55
  break
113
56
 
114
57
  case 'closed':
@@ -127,31 +70,16 @@ export class WebRTCStream extends AbstractStream {
127
70
  }
128
71
 
129
72
  // handle RTCDataChannel events
130
- this.channel.onopen = (_evt) => {
131
- this.timeline.open = new Date().getTime()
132
- }
133
-
134
73
  this.channel.onclose = (_evt) => {
135
74
  this.log.trace('received onclose event')
136
75
 
137
- // stop any in-progress writes
138
- this.closeController.abort()
139
-
140
- // if the channel has closed we'll never receive a FIN_ACK so resolve the
141
- // promise so we don't try to wait later
142
- this.receiveFinAck.resolve()
143
-
144
- void this.close().catch(err => {
145
- this.log.error('error closing stream after channel closed', err)
146
- })
76
+ this.onRemoteCloseWrite()
77
+ this.onTransportClosed()
147
78
  }
148
79
 
149
80
  this.channel.onerror = (evt) => {
150
81
  this.log.trace('received onerror event')
151
82
 
152
- // stop any in-progress writes
153
- this.closeController.abort()
154
-
155
83
  const err = (evt as RTCErrorEvent).error
156
84
  this.abort(err)
157
85
  }
@@ -166,6 +94,12 @@ export class WebRTCStream extends AbstractStream {
166
94
  this.incomingData.push(new Uint8Array(data, 0, data.byteLength))
167
95
  }
168
96
 
97
+ // dispatch drain event when the buffered amount drops to zero
98
+ this.channel.bufferedAmountLowThreshold = 0
99
+ this.channel.onbufferedamountlow = () => {
100
+ this.safeDispatchEvent('drain')
101
+ }
102
+
169
103
  const self = this
170
104
 
171
105
  // pipe framed protobuf messages through a length prefixed decoder, and
@@ -175,94 +109,59 @@ export class WebRTCStream extends AbstractStream {
175
109
  const message = self.processIncomingProtobuf(buf)
176
110
 
177
111
  if (message != null) {
178
- self.sourcePush(new Uint8ArrayList(message))
112
+ self.onData(new Uint8ArrayList(message))
179
113
  }
180
114
  }
181
115
  })
182
116
  .catch(err => {
183
117
  this.log.error('error processing incoming data channel messages', err)
184
118
  })
119
+
120
+ // clean up the datachannel when both ends have sent a FIN
121
+ const webRTCStreamOnClose = (): void => {
122
+ this.channel.close()
123
+ }
124
+ this.addEventListener('close', webRTCStreamOnClose)
185
125
  }
186
126
 
187
127
  sendNewStream (): void {
188
128
  // opening new streams is handled by WebRTC so this is a noop
189
129
  }
190
130
 
191
- async _sendMessage (data: Uint8ArrayList, checkBuffer: boolean = true): Promise<void> {
131
+ _sendMessage (data: Uint8ArrayList): void {
192
132
  if (this.channel.readyState === 'closed' || this.channel.readyState === 'closing') {
193
133
  throw new StreamStateError(`Invalid datachannel state - ${this.channel.readyState}`)
194
134
  }
195
135
 
196
- if (this.channel.readyState !== 'open') {
197
- const timeout = AbortSignal.timeout(this.openTimeout)
198
- const signal = anySignal([
199
- this.closeController.signal,
200
- timeout
201
- ])
202
-
203
- try {
204
- this.log('channel state is "%s" and not "open", waiting for "open" event before sending data', this.channel.readyState)
205
- await raceEvent(this.channel, 'open', signal)
206
- } finally {
207
- signal.clear()
208
- }
209
-
210
- this.log('channel state is now "%s", sending data', this.channel.readyState)
211
- }
212
-
213
- if (checkBuffer && this.channel.bufferedAmount > this.maxBufferedAmount) {
214
- const timeout = AbortSignal.timeout(this.bufferedAmountLowEventTimeout)
215
- const signal = anySignal([
216
- this.closeController.signal,
217
- timeout
218
- ])
219
-
220
- try {
221
- this.log('channel buffer is %d, wait for "bufferedamountlow" event', this.channel.bufferedAmount)
222
- await raceEvent(this.channel, 'bufferedamountlow', signal)
223
- } catch (err: any) {
224
- if (timeout.aborted) {
225
- throw new TimeoutError(`Timed out waiting for DataChannel buffer to clear after ${this.bufferedAmountLowEventTimeout}ms`)
226
- }
227
-
228
- throw err
229
- } finally {
230
- signal.clear()
231
- }
232
- }
233
-
234
136
  try {
235
137
  this.log.trace('sending message, channel state "%s"', this.channel.readyState)
236
138
  // send message without copying data
237
- this.channel.send(data.subarray())
139
+ for (const buf of data) {
140
+ this.channel.send(buf)
141
+ }
238
142
  } catch (err: any) {
239
143
  this.log.error('error while sending message', err)
240
144
  }
241
145
  }
242
146
 
243
- async sendData (data: Uint8ArrayList): Promise<void> {
244
- const bytesTotal = data.byteLength
245
- // sending messages is an async operation so use a copy of the list as it
246
- // may be changed beneath us
247
- data = data.sublist()
248
-
249
- while (data.byteLength > 0) {
250
- const toSend = Math.min(data.byteLength, this.maxMessageSize)
251
- const buf = data.subarray(0, toSend)
252
- const messageBuf = Message.encode({ message: buf })
253
- const sendBuf = lengthPrefixed.encode.single(messageBuf)
254
- this.log.trace('sending %d/%d bytes on channel', buf.byteLength, bytesTotal)
255
- await this._sendMessage(sendBuf)
256
-
257
- data.consume(toSend)
258
- }
147
+ sendData (data: Uint8ArrayList): SendResult {
148
+ const messageBuf = Message.encode({
149
+ message: data.subarray()
150
+ })
151
+ const prefixedBuf = lengthPrefixed.encode.single(messageBuf)
152
+ this._sendMessage(prefixedBuf)
259
153
 
260
- this.log.trace('finished sending data, channel state "%s"', this.channel.readyState)
154
+ return {
155
+ sentBytes: data.byteLength,
156
+ canSendMore: this.channel.bufferedAmount < this.maxBufferedAmount
157
+ }
261
158
  }
262
159
 
263
- async sendReset (): Promise<void> {
160
+ sendReset (): void {
161
+ this.receivedFinAck.resolve()
162
+
264
163
  try {
265
- await this._sendFlag(Message.Flag.RESET)
164
+ this._sendFlag(Message.Flag.RESET)
266
165
  } catch (err) {
267
166
  this.log.error('failed to send reset - %e', err)
268
167
  } finally {
@@ -270,38 +169,20 @@ export class WebRTCStream extends AbstractStream {
270
169
  }
271
170
  }
272
171
 
273
- async sendCloseWrite (options: AbortOptions): Promise<void> {
274
- if (this.channel.readyState !== 'open') {
275
- this.receiveFinAck.resolve()
276
- return
277
- }
278
-
279
- const sent = await this._sendFlag(Message.Flag.FIN)
280
-
281
- if (sent) {
282
- this.log.trace('awaiting FIN_ACK')
283
- try {
284
- await raceSignal(this.receiveFinAck.promise, options?.signal, {
285
- errorMessage: 'sending close-write was aborted before FIN_ACK was received',
286
- errorName: 'FinAckNotReceivedError'
287
- })
288
- } catch (err) {
289
- this.log.error('failed to await FIN_ACK', err)
290
- }
291
- } else {
292
- this.log.trace('sending FIN failed, not awaiting FIN_ACK')
172
+ async sendCloseWrite (options?: AbortOptions): Promise<void> {
173
+ if (this.channel.readyState === 'open') {
174
+ this._sendFlag(Message.Flag.FIN)
293
175
  }
294
176
 
295
- // if we've attempted to receive a FIN_ACK, do not try again
296
- this.receiveFinAck.resolve()
177
+ await raceSignal(this.receivedFinAck.promise, options?.signal)
297
178
  }
298
179
 
299
- async sendCloseRead (): Promise<void> {
300
- if (this.channel.readyState !== 'open') {
301
- return
180
+ async sendCloseRead (options?: AbortOptions): Promise<void> {
181
+ if (this.channel.readyState === 'open') {
182
+ this._sendFlag(Message.Flag.STOP_SENDING)
302
183
  }
303
184
 
304
- await this._sendFlag(Message.Flag.STOP_SENDING)
185
+ options?.signal?.throwIfAborted()
305
186
  }
306
187
 
307
188
  /**
@@ -315,38 +196,33 @@ export class WebRTCStream extends AbstractStream {
315
196
 
316
197
  if (message.flag === Message.Flag.FIN) {
317
198
  // We should expect no more data from the remote, stop reading
318
- this.remoteCloseWrite()
319
-
320
- this.log.trace('sending FIN_ACK')
321
- void this._sendFlag(Message.Flag.FIN_ACK)
322
- .catch(err => {
323
- this.log.error('error sending FIN_ACK immediately', err)
324
- })
199
+ this.onRemoteCloseWrite()
200
+ this._sendFlag(Message.Flag.FIN_ACK)
325
201
  }
326
202
 
327
203
  if (message.flag === Message.Flag.RESET) {
204
+ this.receivedFinAck.resolve()
328
205
  // Stop reading and writing to the stream immediately
329
- this.reset()
206
+ this.onRemoteReset()
330
207
  }
331
208
 
332
209
  if (message.flag === Message.Flag.STOP_SENDING) {
333
210
  // The remote has stopped reading
334
- this.remoteCloseRead()
211
+ this.onRemoteCloseRead()
335
212
  }
336
213
 
337
214
  if (message.flag === Message.Flag.FIN_ACK) {
338
- this.log.trace('received FIN_ACK')
339
- this.receiveFinAck.resolve()
215
+ this.receivedFinAck.resolve()
340
216
  }
341
217
  }
342
218
 
343
219
  // ignore data messages if we've closed the readable end already
344
- if (this.readStatus === 'ready') {
220
+ if (this.readStatus === 'readable' || this.readStatus === 'paused') {
345
221
  return message.message
346
222
  }
347
223
  }
348
224
 
349
- private async _sendFlag (flag: Message.Flag): Promise<boolean> {
225
+ private _sendFlag (flag: Message.Flag): boolean {
350
226
  if (this.channel.readyState !== 'open') {
351
227
  // flags can be sent while we or the remote are closing the datachannel so
352
228
  // if the channel isn't open, don't try to send it but return false to let
@@ -360,7 +236,7 @@ export class WebRTCStream extends AbstractStream {
360
236
  const prefixedBuf = lengthPrefixed.encode.single(messageBuf)
361
237
 
362
238
  try {
363
- await this._sendMessage(prefixedBuf, false)
239
+ this._sendMessage(prefixedBuf)
364
240
 
365
241
  return true
366
242
  } catch (err: any) {
@@ -369,6 +245,14 @@ export class WebRTCStream extends AbstractStream {
369
245
 
370
246
  return false
371
247
  }
248
+
249
+ sendPause (): void {
250
+ // TODO: read backpressure?
251
+ }
252
+
253
+ sendResume (): void {
254
+ // TODO: read backpressure?
255
+ }
372
256
  }
373
257
 
374
258
  export interface WebRTCStreamOptions extends DataChannelOptions {
@@ -383,12 +267,7 @@ export interface WebRTCStreamOptions extends DataChannelOptions {
383
267
  /**
384
268
  * The stream direction
385
269
  */
386
- direction: Direction
387
-
388
- /**
389
- * A callback invoked when the channel ends
390
- */
391
- onEnd?(err?: Error | undefined): void
270
+ direction: MessageStreamDirection
392
271
 
393
272
  /**
394
273
  * The logger to create a scope from
@@ -399,15 +278,16 @@ export interface WebRTCStreamOptions extends DataChannelOptions {
399
278
  * If true the underlying datachannel is being used to perform the noise
400
279
  * handshake during connection establishment
401
280
  */
402
- handshake?: boolean
281
+ isHandshake?: boolean
403
282
  }
404
283
 
405
284
  export function createStream (options: WebRTCStreamOptions): WebRTCStream {
406
- const { channel, direction, handshake } = options
285
+ const { channel, direction, isHandshake } = options
407
286
 
408
287
  return new WebRTCStream({
409
288
  ...options,
410
289
  id: `${channel.id}`,
411
- log: options.log.newScope(`${handshake === true ? 'handshake' : direction}:${channel.id}`)
290
+ log: options.log.newScope(`${isHandshake === true ? 'handshake' : direction}:${channel.id}`),
291
+ protocol: ''
412
292
  })
413
293
  }