livekit-client 2.20.2 → 2.21.0
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/livekit-client.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +4 -0
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +1993 -791
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.fm.worker.js +1 -1
- package/dist/livekit-client.fm.worker.js.map +1 -1
- package/dist/livekit-client.fm.worker.mjs +4 -0
- package/dist/livekit-client.fm.worker.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +28 -64
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +4 -0
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-channel/DataChannelManager.d.ts +62 -0
- package/dist/src/room/data-channel/DataChannelManager.d.ts.map +1 -0
- package/dist/src/room/data-channel/FlowControlledDataChannel.d.ts +95 -0
- package/dist/src/room/data-channel/FlowControlledDataChannel.d.ts.map +1 -0
- package/dist/src/room/data-channel/LossyDataChannel.d.ts +40 -0
- package/dist/src/room/data-channel/LossyDataChannel.d.ts.map +1 -0
- package/dist/src/room/data-channel/ReliableDataChannel.d.ts +56 -0
- package/dist/src/room/data-channel/ReliableDataChannel.d.ts.map +1 -0
- package/dist/src/room/data-channel/types.d.ts +12 -0
- package/dist/src/room/data-channel/types.d.ts.map +1 -0
- package/dist/src/room/data-stream/compression.d.ts +49 -0
- package/dist/src/room/data-stream/compression.d.ts.map +1 -0
- package/dist/src/room/data-stream/constants.d.ts +18 -0
- package/dist/src/room/data-stream/constants.d.ts.map +1 -0
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +4 -0
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -1
- package/dist/src/room/data-stream/incoming/StreamReader.d.ts.map +1 -1
- package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +49 -2
- package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -1
- package/dist/src/room/data-stream/outgoing/header-utils.d.ts +18 -0
- package/dist/src/room/data-stream/outgoing/header-utils.d.ts.map +1 -0
- package/dist/src/room/errors.d.ts +3 -1
- package/dist/src/room/errors.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +9 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts +11 -2
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/rpc/client/RpcClientManager.d.ts.map +1 -1
- package/dist/src/room/rpc/server/RpcServerManager.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +17 -0
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +13 -0
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/version.d.ts +5 -1
- package/dist/src/version.d.ts.map +1 -1
- package/dist/ts4.2/index.d.ts +1 -1
- package/dist/ts4.2/room/RTCEngine.d.ts +28 -64
- package/dist/ts4.2/room/Room.d.ts +4 -0
- package/dist/ts4.2/room/data-channel/DataChannelManager.d.ts +62 -0
- package/dist/ts4.2/room/data-channel/FlowControlledDataChannel.d.ts +95 -0
- package/dist/ts4.2/room/data-channel/LossyDataChannel.d.ts +41 -0
- package/dist/ts4.2/room/data-channel/ReliableDataChannel.d.ts +57 -0
- package/dist/ts4.2/room/data-channel/types.d.ts +12 -0
- package/dist/ts4.2/room/data-stream/compression.d.ts +49 -0
- package/dist/ts4.2/room/data-stream/constants.d.ts +18 -0
- package/dist/ts4.2/room/data-stream/incoming/IncomingDataStreamManager.d.ts +4 -0
- package/dist/ts4.2/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +49 -2
- package/dist/ts4.2/room/data-stream/outgoing/header-utils.d.ts +18 -0
- package/dist/ts4.2/room/errors.d.ts +3 -1
- package/dist/ts4.2/room/participant/LocalParticipant.d.ts +9 -1
- package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +11 -1
- package/dist/ts4.2/room/types.d.ts +17 -0
- package/dist/ts4.2/room/utils.d.ts +13 -0
- package/dist/ts4.2/version.d.ts +5 -1
- package/package.json +2 -2
- package/src/index.ts +2 -0
- package/src/room/RTCEngine.test.ts +58 -99
- package/src/room/RTCEngine.ts +92 -534
- package/src/room/Room.test.ts +4 -1
- package/src/room/Room.ts +40 -8
- package/src/room/data-channel/DataChannelManager.test.ts +152 -0
- package/src/room/data-channel/DataChannelManager.ts +237 -0
- package/src/room/data-channel/FlowControlledDataChannel.test.ts +154 -0
- package/src/room/data-channel/FlowControlledDataChannel.ts +216 -0
- package/src/room/data-channel/LossyDataChannel.test.ts +118 -0
- package/src/room/data-channel/LossyDataChannel.ts +125 -0
- package/src/room/data-channel/ReliableDataChannel.test.ts +158 -0
- package/src/room/data-channel/ReliableDataChannel.ts +153 -0
- package/src/room/data-channel/types.ts +30 -0
- package/src/room/data-stream/compression.ts +117 -0
- package/src/room/data-stream/constants.ts +18 -0
- package/src/room/data-stream/incoming/IncomingDataStreamManager.test.ts +1601 -0
- package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +438 -86
- package/src/room/data-stream/incoming/StreamReader.ts +8 -2
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.test.ts +1292 -0
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +489 -116
- package/src/room/data-stream/outgoing/header-utils.ts +87 -0
- package/src/room/errors.ts +6 -0
- package/src/room/participant/LocalParticipant.ts +13 -0
- package/src/room/participant/RemoteParticipant.ts +19 -5
- package/src/room/rpc/client/RpcClientManager.test.ts +20 -27
- package/src/room/rpc/client/RpcClientManager.ts +1 -4
- package/src/room/rpc/server/RpcServerManager.test.ts +24 -23
- package/src/room/rpc/server/RpcServerManager.ts +1 -3
- package/src/room/types.ts +18 -0
- package/src/room/utils.ts +67 -0
- package/src/version.ts +5 -1
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { NonSharedUint8Array } from '../../type-polyfills/non-shared-typed-arrays';
|
|
2
|
+
import { DataPacketBuffer } from '../../utils/dataPacketBuffer';
|
|
3
|
+
import {
|
|
4
|
+
FlowControlledDataChannel,
|
|
5
|
+
type FlowControlledDataChannelOptions,
|
|
6
|
+
} from './FlowControlledDataChannel';
|
|
7
|
+
|
|
8
|
+
export interface ReliableDataChannelOptions extends FlowControlledDataChannelOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Whether sends should currently be deferred to the resume replay instead of hitting the wire
|
|
11
|
+
* (i.e. a reconnect attempt is underway). Read at send time so the reliable channel matches the
|
|
12
|
+
* engine's reconnect state without owning it.
|
|
13
|
+
*/
|
|
14
|
+
isDeferringSends: () => boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The reliable channel: flow control plus delivery-across-resume semantics.
|
|
19
|
+
*
|
|
20
|
+
* Every packet gets a monotonic sequence (stamped into the protobuf by the caller before
|
|
21
|
+
* serialization, via {@link nextSequence}) and is retained in a replay buffer until the channel's
|
|
22
|
+
* `bufferedAmount` confirms it has been handed to the transport. Sends that land in a reconnect
|
|
23
|
+
* window — or whose headroom wait is torn down transiently — are queued unsent and resolve;
|
|
24
|
+
* {@link replay} delivers them (plus any unacked packets) after a resume. Only an engine close
|
|
25
|
+
* rejects, because no replay is coming after that.
|
|
26
|
+
*/
|
|
27
|
+
export class ReliableDataChannel extends FlowControlledDataChannel {
|
|
28
|
+
private messageBuffer = new DataPacketBuffer();
|
|
29
|
+
|
|
30
|
+
private sequence = 1;
|
|
31
|
+
|
|
32
|
+
private isDeferringSends: () => boolean;
|
|
33
|
+
|
|
34
|
+
constructor(opts: ReliableDataChannelOptions) {
|
|
35
|
+
super(opts);
|
|
36
|
+
this.isDeferringSends = opts.isDeferringSends;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Claims the next packet sequence. The caller stamps it into the packet before serialization,
|
|
41
|
+
* then passes it back to {@link send} so the replay buffer stays keyed by wire sequence.
|
|
42
|
+
*/
|
|
43
|
+
nextSequence(): number {
|
|
44
|
+
const sequence = this.sequence;
|
|
45
|
+
this.sequence += 1;
|
|
46
|
+
return sequence;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Sends prepared bytes with reliable semantics. Resolves once the packet has either been handed
|
|
51
|
+
* to the channel or queued for the resume replay; throws only when the engine is closed.
|
|
52
|
+
*/
|
|
53
|
+
async send(msg: NonSharedUint8Array, sequence: number) {
|
|
54
|
+
if (this.isDeferringSends()) {
|
|
55
|
+
// A reconnect is already underway — queue for the resume replay instead of parking on a
|
|
56
|
+
// channel that is being torn down. The send resolves; delivery is deferred to the replay.
|
|
57
|
+
this.messageBuffer.push({ data: msg, sequence, sent: false });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const dc = this.getChannel();
|
|
62
|
+
if (!dc) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
await this.waitForHeadroomWithLock();
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (this.isEngineClosed()) {
|
|
70
|
+
// No replay is coming after an engine close — surface the failure.
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
// Transient teardown (the channel closed or was replaced while we waited): the reliable
|
|
74
|
+
// channel promises delivery across resume, so queue the packet for the replay instead of
|
|
75
|
+
// rejecting a send the app can't meaningfully retry.
|
|
76
|
+
this.messageBuffer.push({ data: msg, sequence, sent: false });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (this.isDeferringSends()) {
|
|
81
|
+
// A reconnect began while we waited for headroom — same deal as above.
|
|
82
|
+
this.messageBuffer.push({ data: msg, sequence, sent: false });
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.messageBuffer.push({ data: msg, sequence, sent: true });
|
|
87
|
+
dc.send(msg);
|
|
88
|
+
this.refreshBufferStatus();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Replays the buffered backlog after a resume: drops everything the server acked
|
|
93
|
+
* (`lastMessageSeq`), then re-sends the rest in order. The headroom lock is held across the
|
|
94
|
+
* whole replay — releasing it between messages would let a concurrent send (whose newer
|
|
95
|
+
* sequence was already assigned before it queued on the lock) hit the wire mid-replay, and
|
|
96
|
+
* receivers would then discard the remaining lower-sequence resent messages as duplicates.
|
|
97
|
+
*/
|
|
98
|
+
async replay(lastMessageSeq: number) {
|
|
99
|
+
const dc = this.getChannel();
|
|
100
|
+
if (!dc) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
this.messageBuffer.popToSequence(lastMessageSeq);
|
|
104
|
+
const unlock = await this.lockHeadroom();
|
|
105
|
+
try {
|
|
106
|
+
// Everything left after the ack cutoff must be re-handed to the current channel.
|
|
107
|
+
this.messageBuffer.markAllUnsent();
|
|
108
|
+
// Drain in passes, re-scanning the live buffer each time: a send that arrives (deferred,
|
|
109
|
+
// sent:false) during our own awaits appends after this pass started, so we pick it up on
|
|
110
|
+
// the next one. Mark each packet only once we've actually handed it to the channel — a
|
|
111
|
+
// blanket "mark all sent" would flip such a late arrival to sent without transmitting it,
|
|
112
|
+
// and a later alignBufferedAmount would then drop it for good. If the loop throws
|
|
113
|
+
// mid-drain, unsent entries keep their flag and the next replay picks them up.
|
|
114
|
+
for (
|
|
115
|
+
let batch = this.messageBuffer.getUnsent();
|
|
116
|
+
batch.length > 0;
|
|
117
|
+
batch = this.messageBuffer.getUnsent()
|
|
118
|
+
) {
|
|
119
|
+
for (const item of batch) {
|
|
120
|
+
// Respect flow control on resume too, so a large resend doesn't overflow the buffer.
|
|
121
|
+
await this.waitForHeadroomWithoutLock();
|
|
122
|
+
dc.send(item.data);
|
|
123
|
+
this.messageBuffer.markSent(item);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} finally {
|
|
127
|
+
unlock();
|
|
128
|
+
}
|
|
129
|
+
this.refreshBufferStatus();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Before recomputing status, trim packets the transport has now delivered — a send or a drain
|
|
134
|
+
* may have acked buffered packets, and the replay buffer is keyed off the channel's buffered
|
|
135
|
+
* bytes.
|
|
136
|
+
*/
|
|
137
|
+
override refreshBufferStatus() {
|
|
138
|
+
const dc = this.channelHandle;
|
|
139
|
+
if (dc) {
|
|
140
|
+
this.messageBuffer.alignBufferedAmount(dc.bufferedAmount);
|
|
141
|
+
}
|
|
142
|
+
super.refreshBufferStatus();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Drops all replay state and restarts sequencing. Only valid on a full reconnect, where the
|
|
147
|
+
* session (and the receivers' sequence tracking) starts over.
|
|
148
|
+
*/
|
|
149
|
+
reset() {
|
|
150
|
+
this.messageBuffer = new DataPacketBuffer();
|
|
151
|
+
this.sequence = 1;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { DataPacket_Kind } from '@livekit/protocol';
|
|
2
|
+
|
|
3
|
+
export enum DataChannelKind {
|
|
4
|
+
RELIABLE = DataPacket_Kind.RELIABLE,
|
|
5
|
+
LOSSY = DataPacket_Kind.LOSSY,
|
|
6
|
+
DATA_TRACK_LOSSY = 2,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Two-watermark flow control for the reliable and data-track channels. Senders fill the buffer
|
|
10
|
+
// freely up to the high-water mark; once it's exceeded they block until the browser's
|
|
11
|
+
// `bufferedamountlow` event (which we arm at the low-water mark) signals the buffer has drained.
|
|
12
|
+
// The gap between the marks keeps the SCTP send buffer saturated while we refill, so throughput
|
|
13
|
+
// isn't starved, while the high-water mark bounds the buffer well below the level that would abort
|
|
14
|
+
// the channel (see livekit/client-sdk-js#1995).
|
|
15
|
+
export const reliableDataChannelWaterMarkLow = 64 * 1024;
|
|
16
|
+
export const reliableDataChannelWaterMarkHigh = 1024 * 1024;
|
|
17
|
+
export const lossyDataChannelWaterMarkLow = 8 * 1024;
|
|
18
|
+
export const lossyDataChannelWaterMarkHigh = 256 * 1024;
|
|
19
|
+
|
|
20
|
+
export function dataChannelLowWaterMark(kind: DataChannelKind): number {
|
|
21
|
+
return kind === DataChannelKind.RELIABLE
|
|
22
|
+
? reliableDataChannelWaterMarkLow
|
|
23
|
+
: lossyDataChannelWaterMarkLow;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function dataChannelHighWaterMark(kind: DataChannelKind): number {
|
|
27
|
+
return kind === DataChannelKind.RELIABLE
|
|
28
|
+
? reliableDataChannelWaterMarkHigh
|
|
29
|
+
: lossyDataChannelWaterMarkHigh;
|
|
30
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compression helpers for data streams. The buffered deflate-raw variant ({@link deflateRawCompress})
|
|
3
|
+
* is for the inline (single-packet) case where the payload is small and bounded;
|
|
4
|
+
* {@link deflateRawTransform} / {@link inflateRawTransform} serve the chunked (multi-packet)
|
|
5
|
+
* `sendText`/`sendBytes`/`sendFile` paths, streaming the bytes through without buffering the whole
|
|
6
|
+
* payload.
|
|
7
|
+
*
|
|
8
|
+
* These operate on bytes (not strings) so a single set of helpers serves both text and byte streams;
|
|
9
|
+
* the `TextEncoder`/`TextDecoder` boundary lives at the manager/reader edges.
|
|
10
|
+
*
|
|
11
|
+
* Both streaming variants are exposed as `ReadableWritablePair`s so they drop straight into a
|
|
12
|
+
* `pipeThrough` chain. Each needs one localized cast to bridge a DOM lib-type mismatch: the platform
|
|
13
|
+
* `CompressionStream`/`DecompressionStream` type their `writable` as `WritableStream<BufferSource>`
|
|
14
|
+
* (a wider element type than `Uint8Array`), and `WritableStream<W>` is covariant in `W`, so neither
|
|
15
|
+
* is structurally a `ReadableWritablePair<Uint8Array, Uint8Array>` without help.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
import { type NonSharedUint8Array } from '../../type-polyfills/non-shared-typed-arrays';
|
|
20
|
+
import { DataStreamError, DataStreamErrorReason } from '../errors';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A `deflate-raw` compression transform (inverse of {@link inflateRawTransform}): pipe a byte stream
|
|
24
|
+
* through it to get the compressed bytes without buffering the whole payload. Used for the chunked
|
|
25
|
+
* `sendText`/`sendBytes`/`sendFile` paths, where the full payload is known up front but is streamed
|
|
26
|
+
* (e.g. from `file.stream()`) rather than held in memory.
|
|
27
|
+
*/
|
|
28
|
+
export function deflateRawTransform(): ReadableWritablePair<
|
|
29
|
+
NonSharedUint8Array,
|
|
30
|
+
NonSharedUint8Array
|
|
31
|
+
> {
|
|
32
|
+
return new CompressionStream('deflate-raw') as unknown as ReadableWritablePair<
|
|
33
|
+
NonSharedUint8Array,
|
|
34
|
+
NonSharedUint8Array
|
|
35
|
+
>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A `deflate-raw` decompression transform (inverse of {@link deflateRawTransform}): pipe a
|
|
40
|
+
* stream of compressed bytes through it to get the decompressed bytes. Inflate emits output greedily,
|
|
41
|
+
* so as long as the sender flushed at write boundaries each write's content is produced as soon as
|
|
42
|
+
* its compressed bytes arrive.
|
|
43
|
+
*/
|
|
44
|
+
export function inflateRawTransform(): ReadableWritablePair<
|
|
45
|
+
NonSharedUint8Array,
|
|
46
|
+
NonSharedUint8Array
|
|
47
|
+
> {
|
|
48
|
+
return new DecompressionStream('deflate-raw') as unknown as ReadableWritablePair<
|
|
49
|
+
NonSharedUint8Array,
|
|
50
|
+
NonSharedUint8Array
|
|
51
|
+
>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** deflate-raw compresses a byte array in full. Use for inline payloads; prefer the streaming
|
|
55
|
+
* path for the chunked case. */
|
|
56
|
+
export async function deflateRawCompress(data: NonSharedUint8Array): Promise<NonSharedUint8Array> {
|
|
57
|
+
const cs = new CompressionStream('deflate-raw');
|
|
58
|
+
const writer = cs.writable.getWriter();
|
|
59
|
+
writer.write(data as NonSharedUint8Array);
|
|
60
|
+
writer.close();
|
|
61
|
+
return collect(cs.readable);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Decompresses a raw-deflate byte array in full (inverse of {@link deflateRawCompress}).
|
|
66
|
+
* `maxByteLength` bounds the decompressed output (decompression-bomb guard); exceeding it rejects
|
|
67
|
+
* with a `PayloadTooLarge` error.
|
|
68
|
+
*/
|
|
69
|
+
export async function deflateRawDecompress(
|
|
70
|
+
data: NonSharedUint8Array,
|
|
71
|
+
maxByteLength?: number,
|
|
72
|
+
): Promise<NonSharedUint8Array> {
|
|
73
|
+
const ds = new DecompressionStream('deflate-raw');
|
|
74
|
+
const writer = ds.writable.getWriter();
|
|
75
|
+
// The writer promises are intentionally not awaited (output is consumed via `collect`), but
|
|
76
|
+
// they reject when the byte cap cancels the readable mid-stream — swallow that so an enforced
|
|
77
|
+
// cap doesn't surface as an unhandled rejection.
|
|
78
|
+
writer.write(data as NonSharedUint8Array).catch(() => {});
|
|
79
|
+
writer.close().catch(() => {});
|
|
80
|
+
return collect(ds.readable, maxByteLength);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Drains a byte stream, concatenating all of its chunks into a single array. When
|
|
85
|
+
* `maxByteLength` is given, drops the stream and throws `PayloadTooLarge` as soon as the
|
|
86
|
+
* accumulated output exceeds it.
|
|
87
|
+
*/
|
|
88
|
+
export async function collect(
|
|
89
|
+
stream: ReadableStream<NonSharedUint8Array>,
|
|
90
|
+
maxByteLength?: number,
|
|
91
|
+
): Promise<NonSharedUint8Array> {
|
|
92
|
+
const reader = stream.getReader();
|
|
93
|
+
const chunks: Array<NonSharedUint8Array> = [];
|
|
94
|
+
let total = 0;
|
|
95
|
+
while (true) {
|
|
96
|
+
const { done, value } = await reader.read();
|
|
97
|
+
if (done) {
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
chunks.push(value);
|
|
101
|
+
total += value.byteLength;
|
|
102
|
+
if (typeof maxByteLength === 'number' && total > maxByteLength) {
|
|
103
|
+
await reader.cancel();
|
|
104
|
+
throw new DataStreamError(
|
|
105
|
+
`Decompressed payload exceeds the maximum payload size of ${maxByteLength} bytes`,
|
|
106
|
+
DataStreamErrorReason.PayloadTooLarge,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const result = new Uint8Array(total);
|
|
111
|
+
let offset = 0;
|
|
112
|
+
for (const chunk of chunks) {
|
|
113
|
+
result.set(chunk, offset);
|
|
114
|
+
offset += chunk.byteLength;
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maximum size of a single data-stream chunk in bytes, and the budget used to decide whether a
|
|
3
|
+
* payload can be sent inline as a single header packet. Kept below the ~16k data-channel MTU to
|
|
4
|
+
* leave headroom for protocol framing and E2EE overhead.
|
|
5
|
+
*
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export const STREAM_CHUNK_SIZE_BYTES = 15_000;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default cap on the number of decompressed bytes a single incoming compressed data stream may
|
|
12
|
+
* produce (5 GB). A tiny compressed payload can inflate to an arbitrarily large output
|
|
13
|
+
* (decompression bomb), so the decompressor's output is bounded rather than trusting the wire
|
|
14
|
+
* size; streams exceeding the cap error with `PayloadTooLarge`.
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_MAX_PAYLOAD_BYTE_LENGTH = 5_000_000_000;
|