@libp2p/webrtc 1.0.2 → 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/src/muxer.ts CHANGED
@@ -98,12 +98,9 @@ export class DataChannelMuxer implements StreamMuxer {
98
98
  }
99
99
  }
100
100
 
101
- /**
102
- * Initiate a new stream with the given name. If no name is
103
- * provided, the id of the stream will be used.
104
- */
105
- newStream (name: string = ''): Stream {
106
- const channel = this.peerConnection.createDataChannel(name)
101
+ newStream (): Stream {
102
+ // The spec says the label SHOULD be an empty string: https://github.com/libp2p/specs/blob/master/webrtc/README.md#rtcdatachannel-label
103
+ const channel = this.peerConnection.createDataChannel('')
107
104
  const stream = new WebRTCStream({
108
105
  channel,
109
106
  stat: {
package/src/sdp.ts CHANGED
@@ -11,12 +11,8 @@ const log = logger('libp2p:webrtc:sdp')
11
11
  /**
12
12
  * Get base2 | identity decoders
13
13
  */
14
- export const mbdecoder: any = (function () {
15
- const decoders = Object.values(bases).map((b) => b.decoder)
16
- let acc = decoders[0].or(decoders[1])
17
- decoders.slice(2).forEach((d) => (acc = acc.or(d)))
18
- return acc
19
- })()
14
+ // @ts-expect-error - Not easy to combine these types.
15
+ export const mbdecoder: any = Object.values(bases).map(b => b.decoder).reduce((d, b) => d.or(b))
20
16
 
21
17
  /**
22
18
  * Get base2 | identity decoders
@@ -57,7 +53,6 @@ export function decodeCerthash (certhash: string) {
57
53
  * Extract the fingerprint from a multiaddr
58
54
  */
59
55
  export function ma2Fingerprint (ma: Multiaddr): string[] {
60
- // certhash_value is a multibase encoded multihash encoded string
61
56
  const mhdecoded = decodeCerthash(certhash(ma))
62
57
  const prefix = toSupportedHashFunction(mhdecoded.name)
63
58
  const fingerprint = mhdecoded.digest.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '')
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, Sink } from 'it-stream-types'
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 protobuffs
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
- // no default
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
- // no default
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
- streamState = new StreamState();
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
- private readonly _innersrc = pushable();
181
-
182
- /**
183
- * Write data to the remote peer.
184
- * It takes care of wrapping data in a protobuf and adding the length prefix.
185
- */
186
- sink: Sink<Uint8ArrayList | Uint8Array, Promise<void>>;
187
-
188
- /**
189
- * Deferred promise that resolves when the underlying datachannel is in the
190
- * open state.
191
- */
192
- opened: DeferredPromise<void> = defer();
193
-
194
- /**
195
- * Triggers a generator which can be used to close the sink.
196
- */
197
- closeWritePromise: DeferredPromise<void> = defer();
198
-
199
- /**
200
- * Callback to invoke when the stream is closed.
201
- */
202
- closeCb?: (stream: WebRTCStream) => void
203
-
204
- constructor (opts: StreamInitOpts) {
205
- this.channel = opts.channel
206
- this.id = this.channel.label
207
-
208
- this.stat = opts.stat
209
- switch (this.channel.readyState) {
210
- case 'open':
211
- this.opened.resolve()
212
- break
213
-
214
- case 'closed':
215
- case 'closing':
216
- this.streamState.state = StreamStates.CLOSED
217
- if (this.stat.timeline.close === undefined || this.stat.timeline.close === 0) {
218
- this.stat.timeline.close = new Date().getTime()
219
- }
220
- this.opened.resolve()
221
- break
222
-
223
- // no default
224
- }
225
-
226
- this.metadata = opts.metadata ?? {}
227
-
228
- // closable sink
229
- this.sink = this._sinkFn
230
-
231
- // handle RTCDataChannel events
232
- this.channel.onopen = (_evt) => {
233
- this.stat.timeline.open = new Date().getTime()
234
- this.opened.resolve()
235
- }
236
-
237
- this.channel.onclose = (_evt) => {
238
- this.close()
239
- }
240
-
241
- this.channel.onerror = (evt) => {
242
- const err = (evt as RTCErrorEvent).error
243
- this.abort(err)
244
- }
245
-
246
- const self = this
247
-
248
- // reader pipe
249
- this.channel.onmessage = async ({ data }) => {
250
- if (data === null || data.length === 0) {
251
- return
252
- }
253
- this._innersrc.push(new Uint8Array(data as ArrayBufferLike))
254
- }
255
-
256
- // pipe framed protobuf messages through a length prefixed decoder, and
257
- // surface data from the `Message.message` field through a source.
258
- this._src = pipe(
259
- this._innersrc,
260
- lengthPrefixed.decode(),
261
- (source) => (async function * () {
262
- for await (const buf of source) {
263
- const message = self.processIncomingProtobuf(buf.subarray())
264
- if (message != null) {
265
- yield new Uint8ArrayList(message)
266
- }
267
- }
268
- })()
269
- )
270
- }
271
-
272
- // If user attempts to set a new source this should be a noop
273
- set source (_src: Source<Uint8ArrayList>) { }
274
-
275
- get source (): Source<Uint8ArrayList> {
276
- return this._src
277
- }
278
-
279
- /**
280
- * Closable sink
281
- */
282
- private async _sinkFn (src: Source<Uint8ArrayList | Uint8Array>): Promise<void> {
283
- await this.opened.promise
284
-
285
- const isClosed = (state: StreamStates) => state === StreamStates.CLOSED || state === StreamStates.WRITE_CLOSED
286
-
287
- if (isClosed(this.streamState.state)) {
288
- return
289
- }
290
-
291
- const self = this
292
- const closeWriteIterable = {
293
- async * [Symbol.asyncIterator] () {
294
- await self.closeWritePromise.promise
295
- yield new Uint8Array(0)
296
- }
297
- }
298
-
299
- for await (const buf of merge(closeWriteIterable, src)) {
300
- if (isClosed(self.streamState.state)) {
301
- return
302
- }
303
-
304
- const msgbuf = pb.Message.toBinary({ message: buf.subarray() })
305
- const sendbuf = lengthPrefixed.encode.single(msgbuf)
306
-
307
- this.channel.send(sendbuf.subarray())
308
- }
309
- }
310
-
311
- /**
312
- * Handle incoming
313
- */
314
- processIncomingProtobuf (buffer: Uint8Array): Uint8Array | undefined {
315
- const message = pb.Message.fromBinary(buffer)
316
-
317
- if (message.flag !== undefined) {
318
- const [currentState, nextState] = this.streamState.transition({ direction: 'inbound', flag: message.flag })
319
-
320
- if (currentState !== nextState) {
321
- // @TODO(ddimaria): determine if we need to check for StreamStates.OPEN
322
- switch (nextState) {
323
- case StreamStates.READ_CLOSED:
324
- this._innersrc.end()
325
- break
326
- case StreamStates.WRITE_CLOSED:
327
- this.closeWritePromise.resolve()
328
- break
329
- case StreamStates.CLOSED:
330
- this.close()
331
- break
332
-
333
- // no default
334
- }
335
- }
336
- }
337
-
338
- return message.message
339
- }
340
-
341
- /**
342
- * Close a stream for reading and writing
343
- */
344
- close (): void {
345
- this.stat.timeline.close = new Date().getTime()
346
- this.streamState.state = StreamStates.CLOSED
347
- this._innersrc.end()
348
- this.closeWritePromise.resolve()
349
- this.channel.close()
350
-
351
- if (this.closeCb !== undefined) {
352
- this.closeCb(this)
353
- }
354
- }
355
-
356
- /**
357
- * Close a stream for reading only
358
- */
359
- closeRead (): void {
360
- const [currentState, nextState] = this.streamState.transition({ direction: 'outbound', flag: pb.Message_Flag.STOP_SENDING })
361
-
362
- if (currentState === StreamStates.OPEN || currentState === StreamStates.WRITE_CLOSED) {
363
- this._sendFlag(pb.Message_Flag.STOP_SENDING);
364
- (this._innersrc).end()
365
- }
366
-
367
- if (currentState !== nextState && nextState === StreamStates.CLOSED) {
368
- this.close()
369
- }
370
- }
371
-
372
- /**
373
- * Close a stream for writing only
374
- */
375
- closeWrite (): void {
376
- const [currentState, nextState] = this.streamState.transition({ direction: 'outbound', flag: pb.Message_Flag.FIN })
377
-
378
- if (currentState === StreamStates.OPEN || currentState === StreamStates.READ_CLOSED) {
379
- this._sendFlag(pb.Message_Flag.FIN)
380
- this.closeWritePromise.resolve()
381
- }
382
-
383
- if (currentState !== nextState && nextState === StreamStates.CLOSED) {
384
- this.close()
385
- }
386
- }
387
-
388
- /**
389
- * Call when a local error occurs, should close the stream for reading and writing
390
- */
391
- abort (err: Error): void {
392
- log.error(`An error occurred, clost the stream for reading and writing: ${err.message}`)
393
- this.close()
394
- }
395
-
396
- /**
397
- * Close the stream for writing, and indicate to the remote side this is being done 'abruptly'
398
- *
399
- * @see closeWrite
400
- */
401
- reset (): void {
402
- this.stat = defaultStat(this.stat.direction)
403
- const [currentState, nextState] = this.streamState.transition({ direction: 'outbound', flag: pb.Message_Flag.RESET })
404
-
405
- if (currentState !== nextState) {
406
- this._sendFlag(pb.Message_Flag.RESET)
407
- this.close()
408
- }
409
- }
410
-
411
- private _sendFlag (flag: pb.Message_Flag): void {
412
- try {
413
- log.trace('Sending flag: %s', flag.toString())
414
- const msgbuf = pb.Message.toBinary({ flag: flag })
415
- this.channel.send(lengthPrefixed.encode.single(msgbuf).subarray())
416
- } catch (err) {
417
- if (err instanceof Error) {
418
- log.error(`Exception while sending flag ${flag}: ${err.message}`)
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
  }