UNPKG

@wioex/stream-sdk

Version:

WebSocket streaming SDK for real-time WioEX market data

1,520 lines (1,456 loc) 144 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.WioexStream = {})); })(this, (function (exports) { 'use strict'; function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } var eventemitter3 = {exports: {}}; (function (module) { var has = Object.prototype.hasOwnProperty , prefix = '~'; /** * Constructor to create a storage for our `EE` objects. * An `Events` instance is a plain object whose properties are event names. * * @constructor * @private */ function Events() {} // // We try to not inherit from `Object.prototype`. In some engines creating an // instance in this way is faster than calling `Object.create(null)` directly. // If `Object.create(null)` is not supported we prefix the event names with a // character to make sure that the built-in object properties are not // overridden or used as an attack vector. // if (Object.create) { Events.prototype = Object.create(null); // // This hack is needed because the `__proto__` property is still inherited in // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5. // if (!new Events().__proto__) prefix = false; } /** * Representation of a single event listener. * * @param {Function} fn The listener function. * @param {*} context The context to invoke the listener with. * @param {Boolean} [once=false] Specify if the listener is a one-time listener. * @constructor * @private */ function EE(fn, context, once) { this.fn = fn; this.context = context; this.once = once || false; } /** * Add a listener for a given event. * * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. * @param {(String|Symbol)} event The event name. * @param {Function} fn The listener function. * @param {*} context The context to invoke the listener with. * @param {Boolean} once Specify if the listener is a one-time listener. * @returns {EventEmitter} * @private */ function addListener(emitter, event, fn, context, once) { if (typeof fn !== 'function') { throw new TypeError('The listener must be a function'); } var listener = new EE(fn, context || emitter, once) , evt = prefix ? prefix + event : event; if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++; else if (!emitter._events[evt].fn) emitter._events[evt].push(listener); else emitter._events[evt] = [emitter._events[evt], listener]; return emitter; } /** * Clear event by name. * * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. * @param {(String|Symbol)} evt The Event name. * @private */ function clearEvent(emitter, evt) { if (--emitter._eventsCount === 0) emitter._events = new Events(); else delete emitter._events[evt]; } /** * Minimal `EventEmitter` interface that is molded against the Node.js * `EventEmitter` interface. * * @constructor * @public */ function EventEmitter() { this._events = new Events(); this._eventsCount = 0; } /** * Return an array listing the events for which the emitter has registered * listeners. * * @returns {Array} * @public */ EventEmitter.prototype.eventNames = function eventNames() { var names = [] , events , name; if (this._eventsCount === 0) return names; for (name in (events = this._events)) { if (has.call(events, name)) names.push(prefix ? name.slice(1) : name); } if (Object.getOwnPropertySymbols) { return names.concat(Object.getOwnPropertySymbols(events)); } return names; }; /** * Return the listeners registered for a given event. * * @param {(String|Symbol)} event The event name. * @returns {Array} The registered listeners. * @public */ EventEmitter.prototype.listeners = function listeners(event) { var evt = prefix ? prefix + event : event , handlers = this._events[evt]; if (!handlers) return []; if (handlers.fn) return [handlers.fn]; for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) { ee[i] = handlers[i].fn; } return ee; }; /** * Return the number of listeners listening to a given event. * * @param {(String|Symbol)} event The event name. * @returns {Number} The number of listeners. * @public */ EventEmitter.prototype.listenerCount = function listenerCount(event) { var evt = prefix ? prefix + event : event , listeners = this._events[evt]; if (!listeners) return 0; if (listeners.fn) return 1; return listeners.length; }; /** * Calls each of the listeners registered for a given event. * * @param {(String|Symbol)} event The event name. * @returns {Boolean} `true` if the event had listeners, else `false`. * @public */ EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { var evt = prefix ? prefix + event : event; if (!this._events[evt]) return false; var listeners = this._events[evt] , len = arguments.length , args , i; if (listeners.fn) { if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); switch (len) { case 1: return listeners.fn.call(listeners.context), true; case 2: return listeners.fn.call(listeners.context, a1), true; case 3: return listeners.fn.call(listeners.context, a1, a2), true; case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; } for (i = 1, args = new Array(len -1); i < len; i++) { args[i - 1] = arguments[i]; } listeners.fn.apply(listeners.context, args); } else { var length = listeners.length , j; for (i = 0; i < length; i++) { if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); switch (len) { case 1: listeners[i].fn.call(listeners[i].context); break; case 2: listeners[i].fn.call(listeners[i].context, a1); break; case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break; default: if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { args[j - 1] = arguments[j]; } listeners[i].fn.apply(listeners[i].context, args); } } } return true; }; /** * Add a listener for a given event. * * @param {(String|Symbol)} event The event name. * @param {Function} fn The listener function. * @param {*} [context=this] The context to invoke the listener with. * @returns {EventEmitter} `this`. * @public */ EventEmitter.prototype.on = function on(event, fn, context) { return addListener(this, event, fn, context, false); }; /** * Add a one-time listener for a given event. * * @param {(String|Symbol)} event The event name. * @param {Function} fn The listener function. * @param {*} [context=this] The context to invoke the listener with. * @returns {EventEmitter} `this`. * @public */ EventEmitter.prototype.once = function once(event, fn, context) { return addListener(this, event, fn, context, true); }; /** * Remove the listeners of a given event. * * @param {(String|Symbol)} event The event name. * @param {Function} fn Only remove the listeners that match this function. * @param {*} context Only remove the listeners that have this context. * @param {Boolean} once Only remove one-time listeners. * @returns {EventEmitter} `this`. * @public */ EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { var evt = prefix ? prefix + event : event; if (!this._events[evt]) return this; if (!fn) { clearEvent(this, evt); return this; } var listeners = this._events[evt]; if (listeners.fn) { if ( listeners.fn === fn && (!once || listeners.once) && (!context || listeners.context === context) ) { clearEvent(this, evt); } } else { for (var i = 0, events = [], length = listeners.length; i < length; i++) { if ( listeners[i].fn !== fn || (once && !listeners[i].once) || (context && listeners[i].context !== context) ) { events.push(listeners[i]); } } // // Reset the array, or remove it completely if we have no more listeners. // if (events.length) this._events[evt] = events.length === 1 ? events[0] : events; else clearEvent(this, evt); } return this; }; /** * Remove all listeners, or those of the specified event. * * @param {(String|Symbol)} [event] The event name. * @returns {EventEmitter} `this`. * @public */ EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { var evt; if (event) { evt = prefix ? prefix + event : event; if (this._events[evt]) clearEvent(this, evt); } else { this._events = new Events(); this._eventsCount = 0; } return this; }; // // Alias methods names because people roll like that. // EventEmitter.prototype.off = EventEmitter.prototype.removeListener; EventEmitter.prototype.addListener = EventEmitter.prototype.on; // // Expose the prefix. // EventEmitter.prefixed = prefix; // // Allow `EventEmitter` to be imported as module namespace. // EventEmitter.EventEmitter = EventEmitter; // // Expose the module. // { module.exports = EventEmitter; } } (eventemitter3)); var eventemitter3Exports = eventemitter3.exports; var EventEmitter = /*@__PURE__*/getDefaultExportFromCjs(eventemitter3Exports); function utf8Count(str) { const strLength = str.length; let byteLength = 0; let pos = 0; while (pos < strLength) { let value = str.charCodeAt(pos++); if ((value & 0xffffff80) === 0) { // 1-byte byteLength++; continue; } else if ((value & 0xfffff800) === 0) { // 2-bytes byteLength += 2; } else { // handle surrogate pair if (value >= 0xd800 && value <= 0xdbff) { // high surrogate if (pos < strLength) { const extra = str.charCodeAt(pos); if ((extra & 0xfc00) === 0xdc00) { ++pos; value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000; } } } if ((value & 0xffff0000) === 0) { // 3-byte byteLength += 3; } else { // 4-byte byteLength += 4; } } } return byteLength; } function utf8EncodeJs(str, output, outputOffset) { const strLength = str.length; let offset = outputOffset; let pos = 0; while (pos < strLength) { let value = str.charCodeAt(pos++); if ((value & 0xffffff80) === 0) { // 1-byte output[offset++] = value; continue; } else if ((value & 0xfffff800) === 0) { // 2-bytes output[offset++] = ((value >> 6) & 0x1f) | 0xc0; } else { // handle surrogate pair if (value >= 0xd800 && value <= 0xdbff) { // high surrogate if (pos < strLength) { const extra = str.charCodeAt(pos); if ((extra & 0xfc00) === 0xdc00) { ++pos; value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000; } } } if ((value & 0xffff0000) === 0) { // 3-byte output[offset++] = ((value >> 12) & 0x0f) | 0xe0; output[offset++] = ((value >> 6) & 0x3f) | 0x80; } else { // 4-byte output[offset++] = ((value >> 18) & 0x07) | 0xf0; output[offset++] = ((value >> 12) & 0x3f) | 0x80; output[offset++] = ((value >> 6) & 0x3f) | 0x80; } } output[offset++] = (value & 0x3f) | 0x80; } } // TextEncoder and TextDecoder are standardized in whatwg encoding: // https://encoding.spec.whatwg.org/ // and available in all the modern browsers: // https://caniuse.com/textencoder // They are available in Node.js since v12 LTS as well: // https://nodejs.org/api/globals.html#textencoder const sharedTextEncoder = new TextEncoder(); // This threshold should be determined by benchmarking, which might vary in engines and input data. // Run `npx ts-node benchmark/encode-string.ts` for details. const TEXT_ENCODER_THRESHOLD = 50; function utf8EncodeTE(str, output, outputOffset) { sharedTextEncoder.encodeInto(str, output.subarray(outputOffset)); } function utf8Encode(str, output, outputOffset) { if (str.length > TEXT_ENCODER_THRESHOLD) { utf8EncodeTE(str, output, outputOffset); } else { utf8EncodeJs(str, output, outputOffset); } } const CHUNK_SIZE = 4096; function utf8DecodeJs(bytes, inputOffset, byteLength) { let offset = inputOffset; const end = offset + byteLength; const units = []; let result = ""; while (offset < end) { const byte1 = bytes[offset++]; if ((byte1 & 0x80) === 0) { // 1 byte units.push(byte1); } else if ((byte1 & 0xe0) === 0xc0) { // 2 bytes const byte2 = bytes[offset++] & 0x3f; units.push(((byte1 & 0x1f) << 6) | byte2); } else if ((byte1 & 0xf0) === 0xe0) { // 3 bytes const byte2 = bytes[offset++] & 0x3f; const byte3 = bytes[offset++] & 0x3f; units.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; units.push(((unit >>> 10) & 0x3ff) | 0xd800); unit = 0xdc00 | (unit & 0x3ff); } units.push(unit); } else { units.push(byte1); } if (units.length >= CHUNK_SIZE) { result += String.fromCharCode(...units); units.length = 0; } } if (units.length > 0) { result += String.fromCharCode(...units); } return result; } const sharedTextDecoder = new TextDecoder(); // This threshold should be determined by benchmarking, which might vary in engines and input data. // Run `npx ts-node benchmark/decode-string.ts` for details. const TEXT_DECODER_THRESHOLD = 200; function utf8DecodeTD(bytes, inputOffset, byteLength) { const stringBytes = bytes.subarray(inputOffset, inputOffset + byteLength); return sharedTextDecoder.decode(stringBytes); } function utf8Decode(bytes, inputOffset, byteLength) { if (byteLength > TEXT_DECODER_THRESHOLD) { return utf8DecodeTD(bytes, inputOffset, byteLength); } else { return utf8DecodeJs(bytes, inputOffset, byteLength); } } /** * ExtData is used to handle Extension Types that are not registered to ExtensionCodec. */ class ExtData { constructor(type, data) { this.type = type; this.data = data; } } class DecodeError extends Error { constructor(message) { super(message); // fix the prototype chain in a cross-platform way const proto = Object.create(DecodeError.prototype); Object.setPrototypeOf(this, proto); Object.defineProperty(this, "name", { configurable: true, enumerable: false, value: DecodeError.name, }); } } // Integer Utility const UINT32_MAX = 4294967295; // DataView extension to handle int64 / uint64, // where the actual range is 53-bits integer (a.k.a. safe integer) function setUint64(view, offset, value) { const high = value / 4294967296; const low = value; // high bits are truncated by DataView view.setUint32(offset, high); view.setUint32(offset + 4, low); } function setInt64(view, offset, value) { const high = Math.floor(value / 4294967296); const low = value; // high bits are truncated by DataView view.setUint32(offset, high); view.setUint32(offset + 4, low); } function getInt64(view, offset) { const high = view.getInt32(offset); const low = view.getUint32(offset + 4); return high * 4294967296 + low; } function getUint64(view, offset) { const high = view.getUint32(offset); const low = view.getUint32(offset + 4); return high * 4294967296 + low; } // https://github.com/msgpack/msgpack/blob/master/spec.md#timestamp-extension-type const EXT_TIMESTAMP = -1; const TIMESTAMP32_MAX_SEC = 0x100000000 - 1; // 32-bit unsigned int const TIMESTAMP64_MAX_SEC = 0x400000000 - 1; // 34-bit unsigned int function encodeTimeSpecToTimestamp({ sec, nsec }) { if (sec >= 0 && nsec >= 0 && sec <= TIMESTAMP64_MAX_SEC) { // Here sec >= 0 && nsec >= 0 if (nsec === 0 && sec <= TIMESTAMP32_MAX_SEC) { // timestamp 32 = { sec32 (unsigned) } const rv = new Uint8Array(4); const view = new DataView(rv.buffer); view.setUint32(0, sec); return rv; } else { // timestamp 64 = { nsec30 (unsigned), sec34 (unsigned) } const secHigh = sec / 0x100000000; const secLow = sec & 0xffffffff; const rv = new Uint8Array(8); const view = new DataView(rv.buffer); // nsec30 | secHigh2 view.setUint32(0, (nsec << 2) | (secHigh & 0x3)); // secLow32 view.setUint32(4, secLow); return rv; } } else { // timestamp 96 = { nsec32 (unsigned), sec64 (signed) } const rv = new Uint8Array(12); const view = new DataView(rv.buffer); view.setUint32(0, nsec); setInt64(view, 4, sec); return rv; } } function encodeDateToTimeSpec(date) { const msec = date.getTime(); const sec = Math.floor(msec / 1e3); const nsec = (msec - sec * 1e3) * 1e6; // Normalizes { sec, nsec } to ensure nsec is unsigned. const nsecInSec = Math.floor(nsec / 1e9); return { sec: sec + nsecInSec, nsec: nsec - nsecInSec * 1e9, }; } function encodeTimestampExtension(object) { if (object instanceof Date) { const timeSpec = encodeDateToTimeSpec(object); return encodeTimeSpecToTimestamp(timeSpec); } else { return null; } } function decodeTimestampToTimeSpec(data) { const view = new DataView(data.buffer, data.byteOffset, data.byteLength); // data may be 32, 64, or 96 bits switch (data.byteLength) { case 4: { // timestamp 32 = { sec32 } const sec = view.getUint32(0); const nsec = 0; return { sec, nsec }; } case 8: { // timestamp 64 = { nsec30, sec34 } const nsec30AndSecHigh2 = view.getUint32(0); const secLow32 = view.getUint32(4); const sec = (nsec30AndSecHigh2 & 0x3) * 0x100000000 + secLow32; const nsec = nsec30AndSecHigh2 >>> 2; return { sec, nsec }; } case 12: { // timestamp 96 = { nsec32 (unsigned), sec64 (signed) } const sec = getInt64(view, 4); const nsec = view.getUint32(0); return { sec, nsec }; } default: throw new DecodeError(`Unrecognized data size for timestamp (expected 4, 8, or 12): ${data.length}`); } } function decodeTimestampExtension(data) { const timeSpec = decodeTimestampToTimeSpec(data); return new Date(timeSpec.sec * 1e3 + timeSpec.nsec / 1e6); } const timestampExtension = { type: EXT_TIMESTAMP, encode: encodeTimestampExtension, decode: decodeTimestampExtension, }; // ExtensionCodec to handle MessagePack extensions class ExtensionCodec { constructor() { // built-in extensions this.builtInEncoders = []; this.builtInDecoders = []; // custom extensions this.encoders = []; this.decoders = []; this.register(timestampExtension); } register({ type, encode, decode, }) { if (type >= 0) { // custom extensions this.encoders[type] = encode; this.decoders[type] = decode; } else { // built-in extensions const index = -1 - type; this.builtInEncoders[index] = encode; this.builtInDecoders[index] = decode; } } tryToEncode(object, context) { // built-in extensions for (let i = 0; i < this.builtInEncoders.length; i++) { const encodeExt = this.builtInEncoders[i]; if (encodeExt != null) { const data = encodeExt(object, context); if (data != null) { const type = -1 - i; return new ExtData(type, data); } } } // custom extensions for (let i = 0; i < this.encoders.length; i++) { const encodeExt = this.encoders[i]; if (encodeExt != null) { const data = encodeExt(object, context); if (data != null) { const type = i; return new ExtData(type, data); } } } if (object instanceof ExtData) { // to keep ExtData as is return object; } return null; } decode(data, type, context) { const decodeExt = type < 0 ? this.builtInDecoders[-1 - type] : this.decoders[type]; if (decodeExt) { return decodeExt(data, type, context); } else { // decode() does not fail, returns ExtData instead. return new ExtData(type, data); } } } ExtensionCodec.defaultCodec = new ExtensionCodec(); function isArrayBufferLike(buffer) { return (buffer instanceof ArrayBuffer || (typeof SharedArrayBuffer !== "undefined" && buffer instanceof SharedArrayBuffer)); } function ensureUint8Array(buffer) { if (buffer instanceof Uint8Array) { return buffer; } else if (ArrayBuffer.isView(buffer)) { return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength); } else if (isArrayBufferLike(buffer)) { return new Uint8Array(buffer); } else { // ArrayLike<number> return Uint8Array.from(buffer); } } const DEFAULT_MAX_DEPTH = 100; const DEFAULT_INITIAL_BUFFER_SIZE = 2048; class Encoder { constructor(options) { this.entered = false; this.extensionCodec = options?.extensionCodec ?? ExtensionCodec.defaultCodec; this.context = options?.context; // needs a type assertion because EncoderOptions has no context property when ContextType is undefined this.useBigInt64 = options?.useBigInt64 ?? false; this.maxDepth = options?.maxDepth ?? DEFAULT_MAX_DEPTH; this.initialBufferSize = options?.initialBufferSize ?? DEFAULT_INITIAL_BUFFER_SIZE; this.sortKeys = options?.sortKeys ?? false; this.forceFloat32 = options?.forceFloat32 ?? false; this.ignoreUndefined = options?.ignoreUndefined ?? false; this.forceIntegerToFloat = options?.forceIntegerToFloat ?? false; this.pos = 0; this.view = new DataView(new ArrayBuffer(this.initialBufferSize)); this.bytes = new Uint8Array(this.view.buffer); } clone() { // Because of slightly special argument `context`, // type assertion is needed. // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return new Encoder({ extensionCodec: this.extensionCodec, context: this.context, useBigInt64: this.useBigInt64, maxDepth: this.maxDepth, initialBufferSize: this.initialBufferSize, sortKeys: this.sortKeys, forceFloat32: this.forceFloat32, ignoreUndefined: this.ignoreUndefined, forceIntegerToFloat: this.forceIntegerToFloat, }); } reinitializeState() { this.pos = 0; } /** * This is almost equivalent to {@link Encoder#encode}, but it returns an reference of the encoder's internal buffer and thus much faster than {@link Encoder#encode}. * * @returns Encodes the object and returns a shared reference the encoder's internal buffer. */ encodeSharedRef(object) { if (this.entered) { const instance = this.clone(); return instance.encodeSharedRef(object); } try { this.entered = true; this.reinitializeState(); this.doEncode(object, 1); return this.bytes.subarray(0, this.pos); } finally { this.entered = false; } } /** * @returns Encodes the object and returns a copy of the encoder's internal buffer. */ encode(object) { if (this.entered) { const instance = this.clone(); return instance.encode(object); } try { this.entered = true; this.reinitializeState(); this.doEncode(object, 1); return this.bytes.slice(0, this.pos); } finally { this.entered = false; } } doEncode(object, depth) { if (depth > this.maxDepth) { throw new Error(`Too deep objects in depth ${depth}`); } if (object == null) { this.encodeNil(); } else if (typeof object === "boolean") { this.encodeBoolean(object); } else if (typeof object === "number") { if (!this.forceIntegerToFloat) { this.encodeNumber(object); } else { this.encodeNumberAsFloat(object); } } else if (typeof object === "string") { this.encodeString(object); } else if (this.useBigInt64 && typeof object === "bigint") { this.encodeBigInt64(object); } else { this.encodeObject(object, depth); } } ensureBufferSizeToWrite(sizeToWrite) { const requiredSize = this.pos + sizeToWrite; if (this.view.byteLength < requiredSize) { this.resizeBuffer(requiredSize * 2); } } resizeBuffer(newSize) { const newBuffer = new ArrayBuffer(newSize); const newBytes = new Uint8Array(newBuffer); const newView = new DataView(newBuffer); newBytes.set(this.bytes); this.view = newView; this.bytes = newBytes; } encodeNil() { this.writeU8(0xc0); } encodeBoolean(object) { if (object === false) { this.writeU8(0xc2); } else { this.writeU8(0xc3); } } encodeNumber(object) { if (!this.forceIntegerToFloat && Number.isSafeInteger(object)) { if (object >= 0) { if (object < 0x80) { // positive fixint this.writeU8(object); } else if (object < 0x100) { // uint 8 this.writeU8(0xcc); this.writeU8(object); } else if (object < 0x10000) { // uint 16 this.writeU8(0xcd); this.writeU16(object); } else if (object < 0x100000000) { // uint 32 this.writeU8(0xce); this.writeU32(object); } else if (!this.useBigInt64) { // uint 64 this.writeU8(0xcf); this.writeU64(object); } else { this.encodeNumberAsFloat(object); } } else { if (object >= -32) { // negative fixint this.writeU8(0xe0 | (object + 0x20)); } else if (object >= -128) { // int 8 this.writeU8(0xd0); this.writeI8(object); } else if (object >= -32768) { // int 16 this.writeU8(0xd1); this.writeI16(object); } else if (object >= -2147483648) { // int 32 this.writeU8(0xd2); this.writeI32(object); } else if (!this.useBigInt64) { // int 64 this.writeU8(0xd3); this.writeI64(object); } else { this.encodeNumberAsFloat(object); } } } else { this.encodeNumberAsFloat(object); } } encodeNumberAsFloat(object) { if (this.forceFloat32) { // float 32 this.writeU8(0xca); this.writeF32(object); } else { // float 64 this.writeU8(0xcb); this.writeF64(object); } } encodeBigInt64(object) { if (object >= BigInt(0)) { // uint 64 this.writeU8(0xcf); this.writeBigUint64(object); } else { // int 64 this.writeU8(0xd3); this.writeBigInt64(object); } } writeStringHeader(byteLength) { if (byteLength < 32) { // fixstr this.writeU8(0xa0 + byteLength); } else if (byteLength < 0x100) { // str 8 this.writeU8(0xd9); this.writeU8(byteLength); } else if (byteLength < 0x10000) { // str 16 this.writeU8(0xda); this.writeU16(byteLength); } else if (byteLength < 0x100000000) { // str 32 this.writeU8(0xdb); this.writeU32(byteLength); } else { throw new Error(`Too long string: ${byteLength} bytes in UTF-8`); } } encodeString(object) { const maxHeaderSize = 1 + 4; const byteLength = utf8Count(object); this.ensureBufferSizeToWrite(maxHeaderSize + byteLength); this.writeStringHeader(byteLength); utf8Encode(object, this.bytes, this.pos); this.pos += byteLength; } encodeObject(object, depth) { // try to encode objects with custom codec first of non-primitives const ext = this.extensionCodec.tryToEncode(object, this.context); if (ext != null) { this.encodeExtension(ext); } else if (Array.isArray(object)) { this.encodeArray(object, depth); } else if (ArrayBuffer.isView(object)) { this.encodeBinary(object); } else if (typeof object === "object") { this.encodeMap(object, depth); } else { // symbol, function and other special object come here unless extensionCodec handles them. throw new Error(`Unrecognized object: ${Object.prototype.toString.apply(object)}`); } } encodeBinary(object) { const size = object.byteLength; if (size < 0x100) { // bin 8 this.writeU8(0xc4); this.writeU8(size); } else if (size < 0x10000) { // bin 16 this.writeU8(0xc5); this.writeU16(size); } else if (size < 0x100000000) { // bin 32 this.writeU8(0xc6); this.writeU32(size); } else { throw new Error(`Too large binary: ${size}`); } const bytes = ensureUint8Array(object); this.writeU8a(bytes); } encodeArray(object, depth) { const size = object.length; if (size < 16) { // fixarray this.writeU8(0x90 + size); } else if (size < 0x10000) { // array 16 this.writeU8(0xdc); this.writeU16(size); } else if (size < 0x100000000) { // array 32 this.writeU8(0xdd); this.writeU32(size); } else { throw new Error(`Too large array: ${size}`); } for (const item of object) { this.doEncode(item, depth + 1); } } countWithoutUndefined(object, keys) { let count = 0; for (const key of keys) { if (object[key] !== undefined) { count++; } } return count; } encodeMap(object, depth) { const keys = Object.keys(object); if (this.sortKeys) { keys.sort(); } const size = this.ignoreUndefined ? this.countWithoutUndefined(object, keys) : keys.length; if (size < 16) { // fixmap this.writeU8(0x80 + size); } else if (size < 0x10000) { // map 16 this.writeU8(0xde); this.writeU16(size); } else if (size < 0x100000000) { // map 32 this.writeU8(0xdf); this.writeU32(size); } else { throw new Error(`Too large map object: ${size}`); } for (const key of keys) { const value = object[key]; if (!(this.ignoreUndefined && value === undefined)) { this.encodeString(key); this.doEncode(value, depth + 1); } } } encodeExtension(ext) { if (typeof ext.data === "function") { const data = ext.data(this.pos + 6); const size = data.length; if (size >= 0x100000000) { throw new Error(`Too large extension object: ${size}`); } this.writeU8(0xc9); this.writeU32(size); this.writeI8(ext.type); this.writeU8a(data); return; } const size = ext.data.length; if (size === 1) { // fixext 1 this.writeU8(0xd4); } else if (size === 2) { // fixext 2 this.writeU8(0xd5); } else if (size === 4) { // fixext 4 this.writeU8(0xd6); } else if (size === 8) { // fixext 8 this.writeU8(0xd7); } else if (size === 16) { // fixext 16 this.writeU8(0xd8); } else if (size < 0x100) { // ext 8 this.writeU8(0xc7); this.writeU8(size); } else if (size < 0x10000) { // ext 16 this.writeU8(0xc8); this.writeU16(size); } else if (size < 0x100000000) { // ext 32 this.writeU8(0xc9); this.writeU32(size); } else { throw new Error(`Too large extension object: ${size}`); } this.writeI8(ext.type); this.writeU8a(ext.data); } writeU8(value) { this.ensureBufferSizeToWrite(1); this.view.setUint8(this.pos, value); this.pos++; } writeU8a(values) { const size = values.length; this.ensureBufferSizeToWrite(size); this.bytes.set(values, this.pos); this.pos += size; } writeI8(value) { this.ensureBufferSizeToWrite(1); this.view.setInt8(this.pos, value); this.pos++; } writeU16(value) { this.ensureBufferSizeToWrite(2); this.view.setUint16(this.pos, value); this.pos += 2; } writeI16(value) { this.ensureBufferSizeToWrite(2); this.view.setInt16(this.pos, value); this.pos += 2; } writeU32(value) { this.ensureBufferSizeToWrite(4); this.view.setUint32(this.pos, value); this.pos += 4; } writeI32(value) { this.ensureBufferSizeToWrite(4); this.view.setInt32(this.pos, value); this.pos += 4; } writeF32(value) { this.ensureBufferSizeToWrite(4); this.view.setFloat32(this.pos, value); this.pos += 4; } writeF64(value) { this.ensureBufferSizeToWrite(8); this.view.setFloat64(this.pos, value); this.pos += 8; } writeU64(value) { this.ensureBufferSizeToWrite(8); setUint64(this.view, this.pos, value); this.pos += 8; } writeI64(value) { this.ensureBufferSizeToWrite(8); setInt64(this.view, this.pos, value); this.pos += 8; } writeBigUint64(value) { this.ensureBufferSizeToWrite(8); this.view.setBigUint64(this.pos, value); this.pos += 8; } writeBigInt64(value) { this.ensureBufferSizeToWrite(8); this.view.setBigInt64(this.pos, value); this.pos += 8; } } /** * It encodes `value` in the MessagePack format and * returns a byte buffer. * * The returned buffer is a slice of a larger `ArrayBuffer`, so you have to use its `#byteOffset` and `#byteLength` in order to convert it to another typed arrays including NodeJS `Buffer`. */ function encode(value, options) { const encoder = new Encoder(options); return encoder.encodeSharedRef(value); } function prettyByte(byte) { return `${byte < 0 ? "-" : ""}0x${Math.abs(byte).toString(16).padStart(2, "0")}`; } const DEFAULT_MAX_KEY_LENGTH = 16; const DEFAULT_MAX_LENGTH_PER_KEY = 16; class CachedKeyDecoder { constructor(maxKeyLength = DEFAULT_MAX_KEY_LENGTH, maxLengthPerKey = DEFAULT_MAX_LENGTH_PER_KEY) { this.hit = 0; this.miss = 0; this.maxKeyLength = maxKeyLength; this.maxLengthPerKey = maxLengthPerKey; // avoid `new Array(N)`, which makes a sparse array, // because a sparse array is typically slower than a non-sparse array. this.caches = []; for (let i = 0; i < this.maxKeyLength; i++) { this.caches.push([]); } } canBeCached(byteLength) { return byteLength > 0 && byteLength <= this.maxKeyLength; } find(bytes, inputOffset, byteLength) { const records = this.caches[byteLength - 1]; FIND_CHUNK: for (const record of records) { const recordBytes = record.bytes; for (let j = 0; j < byteLength; j++) { if (recordBytes[j] !== bytes[inputOffset + j]) { continue FIND_CHUNK; } } return record.str; } return null; } store(bytes, value) { const records = this.caches[bytes.length - 1]; const record = { bytes, str: value }; if (records.length >= this.maxLengthPerKey) { // `records` are full! // Set `record` to an arbitrary position. records[(Math.random() * records.length) | 0] = record; } else { records.push(record); } } decode(bytes, inputOffset, byteLength) { const cachedValue = this.find(bytes, inputOffset, byteLength); if (cachedValue != null) { this.hit++; return cachedValue; } this.miss++; const str = utf8DecodeJs(bytes, inputOffset, byteLength); // Ensure to copy a slice of bytes because the bytes may be a NodeJS Buffer and Buffer#slice() returns a reference to its internal ArrayBuffer. const slicedCopyOfBytes = Uint8Array.prototype.slice.call(bytes, inputOffset, inputOffset + byteLength); this.store(slicedCopyOfBytes, str); return str; } } const STATE_ARRAY = "array"; const STATE_MAP_KEY = "map_key"; const STATE_MAP_VALUE = "map_value"; const mapKeyConverter = (key) => { if (typeof key === "string" || typeof key === "number") { return key; } throw new DecodeError("The type of key must be string or number but " + typeof key); }; class StackPool { constructor() { this.stack = []; this.stackHeadPosition = -1; } get length() { return this.stackHeadPosition + 1; } top() { return this.stack[this.stackHeadPosition]; } pushArrayState(size) { const state = this.getUninitializedStateFromPool(); state.type = STATE_ARRAY; state.position = 0; state.size = size; state.array = new Array(size); } pushMapState(size) { const state = this.getUninitializedStateFromPool(); state.type = STATE_MAP_KEY; state.readCount = 0; state.size = size; state.map = {}; } getUninitializedStateFromPool() { this.stackHeadPosition++; if (this.stackHeadPosition === this.stack.length) { const partialState = { type: undefined, size: 0, array: undefined, position: 0, readCount: 0, map: undefined, key: null, }; this.stack.push(partialState); } return this.stack[this.stackHeadPosition]; } release(state) { const topStackState = this.stack[this.stackHeadPosition]; if (topStackState !== state) { throw new Error("Invalid stack state. Released state is not on top of the stack."); } if (state.type === STATE_ARRAY) { const partialState = state; partialState.size = 0; partialState.array = undefined; partialState.position = 0; partialState.type = undefined; } if (state.type === STATE_MAP_KEY || state.type === STATE_MAP_VALUE) { const partialState = state; partialState.size = 0; partialState.map = undefined; partialState.readCount = 0; partialState.type = undefined; } this.stackHeadPosition--; } reset() { this.stack.length = 0; this.stackHeadPosition = -1; } } const HEAD_BYTE_REQUIRED = -1; const EMPTY_VIEW = new DataView(new ArrayBuffer(0)); const EMPTY_BYTES = new Uint8Array(EMPTY_VIEW.buffer); try { // IE11: The spec says it should throw RangeError, // IE11: but in IE11 it throws TypeError. EMPTY_VIEW.getInt8(0); } catch (e) { if (!(e instanceof RangeError)) { throw new Error("This module is not supported in the current JavaScript engine because DataView does not throw RangeError on out-of-bounds access"); } } const MORE_DATA = new RangeError("Insufficient data"); const sharedCachedKeyDecoder = new CachedKeyDecoder(); class Decoder { constructor(options) { this.totalPos = 0; this.pos = 0; this.view = EMPTY_VIEW; this.bytes = EMPTY_BYTES; this.headByte = HEAD_BYTE_REQUIRED; this.stack = new StackPool(); this.entered = false; this.extensionCodec = options?.extensionCodec ?? ExtensionCodec.defaultCodec; this.context = options?.context; // needs a type assertion because EncoderOptions has no context property when ContextType is undefined this.useBigInt64 = options?.useBigInt64 ?? false; this.rawStrings = options?.rawStrings ?? false; this.maxStrLength = options?.maxStrLength ?? UINT32_MAX; this.maxBinLength = options?.maxBinLength ?? UINT32_MAX; this.maxArrayLength = options?.maxArrayLength ?? UINT32_MAX; this.maxMapLength = options?.maxMapLength ?? UINT32_MAX; this.maxExtLength = options?.maxExtLength ?? UINT32_MAX; this.keyDecoder = options?.keyDecoder !== undefined ? options.keyDecoder : sharedCachedKeyDecoder; this.mapKeyConverter = options?.mapKeyConverter ?? mapKeyConverter; } clone() { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return new Decoder({ extensionCodec: this.extensionCodec, context: this.context, useBigInt64: this.useBigInt64, rawStrings: this.rawStrings, maxStrLength: this.maxStrLength, maxBinLength: this.maxBinLength, maxArrayLength: this.maxArrayLength, maxMapLength: this.maxMapLength, maxExtLength: this.maxExtLength, keyDecoder: this.keyDecoder, }); } reinitializeState() { this.totalPos = 0; this.headByte = HEAD_BYTE_REQUIRED; this.stack.reset(); // view, bytes, and pos will be re-initialized in setBuffer() } setBuffer(buffer) { const bytes = ensureUint8Array(buffer); this.bytes = bytes; this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); this.pos = 0; } appendBuffer(buffer) { if (this.headByte === HEAD_BYTE_REQUIRED && !this.hasRemaining(1)) { this.setBuffer(buffer); } else { const remainingData = this.bytes.subarray(this.pos); const newData = ensureUint8Array(buffer); // concat remainingData + newData const newBuffer = new Uint8Array(remainingData.length + newData.length); newBuffer.set(remainingData); newBuffer.set(newData, remainingData.length); this.setBuffer(newBuffer); } } hasRemaining(size) { return this.view.byteLength - this.pos >= size; } createExtraByteError(posToShow) { const { view, pos } = this; return new RangeError(`Extra ${view.byteLength - pos} of ${view.byteLength} byte(s) found at buffer[${posToShow}]`); } /** * @throws {@link DecodeError} * @throws {@link RangeError} */ decode(buffer) { if (this.entered) { const instance = this.clone(); return instance.decode(buffer); } try { this.entered = true; this.reinitializeState(); this.setBuffer(buffer); const object = this.doDecodeSync(); if (this.hasRemaining(1)) { throw this.createExtraByteError(this.pos); } return object; } finally { this.entered = false; } } *decodeMulti(buffer) { if (this.entered) { const instance = this.clone(); yield* instance.decodeMulti(buffer); return; } try { this.entered = true; this.reinitializeState(); this.setBuffer(buffer); while (this.hasRemaining(1)) { yield this.doDecodeSync(); } } finally { this.entered = false; } } async decodeAsync(stream) { if (this.entered) { const instance = this.clone(); return instance.decodeAsync(stream); } try { this.entered = true; let decoded = false; let object; for await (const buffer of stream) { if (decoded) { this.entered = false; throw this.createExtraByteError(this.totalPos); } this.appendBuffer(buffer); try { object = this.doDeco