feat: Initial commit

This commit is contained in:
Pascal Bourque
2025-05-25 11:03:21 -04:00
commit ff0163043a
50 changed files with 15156 additions and 0 deletions

106
src/lib/EventEmitter.ts Normal file
View File

@@ -0,0 +1,106 @@
import { EventEmitter as NodeEventEmitter } from 'node:events';
/**
* Typed wrapper around Node's `EventEmitter` class.
*
* @remarks
* Source: {@link https://blog.makerx.com.au/a-type-safe-event-emitter-in-node-js}
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
export class EventEmitter<TEvents extends Record<string, any>> implements NodeJS.EventEmitter {
private _emitter = new NodeEventEmitter();
emit<TEventName extends keyof TEvents & string>(eventName: TEventName, ...eventArg: TEvents[TEventName]) {
return this._emitter.emit(eventName, ...(eventArg as []));
}
on<TEventName extends keyof TEvents & string>(
eventName: TEventName,
handler: (...eventArg: TEvents[TEventName]) => void
) {
this._emitter.on(eventName, handler as any);
return this;
}
once<TEventName extends keyof TEvents & string>(
eventName: TEventName,
handler: (...eventArg: TEvents[TEventName]) => void
) {
this._emitter.once(eventName, handler as any);
return this;
}
off<TEventName extends keyof TEvents & string>(
eventName: TEventName,
handler: (...eventArg: TEvents[TEventName]) => void
) {
this._emitter.off(eventName, handler as any);
return this;
}
addListener<TEventName extends keyof TEvents & string>(
eventName: TEventName,
listener: (...args: TEvents[TEventName]) => void
) {
this._emitter.addListener(eventName, listener);
return this;
}
removeListener<TEventName extends keyof TEvents & string>(
eventName: TEventName,
listener: (...args: TEvents[TEventName]) => void
) {
this._emitter.removeListener(eventName, listener);
return this;
}
removeAllListeners<TEventName extends keyof TEvents & string>(eventName?: TEventName | undefined) {
this._emitter.removeAllListeners(eventName);
return this;
}
setMaxListeners(n: number) {
this._emitter.setMaxListeners(n);
return this;
}
getMaxListeners(): number {
return this._emitter.getMaxListeners();
}
listeners<TEventName extends keyof TEvents & string>(eventName: TEventName) {
return this._emitter.listeners(eventName);
}
rawListeners<TEventName extends keyof TEvents & string>(eventName: TEventName) {
return this._emitter.rawListeners(eventName);
}
listenerCount<TEventName extends keyof TEvents & string>(
eventName: TEventName,
listener?: (...args: TEvents[TEventName]) => void
) {
return this._emitter.listenerCount(eventName, listener);
}
prependListener<TEventName extends keyof TEvents & string>(
eventName: TEventName,
listener: (...args: TEvents[TEventName]) => void
) {
this._emitter.prependListener(eventName, listener);
return this;
}
prependOnceListener<TEventName extends keyof TEvents & string>(
eventName: TEventName,
listener: (...args: TEvents[TEventName]) => void
) {
this._emitter.prependOnceListener(eventName, listener);
return this;
}
eventNames() {
return this._emitter.eventNames();
}
}
/* eslint-enable @typescript-eslint/no-explicit-any */

39
src/lib/PayloadParser.ts Normal file
View File

@@ -0,0 +1,39 @@
import { InPayload } from '@/types/mqtt/InPayload';
import { OutPayload } from '@/types/mqtt/OutPayload';
/**
* Parses an MQTT payload from binary data into a typed OutPayload object.
*
* Converts the raw ArrayBuffer received from MQTT messages into a structured TypeScript object representing device
* status, state changes, or other outgoing message types from Mysa devices.
*
* @param payload - The raw binary MQTT message payload as ArrayBuffer
* @returns The parsed payload as a typed OutPayload object
* @throws Error if the payload cannot be decoded or parsed as valid JSON
*/
export function parseMqttPayload(payload: ArrayBuffer): OutPayload {
try {
const decoder = new TextDecoder('utf-8');
const jsonString = decoder.decode(payload);
return JSON.parse(jsonString);
} catch (error) {
console.error('Error parsing MQTT payload:', error);
throw new Error('Failed to parse MQTT payload');
}
}
/**
* Serializes an InPayload object into binary data for MQTT transmission.
*
* Converts a typed TypeScript payload object into the binary ArrayBuffer format required for sending commands and
* requests to Mysa devices via MQTT.
*
* @typeParam T - The specific InPayload type being serialized
* @param payload - The typed payload object to serialize
* @returns The serialized payload as ArrayBuffer ready for MQTT transmission
*/
export function serializeMqttPayload<T extends InPayload>(payload: T): ArrayBuffer {
const jsonString = JSON.stringify(payload);
const encoder = new TextEncoder();
return encoder.encode(jsonString);
}

View File

@@ -0,0 +1,29 @@
import { MsgOutPayload } from '@/types/mqtt/MsgOutPayload';
import { MsgTypeOutPayload } from '@/types/mqtt/MsgTypeOutPayload';
import { OutPayload } from '@/types/mqtt/OutPayload';
/**
* Type guard function to determine if an OutPayload is a MsgType-based payload.
*
* Checks whether the payload uses the legacy MsgType field format for message type identification. This is used to
* differentiate between different payload structures and ensure proper type narrowing in TypeScript.
*
* @param payload - The OutPayload to check
* @returns True if the payload is a MsgTypeOutPayload, false otherwise
*/
export function isMsgTypeOutPayload(payload: OutPayload): payload is MsgTypeOutPayload {
return 'MsgType' in payload;
}
/**
* Type guard function to determine if an OutPayload is a message-based payload.
*
* Checks whether the payload uses the newer msg field format for message type identification. This is used to
* differentiate between different payload structures and ensure proper type narrowing in TypeScript.
*
* @param payload - The OutPayload to check
* @returns True if the payload is a MsgOutPayload, false otherwise
*/
export function isMsgOutPayload(payload: OutPayload): payload is MsgOutPayload {
return 'msg' in payload;
}