@libp2p/webrtc 5.2.24-8484de8a2 → 5.2.24-9a9b11fd4

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 (46) hide show
  1. package/dist/index.min.js +13 -13
  2. package/dist/index.min.js.map +4 -4
  3. package/dist/src/constants.d.ts +4 -0
  4. package/dist/src/constants.d.ts.map +1 -1
  5. package/dist/src/constants.js +4 -0
  6. package/dist/src/constants.js.map +1 -1
  7. package/dist/src/index.d.ts +8 -0
  8. package/dist/src/index.d.ts.map +1 -1
  9. package/dist/src/index.js.map +1 -1
  10. package/dist/src/muxer.d.ts.map +1 -1
  11. package/dist/src/muxer.js +7 -12
  12. package/dist/src/muxer.js.map +1 -1
  13. package/dist/src/private-to-private/initiate-connection.d.ts.map +1 -1
  14. package/dist/src/private-to-private/initiate-connection.js +15 -1
  15. package/dist/src/private-to-private/initiate-connection.js.map +1 -1
  16. package/dist/src/private-to-private/signaling-stream-handler.d.ts.map +1 -1
  17. package/dist/src/private-to-private/signaling-stream-handler.js +10 -2
  18. package/dist/src/private-to-private/signaling-stream-handler.js.map +1 -1
  19. package/dist/src/private-to-private/transport.d.ts +0 -7
  20. package/dist/src/private-to-private/transport.d.ts.map +1 -1
  21. package/dist/src/private-to-private/transport.js.map +1 -1
  22. package/dist/src/private-to-private/util.d.ts +0 -1
  23. package/dist/src/private-to-private/util.d.ts.map +1 -1
  24. package/dist/src/private-to-private/util.js +11 -11
  25. package/dist/src/private-to-private/util.js.map +1 -1
  26. package/dist/src/private-to-public/transport.d.ts +0 -8
  27. package/dist/src/private-to-public/transport.d.ts.map +1 -1
  28. package/dist/src/private-to-public/transport.js.map +1 -1
  29. package/dist/src/rtcpeerconnection-to-conn.d.ts.map +1 -1
  30. package/dist/src/rtcpeerconnection-to-conn.js +3 -0
  31. package/dist/src/rtcpeerconnection-to-conn.js.map +1 -1
  32. package/dist/src/stream.d.ts +3 -2
  33. package/dist/src/stream.d.ts.map +1 -1
  34. package/dist/src/stream.js +100 -68
  35. package/dist/src/stream.js.map +1 -1
  36. package/package.json +10 -10
  37. package/src/constants.ts +5 -0
  38. package/src/index.ts +9 -0
  39. package/src/muxer.ts +6 -13
  40. package/src/private-to-private/initiate-connection.ts +18 -2
  41. package/src/private-to-private/signaling-stream-handler.ts +12 -2
  42. package/src/private-to-private/transport.ts +0 -8
  43. package/src/private-to-private/util.ts +12 -12
  44. package/src/private-to-public/transport.ts +0 -10
  45. package/src/rtcpeerconnection-to-conn.ts +4 -0
  46. package/src/stream.ts +115 -79
package/src/stream.ts CHANGED
@@ -1,11 +1,13 @@
1
- import { StreamStateError } from '@libp2p/interface'
1
+ import { StreamResetError, StreamStateError } from '@libp2p/interface'
2
2
  import { AbstractStream } from '@libp2p/utils'
3
3
  import * as lengthPrefixed from 'it-length-prefixed'
4
4
  import { pushable } from 'it-pushable'
5
+ import { pEvent } from 'p-event'
5
6
  import { raceSignal } from 'race-signal'
6
7
  import { Uint8ArrayList } from 'uint8arraylist'
7
- import { MAX_BUFFERED_AMOUNT, MAX_MESSAGE_SIZE, PROTOBUF_OVERHEAD } from './constants.js'
8
+ import { DEFAULT_FIN_ACK_TIMEOUT, MAX_BUFFERED_AMOUNT, MAX_MESSAGE_SIZE, PROTOBUF_OVERHEAD } from './constants.js'
8
9
  import { Message } from './private-to-public/pb/message.js'
