UNPKG

@eventmsg/core

Version:

EventMsgV3 TypeScript library - Core protocol implementation with transport abstraction

1,601 lines (1,587 loc) 73.9 kB
//#region rolldown:runtime var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __commonJS = (cb, mod) => function() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { key = keys[i]; if (!__hasOwnProp.call(to, key) && key !== except) { __defProp(to, key, { get: ((k) => from[k]).bind(null, key), enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } } } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); //#endregion //#region src/errors/event-msg-error.ts /** * Base error class for all EventMsg-related errors. * Provides consistent error handling with context and solutions. */ var EventMsgError = class extends Error { /** Error code for programmatic handling */ code; /** Additional context about the error */ context; /** Suggested solutions for the error */ solutions; /** Original error that caused this error */ cause; constructor(message, code, options = {}) { super(message); this.name = this.constructor.name; this.code = code; this.context = options.context; this.solutions = options.solutions || []; this.cause = options.cause; if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor); } /** * Get a detailed error message including context and solutions */ getDetailedMessage() { let details = `${this.name}: ${this.message}`; if (this.code) details += ` (${this.code})`; if (this.context && Object.keys(this.context).length > 0) { details += "\nContext:"; for (const [key, value] of Object.entries(this.context)) details += `\n ${key}: ${JSON.stringify(value)}`; } if (this.solutions.length > 0) { details += "\nSuggested solutions:"; for (const solution of this.solutions) details += `\n • ${solution}`; } if (this.cause) details += `\nCaused by: ${this.cause.message}`; return details; } /** * Convert error to JSON for logging/serialization */ toJSON() { return { name: this.name, message: this.message, code: this.code, context: this.context, solutions: this.solutions, stack: this.stack, cause: this.cause ? { name: this.cause.name, message: this.cause.message } : void 0 }; } }; //#endregion //#region src/errors/protocol-error.ts /** * Error related to EventMsgV3 protocol operations (encoding, decoding, validation) */ var ProtocolError = class extends EventMsgError { constructor(message, options = {}) { super(message, "PROTOCOL_ERROR", { ...options, solutions: options.solutions || [ "Check message format", "Verify protocol specification compliance", "Review message content" ] }); } }; /** * Message encoding failed */ var EncodingError = class extends ProtocolError { constructor(message = "Failed to encode message", options = {}) { super(message, { ...options, solutions: [ "Check event name length (≤64 bytes)", "Check event data length (≤3048 bytes)", "Verify data is JSON serializable", "Check for invalid characters" ] }); this.code = "ENCODING_ERROR"; } }; /** * Message decoding failed */ var DecodingError = class extends ProtocolError { constructor(message = "Failed to decode message", options = {}) { super(message, { ...options, solutions: [ "Check message framing (SOH, STX, US, EOT)", "Verify byte stuffing is correct", "Check for data corruption", "Ensure sender uses same protocol version" ] }); this.code = "DECODING_ERROR"; } }; /** * Message validation failed */ var ValidationError = class extends ProtocolError { constructor(message = "Message validation failed", options = {}) { super(message, { ...options, solutions: [ "Check address ranges (0-255)", "Verify message size limits", "Check required fields are present", "Validate data types" ] }); this.code = "VALIDATION_ERROR"; } }; /** * Invalid message format */ var InvalidMessageError = class extends ProtocolError { constructor(message = "Invalid message format", options = {}) { super(message, { ...options, solutions: [ "Check message starts with SOH (0x01)", "Check message ends with EOT (0x04)", "Verify minimum message length (10 bytes)", "Check header is exactly 7 bytes after unstuffing" ] }); this.code = "INVALID_MESSAGE_ERROR"; } }; //#endregion //#region src/errors/timeout-error.ts /** * Error for operations that exceed their timeout duration */ var TimeoutError = class extends EventMsgError { /** Timeout duration in milliseconds */ timeoutMs; /** Operation that timed out */ operation; constructor(message, timeoutMs, operation = "operation", options = {}) { super(message, "TIMEOUT_ERROR", { ...options, context: { ...options.context, timeoutMs, operation }, solutions: [ "Increase timeout duration", "Check network connectivity", "Verify target device is responding", "Reduce message frequency" ] }); this.timeoutMs = timeoutMs; this.operation = operation; } }; /** * Send operation timed out */ var SendTimeoutError = class extends TimeoutError { constructor(timeoutMs, options = {}) { super(`Send operation timed out after ${timeoutMs}ms`, timeoutMs, "send", { ...options, solutions: [ "Increase send timeout", "Check transport connection", "Verify target device is reachable", "Check for network congestion" ] }); this.code = "SEND_TIMEOUT_ERROR"; } }; /** * WaitFor operation timed out */ var WaitForTimeoutError = class extends TimeoutError { /** Event name that was being waited for */ eventName; constructor(eventName, timeoutMs, options = {}) { super(`waitFor('${eventName}') timed out after ${timeoutMs}ms`, timeoutMs, "waitFor", { ...options, context: { ...options.context, eventName }, solutions: [ "Increase waitFor timeout", "Check if event name is correct", "Verify sender is responding", "Check event filter conditions" ] }); this.eventName = eventName; this.code = "WAIT_FOR_TIMEOUT_ERROR"; } }; /** * Connection timeout error */ var ConnectionTimeoutError = class extends TimeoutError { constructor(timeoutMs, options = {}) { super(`Connection attempt timed out after ${timeoutMs}ms`, timeoutMs, "connect", { ...options, solutions: [ "Increase connection timeout", "Check device availability", "Verify connection parameters", "Check for interference" ] }); this.code = "CONNECTION_TIMEOUT_ERROR"; } }; //#endregion //#region src/errors/transport-error.ts /** * Error related to transport layer operations (connection, sending, receiving) */ var TransportError = class extends EventMsgError { constructor(message, options = {}) { super(message, "TRANSPORT_ERROR", { ...options, solutions: options.solutions || [ "Check transport connection", "Verify transport configuration", "Retry the operation" ] }); } }; /** * Transport connection failed */ var ConnectionError = class extends TransportError { constructor(message = "Failed to connect to transport", options = {}) { super(message, { ...options, solutions: [ "Check if the device is available", "Verify connection parameters", "Ensure no other process is using the transport", "Check device permissions" ] }); this.code = "CONNECTION_ERROR"; } }; /** * Transport send operation failed */ var SendError = class extends TransportError { constructor(message = "Failed to send data through transport", options = {}) { super(message, { ...options, solutions: [ "Check transport connection status", "Verify message size is within limits", "Retry sending the message", "Check for transport buffer overflow" ] }); this.code = "SEND_ERROR"; } }; /** * Transport disconnection error */ var DisconnectionError = class extends TransportError { constructor(message = "Transport disconnected unexpectedly", options = {}) { super(message, { ...options, solutions: [ "Check physical connection", "Implement reconnection logic", "Monitor connection status", "Handle graceful disconnection" ] }); this.code = "DISCONNECTION_ERROR"; } }; //#endregion //#region src/errors/validation-error.ts /** * Error for input validation failures */ var ValidationError$1 = class extends EventMsgError { /** Field that failed validation */ field; /** Value that was invalid */ value; constructor(message, options = {}) { super(message, "VALIDATION_ERROR", { ...options, context: { ...options.context, field: options.field, value: options.value }, solutions: [ "Check input parameters", "Verify value ranges and types", "Review API documentation" ] }); this.field = options.field; this.value = options.value; } }; /** * Address validation error (0-255 range) */ var AddressValidationError = class extends ValidationError$1 { constructor(address, field = "address", options = {}) { super(`Invalid ${field}: ${address}. Must be 0-255`, { ...options, field, value: address, solutions: [ "Use address in range 0-255", "Check for negative values", "Verify address is an integer" ] }); this.code = "ADDRESS_VALIDATION_ERROR"; } }; /** * Message size validation error */ var MessageSizeError = class extends ValidationError$1 { constructor(size, maxSize, field = "message", options = {}) { super(`${field} size ${size} bytes exceeds maximum ${maxSize} bytes`, { ...options, field, value: size, context: { ...options.context, maxSize }, solutions: [ `Reduce ${field} size to ${maxSize} bytes or less`, "Consider splitting large messages", "Use more efficient encoding" ] }); this.code = "MESSAGE_SIZE_ERROR"; } }; //#endregion //#region ../../node_modules/.bun/eventemitter3@5.0.1/node_modules/eventemitter3/index.js var require_eventemitter3 = /* @__PURE__ */ __commonJS({ "../../node_modules/.bun/eventemitter3@5.0.1/node_modules/eventemitter3/index.js": ((exports, 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() {} if (Object.create) { Events.prototype = Object.create(null); 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$3() { this._events = new Events(); this._eventsCount = 0; } /** * Return an array listing the events for which the emitter has registered * listeners. * * @returns {Array} * @public */ EventEmitter$3.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$3.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$3.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$3.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, void 0, 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, void 0, 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$3.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$3.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$3.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]); 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$3.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; }; EventEmitter$3.prototype.off = EventEmitter$3.prototype.removeListener; EventEmitter$3.prototype.addListener = EventEmitter$3.prototype.on; EventEmitter$3.prefixed = prefix; EventEmitter$3.EventEmitter = EventEmitter$3; if ("undefined" !== typeof module) module.exports = EventEmitter$3; }) }); //#endregion //#region ../../node_modules/.bun/eventemitter3@5.0.1/node_modules/eventemitter3/index.mjs var import_eventemitter3 = /* @__PURE__ */ __toESM(require_eventemitter3(), 1); //#endregion //#region ../../node_modules/.bun/consola@3.4.2/node_modules/consola/dist/core.mjs const LogLevels = { silent: Number.NEGATIVE_INFINITY, fatal: 0, error: 0, warn: 1, log: 2, info: 3, success: 3, fail: 3, ready: 3, start: 3, box: 3, debug: 4, trace: 5, verbose: Number.POSITIVE_INFINITY }; const LogTypes = { silent: { level: -1 }, fatal: { level: LogLevels.fatal }, error: { level: LogLevels.error }, warn: { level: LogLevels.warn }, log: { level: LogLevels.log }, info: { level: LogLevels.info }, success: { level: LogLevels.success }, fail: { level: LogLevels.fail }, ready: { level: LogLevels.info }, start: { level: LogLevels.info }, box: { level: LogLevels.info }, debug: { level: LogLevels.debug }, trace: { level: LogLevels.trace }, verbose: { level: LogLevels.verbose } }; function isPlainObject$1(value) { if (value === null || typeof value !== "object") return false; const prototype = Object.getPrototypeOf(value); if (prototype !== null && prototype !== Object.prototype && Object.getPrototypeOf(prototype) !== null) return false; if (Symbol.iterator in value) return false; if (Symbol.toStringTag in value) return Object.prototype.toString.call(value) === "[object Module]"; return true; } function _defu(baseObject, defaults, namespace = ".", merger) { if (!isPlainObject$1(defaults)) return _defu(baseObject, {}, namespace, merger); const object = Object.assign({}, defaults); for (const key in baseObject) { if (key === "__proto__" || key === "constructor") continue; const value = baseObject[key]; if (value === null || value === void 0) continue; if (merger && merger(object, key, value, namespace)) continue; if (Array.isArray(value) && Array.isArray(object[key])) object[key] = [...value, ...object[key]]; else if (isPlainObject$1(value) && isPlainObject$1(object[key])) object[key] = _defu(value, object[key], (namespace ? `${namespace}.` : "") + key.toString(), merger); else object[key] = value; } return object; } function createDefu(merger) { return (...arguments_) => arguments_.reduce((p, c) => _defu(p, c, "", merger), {}); } const defu = createDefu(); function isPlainObject(obj) { return Object.prototype.toString.call(obj) === "[object Object]"; } function isLogObj(arg) { if (!isPlainObject(arg)) return false; if (!arg.message && !arg.args) return false; if (arg.stack) return false; return true; } let paused = false; const queue = []; var Consola = class Consola { options; _lastLog; _mockFn; /** * Creates an instance of Consola with specified options or defaults. * * @param {Partial<ConsolaOptions>} [options={}] - Configuration options for the Consola instance. */ constructor(options = {}) { const types = options.types || LogTypes; this.options = defu({ ...options, defaults: { ...options.defaults }, level: _normalizeLogLevel(options.level, types), reporters: [...options.reporters || []] }, { types: LogTypes, throttle: 1e3, throttleMin: 5, formatOptions: { date: true, colors: false, compact: true } }); for (const type in types) { const defaults = { type, ...this.options.defaults, ...types[type] }; this[type] = this._wrapLogFn(defaults); this[type].raw = this._wrapLogFn(defaults, true); } if (this.options.mockFn) this.mockTypes(); this._lastLog = {}; } /** * Gets the current log level of the Consola instance. * * @returns {number} The current log level. */ get level() { return this.options.level; } /** * Sets the minimum log level that will be output by the instance. * * @param {number} level - The new log level to set. */ set level(level) { this.options.level = _normalizeLogLevel(level, this.options.types, this.options.level); } /** * Displays a prompt to the user and returns the response. * Throw an error if `prompt` is not supported by the current configuration. * * @template T * @param {string} message - The message to display in the prompt. * @param {T} [opts] - Optional options for the prompt. See {@link PromptOptions}. * @returns {promise<T>} A promise that infer with the prompt options. See {@link PromptOptions}. */ prompt(message, opts) { if (!this.options.prompt) throw new Error("prompt is not supported!"); return this.options.prompt(message, opts); } /** * Creates a new instance of Consola, inheriting options from the current instance, with possible overrides. * * @param {Partial<ConsolaOptions>} options - Optional overrides for the new instance. See {@link ConsolaOptions}. * @returns {ConsolaInstance} A new Consola instance. See {@link ConsolaInstance}. */ create(options) { const instance = new Consola({ ...this.options, ...options }); if (this._mockFn) instance.mockTypes(this._mockFn); return instance; } /** * Creates a new Consola instance with the specified default log object properties. * * @param {InputLogObject} defaults - Default properties to include in any log from the new instance. See {@link InputLogObject}. * @returns {ConsolaInstance} A new Consola instance. See {@link ConsolaInstance}. */ withDefaults(defaults) { return this.create({ ...this.options, defaults: { ...this.options.defaults, ...defaults } }); } /** * Creates a new Consola instance with a specified tag, which will be included in every log. * * @param {string} tag - The tag to include in each log of the new instance. * @returns {ConsolaInstance} A new Consola instance. See {@link ConsolaInstance}. */ withTag(tag) { return this.withDefaults({ tag: this.options.defaults.tag ? this.options.defaults.tag + ":" + tag : tag }); } /** * Adds a custom reporter to the Consola instance. * Reporters will be called for each log message, depending on their implementation and log level. * * @param {ConsolaReporter} reporter - The reporter to add. See {@link ConsolaReporter}. * @returns {Consola} The current Consola instance. */ addReporter(reporter) { this.options.reporters.push(reporter); return this; } /** * Removes a custom reporter from the Consola instance. * If no reporter is specified, all reporters will be removed. * * @param {ConsolaReporter} reporter - The reporter to remove. See {@link ConsolaReporter}. * @returns {Consola} The current Consola instance. */ removeReporter(reporter) { if (reporter) { const i = this.options.reporters.indexOf(reporter); if (i !== -1) return this.options.reporters.splice(i, 1); } else this.options.reporters.splice(0); return this; } /** * Replaces all reporters of the Consola instance with the specified array of reporters. * * @param {ConsolaReporter[]} reporters - The new reporters to set. See {@link ConsolaReporter}. * @returns {Consola} The current Consola instance. */ setReporters(reporters) { this.options.reporters = Array.isArray(reporters) ? reporters : [reporters]; return this; } wrapAll() { this.wrapConsole(); this.wrapStd(); } restoreAll() { this.restoreConsole(); this.restoreStd(); } /** * Overrides console methods with Consola logging methods for consistent logging. */ wrapConsole() { for (const type in this.options.types) { if (!console["__" + type]) console["__" + type] = console[type]; console[type] = this[type].raw; } } /** * Restores the original console methods, removing Consola overrides. */ restoreConsole() { for (const type in this.options.types) if (console["__" + type]) { console[type] = console["__" + type]; delete console["__" + type]; } } /** * Overrides standard output and error streams to redirect them through Consola. */ wrapStd() { this._wrapStream(this.options.stdout, "log"); this._wrapStream(this.options.stderr, "log"); } _wrapStream(stream, type) { if (!stream) return; if (!stream.__write) stream.__write = stream.write; stream.write = (data) => { this[type].raw(String(data).trim()); }; } /** * Restores the original standard output and error streams, removing the Consola redirection. */ restoreStd() { this._restoreStream(this.options.stdout); this._restoreStream(this.options.stderr); } _restoreStream(stream) { if (!stream) return; if (stream.__write) { stream.write = stream.__write; delete stream.__write; } } /** * Pauses logging, queues incoming logs until resumed. */ pauseLogs() { paused = true; } /** * Resumes logging, processing any queued logs. */ resumeLogs() { paused = false; const _queue = queue.splice(0); for (const item of _queue) item[0]._logFn(item[1], item[2]); } /** * Replaces logging methods with mocks if a mock function is provided. * * @param {ConsolaOptions["mockFn"]} mockFn - The function to use for mocking logging methods. See {@link ConsolaOptions["mockFn"]}. */ mockTypes(mockFn) { const _mockFn = mockFn || this.options.mockFn; this._mockFn = _mockFn; if (typeof _mockFn !== "function") return; for (const type in this.options.types) { this[type] = _mockFn(type, this.options.types[type]) || this[type]; this[type].raw = this[type]; } } _wrapLogFn(defaults, isRaw) { return (...args) => { if (paused) { queue.push([ this, defaults, args, isRaw ]); return; } return this._logFn(defaults, args, isRaw); }; } _logFn(defaults, args, isRaw) { if ((defaults.level || 0) > this.level) return false; const logObj = { date: /* @__PURE__ */ new Date(), args: [], ...defaults, level: _normalizeLogLevel(defaults.level, this.options.types) }; if (!isRaw && args.length === 1 && isLogObj(args[0])) Object.assign(logObj, args[0]); else logObj.args = [...args]; if (logObj.message) { logObj.args.unshift(logObj.message); delete logObj.message; } if (logObj.additional) { if (!Array.isArray(logObj.additional)) logObj.additional = logObj.additional.split("\n"); logObj.args.push("\n" + logObj.additional.join("\n")); delete logObj.additional; } logObj.type = typeof logObj.type === "string" ? logObj.type.toLowerCase() : "log"; logObj.tag = typeof logObj.tag === "string" ? logObj.tag : ""; const resolveLog = (newLog = false) => { const repeated = (this._lastLog.count || 0) - this.options.throttleMin; if (this._lastLog.object && repeated > 0) { const args2 = [...this._lastLog.object.args]; if (repeated > 1) args2.push(`(repeated ${repeated} times)`); this._log({ ...this._lastLog.object, args: args2 }); this._lastLog.count = 1; } if (newLog) { this._lastLog.object = logObj; this._log(logObj); } }; clearTimeout(this._lastLog.timeout); const diffTime = this._lastLog.time && logObj.date ? logObj.date.getTime() - this._lastLog.time.getTime() : 0; this._lastLog.time = logObj.date; if (diffTime < this.options.throttle) try { const serializedLog = JSON.stringify([ logObj.type, logObj.tag, logObj.args ]); const isSameLog = this._lastLog.serialized === serializedLog; this._lastLog.serialized = serializedLog; if (isSameLog) { this._lastLog.count = (this._lastLog.count || 0) + 1; if (this._lastLog.count > this.options.throttleMin) { this._lastLog.timeout = setTimeout(resolveLog, this.options.throttle); return; } } } catch {} resolveLog(true); } _log(logObj) { for (const reporter of this.options.reporters) reporter.log(logObj, { options: this.options }); } }; function _normalizeLogLevel(input, types = {}, defaultLevel = 3) { if (input === void 0) return defaultLevel; if (typeof input === "number") return input; if (types[input] && types[input].level !== void 0) return types[input].level; return defaultLevel; } Consola.prototype.add = Consola.prototype.addReporter; Consola.prototype.remove = Consola.prototype.removeReporter; Consola.prototype.clear = Consola.prototype.removeReporter; Consola.prototype.withScope = Consola.prototype.withTag; Consola.prototype.mock = Consola.prototype.mockTypes; Consola.prototype.pause = Consola.prototype.pauseLogs; Consola.prototype.resume = Consola.prototype.resumeLogs; function createConsola$1(options = {}) { return new Consola(options); } //#endregion //#region ../../node_modules/.bun/consola@3.4.2/node_modules/consola/dist/browser.mjs var BrowserReporter = class { options; defaultColor; levelColorMap; typeColorMap; constructor(options) { this.options = { ...options }; this.defaultColor = "#7f8c8d"; this.levelColorMap = { 0: "#c0392b", 1: "#f39c12", 3: "#00BCD4" }; this.typeColorMap = { success: "#2ecc71" }; } _getLogFn(level) { if (level < 1) return console.__error || console.error; if (level === 1) return console.__warn || console.warn; return console.__log || console.log; } log(logObj) { const consoleLogFn = this._getLogFn(logObj.level); const type = logObj.type === "log" ? "" : logObj.type; const tag = logObj.tag || ""; const style = ` background: ${this.typeColorMap[logObj.type] || this.levelColorMap[logObj.level] || this.defaultColor}; border-radius: 0.5em; color: white; font-weight: bold; padding: 2px 0.5em; `; const badge = `%c${[tag, type].filter(Boolean).join(":")}`; if (typeof logObj.args[0] === "string") consoleLogFn(`${badge}%c ${logObj.args[0]}`, style, "", ...logObj.args.slice(1)); else consoleLogFn(badge, style, ...logObj.args); } }; function createConsola(options = {}) { return createConsola$1({ reporters: options.reporters || [new BrowserReporter({})], prompt(message, options2 = {}) { if (options2.type === "confirm") return Promise.resolve(confirm(message)); return Promise.resolve(prompt(message)); }, ...options }); } const consola = createConsola(); //#endregion //#region src/types/logger.ts /** * Default logging configuration */ const DEFAULT_LOGGING_CONFIG = { enabled: true, level: 3 }; /** * Log namespaces for tagging */ const LOG_NAMESPACES = { CORE: "eventmsg:core", PROTOCOL: "eventmsg:protocol", TRANSPORT: "eventmsg:transport", TRANSPORT_WEBBLE: "eventmsg:transport:webble" }; //#endregion //#region src/internal/logger.ts let globalConsola = createConsola({ level: DEFAULT_LOGGING_CONFIG.level ?? 3 }); /** * Configure global logging */ function configureLogging(config) { const finalConfig = { ...DEFAULT_LOGGING_CONFIG, ...config }; if (finalConfig.enabled === false) globalConsola = createConsola({ level: -999, reporters: [] }); else if (finalConfig.enabled && finalConfig.level !== -999) globalConsola = createConsola({ level: finalConfig.level ?? 3 }); else globalConsola = createConsola({ level: -999, reporters: [] }); } /** * Get logger for a component */ function getLogger(namespace) { return globalConsola.withTag(LOG_NAMESPACES[namespace]); } /** * Simple hex dump for debugging */ function hexDump(data, maxBytes = 64) { const limited = data.length > maxBytes ? data.slice(0, maxBytes) : data; const hex = Array.from(limited).map((b) => b.toString(16).padStart(2, "0")).join(" "); if (data.length > maxBytes) return `${hex} ... (${data.length - maxBytes} more bytes)`; return hex; } //#endregion //#region src/internal/byte-stuffing.ts /** * EventMsgV3 Byte Stuffing Implementation * * Implements the exact byte stuffing algorithm from the C implementation * to ensure protocol compatibility. */ /** * Control characters used in EventMsgV3 protocol */ const CONTROL_CHARS = { SOH: 1, STX: 2, EOT: 4, US: 31, ESC: 27 }; /** * XOR mask for byte stuffing */ const STUFF_MASK = 32; /** * Check if a byte is a control character that needs stuffing */ function isControlChar(byte) { return byte === CONTROL_CHARS.SOH || byte === CONTROL_CHARS.STX || byte === CONTROL_CHARS.EOT || byte === CONTROL_CHARS.US || byte === CONTROL_CHARS.ESC; } /** * Stuff (encode) bytes by escaping control characters * * Algorithm: * - If byte is a control character: Insert ESC, then XOR byte with 0x20 * - Otherwise: Insert byte as-is * * @param data Input data to stuff * @returns Stuffed data */ function stuff(data) { const result = []; for (let i = 0; i < data.length; i++) { const byte = data[i]; if (byte === void 0) continue; if (isControlChar(byte)) { result.push(CONTROL_CHARS.ESC); result.push(byte ^ STUFF_MASK); } else result.push(byte); } return new Uint8Array(result); } /** * Unstuff (decode) bytes by handling escaped characters * * Algorithm: * - If byte is ESC: Mark next byte as escaped, continue * - If previous byte was ESC: XOR current byte with 0x20, add to output * - Otherwise: Add byte as-is to output * * @param data Stuffed data to unstuff * @returns Unstuffed data * @throws {Error} If data contains invalid escape sequences */ function unstuff(data) { const result = []; let escaped = false; for (let i = 0; i < data.length; i++) { const byte = data[i]; if (byte === void 0) continue; if (escaped) { result.push(byte ^ STUFF_MASK); escaped = false; } else if (byte === CONTROL_CHARS.ESC) escaped = true; else result.push(byte); } if (escaped) throw new Error("Invalid byte stuffing: data ends with incomplete escape sequence"); return new Uint8Array(result); } /** * Calculate the maximum possible size after stuffing * In worst case, every byte could be a control character, doubling the size * * @param originalSize Original data size * @returns Maximum size after stuffing */ function getMaxStuffedSize(originalSize) { return originalSize * 2; } /** * Calculate the minimum possible size after unstuffing * In best case, no bytes are stuffed * * @param stuffedSize Stuffed data size * @returns Minimum size after unstuffing */ function getMinUnstuffedSize(stuffedSize) { return Math.floor(stuffedSize / 2); } /** * Test if data contains any control characters that would need stuffing * * @param data Data to test * @returns True if data contains control characters */ function needsStuffing(data) { for (let i = 0; i < data.length; i++) { const byte = data[i]; if (byte !== void 0 && isControlChar(byte)) return true; } return false; } /** * Validate that stuffed data has proper escape sequences * * @param data Stuffed data to validate * @returns True if data has valid stuffing */ function isValidStuffing(data) { let escaped = false; for (let i = 0; i < data.length; i++) { const byte = data[i]; if (byte === void 0) continue; if (escaped) { if (!isControlChar(byte ^ STUFF_MASK)) return false; escaped = false; } else if (byte === CONTROL_CHARS.ESC) escaped = true; else if (isControlChar(byte)) return false; } return !escaped; } //#endregion //#region src/types/config.ts /** * Default protocol options */ const DEFAULT_PROTOCOL_OPTIONS = { strictValidation: true, maxEventNameLength: 64, maxEventDataLength: 3048 }; /** * Default configuration values */ const DEFAULT_CONFIG = { maxMessageSize: 4096, messageTimeout: 5e3, encoding: "utf8", logging: DEFAULT_LOGGING_CONFIG, protocol: DEFAULT_PROTOCOL_OPTIONS }; //#endregion //#region src/protocol.ts /** * EventMsgV3 Protocol Implementation * * Handles encoding and decoding of EventMsgV3 binary protocol messages. * Message format: [SOH][StuffedHeader][STX][StuffedEventName][US][StuffedEventData][EOT] */ var Protocol = class { options; encoding; logger; constructor(config) { this.options = { ...DEFAULT_PROTOCOL_OPTIONS, ...config.protocol }; this.encoding = config.encoding ?? "utf8"; this.logger = getLogger("PROTOCOL"); } /** * Encode a message into EventMsgV3 binary format * * @param header Message header (7 bytes) * @param eventName Event name string * @param eventData Event data string (JSON) * @returns Encoded binary message * @throws {EncodingError} If encoding fails * @throws {MessageSizeError} If message exceeds size limits */ encode(header, eventName, eventData) { this.logger.debug("Starting message encode", { eventName, eventDataLength: eventData.length, header, encoding: this.encoding }); try { this.validateHeader(header); this.validateEventName(eventName); this.validateEventData(eventData); const eventNameBytes = this.stringToBytes(eventName); const eventDataBytes = this.stringToBytes(eventData); const headerBytes = this.serializeHeader(header); const stuffedHeader = stuff(headerBytes); const stuffedEventName = stuff(eventNameBytes); const stuffedEventData = stuff(eventDataBytes); this.logger.trace("Byte stuffing complete", { headerSize: `${headerBytes.length} -> ${stuffedHeader.length}`, nameSize: `${eventNameBytes.length} -> ${stuffedEventName.length}`, dataSize: `${eventDataBytes.length} -> ${stuffedEventData.length}` }); const totalSize = 1 + stuffedHeader.length + 1 + stuffedEventName.length + 1 + stuffedEventData.length + 1; if (totalSize > this.options.maxEventDataLength + 1e3) throw new MessageSizeError(totalSize, this.options.maxEventDataLength + 1e3, "encoded message"); const message = new Uint8Array(totalSize); let offset = 0; message[offset++] = CONTROL_CHARS.SOH; message.set(stuffedHeader, offset); offset += stuffedHeader.length; message[offset++] = CONTROL_CHARS.STX; message.set(stuffedEventName, offset); offset += stuffedEventName.length; message[offset++] = CONTROL_CHARS.US; message.set(stuffedEventData, offset); offset += stuffedEventData.length; message[offset++] = CONTROL_CHARS.EOT; const finalMessage = message.subarray(0, offset); this.logger.info("Message encoded successfully", { eventName, finalSize: finalMessage.length, messageId: header.messageId }); this.logger.trace("Encoded message hex dump", { hex: hexDump(finalMessage) }); return finalMessage; } catch (error) { this.logger.error("Message encoding failed", { error: error instanceof Error ? error.message : String(error), eventName, eventDataLength: eventData.length, cause: error instanceof Error ? error.name : "Unknown" }); if (error instanceof MessageSizeError) throw error; throw new EncodingError("Failed to encode EventMsgV3 message", { context: { eventName, eventDataLength: eventData.length }, cause: error instanceof Error ? error : new Error(String(error)) }); } } /** * Encode a message with binary data into EventMsgV3 format * * @param header Message header (7 bytes) * @param eventName Event name string * @param eventData Binary event data * @returns Encoded binary message * @throws {EncodingError} If encoding fails * @throws {MessageSizeError} If message exceeds size limits */ encodeBinary(header, eventName, eventData) { this.logger.debug("Starting binary message encode", { eventName, eventDataLength: eventData.length, header }); try { this.validateHeader(header); this.validateEventName(eventName); if (eventData.length > this.options.maxEventDataLength) throw new MessageSizeError(eventData.length, this.options.maxEventDataLength, "eventData"); const eventNameBytes = this.stringToBytes(eventName); const headerBytes = this.serializeHeader(header); const stuffedHeader = stuff(headerBytes); const stuffedEventName = stuff(eventNameBytes); const stuffedEventData = stuff(eventData); this.logger.trace("Binary byte stuffing complete", { headerSize: `${headerBytes.length} -> ${stuffedHeader.length}`, nameSize: `${eventNameBytes.length} -> ${stuffedEventName.length}`, dataSize: `${eventData.length} -> ${stuffedEventData.length}` }); const totalSize = 1 + stuffedHeader.length + 1 + stuffedEventName.length + 1 + stuffedEventData.length + 1; if (totalSize > this.options.maxEventDataLength + 1e3) throw new MessageSizeError(totalSize, this.options.maxEventDataLength + 1e3, "encoded message"); const message = new Uint8Array(totalSize); let offset = 0; message[offset++] = CONTROL_CHARS.SOH; message.set(stuffedHeader, offset); offset += stuffedHeader.length; message[offset++] = CONTROL_CHARS.STX; message.set(stuffedEventName, offset); offset += stuffedEventName.length; message[offset++] = CONTROL_CHARS.US; message.set(stuffedEventData, offset); offset += stuffedEventData.length; message[offset++] = CONTROL_CHARS.EOT; const finalMessage = message.subarray(0, offset); this.logger.info("Binary message encoded successfully", { eventName, finalSize: finalMessage.length, messageId: header.messageId }); this.logger.trace("Encoded binary message hex dump", { hex: hexDump(finalMessage) }); return finalMessage; } catch (error) { this.logger.error("Binary message encoding failed", { error: error instanceof Error ? error.message : String(error), eventName, eventDataLength: eventData.length, cause: error instanceof Error ? error.name : "Unknown" }); if (error instanceof MessageSizeError) throw error; throw new EncodingError("Failed to encode EventMsgV3 binary message", { context: { eventName, eventDataLength: eventData.length }, cause: error instanceof Error ? error : new Error(String(error)) }); } } /** * Decode a binary message into EventMsgV3 components * * @param data Binary message data * @returns Decoded message components * @throws {DecodingError} If decoding fails * @throws {InvalidMessageError} If message format is invalid */ decode(data) { this.logger.debug("Starting message decode", { messageLength: data.length, encoding: this.encoding }); this.logger.trace("Incoming message hex dump", { hex: hexDump(data) }); try { if (data.length < 10) throw new InvalidMessageError(`Message too short: ${data.length} bytes (minimum 10)`, { context: { messageLength: data.length } }); if (data[0] !== CONTROL_CHARS.SOH) throw new InvalidMessageError(`Message must start with SOH (0x01), got 0x${data[0]?.toString(16).padStart(2, "0")}`, { context: { firstByte: data[0] } }); if (data.at(-1) !== CONTROL_CHARS.EOT) throw new InvalidMessageError(`Message must end with EOT (0x04), got 0x${data.at(-1)?.toString(16).padStart(2, "0")}`, { context: { lastByte: data.at(-1) } }); const stxIndex = this.findControlChar(data, CONTROL_CHARS.STX, 1); const usIndex = this.findControlChar(data, CONTROL_CHARS.US, stxIndex + 1); if (stxIndex === -1) throw new InvalidMessageError("STX (0x02) not found in message"); if (usIndex === -1) throw new InvalidMessageError("US (0x1F) not found in message"); const stuffedHeader = data.subarray(1, stxIndex); const stuffedEventName = data.subarray(stxIndex + 1, usIndex); const stuffedEventData = data.subarray(usIndex + 1, data.length - 1); const headerBytes = unstuff(stuffedHeader); const eventNameBytes = unstuff(stuffedEventName); const eventDataBytes = unstuff(stuffedEventData); this.logger.trace("Byte unstuffing complete", { headerSize: `${stuffedHeader.length} -> ${headerBytes.length}`, nameSize: `${stuffedEventName.length} -> ${eventNameBytes.length}`, dataSize: `${stuffedEventData.length} -> ${eventDataBytes.length}` }); const header = this.deserializeHeader(headerBytes); const eventName = this.bytesToString(eventNameBytes); const eventData = this.bytesToString(eventDataBytes); if (this.options.strictValidation) { this.validateHeader(header); this.validateEventName(eventName); this.validateEventData(eventData); } this.logger.info("Message decoded successfully", { eventName, messageId: header.messageId, senderId: header.senderId, receiverId: header.receiverId, eventDataLength: eventData.length }); return { header, eventName, eventData }; } catch (error) { this.logger.error("Message decoding failed", { error: error instanceof Error ? error.message : String(error), messageLength: data.length, cause: error instanceof Error ? error.name : "Unknown", hex: hexDump(data, 64) }); if (error instanceof InvalidMessageError) throw error; throw new DecodingError("Failed to decode EventMsgV3 message", { context: { messageLength: data.length }, cause: error instanceof Error ? error : new Error(String(error)) }); } } /** * Find a control character in data, skipping escaped instances */ findControlChar(data, target, startIndex) { let escaped = false; for (let i = startIndex; i < data.length; i++) { const byte = data[i]; if (byte === void 0) continue; if (escaped) escaped = false; else if (byte === CONTROL_CHARS.ESC) escaped = true; else if (byte === target) return i; } return -1; } /** * Serialize message header to 7 bytes (big-endian messageId) */ serializeHeader(header) { const bytes = new Uint8Array(7); bytes[0] = header.senderId; bytes[1] = header.receiverId; bytes[2] = header.senderGroupId; bytes[3] = header.receiverGroupId; bytes[4] = header.flags; bytes[5] = header.messageId >> 8 & 255; bytes[6] = header.messageId & 255; return bytes; } /** * Deserialize 7 bytes to message header (big-endian messageId) */ deserializeHeader(bytes) { if (bytes.length !== 7) throw new InvalidMessageError(`Header must be exactly 7 bytes, got ${bytes.length}`, { context: { headerLength: bytes.length } }); if (bytes[0] === void 0 || bytes[1] === void 0 || bytes[2] === void 0 || bytes[3] === void 0 || bytes[4] === void 0 || bytes[5] === void 0 || bytes[6] === void 0) throw new InvalidMessageError(`Header must be exactly 7 bytes, got ${bytes.length}`, { context: { headerLength: bytes.length } }); return { senderId: bytes[0], receiverId: bytes[1], senderGroupId: bytes[2], receiverGroupId: bytes[3], flags: bytes[4], messageId: bytes[5] << 8 | bytes[6] }; } /** * Convert string to bytes using configured encoding */ stringToBytes(str) { if (this.encoding === "utf8") return new TextEncoder().encode(str); const bytes = new Uint8Array(str.length); for (let i = 0; i < str.length; i++) bytes[i] = str.charCodeAt(i) & 255; return bytes; } /** * Convert bytes to string using configured encoding */ bytesToString(bytes) { if (this.encoding === "utf8") return new TextDecoder("utf8").decode(bytes); return String.fromCharCode(...bytes); } /** * Validate message header fields */ validateHeader(header) { const fields = [ { name: "senderId", value: header.senderId }, { name: "receiverId", value: header.receiverId }, { name: "senderGroupId", value: header.senderGroupId }, { name: "receiverGroupId", value: header.receiverGroupId }, { name: "flags", value: header.flags } ]; for (const field of fields) if (field.value < 0 || field.value > 255 || !Number.isInteger(field.value)) throw new MessageSizeError(field.value, 255, field.name, { context: { validRange: "0-255" } }); if (header.messageId < 0 || header.messageId > 65535 || !Number.isInteger(header.messageId)) throw new MessageSizeError(header.messageId, 65535, "messageId", { context: { validRange: "0-65535" } }); } /** * Validate event name leng