@libp2p/webrtc 1.0.2 → 1.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 +19 -19
- package/dist/src/error.d.ts +9 -10
- package/dist/src/error.d.ts.map +1 -1
- package/dist/src/maconn.d.ts.map +1 -1
- package/dist/src/maconn.js.map +1 -1
- 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 +3 -2
- 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 +75 -30
- 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/dist/src/util.d.ts.map +1 -1
- package/dist/src/util.js.map +1 -1
- package/package.json +8 -7
- package/src/error.ts +9 -9
- package/src/maconn.ts +4 -4
- package/src/muxer.ts +5 -8
- package/src/sdp.ts +4 -8
- package/src/stream.ts +288 -253
- package/src/transport.ts +34 -34
- package/src/util.ts +1 -1
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]
|
|
@@ -145,278 +156,302 @@ export class WebRTCStream implements Stream {
|
|
|
145
156
|
/**
|
|
146
157
|
* Unique identifier for a stream
|
|
147
158
|
*/
|
|
148
|
-
id: string
|
|
159
|
+
id: string
|
|
149
160
|
|
|
150
161
|
/**
|
|
151
162
|
* Stats about this stream
|
|
152
163
|
*/
|
|
153
|
-
stat: StreamStat
|
|
164
|
+
stat: StreamStat
|
|
154
165
|
|
|
155
166
|
/**
|
|
156
167
|
* User defined stream metadata
|
|
157
168
|
*/
|
|
158
|
-
metadata: Record<string, any
|
|
169
|
+
metadata: Record<string, any>
|
|
159
170
|
|
|
160
171
|
/**
|
|
161
172
|
* The data channel used to send and receive data
|
|
162
173
|
*/
|
|
163
|
-
private readonly channel: RTCDataChannel
|
|
174
|
+
private readonly channel: RTCDataChannel
|
|
164
175
|
|
|
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.
|
|
172
183
|
* _src is exposed to the user via the `source` getter to .
|
|
173
184
|
*/
|
|
174
|
-
private readonly _src: Source<Uint8ArrayList
|
|
185
|
+
private readonly _src: Source<Uint8ArrayList>
|
|
175
186
|
|
|
176
187
|
/**
|
|
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 })
|
|
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
|
}
|