UNPKG

6.44 kBJavaScriptView Raw
1import { encodePacket, encodePacketToBinary } from "./encodePacket.js";
2import { decodePacket } from "./decodePacket.js";
3import { ERROR_PACKET, } from "./commons.js";
4const SEPARATOR = String.fromCharCode(30); // see https://en.wikipedia.org/wiki/Delimiter#ASCII_delimited_text
5const encodePayload = (packets, callback) => {
6 // some packets may be added to the array while encoding, so the initial length must be saved
7 const length = packets.length;
8 const encodedPackets = new Array(length);
9 let count = 0;
10 packets.forEach((packet, i) => {
11 // force base64 encoding for binary packets
12 encodePacket(packet, false, (encodedPacket) => {
13 encodedPackets[i] = encodedPacket;
14 if (++count === length) {
15 callback(encodedPackets.join(SEPARATOR));
16 }
17 });
18 });
19};
20const decodePayload = (encodedPayload, binaryType) => {
21 const encodedPackets = encodedPayload.split(SEPARATOR);
22 const packets = [];
23 for (let i = 0; i < encodedPackets.length; i++) {
24 const decodedPacket = decodePacket(encodedPackets[i], binaryType);
25 packets.push(decodedPacket);
26 if (decodedPacket.type === "error") {
27 break;
28 }
29 }
30 return packets;
31};
32export function createPacketEncoderStream() {
33 return new TransformStream({
34 transform(packet, controller) {
35 encodePacketToBinary(packet, (encodedPacket) => {
36 const payloadLength = encodedPacket.length;
37 let header;
38 // inspired by the WebSocket format: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#decoding_payload_length
39 if (payloadLength < 126) {
40 header = new Uint8Array(1);
41 new DataView(header.buffer).setUint8(0, payloadLength);
42 }
43 else if (payloadLength < 65536) {
44 header = new Uint8Array(3);
45 const view = new DataView(header.buffer);
46 view.setUint8(0, 126);
47 view.setUint16(1, payloadLength);
48 }
49 else {
50 header = new Uint8Array(9);
51 const view = new DataView(header.buffer);
52 view.setUint8(0, 127);
53 view.setBigUint64(1, BigInt(payloadLength));
54 }
55 // first bit indicates whether the payload is plain text (0) or binary (1)
56 if (packet.data && typeof packet.data !== "string") {
57 header[0] |= 0x80;
58 }
59 controller.enqueue(header);
60 controller.enqueue(encodedPacket);
61 });
62 },
63 });
64}
65let TEXT_DECODER;
66function totalLength(chunks) {
67 return chunks.reduce((acc, chunk) => acc + chunk.length, 0);
68}
69function concatChunks(chunks, size) {
70 if (chunks[0].length === size) {
71 return chunks.shift();
72 }
73 const buffer = new Uint8Array(size);
74 let j = 0;
75 for (let i = 0; i < size; i++) {
76 buffer[i] = chunks[0][j++];
77 if (j === chunks[0].length) {
78 chunks.shift();
79 j = 0;
80 }
81 }
82 if (chunks.length && j < chunks[0].length) {
83 chunks[0] = chunks[0].slice(j);
84 }
85 return buffer;
86}
87export function createPacketDecoderStream(maxPayload, binaryType) {
88 if (!TEXT_DECODER) {
89 TEXT_DECODER = new TextDecoder();
90 }
91 const chunks = [];
92 let state = 0 /* State.READ_HEADER */;
93 let expectedLength = -1;
94 let isBinary = false;
95 return new TransformStream({
96 transform(chunk, controller) {
97 chunks.push(chunk);
98 while (true) {
99 if (state === 0 /* State.READ_HEADER */) {
100 if (totalLength(chunks) < 1) {
101 break;
102 }
103 const header = concatChunks(chunks, 1);
104 isBinary = (header[0] & 0x80) === 0x80;
105 expectedLength = header[0] & 0x7f;
106 if (expectedLength < 126) {
107 state = 3 /* State.READ_PAYLOAD */;
108 }
109 else if (expectedLength === 126) {
110 state = 1 /* State.READ_EXTENDED_LENGTH_16 */;
111 }
112 else {
113 state = 2 /* State.READ_EXTENDED_LENGTH_64 */;
114 }
115 }
116 else if (state === 1 /* State.READ_EXTENDED_LENGTH_16 */) {
117 if (totalLength(chunks) < 2) {
118 break;
119 }
120 const headerArray = concatChunks(chunks, 2);
121 expectedLength = new DataView(headerArray.buffer, headerArray.byteOffset, headerArray.length).getUint16(0);
122 state = 3 /* State.READ_PAYLOAD */;
123 }
124 else if (state === 2 /* State.READ_EXTENDED_LENGTH_64 */) {
125 if (totalLength(chunks) < 8) {
126 break;
127 }
128 const headerArray = concatChunks(chunks, 8);
129 const view = new DataView(headerArray.buffer, headerArray.byteOffset, headerArray.length);
130 const n = view.getUint32(0);
131 if (n > Math.pow(2, 53 - 32) - 1) {
132 // the maximum safe integer in JavaScript is 2^53 - 1
133 controller.enqueue(ERROR_PACKET);
134 break;
135 }
136 expectedLength = n * Math.pow(2, 32) + view.getUint32(4);
137 state = 3 /* State.READ_PAYLOAD */;
138 }
139 else {
140 if (totalLength(chunks) < expectedLength) {
141 break;
142 }
143 const data = concatChunks(chunks, expectedLength);
144 controller.enqueue(decodePacket(isBinary ? data : TEXT_DECODER.decode(data), binaryType));
145 state = 0 /* State.READ_HEADER */;
146 }
147 if (expectedLength === 0 || expectedLength > maxPayload) {
148 controller.enqueue(ERROR_PACKET);
149 break;
150 }
151 }
152 },
153 });
154}
155export const protocol = 4;
156export { encodePacket, encodePayload, decodePacket, decodePayload, };