UNPKG

socket.me

Version:

Fast Strong-typed WebSockets

340 lines (258 loc) 8.09 kB
const C_MODE_BIN = 1; const C_MODE_JSON = 2; const C_MODE_ACK = 4; const C_MODE_EMPTY = 8; //-------------------------------------------------- const packBufferCacheSize = 1024 * 128; const packBufferCache = new ArrayBuffer(packBufferCacheSize); const unpackCache = [undefined, undefined, undefined]; //-------------------------------------------------- /** * * @param {string} type * @param {(null|number)} ack * @param {(ArrayBuffer|Uint8Array|object)} data * @returns {Uint8Array} */ function pack(type, ack, data) { const isAB = data instanceof ArrayBuffer; const isUB = data instanceof Uint8Array; const isBin = isAB || isUB; const isEmpty = typeof data === 'undefined'; const useAck = typeof ack === 'number'; //---]> const typeBuf = encodeString(type); const dataBuf = isBin ? (isUB ? data : new Uint8Array(data)) : (isEmpty ? null : encodeString(JSON.stringify(data))); //---]> const typeLen = typeBuf.byteLength; const dataSize = isEmpty ? 0 : dataBuf.byteLength; const bufSize = (1) + (1 + typeLen) + (useAck ? 1 : 0) + (dataSize); // mode + typeLen + type + ack + data const bufView = bufSize > packBufferCacheSize ? new Uint8Array(bufSize) : new Uint8Array(packBufferCache, 0, bufSize); //---]> let offset = 0; //---]> // protocol: u8 - 256 if(typeLen >= 256) { throw new Error('`type` is too long'); } //---]> // mode bufView[offset] = (isBin ? C_MODE_BIN : C_MODE_JSON) | (useAck ? C_MODE_ACK : 0) | (isEmpty ? C_MODE_EMPTY : 0); offset += 1; // type length bufView[offset] = typeLen; offset += 1; // type bufView.set(typeBuf, offset); offset += typeLen; // ack if(useAck) { bufView[offset] = ack; offset += 1; } // data if(dataBuf) { bufView.set(dataBuf, offset); } //---]> return bufView; } /** * * @param {(ArrayBuffer|Uint8Array)} buffer * @returns {(null|Error|Array)} */ function unpack(buffer) { const isAB = buffer instanceof ArrayBuffer; const isUB = buffer instanceof Uint8Array; const bufSize = (isAB || isUB) ? buffer.byteLength : 0; //---]> if(!bufSize) { return null; } //---]> let offset = 0; let mode; let type, typeLen; let ack; let data; //---]> const bufView = isAB ? new Uint8Array(buffer) : buffer; //---]> try { mode = bufView[offset]; offset += 1; typeLen = bufView[offset]; offset += 1; type = decodeString(bufView, offset, offset + typeLen); offset += typeLen; if((mode & C_MODE_ACK) === C_MODE_ACK) { ack = bufView[offset]; offset += 1; } if((mode & C_MODE_EMPTY) !== C_MODE_EMPTY) { if((mode & C_MODE_BIN) === C_MODE_BIN) { data = bufView.slice(offset, bufSize).buffer; } else if((mode & C_MODE_JSON) === C_MODE_JSON) { data = JSON.parse(decodeString(bufView, offset, bufSize)); } else { return null; } } } catch(e) { return e; } //---]> unpackCache[0] = type; unpackCache[1] = ack; unpackCache[2] = data; //---]> return unpackCache; } //-------------------------------------------------- module.exports = { pack, unpack }; //-------------------------------------------------- const enc = new TextEncoder(); const dec = new TextDecoder(); //---]> const encStrBufferSymSize = 256; const encStrBufferBytes = encStrBufferSymSize * 4; // ~ utf32 const encStrBuffersSize = encStrBufferBytes * 2; // buf_1 + buf_2 = x2 const encStrBuffersCache = new ArrayBuffer(encStrBuffersSize); let encStrBufferOffset = 0; //---]> /** * * @param {string} str * @returns {ArrayBufferLike} */ function encodeString(str) { const len = str.length; if(len <= encStrBufferSymSize) { const byteLength = utf8ByteLength(str, len); const bufView = new Uint8Array(encStrBuffersCache, encStrBufferOffset, byteLength); encStrBufferOffset = (encStrBufferOffset + encStrBufferBytes) % encStrBuffersSize; utf8Encode(bufView, 0, str, len); return bufView; } else if(len <= 500 && typeof Buffer !== 'undefined') { return Buffer.from(str); } return enc.encode(str); } /** * * @param {ArrayBufferLike} bytes * @param {number} start * @param {number} end * @returns {string} */ function decodeString(bytes, start, end) { const len = end - start; if(typeof Buffer !== 'undefined') { return Buffer.from(bytes).toString('utf8', start, end); } if(len <= 200) { return utf8Decode(bytes, start, len); } return dec.decode(bytes.slice(start, end).buffer); } //-------------------------------------------------- // Faster for short strings (API requires calls across JS <-> Native bridge) function utf8ByteLength(str, length) { let c = 0; let byteLength = 0; for(let i = 0; i < length; ++i) { c = str.charCodeAt(i); if(c < 0x80) { byteLength += 1; } else if(c < 0x800) { byteLength += 2; } else if(c < 0xd800 || c >= 0xe000) { byteLength += 3; } else { ++i; byteLength += 4; } } return byteLength; } function utf8Encode(arr, offset, str, length) { let c = 0; for(let i = 0; i < length; ++i) { c = str.charCodeAt(i); if(c < 0x80) { arr[offset++] = c; } else if(c < 0x800) { arr[offset++] = 0xc0 | (c >> 6); arr[offset++] = 0x80 | (c & 0x3f); } else if(c < 0xd800 || c >= 0xe000) { arr[offset++] = 0xe0 | (c >> 12); arr[offset++] = 0x80 | (c >> 6) & 0x3f; arr[offset++] = 0x80 | (c & 0x3f); } else { ++i; c = 0x10000 + (((c & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff)); arr[offset++] = 0xf0 | (c >> 18); arr[offset++] = 0x80 | (c >> 12) & 0x3f; arr[offset++] = 0x80 | (c >> 6) & 0x3f; arr[offset++] = 0x80 | (c & 0x3f); } } } function utf8Decode(bytes, inputOffset, byteLength) { let offset = inputOffset; const end = offset + byteLength; const out = []; while(offset < end) { const byte1 = bytes[offset++]; if((byte1 & 0x80) === 0) { // 1 byte out.push(byte1); } else if((byte1 & 0xe0) === 0xc0) { // 2 bytes const byte2 = bytes[offset++] & 0x3f; out.push(((byte1 & 0x1f) << 6) | byte2); } else if((byte1 & 0xf0) === 0xe0) { // 3 bytes const byte2 = bytes[offset++] & 0x3f; const byte3 = bytes[offset++] & 0x3f; out.push(((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3); } else if((byte1 & 0xf8) === 0xf0) { // 4 bytes const byte2 = bytes[offset++] & 0x3f; const byte3 = bytes[offset++] & 0x3f; const byte4 = bytes[offset++] & 0x3f; let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4; if(unit > 0xffff) { unit -= 0x10000; out.push(((unit >>> 10) & 0x3ff) | 0xd800); unit = 0xdc00 | (unit & 0x3ff); } out.push(unit); } else { out.push(byte1); } } return String.fromCharCode.apply(String, out); }