@libp2p/webrtc 2.0.2 → 2.0.4
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 +17 -17
- package/dist/src/index.d.ts +19 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +20 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/muxer.d.ts +27 -17
- package/dist/src/muxer.d.ts.map +1 -1
- package/dist/src/muxer.js +47 -71
- package/dist/src/muxer.js.map +1 -1
- package/dist/src/private-to-private/handler.d.ts +5 -2
- package/dist/src/private-to-private/handler.d.ts.map +1 -1
- package/dist/src/private-to-private/handler.js +4 -4
- package/dist/src/private-to-private/handler.js.map +1 -1
- package/dist/src/private-to-private/transport.d.ts +3 -1
- package/dist/src/private-to-private/transport.d.ts.map +1 -1
- package/dist/src/private-to-private/transport.js +4 -2
- package/dist/src/private-to-private/transport.js.map +1 -1
- package/dist/src/private-to-public/transport.d.ts +6 -1
- package/dist/src/private-to-public/transport.d.ts.map +1 -1
- package/dist/src/private-to-public/transport.js +6 -4
- package/dist/src/private-to-public/transport.js.map +1 -1
- package/dist/src/stream.d.ts +20 -133
- package/dist/src/stream.d.ts.map +1 -1
- package/dist/src/stream.js +121 -289
- package/dist/src/stream.js.map +1 -1
- package/package.json +12 -4
- package/src/index.ts +21 -4
- package/src/muxer.ts +75 -76
- package/src/private-to-private/handler.ts +7 -6
- package/src/private-to-private/transport.ts +6 -2
- package/src/private-to-public/transport.ts +11 -5
- package/src/stream.ts +156 -345
package/src/stream.ts
CHANGED
|
@@ -1,30 +1,22 @@
|
|
|
1
|
+
import { AbstractStream, type AbstractStreamInit } from '@libp2p/interface-stream-muxer/stream'
|
|
2
|
+
import { CodeError } from '@libp2p/interfaces/errors'
|
|
1
3
|
import { logger } from '@libp2p/logger'
|
|
2
4
|
import * as lengthPrefixed from 'it-length-prefixed'
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import { pushable } from 'it-pushable'
|
|
6
|
-
import defer, { type DeferredPromise } from 'p-defer'
|
|
5
|
+
import { type Pushable, pushable } from 'it-pushable'
|
|
6
|
+
import { pEvent, TimeoutError } from 'p-event'
|
|
7
7
|
import { Uint8ArrayList } from 'uint8arraylist'
|
|
8
8
|
import { Message } from './pb/message.js'
|
|
9
|
-
import type {
|
|
10
|
-
import type { Source } from 'it-stream-types'
|
|
9
|
+
import type { Direction, Stream } from '@libp2p/interface-connection'
|
|
11
10
|
|
|
12
11
|
const log = logger('libp2p:webrtc:stream')
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return {
|
|
19
|
-
direction: dir,
|
|
20
|
-
timeline: {
|
|
21
|
-
open: 0,
|
|
22
|
-
close: undefined
|
|
23
|
-
}
|
|
24
|
-
}
|
|
13
|
+
export interface DataChannelOpts {
|
|
14
|
+
maxMessageSize: number
|
|
15
|
+
maxBufferedAmount: number
|
|
16
|
+
bufferedAmountLowEventTimeout: number
|
|
25
17
|
}
|
|
26
18
|
|
|
27
|
-
interface
|
|
19
|
+
export interface WebRTCStreamInit extends AbstractStreamInit {
|
|
28
20
|
/**
|
|
29
21
|
* The network channel used for bidirectional peer-to-peer transfers of
|
|
30
22
|
* arbitrary data
|
|
@@ -33,216 +25,85 @@ interface StreamInitOpts {
|
|
|
33
25
|
*/
|
|
34
26
|
channel: RTCDataChannel
|
|
35
27
|
|
|
36
|
-
|
|
37
|
-
* User defined stream metadata
|
|
38
|
-
*/
|
|
39
|
-
metadata?: Record<string, any>
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Stats about this stream
|
|
43
|
-
*/
|
|
44
|
-
stat: StreamStat
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Callback to invoke when the stream is closed.
|
|
48
|
-
*/
|
|
49
|
-
closeCb?: (stream: WebRTCStream) => void
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/*
|
|
53
|
-
* State transitions for a stream
|
|
54
|
-
*/
|
|
55
|
-
interface StreamStateInput {
|
|
56
|
-
/**
|
|
57
|
-
* Outbound conections are opened by the local node, inbound streams are
|
|
58
|
-
* opened by the remote
|
|
59
|
-
*/
|
|
60
|
-
direction: 'inbound' | 'outbound'
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Message flag from the protobufs
|
|
64
|
-
*
|
|
65
|
-
* 0 = FIN
|
|
66
|
-
* 1 = STOP_SENDING
|
|
67
|
-
* 2 = RESET
|
|
68
|
-
*/
|
|
69
|
-
flag: Message.Flag
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export enum StreamStates {
|
|
73
|
-
OPEN,
|
|
74
|
-
READ_CLOSED,
|
|
75
|
-
WRITE_CLOSED,
|
|
76
|
-
CLOSED,
|
|
28
|
+
dataChannelOptions?: Partial<DataChannelOpts>
|
|
77
29
|
}
|
|
78
30
|
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
function unreachableBranch (x: never): never {
|
|
82
|
-
throw new Error('Case not handled in switch')
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
class StreamState {
|
|
86
|
-
state: StreamStates = StreamStates.OPEN
|
|
87
|
-
|
|
88
|
-
isWriteClosed (): boolean {
|
|
89
|
-
return (this.state === StreamStates.CLOSED || this.state === StreamStates.WRITE_CLOSED)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
transition ({ direction, flag }: StreamStateInput): [StreamStates, StreamStates] {
|
|
93
|
-
const prev = this.state
|
|
94
|
-
|
|
95
|
-
// return early if the stream is closed
|
|
96
|
-
if (this.state === StreamStates.CLOSED) {
|
|
97
|
-
return [prev, StreamStates.CLOSED]
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (direction === 'inbound') {
|
|
101
|
-
switch (flag) {
|
|
102
|
-
case Message.Flag.FIN:
|
|
103
|
-
if (this.state === StreamStates.OPEN) {
|
|
104
|
-
this.state = StreamStates.READ_CLOSED
|
|
105
|
-
} else if (this.state === StreamStates.WRITE_CLOSED) {
|
|
106
|
-
this.state = StreamStates.CLOSED
|
|
107
|
-
}
|
|
108
|
-
break
|
|
109
|
-
|
|
110
|
-
case Message.Flag.STOP_SENDING:
|
|
111
|
-
if (this.state === StreamStates.OPEN) {
|
|
112
|
-
this.state = StreamStates.WRITE_CLOSED
|
|
113
|
-
} else if (this.state === StreamStates.READ_CLOSED) {
|
|
114
|
-
this.state = StreamStates.CLOSED
|
|
115
|
-
}
|
|
116
|
-
break
|
|
117
|
-
|
|
118
|
-
case Message.Flag.RESET:
|
|
119
|
-
this.state = StreamStates.CLOSED
|
|
120
|
-
break
|
|
121
|
-
default:
|
|
122
|
-
unreachableBranch(flag)
|
|
123
|
-
}
|
|
124
|
-
} else {
|
|
125
|
-
switch (flag) {
|
|
126
|
-
case Message.Flag.FIN:
|
|
127
|
-
if (this.state === StreamStates.OPEN) {
|
|
128
|
-
this.state = StreamStates.WRITE_CLOSED
|
|
129
|
-
} else if (this.state === StreamStates.READ_CLOSED) {
|
|
130
|
-
this.state = StreamStates.CLOSED
|
|
131
|
-
}
|
|
132
|
-
break
|
|
133
|
-
|
|
134
|
-
case Message.Flag.STOP_SENDING:
|
|
135
|
-
if (this.state === StreamStates.OPEN) {
|
|
136
|
-
this.state = StreamStates.READ_CLOSED
|
|
137
|
-
} else if (this.state === StreamStates.WRITE_CLOSED) {
|
|
138
|
-
this.state = StreamStates.CLOSED
|
|
139
|
-
}
|
|
140
|
-
break
|
|
141
|
-
|
|
142
|
-
case Message.Flag.RESET:
|
|
143
|
-
this.state = StreamStates.CLOSED
|
|
144
|
-
break
|
|
145
|
-
|
|
146
|
-
default:
|
|
147
|
-
unreachableBranch(flag)
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
return [prev, this.state]
|
|
151
|
-
}
|
|
152
|
-
}
|
|
31
|
+
// Max message size that can be sent to the DataChannel
|
|
32
|
+
const MAX_MESSAGE_SIZE = 16 * 1024
|
|
153
33
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
* Unique identifier for a stream
|
|
157
|
-
*/
|
|
158
|
-
id: string
|
|
34
|
+
// How much can be buffered to the DataChannel at once
|
|
35
|
+
const MAX_BUFFERED_AMOUNT = 16 * 1024 * 1024
|
|
159
36
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
*/
|
|
163
|
-
stat: StreamStat
|
|
37
|
+
// How long time we wait for the 'bufferedamountlow' event to be emitted
|
|
38
|
+
const BUFFERED_AMOUNT_LOW_TIMEOUT = 30 * 1000
|
|
164
39
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
*/
|
|
168
|
-
metadata: Record<string, any>
|
|
40
|
+
// protobuf field definition overhead
|
|
41
|
+
const PROTOBUF_OVERHEAD = 3
|
|
169
42
|
|
|
43
|
+
class WebRTCStream extends AbstractStream {
|
|
170
44
|
/**
|
|
171
45
|
* The data channel used to send and receive data
|
|
172
46
|
*/
|
|
173
47
|
private readonly channel: RTCDataChannel
|
|
174
48
|
|
|
175
49
|
/**
|
|
176
|
-
*
|
|
50
|
+
* Data channel options
|
|
177
51
|
*/
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Read unwrapped protobuf data from the underlying datachannel.
|
|
182
|
-
* _src is exposed to the user via the `source` getter to .
|
|
183
|
-
*/
|
|
184
|
-
private readonly _src: AsyncGenerator<Uint8ArrayList, any, unknown>
|
|
52
|
+
private readonly dataChannelOptions: DataChannelOpts
|
|
185
53
|
|
|
186
54
|
/**
|
|
187
55
|
* push data from the underlying datachannel to the length prefix decoder
|
|
188
56
|
* and then the protobuf decoder.
|
|
189
57
|
*/
|
|
190
|
-
private readonly
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Deferred promise that resolves when the underlying datachannel is in the
|
|
194
|
-
* open state.
|
|
195
|
-
*/
|
|
196
|
-
opened: DeferredPromise<void> = defer()
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* sinkCreated is set to true once the sinkFunction is invoked
|
|
200
|
-
*/
|
|
201
|
-
_sinkCalled: boolean = false
|
|
58
|
+
private readonly incomingData: Pushable<Uint8Array>
|
|
202
59
|
|
|
203
|
-
|
|
204
|
-
* Triggers a generator which can be used to close the sink.
|
|
205
|
-
*/
|
|
206
|
-
closeWritePromise: DeferredPromise<void> = defer()
|
|
60
|
+
private messageQueue?: Uint8ArrayList
|
|
207
61
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
*/
|
|
211
|
-
closeCb?: (stream: WebRTCStream) => void
|
|
62
|
+
constructor (init: WebRTCStreamInit) {
|
|
63
|
+
super(init)
|
|
212
64
|
|
|
213
|
-
|
|
214
|
-
this.channel = opts.channel
|
|
65
|
+
this.channel = init.channel
|
|
215
66
|
this.channel.binaryType = 'arraybuffer'
|
|
216
|
-
this.
|
|
67
|
+
this.incomingData = pushable()
|
|
68
|
+
this.messageQueue = new Uint8ArrayList()
|
|
69
|
+
this.dataChannelOptions = {
|
|
70
|
+
bufferedAmountLowEventTimeout: init.dataChannelOptions?.bufferedAmountLowEventTimeout ?? BUFFERED_AMOUNT_LOW_TIMEOUT,
|
|
71
|
+
maxBufferedAmount: init.dataChannelOptions?.maxBufferedAmount ?? MAX_BUFFERED_AMOUNT,
|
|
72
|
+
maxMessageSize: init.dataChannelOptions?.maxMessageSize ?? MAX_MESSAGE_SIZE
|
|
73
|
+
}
|
|
217
74
|
|
|
218
|
-
|
|
75
|
+
// set up initial state
|
|
219
76
|
switch (this.channel.readyState) {
|
|
220
77
|
case 'open':
|
|
221
|
-
this.opened.resolve()
|
|
222
78
|
break
|
|
223
79
|
|
|
224
80
|
case 'closed':
|
|
225
81
|
case 'closing':
|
|
226
|
-
this.streamState.state = StreamStates.CLOSED
|
|
227
82
|
if (this.stat.timeline.close === undefined || this.stat.timeline.close === 0) {
|
|
228
|
-
this.stat.timeline.close =
|
|
83
|
+
this.stat.timeline.close = Date.now()
|
|
229
84
|
}
|
|
230
|
-
this.opened.resolve()
|
|
231
85
|
break
|
|
232
86
|
case 'connecting':
|
|
233
87
|
// noop
|
|
234
88
|
break
|
|
235
89
|
|
|
236
90
|
default:
|
|
237
|
-
|
|
91
|
+
log.error('unknown datachannel state %s', this.channel.readyState)
|
|
92
|
+
throw new CodeError('Unknown datachannel state', 'ERR_INVALID_STATE')
|
|
238
93
|
}
|
|
239
94
|
|
|
240
|
-
this.metadata = opts.metadata ?? {}
|
|
241
|
-
|
|
242
95
|
// handle RTCDataChannel events
|
|
243
96
|
this.channel.onopen = (_evt) => {
|
|
244
97
|
this.stat.timeline.open = new Date().getTime()
|
|
245
|
-
|
|
98
|
+
|
|
99
|
+
if (this.messageQueue != null) {
|
|
100
|
+
// send any queued messages
|
|
101
|
+
this._sendMessage(this.messageQueue)
|
|
102
|
+
.catch(err => {
|
|
103
|
+
this.abort(err)
|
|
104
|
+
})
|
|
105
|
+
this.messageQueue = undefined
|
|
106
|
+
}
|
|
246
107
|
}
|
|
247
108
|
|
|
248
109
|
this.channel.onclose = (_evt) => {
|
|
@@ -256,207 +117,157 @@ export class WebRTCStream implements Stream {
|
|
|
256
117
|
|
|
257
118
|
const self = this
|
|
258
119
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
120
|
+
this.channel.onmessage = async (event: MessageEvent<ArrayBuffer>) => {
|
|
121
|
+
const { data } = event
|
|
122
|
+
|
|
123
|
+
if (data === null || data.byteLength === 0) {
|
|
262
124
|
return
|
|
263
125
|
}
|
|
264
|
-
|
|
126
|
+
|
|
127
|
+
this.incomingData.push(new Uint8Array(data, 0, data.byteLength))
|
|
265
128
|
}
|
|
266
129
|
|
|
267
130
|
// pipe framed protobuf messages through a length prefixed decoder, and
|
|
268
131
|
// surface data from the `Message.message` field through a source.
|
|
269
|
-
|
|
270
|
-
this.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (message != null) {
|
|
276
|
-
yield new Uint8ArrayList(message)
|
|
277
|
-
}
|
|
132
|
+
Promise.resolve().then(async () => {
|
|
133
|
+
for await (const buf of lengthPrefixed.decode(this.incomingData)) {
|
|
134
|
+
const message = self.processIncomingProtobuf(buf.subarray())
|
|
135
|
+
|
|
136
|
+
if (message != null) {
|
|
137
|
+
self.sourcePush(new Uint8ArrayList(message))
|
|
278
138
|
}
|
|
279
|
-
}
|
|
280
|
-
)
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
.catch(err => {
|
|
142
|
+
log.error('error processing incoming data channel messages', err)
|
|
143
|
+
})
|
|
281
144
|
}
|
|
282
145
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
get source (): AsyncGenerator<Uint8ArrayList, any, unknown> {
|
|
287
|
-
return this._src
|
|
146
|
+
sendNewStream (): void {
|
|
147
|
+
// opening new streams is handled by WebRTC so this is a noop
|
|
288
148
|
}
|
|
289
149
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
150
|
+
async _sendMessage (data: Uint8ArrayList, checkBuffer: boolean = true): Promise<void> {
|
|
151
|
+
if (checkBuffer && this.channel.bufferedAmount > this.dataChannelOptions.maxBufferedAmount) {
|
|
152
|
+
try {
|
|
153
|
+
await pEvent(this.channel, 'bufferedamountlow', { timeout: this.dataChannelOptions.bufferedAmountLowEventTimeout })
|
|
154
|
+
} catch (err: any) {
|
|
155
|
+
if (err instanceof TimeoutError) {
|
|
156
|
+
this.abort(err)
|
|
157
|
+
throw new Error('Timed out waiting for DataChannel buffer to clear')
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
throw err
|
|
161
|
+
}
|
|
297
162
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
await this._sink(src)
|
|
302
|
-
} finally {
|
|
303
|
-
this.closeWrite()
|
|
163
|
+
|
|
164
|
+
if (this.channel.readyState === 'closed' || this.channel.readyState === 'closing') {
|
|
165
|
+
throw new CodeError('Invalid datachannel state - closed or closing', 'ERR_INVALID_STATE')
|
|
304
166
|
}
|
|
305
|
-
}
|
|
306
167
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
168
|
+
if (this.channel.readyState === 'open') {
|
|
169
|
+
// send message without copying data
|
|
170
|
+
for (const buf of data) {
|
|
171
|
+
this.channel.send(buf)
|
|
172
|
+
}
|
|
173
|
+
} else if (this.channel.readyState === 'connecting') {
|
|
174
|
+
// queue message for when we are open
|
|
175
|
+
if (this.messageQueue == null) {
|
|
176
|
+
this.messageQueue = new Uint8ArrayList()
|
|
315
177
|
}
|
|
316
|
-
const msgbuf = Message.encode({ message: buf.subarray() })
|
|
317
|
-
const sendbuf = lengthPrefixed.encode.single(msgbuf)
|
|
318
178
|
|
|
319
|
-
this.
|
|
179
|
+
this.messageQueue.append(data)
|
|
180
|
+
} else {
|
|
181
|
+
log.error('unknown datachannel state %s', this.channel.readyState)
|
|
182
|
+
throw new CodeError('Unknown datachannel state', 'ERR_INVALID_STATE')
|
|
320
183
|
}
|
|
321
184
|
}
|
|
322
185
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
processIncomingProtobuf (buffer: Uint8Array): Uint8Array | undefined {
|
|
327
|
-
const message = Message.decode(buffer)
|
|
186
|
+
async sendData (data: Uint8ArrayList): Promise<void> {
|
|
187
|
+
const msgbuf = Message.encode({ message: data.subarray() })
|
|
188
|
+
const sendbuf = lengthPrefixed.encode.single(msgbuf)
|
|
328
189
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (currentState !== nextState) {
|
|
333
|
-
switch (nextState) {
|
|
334
|
-
case StreamStates.READ_CLOSED:
|
|
335
|
-
this._innersrc.end()
|
|
336
|
-
break
|
|
337
|
-
case StreamStates.WRITE_CLOSED:
|
|
338
|
-
this.closeWritePromise.resolve()
|
|
339
|
-
break
|
|
340
|
-
case StreamStates.CLOSED:
|
|
341
|
-
this.close()
|
|
342
|
-
break
|
|
343
|
-
// StreamStates.OPEN will never be a nextState
|
|
344
|
-
case StreamStates.OPEN:
|
|
345
|
-
break
|
|
346
|
-
default:
|
|
347
|
-
unreachableBranch(nextState)
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
}
|
|
190
|
+
await this._sendMessage(sendbuf)
|
|
191
|
+
}
|
|
351
192
|
|
|
352
|
-
|
|
193
|
+
async sendReset (): Promise<void> {
|
|
194
|
+
await this._sendFlag(Message.Flag.RESET)
|
|
353
195
|
}
|
|
354
196
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
this.
|
|
361
|
-
this._innersrc.end()
|
|
362
|
-
this.closeWritePromise.resolve()
|
|
363
|
-
this.channel.close()
|
|
364
|
-
|
|
365
|
-
if (this.closeCb !== undefined) {
|
|
366
|
-
this.closeCb(this)
|
|
367
|
-
}
|
|
197
|
+
async sendCloseWrite (): Promise<void> {
|
|
198
|
+
await this._sendFlag(Message.Flag.FIN)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async sendCloseRead (): Promise<void> {
|
|
202
|
+
await this._sendFlag(Message.Flag.STOP_SENDING)
|
|
368
203
|
}
|
|
369
204
|
|
|
370
205
|
/**
|
|
371
|
-
*
|
|
206
|
+
* Handle incoming
|
|
372
207
|
*/
|
|
373
|
-
|
|
374
|
-
const
|
|
375
|
-
if (currentState === nextState) {
|
|
376
|
-
// No change, no op
|
|
377
|
-
return
|
|
378
|
-
}
|
|
208
|
+
private processIncomingProtobuf (buffer: Uint8Array): Uint8Array | undefined {
|
|
209
|
+
const message = Message.decode(buffer)
|
|
379
210
|
|
|
380
|
-
if (
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
211
|
+
if (message.flag !== undefined) {
|
|
212
|
+
if (message.flag === Message.Flag.FIN) {
|
|
213
|
+
// We should expect no more data from the remote, stop reading
|
|
214
|
+
this.incomingData.end()
|
|
215
|
+
this.closeRead()
|
|
216
|
+
}
|
|
384
217
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
218
|
+
if (message.flag === Message.Flag.RESET) {
|
|
219
|
+
// Stop reading and writing to the stream immediately
|
|
220
|
+
this.reset()
|
|
221
|
+
}
|
|
389
222
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
const [currentState, nextState] = this.streamState.transition({ direction: 'outbound', flag: Message.Flag.FIN })
|
|
395
|
-
if (currentState === nextState) {
|
|
396
|
-
// No change, no op
|
|
397
|
-
return
|
|
223
|
+
if (message.flag === Message.Flag.STOP_SENDING) {
|
|
224
|
+
// The remote has stopped reading
|
|
225
|
+
this.closeWrite()
|
|
226
|
+
}
|
|
398
227
|
}
|
|
399
228
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
this.closeWritePromise.resolve()
|
|
403
|
-
}
|
|
229
|
+
return message.message
|
|
230
|
+
}
|
|
404
231
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
}
|
|
232
|
+
private async _sendFlag (flag: Message.Flag): Promise<void> {
|
|
233
|
+
log.trace('Sending flag: %s', flag.toString())
|
|
234
|
+
const msgbuf = Message.encode({ flag })
|
|
235
|
+
const prefixedBuf = lengthPrefixed.encode.single(msgbuf)
|
|
236
|
+
|
|
237
|
+
await this._sendMessage(prefixedBuf, false)
|
|
408
238
|
}
|
|
239
|
+
}
|
|
409
240
|
|
|
241
|
+
export interface WebRTCStreamOptions {
|
|
410
242
|
/**
|
|
411
|
-
*
|
|
243
|
+
* The network channel used for bidirectional peer-to-peer transfers of
|
|
244
|
+
* arbitrary data
|
|
245
|
+
*
|
|
246
|
+
* {@link https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel}
|
|
412
247
|
*/
|
|
413
|
-
|
|
414
|
-
log.error(`An error occurred, closing the stream for reading and writing: ${err.message}`)
|
|
415
|
-
this.close()
|
|
416
|
-
}
|
|
248
|
+
channel: RTCDataChannel
|
|
417
249
|
|
|
418
250
|
/**
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
* @see this.closeWrite
|
|
251
|
+
* The stream direction
|
|
422
252
|
*/
|
|
423
|
-
|
|
424
|
-
const [currentState, nextState] = this.streamState.transition({ direction: 'outbound', flag: Message.Flag.RESET })
|
|
425
|
-
if (currentState === nextState) {
|
|
426
|
-
// No change, no op
|
|
427
|
-
return
|
|
428
|
-
}
|
|
253
|
+
direction: Direction
|
|
429
254
|
|
|
430
|
-
|
|
431
|
-
this.close()
|
|
432
|
-
}
|
|
255
|
+
dataChannelOptions?: Partial<DataChannelOpts>
|
|
433
256
|
|
|
434
|
-
|
|
435
|
-
try {
|
|
436
|
-
log.trace('Sending flag: %s', flag.toString())
|
|
437
|
-
const msgbuf = Message.encode({ flag })
|
|
438
|
-
this.channel.send(lengthPrefixed.encode.single(msgbuf).subarray())
|
|
439
|
-
} catch (err) {
|
|
440
|
-
if (err instanceof Error) {
|
|
441
|
-
log.error(`Exception while sending flag ${flag}: ${err.message}`)
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
257
|
+
maxMsgSize?: number
|
|
445
258
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
return {
|
|
449
|
-
async * [Symbol.asyncIterator] () {
|
|
450
|
-
await self.closeWritePromise.promise
|
|
451
|
-
yield new Uint8Array(0)
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
259
|
+
onEnd?: (err?: Error | undefined) => void
|
|
260
|
+
}
|
|
455
261
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
262
|
+
export function createStream (options: WebRTCStreamOptions): Stream {
|
|
263
|
+
const { channel, direction, onEnd, dataChannelOptions } = options
|
|
264
|
+
|
|
265
|
+
return new WebRTCStream({
|
|
266
|
+
id: direction === 'inbound' ? (`i${channel.id}`) : `r${channel.id}`,
|
|
267
|
+
direction,
|
|
268
|
+
maxDataSize: (dataChannelOptions?.maxMessageSize ?? MAX_MESSAGE_SIZE) - PROTOBUF_OVERHEAD,
|
|
269
|
+
dataChannelOptions,
|
|
270
|
+
onEnd,
|
|
271
|
+
channel
|
|
272
|
+
})
|
|
462
273
|
}
|