@libp2p/webrtc 1.0.1 → 1.0.3
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 +16 -16
- package/dist/src/muxer.d.ts +1 -5
- package/dist/src/muxer.d.ts.map +1 -1
- package/dist/src/muxer.js +3 -6
- package/dist/src/muxer.js.map +1 -1
- package/dist/src/sdp.d.ts.map +1 -1
- package/dist/src/sdp.js +2 -7
- package/dist/src/sdp.js.map +1 -1
- package/dist/src/stream.d.ts +16 -10
- package/dist/src/stream.d.ts.map +1 -1
- package/dist/src/stream.js +74 -29
- package/dist/src/stream.js.map +1 -1
- package/dist/src/transport.d.ts.map +1 -1
- package/dist/src/transport.js +31 -31
- package/dist/src/transport.js.map +1 -1
- package/package.json +8 -8
- package/src/muxer.ts +3 -6
- package/src/sdp.ts +2 -7
- package/src/stream.ts +283 -248
- package/src/transport.ts +34 -34
package/src/stream.ts
CHANGED
|
@@ -5,7 +5,7 @@ import merge from 'it-merge'
|
|
|
5
5
|
import { pipe } from 'it-pipe'
|
|
6
6
|
import { pushable } from 'it-pushable'
|
|
7
7
|
import defer, { DeferredPromise } from 'p-defer'
|
|
8
|
-
import type { Source
|
|
8
|
+
import type { Source } from 'it-stream-types'
|
|
9
9
|
import { Uint8ArrayList } from 'uint8arraylist'
|
|
10
10
|
|
|
11
11
|
import * as pb from '../proto_ts/message.js'
|
|
@@ -61,7 +61,7 @@ interface StreamStateInput {
|
|
|
61
61
|
direction: 'inbound' | 'outbound'
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
|
-
* Message flag from the
|
|
64
|
+
* Message flag from the protobufs
|
|
65
65
|
*
|
|
66
66
|
* 0 = FIN
|
|
67
67
|
* 1 = STOP_SENDING
|
|
@@ -77,9 +77,19 @@ export enum StreamStates {
|
|
|
77
77
|
CLOSED,
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
// Checked by the Typescript compiler. If this fails it's because the switch
|
|
81
|
+
// statement is not exhaustive.
|
|
82
|
+
function unreachableBranch (x: never): never {
|
|
83
|
+
throw new Error('Case not handled in switch')
|
|
84
|
+
}
|
|
85
|
+
|
|
80
86
|
class StreamState {
|
|
81
87
|
state: StreamStates = StreamStates.OPEN
|
|
82
88
|
|
|
89
|
+
isWriteClosed (): boolean {
|
|
90
|
+
return (this.state === StreamStates.CLOSED || this.state === StreamStates.WRITE_CLOSED)
|
|
91
|
+
}
|
|
92
|
+
|
|
83
93
|
transition ({ direction, flag }: StreamStateInput): [StreamStates, StreamStates] {
|
|
84
94
|
const prev = this.state
|
|
85
95
|
|
|
@@ -109,8 +119,8 @@ class StreamState {
|
|
|
109
119
|
case pb.Message_Flag.RESET:
|
|
110
120
|
this.state = StreamStates.CLOSED
|
|
111
121
|
break
|
|
112
|
-
|
|
113
|
-
|
|
122
|
+
default:
|
|
123
|
+
unreachableBranch(flag)
|
|
114
124
|
}
|
|
115
125
|
} else {
|
|
116
126
|
switch (flag) {
|
|
@@ -134,7 +144,8 @@ class StreamState {
|
|
|
134
144
|
this.state = StreamStates.CLOSED
|
|
135
145
|
break
|
|
136
146
|
|
|
137
|
-
|
|
147
|
+
default:
|
|
148
|
+
unreachableBranch(flag)
|
|
138
149
|
}
|
|
139
150
|
}
|
|
140
151
|
return [prev, this.state]
|
|
@@ -165,7 +176,7 @@ export class WebRTCStream implements Stream {
|
|
|
165
176
|
/**
|
|
166
177
|
* The current state of the stream
|
|
167
178
|
*/
|
|
168
|
-
|
|
179
|
+
streamState = new StreamState();
|
|
169
180
|
|
|
170
181
|
/**
|
|
171
182
|
* Read unwrapped protobuf data from the underlying datachannel.
|
|
@@ -177,246 +188,270 @@ export class WebRTCStream implements Stream {
|
|
|
177
188
|
* push data from the underlying datachannel to the length prefix decoder
|
|
178
189
|
* and then the protobuf decoder.
|
|
179
190
|
*/
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
191
|
+
private readonly _innersrc = pushable();
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Deferred promise that resolves when the underlying datachannel is in the
|
|
195
|
+
* open state.
|
|
196
|
+
*/
|
|
197
|
+
opened: DeferredPromise<void> = defer();
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* sinkCreated is set to true once the sinkFunction is invoked
|
|
201
|
+
*/
|
|
202
|
+
_sinkCalled: boolean = false;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Triggers a generator which can be used to close the sink.
|
|
206
|
+
*/
|
|
207
|
+
closeWritePromise: DeferredPromise<void> = defer();
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Callback to invoke when the stream is closed.
|
|
211
|
+
*/
|
|
212
|
+
closeCb?: (stream: WebRTCStream) => void
|
|
213
|
+
|
|
214
|
+
constructor (opts: StreamInitOpts) {
|
|
215
|
+
this.channel = opts.channel
|
|
216
|
+
this.id = this.channel.label
|
|
217
|
+
|
|
218
|
+
this.stat = opts.stat
|
|
219
|
+
switch (this.channel.readyState) {
|
|
220
|
+
case 'open':
|
|
221
|
+
this.opened.resolve()
|
|
222
|
+
break
|
|
223
|
+
|
|
224
|
+
case 'closed':
|
|
225
|
+
case 'closing':
|
|
226
|
+
this.streamState.state = StreamStates.CLOSED
|
|
227
|
+
if (this.stat.timeline.close === undefined || this.stat.timeline.close === 0) {
|
|
228
|
+
this.stat.timeline.close = new Date().getTime()
|
|
229
|
+
}
|
|
230
|
+
this.opened.resolve()
|
|
231
|
+
break
|
|
232
|
+
case 'connecting':
|
|
233
|
+
// noop
|
|
234
|
+
break
|
|
235
|
+
|
|
236
|
+
default:
|
|
237
|
+
unreachableBranch(this.channel.readyState)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
this.metadata = opts.metadata ?? {}
|
|
241
|
+
|
|
242
|
+
// handle RTCDataChannel events
|
|
243
|
+
this.channel.onopen = (_evt) => {
|
|
244
|
+
this.stat.timeline.open = new Date().getTime()
|
|
245
|
+
this.opened.resolve()
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
this.channel.onclose = (_evt) => {
|
|
249
|
+
this.close()
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
this.channel.onerror = (evt) => {
|
|
253
|
+
const err = (evt as RTCErrorEvent).error
|
|
254
|
+
this.abort(err)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const self = this
|
|
258
|
+
|
|
259
|
+
// reader pipe
|
|
260
|
+
this.channel.onmessage = async ({ data }) => {
|
|
261
|
+
if (data === null || data.length === 0) {
|
|
262
|
+
return
|
|
263
|
+
}
|
|
264
|
+
this._innersrc.push(new Uint8Array(data as ArrayBufferLike))
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// pipe framed protobuf messages through a length prefixed decoder, and
|
|
268
|
+
// surface data from the `Message.message` field through a source.
|
|
269
|
+
this._src = pipe(
|
|
270
|
+
this._innersrc,
|
|
271
|
+
lengthPrefixed.decode(),
|
|
272
|
+
(source) => (async function * () {
|
|
273
|
+
for await (const buf of source) {
|
|
274
|
+
const message = self.processIncomingProtobuf(buf.subarray())
|
|
275
|
+
if (message != null) {
|
|
276
|
+
yield new Uint8ArrayList(message)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
})()
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// If user attempts to set a new source this should be a noop
|
|
284
|
+
set source (_src: Source<Uint8ArrayList>) { }
|
|
285
|
+
|
|
286
|
+
get source (): Source<Uint8ArrayList> {
|
|
287
|
+
return this._src
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Write data to the remote peer.
|
|
292
|
+
* It takes care of wrapping data in a protobuf and adding the length prefix.
|
|
293
|
+
*/
|
|
294
|
+
async sink (src: Source<Uint8ArrayList | Uint8Array>): Promise<void> {
|
|
295
|
+
if (this._sinkCalled) {
|
|
296
|
+
throw new Error('sink already called on this stream')
|
|
297
|
+
}
|
|
298
|
+
// await stream opening before sending data
|
|
299
|
+
await this.opened.promise
|
|
300
|
+
try {
|
|
301
|
+
await this._sink(src)
|
|
302
|
+
} finally {
|
|
303
|
+
this.closeWrite()
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Closable sink implementation
|
|
309
|
+
*/
|
|
310
|
+
private async _sink (src: Source<Uint8ArrayList | Uint8Array>): Promise<void> {
|
|
311
|
+
const closeWrite = this._closeWriteIterable()
|
|
312
|
+
for await (const buf of merge(closeWrite, src)) {
|
|
313
|
+
if (this.streamState.isWriteClosed()) {
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
const msgbuf = pb.Message.toBinary({ message: buf.subarray() })
|
|
317
|
+
const sendbuf = lengthPrefixed.encode.single(msgbuf)
|
|
318
|
+
|
|
319
|
+
this.channel.send(sendbuf.subarray())
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Handle incoming
|
|
325
|
+
*/
|
|
326
|
+
processIncomingProtobuf (buffer: Uint8Array): Uint8Array | undefined {
|
|
327
|
+
const message = pb.Message.fromBinary(buffer)
|
|
328
|
+
|
|
329
|
+
if (message.flag !== undefined) {
|
|
330
|
+
const [currentState, nextState] = this.streamState.transition({ direction: 'inbound', flag: message.flag })
|
|
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
|
+
}
|
|
351
|
+
|
|
352
|
+
return message.message
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Close a stream for reading and writing
|
|
357
|
+
*/
|
|
358
|
+
close (): void {
|
|
359
|
+
this.stat.timeline.close = new Date().getTime()
|
|
360
|
+
this.streamState.state = StreamStates.CLOSED
|
|
361
|
+
this._innersrc.end()
|
|
362
|
+
this.closeWritePromise.resolve()
|
|
363
|
+
this.channel.close()
|
|
364
|
+
|
|
365
|
+
if (this.closeCb !== undefined) {
|
|
366
|
+
this.closeCb(this)
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Close a stream for reading only
|
|
372
|
+
*/
|
|
373
|
+
closeRead (): void {
|
|
374
|
+
const [currentState, nextState] = this.streamState.transition({ direction: 'outbound', flag: pb.Message_Flag.STOP_SENDING })
|
|
375
|
+
if (currentState === nextState) {
|
|
376
|
+
// No change, no op
|
|
377
|
+
return
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (currentState === StreamStates.OPEN || currentState === StreamStates.WRITE_CLOSED) {
|
|
381
|
+
this._sendFlag(pb.Message_Flag.STOP_SENDING)
|
|
382
|
+
this._innersrc.end()
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (nextState === StreamStates.CLOSED) {
|
|
386
|
+
this.close()
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Close a stream for writing only
|
|
392
|
+
*/
|
|
393
|
+
closeWrite (): void {
|
|
394
|
+
const [currentState, nextState] = this.streamState.transition({ direction: 'outbound', flag: pb.Message_Flag.FIN })
|
|
395
|
+
if (currentState === nextState) {
|
|
396
|
+
// No change, no op
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (currentState === StreamStates.OPEN || currentState === StreamStates.READ_CLOSED) {
|
|
401
|
+
this._sendFlag(pb.Message_Flag.FIN)
|
|
402
|
+
this.closeWritePromise.resolve()
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (nextState === StreamStates.CLOSED) {
|
|
406
|
+
this.close()
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Call when a local error occurs, should close the stream for reading and writing
|
|
412
|
+
*/
|
|
413
|
+
abort (err: Error): void {
|
|
414
|
+
log.error(`An error occurred, closing the stream for reading and writing: ${err.message}`)
|
|
415
|
+
this.close()
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Close the stream for writing, and indicate to the remote side this is being done 'abruptly'
|
|
420
|
+
*
|
|
421
|
+
* @see this.closeWrite
|
|
422
|
+
*/
|
|
423
|
+
reset (): void {
|
|
424
|
+
// TODO Why are you resetting the stat here?
|
|
425
|
+
this.stat = defaultStat(this.stat.direction)
|
|
426
|
+
const [currentState, nextState] = this.streamState.transition({ direction: 'outbound', flag: pb.Message_Flag.RESET })
|
|
427
|
+
if (currentState === nextState) {
|
|
428
|
+
// No change, no op
|
|
429
|
+
return
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
this._sendFlag(pb.Message_Flag.RESET)
|
|
433
|
+
this.close()
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
private _sendFlag (flag: pb.Message_Flag): void {
|
|
437
|
+
try {
|
|
438
|
+
log.trace('Sending flag: %s', flag.toString())
|
|
439
|
+
const msgbuf = pb.Message.toBinary({ flag: flag })
|
|
440
|
+
this.channel.send(lengthPrefixed.encode.single(msgbuf).subarray())
|
|
441
|
+
} catch (err) {
|
|
442
|
+
if (err instanceof Error) {
|
|
443
|
+
log.error(`Exception while sending flag ${flag}: ${err.message}`)
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
private _closeWriteIterable (): Source<Uint8ArrayList | Uint8Array> {
|
|
449
|
+
const self = this
|
|
450
|
+
return {
|
|
451
|
+
async * [Symbol.asyncIterator] () {
|
|
452
|
+
await self.closeWritePromise.promise
|
|
453
|
+
yield new Uint8Array(0)
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
422
457
|
}
|