index.js 6.3 KB

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