@libp2p/webrtc 3.2.1 → 3.2.2
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/dist/index.min.js +13 -13
- package/dist/src/index.d.ts +29 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/maconn.d.ts.map +1 -1
- package/dist/src/maconn.js +5 -2
- package/dist/src/maconn.js.map +1 -1
- package/dist/src/muxer.d.ts +10 -13
- package/dist/src/muxer.d.ts.map +1 -1
- package/dist/src/muxer.js +44 -29
- package/dist/src/muxer.js.map +1 -1
- package/dist/src/pb/message.d.ts +2 -1
- package/dist/src/pb/message.d.ts.map +1 -1
- package/dist/src/pb/message.js +2 -0
- package/dist/src/pb/message.js.map +1 -1
- package/dist/src/private-to-private/initiate-connection.d.ts +25 -0
- package/dist/src/private-to-private/initiate-connection.d.ts.map +1 -0
- package/dist/src/private-to-private/initiate-connection.js +145 -0
- package/dist/src/private-to-private/initiate-connection.js.map +1 -0
- package/dist/src/private-to-private/listener.d.ts +6 -2
- package/dist/src/private-to-private/listener.d.ts.map +1 -1
- package/dist/src/private-to-private/listener.js +6 -3
- package/dist/src/private-to-private/listener.js.map +1 -1
- package/dist/src/private-to-private/signaling-stream-handler.d.ts +10 -0
- package/dist/src/private-to-private/signaling-stream-handler.d.ts.map +1 -0
- package/dist/src/private-to-private/signaling-stream-handler.js +97 -0
- package/dist/src/private-to-private/signaling-stream-handler.js.map +1 -0
- package/dist/src/private-to-private/transport.d.ts +12 -2
- package/dist/src/private-to-private/transport.d.ts.map +1 -1
- package/dist/src/private-to-private/transport.js +67 -56
- package/dist/src/private-to-private/transport.js.map +1 -1
- package/dist/src/private-to-private/util.d.ts +6 -5
- package/dist/src/private-to-private/util.d.ts.map +1 -1
- package/dist/src/private-to-private/util.js +72 -21
- package/dist/src/private-to-private/util.js.map +1 -1
- package/dist/src/private-to-public/transport.d.ts +2 -2
- package/dist/src/private-to-public/transport.d.ts.map +1 -1
- package/dist/src/private-to-public/transport.js +2 -2
- package/dist/src/private-to-public/transport.js.map +1 -1
- package/dist/src/stream.d.ts +39 -19
- package/dist/src/stream.d.ts.map +1 -1
- package/dist/src/stream.js +135 -39
- package/dist/src/stream.js.map +1 -1
- package/dist/src/util.d.ts +6 -0
- package/dist/src/util.d.ts.map +1 -1
- package/dist/src/util.js +46 -0
- package/dist/src/util.js.map +1 -1
- package/dist/typedoc-urls.json +2 -0
- package/package.json +17 -11
- package/src/index.ts +34 -0
- package/src/maconn.ts +7 -2
- package/src/muxer.ts +58 -44
- package/src/pb/message.proto +6 -1
- package/src/pb/message.ts +4 -2
- package/src/private-to-private/initiate-connection.ts +191 -0
- package/src/private-to-private/listener.ts +12 -4
- package/src/private-to-private/signaling-stream-handler.ts +129 -0
- package/src/private-to-private/transport.ts +87 -59
- package/src/private-to-private/util.ts +89 -24
- package/src/private-to-public/transport.ts +4 -4
- package/src/stream.ts +163 -61
- package/src/util.ts +60 -0
- package/dist/src/private-to-private/handler.d.ts +0 -26
- package/dist/src/private-to-private/handler.d.ts.map +0 -1
- package/dist/src/private-to-private/handler.js +0 -137
- package/dist/src/private-to-private/handler.js.map +0 -1
- package/src/private-to-private/handler.ts +0 -177
package/src/stream.ts
CHANGED
|
@@ -3,18 +3,18 @@ import { AbstractStream, type AbstractStreamInit } from '@libp2p/interface/strea
|
|
|
3
3
|
import { logger } from '@libp2p/logger'
|
|
4
4
|
import * as lengthPrefixed from 'it-length-prefixed'
|
|
5
5
|
import { type Pushable, pushable } from 'it-pushable'
|
|
6
|
+
import pDefer from 'p-defer'
|
|
6
7
|
import { pEvent, TimeoutError } from 'p-event'
|
|
8
|
+
import pTimeout from 'p-timeout'
|
|
9
|
+
import { raceSignal } from 'race-signal'
|
|
7
10
|
import { Uint8ArrayList } from 'uint8arraylist'
|
|
8
11
|
import { Message } from './pb/message.js'
|
|
12
|
+
import type { DataChannelOptions } from './index.js'
|
|
13
|
+
import type { AbortOptions } from '@libp2p/interface'
|
|
9
14
|
import type { Direction } from '@libp2p/interface/connection'
|
|
15
|
+
import type { DeferredPromise } from 'p-defer'
|
|
10
16
|
|
|
11
|
-
export interface
|
|
12
|
-
maxMessageSize: number
|
|
13
|
-
maxBufferedAmount: number
|
|
14
|
-
bufferedAmountLowEventTimeout: number
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface WebRTCStreamInit extends AbstractStreamInit {
|
|
17
|
+
export interface WebRTCStreamInit extends AbstractStreamInit, DataChannelOptions {
|
|
18
18
|
/**
|
|
19
19
|
* The network channel used for bidirectional peer-to-peer transfers of
|
|
20
20
|
* arbitrary data
|
|
@@ -22,38 +22,46 @@ export interface WebRTCStreamInit extends AbstractStreamInit {
|
|
|
22
22
|
* {@link https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel}
|
|
23
23
|
*/
|
|
24
24
|
channel: RTCDataChannel
|
|
25
|
-
|
|
26
|
-
dataChannelOptions?: Partial<DataChannelOpts>
|
|
27
|
-
|
|
28
|
-
maxDataSize: number
|
|
29
25
|
}
|
|
30
26
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// How much can be buffered to the DataChannel at once
|
|
27
|
+
/**
|
|
28
|
+
* How much can be buffered to the DataChannel at once
|
|
29
|
+
*/
|
|
35
30
|
export const MAX_BUFFERED_AMOUNT = 16 * 1024 * 1024
|
|
36
31
|
|
|
37
|
-
|
|
32
|
+
/**
|
|
33
|
+
* How long time we wait for the 'bufferedamountlow' event to be emitted
|
|
34
|
+
*/
|
|
38
35
|
export const BUFFERED_AMOUNT_LOW_TIMEOUT = 30 * 1000
|
|
39
36
|
|
|
40
|
-
|
|
37
|
+
/**
|
|
38
|
+
* protobuf field definition overhead
|
|
39
|
+
*/
|
|
41
40
|
export const PROTOBUF_OVERHEAD = 5
|
|
42
41
|
|
|
43
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Length of varint, in bytes
|
|
44
|
+
*/
|
|
44
45
|
export const VARINT_LENGTH = 2
|
|
45
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Max message size that can be sent to the DataChannel
|
|
49
|
+
*/
|
|
50
|
+
export const MAX_MESSAGE_SIZE = 16 * 1024
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* When closing streams we send a FIN then wait for the remote to
|
|
54
|
+
* reply with a FIN_ACK. If that does not happen within this timeout
|
|
55
|
+
* we close the stream anyway.
|
|
56
|
+
*/
|
|
57
|
+
export const FIN_ACK_TIMEOUT = 5000
|
|
58
|
+
|
|
46
59
|
export class WebRTCStream extends AbstractStream {
|
|
47
60
|
/**
|
|
48
61
|
* The data channel used to send and receive data
|
|
49
62
|
*/
|
|
50
63
|
private readonly channel: RTCDataChannel
|
|
51
64
|
|
|
52
|
-
/**
|
|
53
|
-
* Data channel options
|
|
54
|
-
*/
|
|
55
|
-
private readonly dataChannelOptions: DataChannelOpts
|
|
56
|
-
|
|
57
65
|
/**
|
|
58
66
|
* push data from the underlying datachannel to the length prefix decoder
|
|
59
67
|
* and then the protobuf decoder.
|
|
@@ -62,24 +70,65 @@ export class WebRTCStream extends AbstractStream {
|
|
|
62
70
|
|
|
63
71
|
private messageQueue?: Uint8ArrayList
|
|
64
72
|
|
|
73
|
+
private readonly maxBufferedAmount: number
|
|
74
|
+
|
|
75
|
+
private readonly bufferedAmountLowEventTimeout: number
|
|
76
|
+
|
|
65
77
|
/**
|
|
66
78
|
* The maximum size of a message in bytes
|
|
67
79
|
*/
|
|
68
|
-
private readonly
|
|
80
|
+
private readonly maxMessageSize: number
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* When this promise is resolved, the remote has sent us a FIN flag
|
|
84
|
+
*/
|
|
85
|
+
private readonly receiveFinAck: DeferredPromise<void>
|
|
86
|
+
private readonly finAckTimeout: number
|
|
87
|
+
// private sentFinAck: boolean
|
|
69
88
|
|
|
70
89
|
constructor (init: WebRTCStreamInit) {
|
|
90
|
+
// override onEnd to send/receive FIN_ACK before closing the stream
|
|
91
|
+
const originalOnEnd = init.onEnd
|
|
92
|
+
init.onEnd = (err?: Error): void => {
|
|
93
|
+
this.log.trace('readable and writeable ends closed', this.status)
|
|
94
|
+
|
|
95
|
+
void Promise.resolve(async () => {
|
|
96
|
+
if (this.timeline.abort != null || this.timeline.reset !== null) {
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// wait for FIN_ACK if we haven't received it already
|
|
101
|
+
try {
|
|
102
|
+
await pTimeout(this.receiveFinAck.promise, {
|
|
103
|
+
milliseconds: this.finAckTimeout
|
|
104
|
+
})
|
|
105
|
+
} catch (err) {
|
|
106
|
+
this.log.error('error receiving FIN_ACK', err)
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
.then(() => {
|
|
110
|
+
// stop processing incoming messages
|
|
111
|
+
this.incomingData.end()
|
|
112
|
+
|
|
113
|
+
// final cleanup
|
|
114
|
+
originalOnEnd?.(err)
|
|
115
|
+
})
|
|
116
|
+
.catch(err => {
|
|
117
|
+
this.log.error('error ending stream', err)
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
71
121
|
super(init)
|
|
72
122
|
|
|
73
123
|
this.channel = init.channel
|
|
74
124
|
this.channel.binaryType = 'arraybuffer'
|
|
75
125
|
this.incomingData = pushable()
|
|
76
126
|
this.messageQueue = new Uint8ArrayList()
|
|
77
|
-
this.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
this.maxDataSize = init.maxDataSize
|
|
127
|
+
this.bufferedAmountLowEventTimeout = init.bufferedAmountLowEventTimeout ?? BUFFERED_AMOUNT_LOW_TIMEOUT
|
|
128
|
+
this.maxBufferedAmount = init.maxBufferedAmount ?? MAX_BUFFERED_AMOUNT
|
|
129
|
+
this.maxMessageSize = (init.maxMessageSize ?? MAX_MESSAGE_SIZE) - PROTOBUF_OVERHEAD - VARINT_LENGTH
|
|
130
|
+
this.receiveFinAck = pDefer()
|
|
131
|
+
this.finAckTimeout = init.closeTimeout ?? FIN_ACK_TIMEOUT
|
|
83
132
|
|
|
84
133
|
// set up initial state
|
|
85
134
|
switch (this.channel.readyState) {
|
|
@@ -105,17 +154,25 @@ export class WebRTCStream extends AbstractStream {
|
|
|
105
154
|
this.channel.onopen = (_evt) => {
|
|
106
155
|
this.timeline.open = new Date().getTime()
|
|
107
156
|
|
|
108
|
-
if (this.messageQueue != null) {
|
|
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
|
+
|
|
109
160
|
// send any queued messages
|
|
110
161
|
this._sendMessage(this.messageQueue)
|
|
111
162
|
.catch(err => {
|
|
163
|
+
this.log.error('error sending queued messages', err)
|
|
112
164
|
this.abort(err)
|
|
113
165
|
})
|
|
114
|
-
this.messageQueue = undefined
|
|
115
166
|
}
|
|
167
|
+
|
|
168
|
+
this.messageQueue = undefined
|
|
116
169
|
}
|
|
117
170
|
|
|
118
171
|
this.channel.onclose = (_evt) => {
|
|
172
|
+
// if the channel has closed we'll never receive a FIN_ACK so resolve the
|
|
173
|
+
// promise so we don't try to wait later
|
|
174
|
+
this.receiveFinAck.resolve()
|
|
175
|
+
|
|
119
176
|
void this.close().catch(err => {
|
|
120
177
|
this.log.error('error closing stream after channel closed', err)
|
|
121
178
|
})
|
|
@@ -126,8 +183,6 @@ export class WebRTCStream extends AbstractStream {
|
|
|
126
183
|
this.abort(err)
|
|
127
184
|
}
|
|
128
185
|
|
|
129
|
-
const self = this
|
|
130
|
-
|
|
131
186
|
this.channel.onmessage = async (event: MessageEvent<ArrayBuffer>) => {
|
|
132
187
|
const { data } = event
|
|
133
188
|
|
|
@@ -138,11 +193,13 @@ export class WebRTCStream extends AbstractStream {
|
|
|
138
193
|
this.incomingData.push(new Uint8Array(data, 0, data.byteLength))
|
|
139
194
|
}
|
|
140
195
|
|
|
196
|
+
const self = this
|
|
197
|
+
|
|
141
198
|
// pipe framed protobuf messages through a length prefixed decoder, and
|
|
142
199
|
// surface data from the `Message.message` field through a source.
|
|
143
200
|
Promise.resolve().then(async () => {
|
|
144
201
|
for await (const buf of lengthPrefixed.decode(this.incomingData)) {
|
|
145
|
-
const message = self.processIncomingProtobuf(buf
|
|
202
|
+
const message = self.processIncomingProtobuf(buf)
|
|
146
203
|
|
|
147
204
|
if (message != null) {
|
|
148
205
|
self.sourcePush(new Uint8ArrayList(message))
|
|
@@ -159,12 +216,12 @@ export class WebRTCStream extends AbstractStream {
|
|
|
159
216
|
}
|
|
160
217
|
|
|
161
218
|
async _sendMessage (data: Uint8ArrayList, checkBuffer: boolean = true): Promise<void> {
|
|
162
|
-
if (checkBuffer && this.channel.bufferedAmount > this.
|
|
219
|
+
if (checkBuffer && this.channel.bufferedAmount > this.maxBufferedAmount) {
|
|
163
220
|
try {
|
|
164
|
-
await pEvent(this.channel, 'bufferedamountlow', { timeout: this.
|
|
221
|
+
await pEvent(this.channel, 'bufferedamountlow', { timeout: this.bufferedAmountLowEventTimeout })
|
|
165
222
|
} catch (err: any) {
|
|
166
223
|
if (err instanceof TimeoutError) {
|
|
167
|
-
throw new
|
|
224
|
+
throw new CodeError(`Timed out waiting for DataChannel buffer to clear after ${this.bufferedAmountLowEventTimeout}ms`, 'ERR_BUFFER_CLEAR_TIMEOUT')
|
|
168
225
|
}
|
|
169
226
|
|
|
170
227
|
throw err
|
|
@@ -172,7 +229,7 @@ export class WebRTCStream extends AbstractStream {
|
|
|
172
229
|
}
|
|
173
230
|
|
|
174
231
|
if (this.channel.readyState === 'closed' || this.channel.readyState === 'closing') {
|
|
175
|
-
throw new CodeError(
|
|
232
|
+
throw new CodeError(`Invalid datachannel state - ${this.channel.readyState}`, 'ERR_INVALID_STATE')
|
|
176
233
|
}
|
|
177
234
|
|
|
178
235
|
if (this.channel.readyState === 'open') {
|
|
@@ -194,10 +251,12 @@ export class WebRTCStream extends AbstractStream {
|
|
|
194
251
|
}
|
|
195
252
|
|
|
196
253
|
async sendData (data: Uint8ArrayList): Promise<void> {
|
|
254
|
+
// sending messages is an async operation so use a copy of the list as it
|
|
255
|
+
// may be changed beneath us
|
|
197
256
|
data = data.sublist()
|
|
198
257
|
|
|
199
258
|
while (data.byteLength > 0) {
|
|
200
|
-
const toSend = Math.min(data.byteLength, this.
|
|
259
|
+
const toSend = Math.min(data.byteLength, this.maxMessageSize)
|
|
201
260
|
const buf = data.subarray(0, toSend)
|
|
202
261
|
const msgbuf = Message.encode({ message: buf })
|
|
203
262
|
const sendbuf = lengthPrefixed.encode.single(msgbuf)
|
|
@@ -211,8 +270,25 @@ export class WebRTCStream extends AbstractStream {
|
|
|
211
270
|
await this._sendFlag(Message.Flag.RESET)
|
|
212
271
|
}
|
|
213
272
|
|
|
214
|
-
async sendCloseWrite (): Promise<void> {
|
|
215
|
-
await this._sendFlag(Message.Flag.FIN)
|
|
273
|
+
async sendCloseWrite (options: AbortOptions): Promise<void> {
|
|
274
|
+
const sent = await this._sendFlag(Message.Flag.FIN)
|
|
275
|
+
|
|
276
|
+
if (sent) {
|
|
277
|
+
this.log.trace('awaiting FIN_ACK')
|
|
278
|
+
try {
|
|
279
|
+
await raceSignal(this.receiveFinAck.promise, options?.signal, {
|
|
280
|
+
errorMessage: 'sending close-write was aborted before FIN_ACK was received',
|
|
281
|
+
errorCode: 'ERR_FIN_ACK_NOT_RECEIVED'
|
|
282
|
+
})
|
|
283
|
+
} catch (err) {
|
|
284
|
+
this.log.error('failed to await FIN_ACK', err)
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
this.log.trace('sending FIN failed, not awaiting FIN_ACK')
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// if we've attempted to receive a FIN_ACK, do not try again
|
|
291
|
+
this.receiveFinAck.resolve()
|
|
216
292
|
}
|
|
217
293
|
|
|
218
294
|
async sendCloseRead (): Promise<void> {
|
|
@@ -222,14 +298,21 @@ export class WebRTCStream extends AbstractStream {
|
|
|
222
298
|
/**
|
|
223
299
|
* Handle incoming
|
|
224
300
|
*/
|
|
225
|
-
private processIncomingProtobuf (buffer:
|
|
301
|
+
private processIncomingProtobuf (buffer: Uint8ArrayList): Uint8Array | undefined {
|
|
226
302
|
const message = Message.decode(buffer)
|
|
227
303
|
|
|
228
304
|
if (message.flag !== undefined) {
|
|
305
|
+
this.log.trace('incoming flag %s, write status "%s", read status "%s"', message.flag, this.writeStatus, this.readStatus)
|
|
306
|
+
|
|
229
307
|
if (message.flag === Message.Flag.FIN) {
|
|
230
308
|
// We should expect no more data from the remote, stop reading
|
|
231
|
-
this.incomingData.end()
|
|
232
309
|
this.remoteCloseWrite()
|
|
310
|
+
|
|
311
|
+
this.log.trace('sending FIN_ACK')
|
|
312
|
+
void this._sendFlag(Message.Flag.FIN_ACK)
|
|
313
|
+
.catch(err => {
|
|
314
|
+
this.log.error('error sending FIN_ACK immediately', err)
|
|
315
|
+
})
|
|
233
316
|
}
|
|
234
317
|
|
|
235
318
|
if (message.flag === Message.Flag.RESET) {
|
|
@@ -241,21 +324,45 @@ export class WebRTCStream extends AbstractStream {
|
|
|
241
324
|
// The remote has stopped reading
|
|
242
325
|
this.remoteCloseRead()
|
|
243
326
|
}
|
|
327
|
+
|
|
328
|
+
if (message.flag === Message.Flag.FIN_ACK) {
|
|
329
|
+
this.log.trace('received FIN_ACK')
|
|
330
|
+
this.receiveFinAck.resolve()
|
|
331
|
+
}
|
|
244
332
|
}
|
|
245
333
|
|
|
246
|
-
|
|
334
|
+
// ignore data messages if we've closed the readable end already
|
|
335
|
+
if (this.readStatus === 'ready') {
|
|
336
|
+
return message.message
|
|
337
|
+
}
|
|
247
338
|
}
|
|
248
339
|
|
|
249
|
-
private async _sendFlag (flag: Message.Flag): Promise<
|
|
250
|
-
this.
|
|
340
|
+
private async _sendFlag (flag: Message.Flag): Promise<boolean> {
|
|
341
|
+
if (this.channel.readyState !== 'open') {
|
|
342
|
+
// flags can be sent while we or the remote are closing the datachannel so
|
|
343
|
+
// if the channel isn't open, don't try to send it but return false to let
|
|
344
|
+
// the caller know and act if they need to
|
|
345
|
+
this.log.trace('not sending flag %s because channel is not open', flag.toString())
|
|
346
|
+
return false
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
this.log.trace('sending flag %s', flag.toString())
|
|
251
350
|
const msgbuf = Message.encode({ flag })
|
|
252
351
|
const prefixedBuf = lengthPrefixed.encode.single(msgbuf)
|
|
253
352
|
|
|
254
|
-
|
|
353
|
+
try {
|
|
354
|
+
await this._sendMessage(prefixedBuf, false)
|
|
355
|
+
|
|
356
|
+
return true
|
|
357
|
+
} catch (err: any) {
|
|
358
|
+
this.log.error('could not send flag %s', flag.toString(), err)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return false
|
|
255
362
|
}
|
|
256
363
|
}
|
|
257
364
|
|
|
258
|
-
export interface WebRTCStreamOptions {
|
|
365
|
+
export interface WebRTCStreamOptions extends DataChannelOptions {
|
|
259
366
|
/**
|
|
260
367
|
* The network channel used for bidirectional peer-to-peer transfers of
|
|
261
368
|
* arbitrary data
|
|
@@ -269,23 +376,18 @@ export interface WebRTCStreamOptions {
|
|
|
269
376
|
*/
|
|
270
377
|
direction: Direction
|
|
271
378
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
onEnd?: (err?: Error | undefined) => void
|
|
379
|
+
/**
|
|
380
|
+
* A callback invoked when the channel ends
|
|
381
|
+
*/
|
|
382
|
+
onEnd?(err?: Error | undefined): void
|
|
277
383
|
}
|
|
278
384
|
|
|
279
385
|
export function createStream (options: WebRTCStreamOptions): WebRTCStream {
|
|
280
|
-
const { channel, direction
|
|
386
|
+
const { channel, direction } = options
|
|
281
387
|
|
|
282
388
|
return new WebRTCStream({
|
|
283
389
|
id: direction === 'inbound' ? (`i${channel.id}`) : `r${channel.id}`,
|
|
284
|
-
direction,
|
|
285
|
-
|
|
286
|
-
dataChannelOptions,
|
|
287
|
-
onEnd,
|
|
288
|
-
channel,
|
|
289
|
-
log: logger(`libp2p:webrtc:stream:${direction}:${channel.id}`)
|
|
390
|
+
log: logger(`libp2p:webrtc:stream:${direction}:${channel.id}`),
|
|
391
|
+
...options
|
|
290
392
|
})
|
|
291
393
|
}
|
package/src/util.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
import { logger } from '@libp2p/logger'
|
|
1
2
|
import { detect } from 'detect-browser'
|
|
3
|
+
import pDefer from 'p-defer'
|
|
4
|
+
import pTimeout from 'p-timeout'
|
|
5
|
+
|
|
6
|
+
const log = logger('libp2p:webrtc:utils')
|
|
2
7
|
|
|
3
8
|
const browser = detect()
|
|
4
9
|
export const isFirefox = ((browser != null) && browser.name === 'firefox')
|
|
@@ -6,3 +11,58 @@ export const isFirefox = ((browser != null) && browser.name === 'firefox')
|
|
|
6
11
|
export const nopSource = async function * nop (): AsyncGenerator<Uint8Array, any, unknown> {}
|
|
7
12
|
|
|
8
13
|
export const nopSink = async (_: any): Promise<void> => {}
|
|
14
|
+
|
|
15
|
+
export const DATA_CHANNEL_DRAIN_TIMEOUT = 30 * 1000
|
|
16
|
+
|
|
17
|
+
export function drainAndClose (channel: RTCDataChannel, direction: string, drainTimeout: number = DATA_CHANNEL_DRAIN_TIMEOUT): void {
|
|
18
|
+
if (channel.readyState !== 'open') {
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
void Promise.resolve()
|
|
23
|
+
.then(async () => {
|
|
24
|
+
// wait for bufferedAmount to become zero
|
|
25
|
+
if (channel.bufferedAmount > 0) {
|
|
26
|
+
log('%s drain channel with %d buffered bytes', direction, channel.bufferedAmount)
|
|
27
|
+
const deferred = pDefer()
|
|
28
|
+
let drained = false
|
|
29
|
+
|
|
30
|
+
channel.bufferedAmountLowThreshold = 0
|
|
31
|
+
|
|
32
|
+
const closeListener = (): void => {
|
|
33
|
+
if (!drained) {
|
|
34
|
+
log('%s drain channel closed before drain', direction)
|
|
35
|
+
deferred.resolve()
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
channel.addEventListener('close', closeListener, {
|
|
40
|
+
once: true
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
channel.addEventListener('bufferedamountlow', () => {
|
|
44
|
+
drained = true
|
|
45
|
+
channel.removeEventListener('close', closeListener)
|
|
46
|
+
deferred.resolve()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
await pTimeout(deferred.promise, {
|
|
50
|
+
milliseconds: drainTimeout
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
.then(async () => {
|
|
55
|
+
// only close if the channel is still open
|
|
56
|
+
if (channel.readyState === 'open') {
|
|
57
|
+
channel.close()
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
.catch(err => {
|
|
61
|
+
log.error('error closing outbound stream', err)
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface AbortPromiseOptions {
|
|
66
|
+
signal?: AbortSignal
|
|
67
|
+
message?: string
|
|
68
|
+
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { RTCPeerConnection } from '../webrtc/index.js';
|
|
2
|
-
import type { DataChannelOpts } from '../stream.js';
|
|
3
|
-
import type { Stream } from '@libp2p/interface/connection';
|
|
4
|
-
import type { StreamMuxerFactory } from '@libp2p/interface/stream-muxer';
|
|
5
|
-
import type { IncomingStreamData } from '@libp2p/interface-internal/registrar';
|
|
6
|
-
export type IncomingStreamOpts = {
|
|
7
|
-
rtcConfiguration?: RTCConfiguration;
|
|
8
|
-
dataChannelOptions?: Partial<DataChannelOpts>;
|
|
9
|
-
} & IncomingStreamData;
|
|
10
|
-
export declare function handleIncomingStream({ rtcConfiguration, dataChannelOptions, stream: rawStream }: IncomingStreamOpts): Promise<{
|
|
11
|
-
pc: RTCPeerConnection;
|
|
12
|
-
muxerFactory: StreamMuxerFactory;
|
|
13
|
-
remoteAddress: string;
|
|
14
|
-
}>;
|
|
15
|
-
export interface ConnectOptions {
|
|
16
|
-
stream: Stream;
|
|
17
|
-
signal: AbortSignal;
|
|
18
|
-
rtcConfiguration?: RTCConfiguration;
|
|
19
|
-
dataChannelOptions?: Partial<DataChannelOpts>;
|
|
20
|
-
}
|
|
21
|
-
export declare function initiateConnection({ rtcConfiguration, dataChannelOptions, signal, stream: rawStream }: ConnectOptions): Promise<{
|
|
22
|
-
pc: RTCPeerConnection;
|
|
23
|
-
muxerFactory: StreamMuxerFactory;
|
|
24
|
-
remoteAddress: string;
|
|
25
|
-
}>;
|
|
26
|
-
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/private-to-private/handler.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,iBAAiB,EAAyB,MAAM,oBAAoB,CAAA;AAG7E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAA;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAA;AAM9E,MAAM,MAAM,kBAAkB,GAAG;IAAE,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAA;CAAE,GAAG,kBAAkB,CAAA;AAE5I,wBAAsB,oBAAoB,CAAE,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,kBAAkB,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,iBAAiB,CAAC;IAAC,YAAY,EAAE,kBAAkB,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC,CAyExN;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,WAAW,CAAA;IACnB,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IACnC,kBAAkB,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAA;CAC9C;AAED,wBAAsB,kBAAkB,CAAE,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,cAAc,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,iBAAiB,CAAC;IAAC,YAAY,EAAE,kBAAkB,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC,CA6D1N"}
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { CodeError } from '@libp2p/interface/errors';
|
|
2
|
-
import { logger } from '@libp2p/logger';
|
|
3
|
-
import { abortableDuplex } from 'abortable-iterator';
|
|
4
|
-
import { pbStream } from 'it-protobuf-stream';
|
|
5
|
-
import pDefer, {} from 'p-defer';
|
|
6
|
-
import { DataChannelMuxerFactory } from '../muxer.js';
|
|
7
|
-
import { RTCPeerConnection, RTCSessionDescription } from '../webrtc/index.js';
|
|
8
|
-
import { Message } from './pb/message.js';
|
|
9
|
-
import { readCandidatesUntilConnected, resolveOnConnected } from './util.js';
|
|
10
|
-
const DEFAULT_TIMEOUT = 30 * 1000;
|
|
11
|
-
const log = logger('libp2p:webrtc:peer');
|
|
12
|
-
export async function handleIncomingStream({ rtcConfiguration, dataChannelOptions, stream: rawStream }) {
|
|
13
|
-
const signal = AbortSignal.timeout(DEFAULT_TIMEOUT);
|
|
14
|
-
const stream = pbStream(abortableDuplex(rawStream, signal)).pb(Message);
|
|
15
|
-
const pc = new RTCPeerConnection(rtcConfiguration);
|
|
16
|
-
try {
|
|
17
|
-
const muxerFactory = new DataChannelMuxerFactory({ peerConnection: pc, dataChannelOptions });
|
|
18
|
-
const connectedPromise = pDefer();
|
|
19
|
-
const answerSentPromise = pDefer();
|
|
20
|
-
signal.onabort = () => {
|
|
21
|
-
connectedPromise.reject(new CodeError('Timed out while trying to connect', 'ERR_TIMEOUT'));
|
|
22
|
-
};
|
|
23
|
-
// candidate callbacks
|
|
24
|
-
pc.onicecandidate = ({ candidate }) => {
|
|
25
|
-
answerSentPromise.promise.then(async () => {
|
|
26
|
-
await stream.write({
|
|
27
|
-
type: Message.Type.ICE_CANDIDATE,
|
|
28
|
-
data: (candidate != null) ? JSON.stringify(candidate.toJSON()) : ''
|
|
29
|
-
});
|
|
30
|
-
}, (err) => {
|
|
31
|
-
log.error('cannot set candidate since sending answer failed', err);
|
|
32
|
-
connectedPromise.reject(err);
|
|
33
|
-
});
|
|
34
|
-
};
|
|
35
|
-
resolveOnConnected(pc, connectedPromise);
|
|
36
|
-
// read an SDP offer
|
|
37
|
-
const pbOffer = await stream.read();
|
|
38
|
-
if (pbOffer.type !== Message.Type.SDP_OFFER) {
|
|
39
|
-
throw new Error(`expected message type SDP_OFFER, received: ${pbOffer.type ?? 'undefined'} `);
|
|
40
|
-
}
|
|
41
|
-
const offer = new RTCSessionDescription({
|
|
42
|
-
type: 'offer',
|
|
43
|
-
sdp: pbOffer.data
|
|
44
|
-
});
|
|
45
|
-
await pc.setRemoteDescription(offer).catch(err => {
|
|
46
|
-
log.error('could not execute setRemoteDescription', err);
|
|
47
|
-
throw new Error('Failed to set remoteDescription');
|
|
48
|
-
});
|
|
49
|
-
// create and write an SDP answer
|
|
50
|
-
const answer = await pc.createAnswer().catch(err => {
|
|
51
|
-
log.error('could not execute createAnswer', err);
|
|
52
|
-
answerSentPromise.reject(err);
|
|
53
|
-
throw new Error('Failed to create answer');
|
|
54
|
-
});
|
|
55
|
-
// write the answer to the remote
|
|
56
|
-
await stream.write({ type: Message.Type.SDP_ANSWER, data: answer.sdp });
|
|
57
|
-
await pc.setLocalDescription(answer).catch(err => {
|
|
58
|
-
log.error('could not execute setLocalDescription', err);
|
|
59
|
-
answerSentPromise.reject(err);
|
|
60
|
-
throw new Error('Failed to set localDescription');
|
|
61
|
-
});
|
|
62
|
-
answerSentPromise.resolve();
|
|
63
|
-
// wait until candidates are connected
|
|
64
|
-
await readCandidatesUntilConnected(connectedPromise, pc, stream);
|
|
65
|
-
const remoteAddress = parseRemoteAddress(pc.currentRemoteDescription?.sdp ?? '');
|
|
66
|
-
return { pc, muxerFactory, remoteAddress };
|
|
67
|
-
}
|
|
68
|
-
catch (err) {
|
|
69
|
-
pc.close();
|
|
70
|
-
throw err;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
export async function initiateConnection({ rtcConfiguration, dataChannelOptions, signal, stream: rawStream }) {
|
|
74
|
-
const stream = pbStream(abortableDuplex(rawStream, signal)).pb(Message);
|
|
75
|
-
// setup peer connection
|
|
76
|
-
const pc = new RTCPeerConnection(rtcConfiguration);
|
|
77
|
-
try {
|
|
78
|
-
const muxerFactory = new DataChannelMuxerFactory({ peerConnection: pc, dataChannelOptions });
|
|
79
|
-
const connectedPromise = pDefer();
|
|
80
|
-
resolveOnConnected(pc, connectedPromise);
|
|
81
|
-
// reject the connectedPromise if the signal aborts
|
|
82
|
-
signal.onabort = connectedPromise.reject;
|
|
83
|
-
// we create the channel so that the peerconnection has a component for which
|
|
84
|
-
// to collect candidates. The label is not relevant to connection initiation
|
|
85
|
-
// but can be useful for debugging
|
|
86
|
-
const channel = pc.createDataChannel('init');
|
|
87
|
-
// setup callback to write ICE candidates to the remote
|
|
88
|
-
// peer
|
|
89
|
-
pc.onicecandidate = ({ candidate }) => {
|
|
90
|
-
void stream.write({
|
|
91
|
-
type: Message.Type.ICE_CANDIDATE,
|
|
92
|
-
data: (candidate != null) ? JSON.stringify(candidate.toJSON()) : ''
|
|
93
|
-
})
|
|
94
|
-
.catch(err => {
|
|
95
|
-
log.error('error sending ICE candidate', err);
|
|
96
|
-
});
|
|
97
|
-
};
|
|
98
|
-
// create an offer
|
|
99
|
-
const offerSdp = await pc.createOffer();
|
|
100
|
-
// write the offer to the stream
|
|
101
|
-
await stream.write({ type: Message.Type.SDP_OFFER, data: offerSdp.sdp });
|
|
102
|
-
// set offer as local description
|
|
103
|
-
await pc.setLocalDescription(offerSdp).catch(err => {
|
|
104
|
-
log.error('could not execute setLocalDescription', err);
|
|
105
|
-
throw new Error('Failed to set localDescription');
|
|
106
|
-
});
|
|
107
|
-
// read answer
|
|
108
|
-
const answerMessage = await stream.read();
|
|
109
|
-
if (answerMessage.type !== Message.Type.SDP_ANSWER) {
|
|
110
|
-
throw new Error('remote should send an SDP answer');
|
|
111
|
-
}
|
|
112
|
-
const answerSdp = new RTCSessionDescription({ type: 'answer', sdp: answerMessage.data });
|
|
113
|
-
await pc.setRemoteDescription(answerSdp).catch(err => {
|
|
114
|
-
log.error('could not execute setRemoteDescription', err);
|
|
115
|
-
throw new Error('Failed to set remoteDescription');
|
|
116
|
-
});
|
|
117
|
-
await readCandidatesUntilConnected(connectedPromise, pc, stream);
|
|
118
|
-
channel.close();
|
|
119
|
-
const remoteAddress = parseRemoteAddress(pc.currentRemoteDescription?.sdp ?? '');
|
|
120
|
-
return { pc, muxerFactory, remoteAddress };
|
|
121
|
-
}
|
|
122
|
-
catch (err) {
|
|
123
|
-
pc.close();
|
|
124
|
-
throw err;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
function parseRemoteAddress(sdp) {
|
|
128
|
-
// 'a=candidate:1746876089 1 udp 2113937151 0614fbad-b...ocal 54882 typ host generation 0 network-cost 999'
|
|
129
|
-
const candidateLine = sdp.split('\r\n').filter(line => line.startsWith('a=candidate')).pop();
|
|
130
|
-
const candidateParts = candidateLine?.split(' ');
|
|
131
|
-
if (candidateLine == null || candidateParts == null || candidateParts.length < 5) {
|
|
132
|
-
log('could not parse remote address from', candidateLine);
|
|
133
|
-
return '/webrtc';
|
|
134
|
-
}
|
|
135
|
-
return `/dnsaddr/${candidateParts[4]}/${candidateParts[2].toLowerCase()}/${candidateParts[5]}/webrtc`;
|
|
136
|
-
}
|
|
137
|
-
//# sourceMappingURL=handler.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../../src/private-to-private/handler.ts"],"names":[],"mappings":"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"}
|