@libp2p/webrtc 3.2.11-9c67c5b3d → 3.2.11-bb6ceb192
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.
- package/README.md +1 -1
- package/dist/index.min.js +12 -12
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/maconn.d.ts +2 -2
- package/dist/src/maconn.d.ts.map +1 -1
- package/dist/src/maconn.js.map +1 -1
- package/dist/src/muxer.d.ts +1 -0
- package/dist/src/muxer.d.ts.map +1 -1
- package/dist/src/muxer.js +20 -0
- package/dist/src/muxer.js.map +1 -1
- package/dist/src/private-to-private/util.d.ts.map +1 -1
- package/dist/src/private-to-private/util.js +22 -34
- package/dist/src/private-to-private/util.js.map +1 -1
- package/dist/src/stream.d.ts +7 -2
- package/dist/src/stream.d.ts.map +1 -1
- package/dist/src/stream.js +17 -30
- package/dist/src/stream.js.map +1 -1
- package/package.json +13 -13
- package/src/index.ts +6 -0
- package/src/maconn.ts +2 -1
- package/src/muxer.ts +28 -0
- package/src/private-to-private/util.ts +25 -37
- package/src/stream.ts +20 -34
package/src/muxer.ts
CHANGED
|
@@ -51,6 +51,7 @@ export class DataChannelMuxerFactory implements StreamMuxerFactory {
|
|
|
51
51
|
private readonly metrics?: CounterGroup
|
|
52
52
|
private readonly dataChannelOptions?: DataChannelOptions
|
|
53
53
|
private readonly components: DataChannelMuxerFactoryComponents
|
|
54
|
+
private readonly log: Logger
|
|
54
55
|
|
|
55
56
|
constructor (components: DataChannelMuxerFactoryComponents, init: DataChannelMuxerFactoryInit) {
|
|
56
57
|
this.components = components
|
|
@@ -58,9 +59,20 @@ export class DataChannelMuxerFactory implements StreamMuxerFactory {
|
|
|
58
59
|
this.metrics = init.metrics
|
|
59
60
|
this.protocol = init.protocol ?? PROTOCOL
|
|
60
61
|
this.dataChannelOptions = init.dataChannelOptions ?? {}
|
|
62
|
+
this.log = components.logger.forComponent('libp2p:webrtc:datachannelmuxerfactory')
|
|
61
63
|
|
|
62
64
|
// store any datachannels opened before upgrade has been completed
|
|
63
65
|
this.peerConnection.ondatachannel = ({ channel }) => {
|
|
66
|
+
this.log.trace('incoming early datachannel with channel id %d and label "%s"', channel.id)
|
|
67
|
+
|
|
68
|
+
// 'init' channel is only used during connection establishment
|
|
69
|
+
if (channel.label === 'init') {
|
|
70
|
+
this.log.trace('closing early init channel')
|
|
71
|
+
channel.close()
|
|
72
|
+
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
64
76
|
// @ts-expect-error fields are set below
|
|
65
77
|
const bufferedStream: BufferedStream = {}
|
|
66
78
|
|
|
@@ -136,10 +148,21 @@ export class DataChannelMuxer implements StreamMuxer {
|
|
|
136
148
|
* {@link https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/datachannel_event}
|
|
137
149
|
*/
|
|
138
150
|
this.peerConnection.ondatachannel = ({ channel }) => {
|
|
151
|
+
this.log.trace('incoming datachannel with channel id %d', channel.id)
|
|
152
|
+
|
|
153
|
+
// 'init' channel is only used during connection establishment
|
|
154
|
+
if (channel.label === 'init') {
|
|
155
|
+
this.log.trace('closing init channel')
|
|
156
|
+
channel.close()
|
|
157
|
+
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
|
|
139
161
|
const stream = createStream({
|
|
140
162
|
channel,
|
|
141
163
|
direction: 'inbound',
|
|
142
164
|
onEnd: () => {
|
|
165
|
+
this.log('incoming channel %s ended with state %s', channel.id, channel.readyState)
|
|
143
166
|
this.#onStreamEnd(stream, channel)
|
|
144
167
|
},
|
|
145
168
|
logger: this.logger,
|
|
@@ -161,6 +184,7 @@ export class DataChannelMuxer implements StreamMuxer {
|
|
|
161
184
|
queueMicrotask(() => {
|
|
162
185
|
this.init.streams.forEach(bufferedStream => {
|
|
163
186
|
bufferedStream.onEnd = () => {
|
|
187
|
+
this.log('incoming early channel %s ended with state %s', bufferedStream.channel.id, bufferedStream.channel.readyState)
|
|
164
188
|
this.#onStreamEnd(bufferedStream.stream, bufferedStream.channel)
|
|
165
189
|
}
|
|
166
190
|
|
|
@@ -220,10 +244,14 @@ export class DataChannelMuxer implements StreamMuxer {
|
|
|
220
244
|
newStream (): Stream {
|
|
221
245
|
// The spec says the label SHOULD be an empty string: https://github.com/libp2p/specs/blob/master/webrtc/README.md#rtcdatachannel-label
|
|
222
246
|
const channel = this.peerConnection.createDataChannel('')
|
|
247
|
+
|
|
248
|
+
this.log.trace('opened outgoing datachannel with channel id %s', channel.id)
|
|
249
|
+
|
|
223
250
|
const stream = createStream({
|
|
224
251
|
channel,
|
|
225
252
|
direction: 'outbound',
|
|
226
253
|
onEnd: () => {
|
|
254
|
+
this.log('outgoing channel %s ended with state %s', channel.id, channel.readyState)
|
|
227
255
|
this.#onStreamEnd(stream, channel)
|
|
228
256
|
},
|
|
229
257
|
logger: this.logger,
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { CodeError } from '@libp2p/interface/errors'
|
|
2
|
-
import {
|
|
2
|
+
import { closeSource } from '@libp2p/utils/close-source'
|
|
3
3
|
import { anySignal } from 'any-signal'
|
|
4
|
-
import * as lp from 'it-length-prefixed'
|
|
5
|
-
import { AbortError, raceSignal } from 'race-signal'
|
|
6
4
|
import { isFirefox } from '../util.js'
|
|
7
5
|
import { RTCIceCandidate } from '../webrtc/index.js'
|
|
8
6
|
import { Message } from './pb/message.js'
|
|
@@ -29,41 +27,40 @@ export const readCandidatesUntilConnected = async (connectedPromise: DeferredPro
|
|
|
29
27
|
options.signal
|
|
30
28
|
])
|
|
31
29
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
}
|
|
30
|
+
const abortListener = (): void => {
|
|
31
|
+
closeSource(stream.unwrap().unwrap().source, options.log)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
signal.addEventListener('abort', abortListener)
|
|
35
35
|
|
|
36
36
|
try {
|
|
37
37
|
// read candidates until we are connected or we reach the end of the stream
|
|
38
|
-
|
|
39
|
-
const message =
|
|
38
|
+
while (true) {
|
|
39
|
+
const message = await Promise.race([
|
|
40
|
+
connectedPromise.promise,
|
|
41
|
+
stream.read()
|
|
42
|
+
])
|
|
43
|
+
|
|
44
|
+
// stream ended or we became connected
|
|
45
|
+
if (message == null) {
|
|
46
|
+
break
|
|
47
|
+
}
|
|
40
48
|
|
|
41
49
|
if (message.type !== Message.Type.ICE_CANDIDATE) {
|
|
42
50
|
throw new CodeError('ICE candidate message expected', 'ERR_NOT_ICE_CANDIDATE')
|
|
43
51
|
}
|
|
44
52
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (candidateInit === '') {
|
|
48
|
-
options.log.trace('end-of-candidates for this generation received')
|
|
49
|
-
candidateInit = {
|
|
50
|
-
candidate: '',
|
|
51
|
-
sdpMid: '0',
|
|
52
|
-
sdpMLineIndex: 0
|
|
53
|
-
}
|
|
54
|
-
}
|
|
53
|
+
const candidateInit = JSON.parse(message.data ?? 'null')
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
// an empty string means this generation of candidates is complete, a null
|
|
56
|
+
// candidate means candidate gathering has finished
|
|
57
|
+
// see - https://www.w3.org/TR/webrtc/#rtcpeerconnectioniceevent
|
|
58
|
+
if (candidateInit === '' || candidateInit === null) {
|
|
57
59
|
options.log.trace('end-of-candidates received')
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
sdpMid: '0',
|
|
61
|
-
sdpMLineIndex: 0
|
|
62
|
-
}
|
|
60
|
+
|
|
61
|
+
continue
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
// a null candidate means end-of-candidates
|
|
66
|
-
// see - https://www.w3.org/TR/webrtc/#rtcpeerconnectioniceevent
|
|
67
64
|
const candidate = new RTCIceCandidate(candidateInit)
|
|
68
65
|
|
|
69
66
|
options.log.trace('%s received new ICE candidate', options.direction, candidate)
|
|
@@ -71,24 +68,15 @@ export const readCandidatesUntilConnected = async (connectedPromise: DeferredPro
|
|
|
71
68
|
try {
|
|
72
69
|
await pc.addIceCandidate(candidate)
|
|
73
70
|
} catch (err) {
|
|
74
|
-
options.log.error('%s bad candidate received', options.direction, err)
|
|
71
|
+
options.log.error('%s bad candidate received', options.direction, candidateInit, err)
|
|
75
72
|
}
|
|
76
73
|
}
|
|
77
74
|
} catch (err) {
|
|
78
75
|
options.log.error('%s error parsing ICE candidate', options.direction, err)
|
|
79
76
|
} finally {
|
|
77
|
+
signal.removeEventListener('abort', abortListener)
|
|
80
78
|
signal.clear()
|
|
81
79
|
}
|
|
82
|
-
|
|
83
|
-
if (options.signal?.aborted === true) {
|
|
84
|
-
throw new AbortError('Aborted while reading ICE candidates', 'ERR_ICE_CANDIDATES_READ_ABORTED')
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// read all available ICE candidates, wait for connection state change
|
|
88
|
-
await raceSignal(connectedPromise.promise, options.signal, {
|
|
89
|
-
errorMessage: 'Aborted before connected',
|
|
90
|
-
errorCode: 'ERR_ABORTED_BEFORE_CONNECTED'
|
|
91
|
-
})
|
|
92
80
|
}
|
|
93
81
|
|
|
94
82
|
export function resolveOnConnected (pc: RTCPeerConnection, promise: DeferredPromise<void>): void {
|
package/src/stream.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CodeError } from '@libp2p/interface/errors'
|
|
2
|
-
import { AbstractStream, type AbstractStreamInit } from '@libp2p/
|
|
2
|
+
import { AbstractStream, type AbstractStreamInit } from '@libp2p/utils/abstract-stream'
|
|
3
3
|
import * as lengthPrefixed from 'it-length-prefixed'
|
|
4
4
|
import { type Pushable, pushable } from 'it-pushable'
|
|
5
5
|
import pDefer from 'p-defer'
|
|
@@ -57,6 +57,12 @@ export const MAX_MESSAGE_SIZE = 16 * 1024
|
|
|
57
57
|
*/
|
|
58
58
|
export const FIN_ACK_TIMEOUT = 5000
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* When sending data messages, if the channel is not in the "open" state, wait
|
|
62
|
+
* this long for the "open" event to fire.
|
|
63
|
+
*/
|
|
64
|
+
export const OPEN_TIMEOUT = 5000
|
|
65
|
+
|
|
60
66
|
export class WebRTCStream extends AbstractStream {
|
|
61
67
|
/**
|
|
62
68
|
* The data channel used to send and receive data
|
|
@@ -69,8 +75,6 @@ export class WebRTCStream extends AbstractStream {
|
|
|
69
75
|
*/
|
|
70
76
|
private readonly incomingData: Pushable<Uint8Array>
|
|
71
77
|
|
|
72
|
-
private messageQueue?: Uint8ArrayList
|
|
73
|
-
|
|
74
78
|
private readonly maxBufferedAmount: number
|
|
75
79
|
|
|
76
80
|
private readonly bufferedAmountLowEventTimeout: number
|
|
@@ -85,6 +89,7 @@ export class WebRTCStream extends AbstractStream {
|
|
|
85
89
|
*/
|
|
86
90
|
private readonly receiveFinAck: DeferredPromise<void>
|
|
87
91
|
private readonly finAckTimeout: number
|
|
92
|
+
private readonly openTimeout: number
|
|
88
93
|
|
|
89
94
|
constructor (init: WebRTCStreamInit) {
|
|
90
95
|
// override onEnd to send/receive FIN_ACK before closing the stream
|
|
@@ -122,17 +127,18 @@ export class WebRTCStream extends AbstractStream {
|
|
|
122
127
|
|
|
123
128
|
this.channel = init.channel
|
|
124
129
|
this.channel.binaryType = 'arraybuffer'
|
|
125
|
-
this.incomingData = pushable()
|
|
126
|
-
this.messageQueue = new Uint8ArrayList()
|
|
130
|
+
this.incomingData = pushable<Uint8Array>()
|
|
127
131
|
this.bufferedAmountLowEventTimeout = init.bufferedAmountLowEventTimeout ?? BUFFERED_AMOUNT_LOW_TIMEOUT
|
|
128
132
|
this.maxBufferedAmount = init.maxBufferedAmount ?? MAX_BUFFERED_AMOUNT
|
|
129
133
|
this.maxMessageSize = (init.maxMessageSize ?? MAX_MESSAGE_SIZE) - PROTOBUF_OVERHEAD - VARINT_LENGTH
|
|
130
134
|
this.receiveFinAck = pDefer()
|
|
131
135
|
this.finAckTimeout = init.closeTimeout ?? FIN_ACK_TIMEOUT
|
|
136
|
+
this.openTimeout = init.openTimeout ?? OPEN_TIMEOUT
|
|
132
137
|
|
|
133
138
|
// set up initial state
|
|
134
139
|
switch (this.channel.readyState) {
|
|
135
140
|
case 'open':
|
|
141
|
+
this.timeline.open = new Date().getTime()
|
|
136
142
|
break
|
|
137
143
|
|
|
138
144
|
case 'closed':
|
|
@@ -153,19 +159,6 @@ export class WebRTCStream extends AbstractStream {
|
|
|
153
159
|
// handle RTCDataChannel events
|
|
154
160
|
this.channel.onopen = (_evt) => {
|
|
155
161
|
this.timeline.open = new Date().getTime()
|
|
156
|
-
|
|
157
|
-
if (this.messageQueue != null && this.messageQueue.byteLength > 0) {
|
|
158
|
-
this.log.trace('dataChannel opened, sending queued messages', this.messageQueue.byteLength, this.channel.readyState)
|
|
159
|
-
|
|
160
|
-
// send any queued messages
|
|
161
|
-
this._sendMessage(this.messageQueue)
|
|
162
|
-
.catch(err => {
|
|
163
|
-
this.log.error('error sending queued messages', err)
|
|
164
|
-
this.abort(err)
|
|
165
|
-
})
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
this.messageQueue = undefined
|
|
169
162
|
}
|
|
170
163
|
|
|
171
164
|
this.channel.onclose = (_evt) => {
|
|
@@ -218,6 +211,7 @@ export class WebRTCStream extends AbstractStream {
|
|
|
218
211
|
async _sendMessage (data: Uint8ArrayList, checkBuffer: boolean = true): Promise<void> {
|
|
219
212
|
if (checkBuffer && this.channel.bufferedAmount > this.maxBufferedAmount) {
|
|
220
213
|
try {
|
|
214
|
+
this.log('channel buffer is %d, wait for "bufferedamountlow" event', this.channel.bufferedAmount)
|
|
221
215
|
await pEvent(this.channel, 'bufferedamountlow', { timeout: this.bufferedAmountLowEventTimeout })
|
|
222
216
|
} catch (err: any) {
|
|
223
217
|
if (err instanceof TimeoutError) {
|
|
@@ -232,22 +226,14 @@ export class WebRTCStream extends AbstractStream {
|
|
|
232
226
|
throw new CodeError(`Invalid datachannel state - ${this.channel.readyState}`, 'ERR_INVALID_STATE')
|
|
233
227
|
}
|
|
234
228
|
|
|
235
|
-
if (this.channel.readyState
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
} else if (this.channel.readyState === 'connecting') {
|
|
241
|
-
// queue message for when we are open
|
|
242
|
-
if (this.messageQueue == null) {
|
|
243
|
-
this.messageQueue = new Uint8ArrayList()
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
this.messageQueue.append(data)
|
|
247
|
-
} else {
|
|
248
|
-
this.log.error('unknown datachannel state %s', this.channel.readyState)
|
|
249
|
-
throw new CodeError('Unknown datachannel state', 'ERR_INVALID_STATE')
|
|
229
|
+
if (this.channel.readyState !== 'open') {
|
|
230
|
+
this.log('channel state is "%s" and not "open", waiting for "open" event before sending data', this.channel.readyState)
|
|
231
|
+
await pEvent(this.channel, 'open', { timeout: this.openTimeout })
|
|
232
|
+
this.log('channel state is now "%s", sending data', this.channel.readyState)
|
|
250
233
|
}
|
|
234
|
+
|
|
235
|
+
// send message without copying data
|
|
236
|
+
this.channel.send(data.subarray())
|
|
251
237
|
}
|
|
252
238
|
|
|
253
239
|
async sendData (data: Uint8ArrayList): Promise<void> {
|
|
@@ -342,7 +328,7 @@ export class WebRTCStream extends AbstractStream {
|
|
|
342
328
|
// flags can be sent while we or the remote are closing the datachannel so
|
|
343
329
|
// if the channel isn't open, don't try to send it but return false to let
|
|
344
330
|
// the caller know and act if they need to
|
|
345
|
-
this.log.trace('not sending flag %s because channel is not open', flag.toString())
|
|
331
|
+
this.log.trace('not sending flag %s because channel is "%s" and not "open"', this.channel.readyState, flag.toString())
|
|
346
332
|
return false
|
|
347
333
|
}
|
|
348
334
|
|