10
+ import { isFirefox } from './util.js'
9
11
  import type { DataChannelOptions } from './index.js'
10
12
  import type { AbortOptions, MessageStreamDirection, Logger } from '@libp2p/interface'
11
13
  import type { AbstractStreamInit, SendResult } from '@libp2p/utils'
@@ -35,7 +37,8 @@ export class WebRTCStream extends AbstractStream {
35
37
  */
36
38
  private readonly incomingData: Pushable<Uint8Array>
37
39
  private readonly maxBufferedAmount: number
38
- private readonly receivedFinAck: PromiseWithResolvers<void>
40
+ private receivedFinAck?: PromiseWithResolvers<void>
41
+ private finAckTimeout: number
39
42
 
40
43
  constructor (init: WebRTCStreamInit) {
41
44
  super({
@@ -47,44 +50,26 @@ export class WebRTCStream extends AbstractStream {
47
50
  this.channel.binaryType = 'arraybuffer'
48
51
  this.incomingData = pushable<Uint8Array>()
49
52
  this.maxBufferedAmount = init.maxBufferedAmount ?? MAX_BUFFERED_AMOUNT
50
- this.receivedFinAck = Promise.withResolvers()
51
-
52
- // set up initial state
53
- switch (this.channel.readyState) {
54
- case 'open':
55
- break
56
-
57
- case 'closed':
58
- case 'closing':
59
- if (this.timeline.close === undefined || this.timeline.close === 0) {
60
- this.timeline.close = Date.now()
61
- }
62
- break
63
- case 'connecting':
64
- // noop
65
- break
66
-
67
- default:
68
- this.log.error('unknown datachannel state %s', this.channel.readyState)
69
- throw new StreamStateError('Unknown datachannel state')
70
- }
53
+ this.finAckTimeout = init.finAckTimeout ?? DEFAULT_FIN_ACK_TIMEOUT
71
54
 
72
55
  // handle RTCDataChannel events
73
- this.channel.onclose = (_evt) => {
74
- this.log.trace('received onclose event')
56
+ this.channel.onclose = () => {
57
+ this.log.trace('received datachannel close event')
75
58
 
76
59
  this.onRemoteCloseWrite()
77
60
  this.onTransportClosed()
78
61
  }
79
62
 
80
63
  this.channel.onerror = (evt) => {
81
- this.log.trace('received onerror event')
82
-
83
64
  const err = (evt as RTCErrorEvent).error
65
+
66
+ this.log.trace('received datachannel error event - %e', err)
67
+
84
68
  this.abort(err)
85
69
  }
86
70
 
87
71
  this.channel.onmessage = async (event: MessageEvent<ArrayBuffer>) => {
72
+ this.log('incoming message %d bytes', event.data.byteLength)
88
73
  const { data } = event
89
74
 
90
75
  if (data === null || data.byteLength === 0) {
@@ -96,32 +81,49 @@ export class WebRTCStream extends AbstractStream {
96
81
 
97
82
  // dispatch drain event when the buffered amount drops to zero
98
83
  this.channel.bufferedAmountLowThreshold = 0
84
+
99
85
  this.channel.onbufferedamountlow = () => {
100
- this.safeDispatchEvent('drain')
86
+ if (this.writableNeedsDrain) {
87
+ this.safeDispatchEvent('drain')
88
+ }
101
89
  }
102
90
 
103
- const self = this
91
+ if (this.channel.readyState !== 'open') {
92
+ this.log('channel ready state is "%s" and not "open", waiting for "open" event before sending data', this.channel.readyState)
93
+ pEvent(this.channel, 'open', {
94
+ rejectionEvents: [
95
+ 'close',
96
+ 'error'
97
+ ]
98
+ })
99
+ .then(() => {
100
+ this.log('channel ready state is now "%s", dispatching drain', this.channel.readyState)
101
+ this.safeDispatchEvent('drain')
102
+ })
103
+ .catch(err => {
104
+ this.abort(err.error ?? err)
105
+ })
106
+ }
104
107
 
105
108
  // pipe framed protobuf messages through a length prefixed decoder, and
106
109
  // surface data from the `Message.message` field through a source.
107
110
  Promise.resolve().then(async () => {
108
111
  for await (const buf of lengthPrefixed.decode(this.incomingData)) {
109
- const message = self.processIncomingProtobuf(buf)
110
-
111
- if (message != null) {
112
- self.onData(new Uint8ArrayList(message))
113
- }
112
+ this.processIncomingProtobuf(buf)
114
113
  }
115
114
  })
116
115
  .catch(err => {
117
116
  this.log.error('error processing incoming data channel messages', err)
118
117
  })
119
118
 
120
- // clean up the datachannel when both ends have sent a FIN
121
- const webRTCStreamOnClose = (): void => {
122
- this.channel.close()
119
+ // close when both writable ends are closed or an error occurs
120
+ const cleanUpDatachannelOnClose = (): void => {
121
+ if (this.channel.readyState === 'open') {
122
+ this.log.trace('stream closed, closing underlying datachannel')
123
+ this.channel.close()
124
+ }
123
125
  }
124
- this.addEventListener('close', webRTCStreamOnClose)
126
+ this.addEventListener('close', cleanUpDatachannelOnClose)
125
127
  }
126
128
 
127
129
  sendNewStream (): void {
@@ -129,27 +131,57 @@ export class WebRTCStream extends AbstractStream {
129
131
  }
130
132
 
131
133
  _sendMessage (data: Uint8ArrayList): void {
132
- if (this.channel.readyState === 'closed' || this.channel.readyState === 'closing') {
134
+ if (this.channel.readyState !== 'open') {
133
135
  throw new StreamStateError(`Invalid datachannel state - ${this.channel.readyState}`)
134
136
  }
135
137
 
136
- try {
137
- this.log.trace('sending message, channel state "%s"', this.channel.readyState)
138
- // send message without copying data
139
- for (const buf of data) {
140
- this.channel.send(buf)
141
- }
142
- } catch (err: any) {
143
- this.log.error('error while sending message', err)
138
+ this.log.trace('sending message, channel state "%s"', this.channel.readyState)
139
+
140
+ if (isFirefox) {
141
+ // TODO: firefox can deliver small messages out of order - remove once a
142
+ // browser with https://bugzilla.mozilla.org/show_bug.cgi?id=1983831 is
143
+ // available in playwright-test
144
+ this.channel.send(data.subarray())
145
+ return
146
+ }
147
+
148
+ // send message without copying data
149
+ for (const buf of data) {
150
+ this.channel.send(buf)
144
151
  }
145
152
  }
146
153
 
147
154
  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)
155
+ if (this.channel.readyState !== 'open') {
156
+ return {
157
+ sentBytes: 0,
158
+ canSendMore: false
159
+ }
160
+ }
161
+
162
+ // TODO: firefox can deliver small messages out of order - remove once a
163
+ // browser with https://bugzilla.mozilla.org/show_bug.cgi?id=1983831 is
164
+ // available in playwright-test
165
+ // ----
166
+ // this is also necessary to work with rust-libp2p 0.54 though 0.53 seems ok
167
+ this._sendMessage(
168
+ lengthPrefixed.encode.single(Message.encode({
169
+ message: data.subarray()
170
+ }))
171
+ )
172
+
173
+ /*
174
+ // TODO: enable this when FF and rust-libp2p are not broken
175
+ // send message without copying data
176
+ for (const message of data) {
177
+ this._sendMessage(
178
+ lengthPrefixed.encode.single(Message.encode({
179
+ message
180
+ }))
181
+ )
182
+ }
183
+ }
184
+ */
153
185
 
154
186
  return {
155
187
  sentBytes: data.byteLength,
@@ -157,69 +189,73 @@ export class WebRTCStream extends AbstractStream {
157
189
  }
158
190
  }
159
191
 
160
- sendReset (): void {
161
- this.receivedFinAck.resolve()
162
-
192
+ sendReset (err: Error): void {
163
193
  try {
194
+ this.log.error('sending reset - %e', err)
164
195
  this._sendFlag(Message.Flag.RESET)
196
+ this.receivedFinAck?.reject(err)
165
197
  } catch (err) {
166
198
  this.log.error('failed to send reset - %e', err)
167
- } finally {
168
- this.channel.close()
169
199
  }
170
200
  }
171
201
 
172
202
  async sendCloseWrite (options?: AbortOptions): Promise<void> {
173
- if (this.channel.readyState === 'open') {
174
- this._sendFlag(Message.Flag.FIN)
175
- }
176
-
177
- await raceSignal(this.receivedFinAck.promise, options?.signal)
203
+ this._sendFlag(Message.Flag.FIN)
204
+ options?.signal?.throwIfAborted()
205
+ this.receivedFinAck = Promise.withResolvers<void>()
206
+
207
+ await Promise.any([
208
+ raceSignal(this.receivedFinAck.promise, options?.signal),
209
+ new Promise<void>(resolve => {
210
+ AbortSignal.timeout(this.finAckTimeout)
211
+ .addEventListener('abort', () => {
212
+ resolve()
213
+ })
214
+ })
215
+ ])
178
216
  }
179
217
 
180
218
  async sendCloseRead (options?: AbortOptions): Promise<void> {
181
- if (this.channel.readyState === 'open') {
182
- this._sendFlag(Message.Flag.STOP_SENDING)
183
- }
184
-
219
+ this._sendFlag(Message.Flag.STOP_SENDING)
185
220
  options?.signal?.throwIfAborted()
186
221
  }
187
222
 
188
223
  /**
189
224
  * Handle incoming
190
225
  */
191
- private processIncomingProtobuf (buffer: Uint8ArrayList): Uint8Array | undefined {
226
+ private processIncomingProtobuf (buffer: Uint8ArrayList): void {
192
227
  const message = Message.decode(buffer)
193
228
 
229
+ // ignore data messages if we've closed the readable end already
230
+ if (message.message != null && (this.readStatus === 'readable' || this.readStatus === 'paused')) {
231
+ this.onData(new Uint8ArrayList(message.message))
232
+ }
233
+
194
234
  if (message.flag !== undefined) {
195
235
  this.log.trace('incoming flag %s, write status "%s", read status "%s"', message.flag, this.writeStatus, this.readStatus)
196
236
 
197
237
  if (message.flag === Message.Flag.FIN) {
198
- // We should expect no more data from the remote, stop reading
199
- this.onRemoteCloseWrite()
238
+ // we should expect no more data from the remote, stop reading
200
239
  this._sendFlag(Message.Flag.FIN_ACK)
240
+ this.onRemoteCloseWrite()
201
241
  }
202
242
 
203
243
  if (message.flag === Message.Flag.RESET) {
204
- this.receivedFinAck.resolve()
205
- // Stop reading and writing to the stream immediately
244
+ // stop reading and writing to the stream immediately
245
+ this.receivedFinAck?.reject(new StreamResetError('The stream was reset'))
206
246
  this.onRemoteReset()
207
247
  }
208
248
 
209
249
  if (message.flag === Message.Flag.STOP_SENDING) {
210
- // The remote has stopped reading
250
+ // the remote has stopped reading
211
251
  this.onRemoteCloseRead()
212
252
  }
213
253
 
214
254
  if (message.flag === Message.Flag.FIN_ACK) {
215
- this.receivedFinAck.resolve()
255
+ // remote received our FIN
256
+ this.receivedFinAck?.resolve()
216
257
  }
217
258
  }
218
-
219
- // ignore data messages if we've closed the readable end already
220
- if (this.readStatus === 'readable' || this.readStatus === 'paused') {
221
- return message.message
222
- }
223
259
  }
224
260
 
225
261
  private _sendFlag (flag: Message.Flag): boolean {