mysa-js-sdk 3.0.0 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/README.md +4 -5
- package/dist/index.cjs +267 -62
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +112 -5
- package/dist/index.d.ts +112 -5
- package/dist/index.js +266 -62
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/api/Errors.ts","../src/api/Logger.ts","../src/lib/EventEmitter.ts","../src/lib/PayloadParser.ts","../src/lib/PayloadTypeGuards.ts","../src/types/mqtt/in/InMessageType.ts","../src/types/mqtt/out/OutMessageType.ts","../src/api/MysaApiClient.ts"],"sourcesContent":["/** Error thrown when attempting to access the Mysa API without proper authentication. */\nexport class UnauthenticatedError extends Error {\n /**\n * The underlying failure that prevented authentication.\n *\n * Callers need it to tell a credential rejection apart from a transport or service failure, since both surface as\n * this error. Declared explicitly rather than passed to `super` so the SDK keeps compiling against its current lib,\n * which predates the two-argument `Error` constructor.\n */\n readonly cause?: unknown;\n\n /**\n * Creates a new UnauthenticatedError instance.\n *\n * @param message - The error message\n * @param cause - The underlying failure that prevented authentication, if any.\n */\n constructor(message: string, cause?: unknown) {\n super(message);\n this.name = 'UnauthenticatedError';\n this.cause = cause;\n }\n}\n\n/** Error thrown when a Mysa API request fails. */\nexport class MysaApiError extends Error {\n /** The HTTP status code returned by the API */\n readonly status: number;\n /** The HTTP status text returned by the API */\n readonly statusText: string;\n\n /**\n * Creates a new MysaApiError instance.\n *\n * @param apiResponse - The failed Response object from the API call\n */\n constructor(apiResponse: Response) {\n super(\n `Failed to call the '${apiResponse.url}' Mysa API endpoint. The server responded with a status of ${apiResponse.status} (${apiResponse.statusText}).`\n );\n this.name = 'MysaApiError';\n this.status = apiResponse.status;\n this.statusText = apiResponse.statusText;\n }\n}\n\n/** Error thrown when a device id does not match any device on the account. */\nexport class UnknownDeviceError extends Error {\n /**\n * Creates a new UnknownDeviceError instance.\n *\n * @param deviceId - The device id that could not be resolved\n */\n constructor(public readonly deviceId: string) {\n super(`Unknown device id '${deviceId}': no such device on this account.`);\n this.name = 'UnknownDeviceError';\n }\n}\n\n/** Error thrown when an MQTT publish ultimately fails after retry attempts. */\nexport class MqttPublishError extends Error {\n /**\n * Creates a new MqttPublishError instance.\n *\n * @param message - A human-readable description of the publish failure.\n * @param attempts - The number of attempts that were made before giving up.\n * @param original - The original error object thrown by the underlying MQTT library (optional).\n */\n constructor(\n message: string,\n public attempts: number,\n public original?: unknown\n ) {\n super(message);\n this.name = 'MqttPublishError';\n }\n}\n","/** Interface for logging operations at different severity levels */\nexport interface Logger {\n /** Logs a debug message with optional metadata */\n debug(message: string, ...meta: unknown[]): void;\n\n /** Logs an info message with optional metadata */\n info(message: string, ...meta: unknown[]): void;\n\n /** Logs a warning message with optional metadata */\n warn(message: string, ...meta: unknown[]): void;\n\n /** Logs an error message with optional metadata */\n error(message: string, ...meta: unknown[]): void;\n}\n\n/** Logger implementation that silently discards all log messages. */\n/* eslint-disable @typescript-eslint/no-unused-vars */\nexport class VoidLogger implements Logger {\n debug(message: string, ...meta: unknown[]): void {}\n info(message: string, ...meta: unknown[]): void {}\n warn(message: string, ...meta: unknown[]): void {}\n error(message: string, ...meta: unknown[]): void {}\n}\n/* eslint-enable @typescript-eslint/no-unused-vars */\n","import { EventEmitter as NodeEventEmitter } from 'node:events';\n\n/**\n * Typed wrapper around Node's `EventEmitter` class.\n *\n * @remarks\n * Source: {@link https://blog.makerx.com.au/a-type-safe-event-emitter-in-node-js}\n */\n/* eslint-disable @typescript-eslint/no-explicit-any */\nexport class EventEmitter<TEvents extends Record<string, any>> implements NodeJS.EventEmitter {\n private _emitter = new NodeEventEmitter();\n\n emit<TEventName extends keyof TEvents & string>(eventName: TEventName, ...eventArg: TEvents[TEventName]) {\n return this._emitter.emit(eventName, ...(eventArg as []));\n }\n\n on<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n handler: (...eventArg: TEvents[TEventName]) => void\n ) {\n this._emitter.on(eventName, handler as any);\n return this;\n }\n\n once<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n handler: (...eventArg: TEvents[TEventName]) => void\n ) {\n this._emitter.once(eventName, handler as any);\n return this;\n }\n\n off<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n handler: (...eventArg: TEvents[TEventName]) => void\n ) {\n this._emitter.off(eventName, handler as any);\n return this;\n }\n\n addListener<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n listener: (...args: TEvents[TEventName]) => void\n ) {\n this._emitter.addListener(eventName, listener);\n return this;\n }\n\n removeListener<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n listener: (...args: TEvents[TEventName]) => void\n ) {\n this._emitter.removeListener(eventName, listener);\n return this;\n }\n\n removeAllListeners<TEventName extends keyof TEvents & string>(eventName?: TEventName | undefined) {\n this._emitter.removeAllListeners(eventName);\n return this;\n }\n\n setMaxListeners(n: number) {\n this._emitter.setMaxListeners(n);\n return this;\n }\n\n getMaxListeners(): number {\n return this._emitter.getMaxListeners();\n }\n\n listeners<TEventName extends keyof TEvents & string>(eventName: TEventName) {\n return this._emitter.listeners(eventName);\n }\n\n rawListeners<TEventName extends keyof TEvents & string>(eventName: TEventName) {\n return this._emitter.rawListeners(eventName);\n }\n\n listenerCount<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n listener?: (...args: TEvents[TEventName]) => void\n ) {\n return this._emitter.listenerCount(eventName, listener);\n }\n\n prependListener<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n listener: (...args: TEvents[TEventName]) => void\n ) {\n this._emitter.prependListener(eventName, listener);\n return this;\n }\n\n prependOnceListener<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n listener: (...args: TEvents[TEventName]) => void\n ) {\n this._emitter.prependOnceListener(eventName, listener);\n return this;\n }\n\n eventNames() {\n return this._emitter.eventNames();\n }\n}\n/* eslint-enable @typescript-eslint/no-explicit-any */\n","import { InPayload } from '@/types/mqtt/InPayload';\nimport { OutPayload } from '@/types/mqtt/OutPayload';\n\n/**\n * Parses an MQTT payload from binary data into a typed OutPayload object.\n *\n * Converts the raw ArrayBuffer received from MQTT messages into a structured TypeScript object representing device\n * status, state changes, or other outgoing message types from Mysa devices.\n *\n * @param payload - The raw binary MQTT message payload as ArrayBuffer\n * @returns The parsed payload as a typed OutPayload object\n * @throws Error if the payload cannot be decoded or parsed as valid JSON\n */\nexport function parseMqttPayload(payload: ArrayBuffer): OutPayload {\n try {\n const decoder = new TextDecoder('utf-8');\n const jsonString = decoder.decode(payload);\n return JSON.parse(jsonString);\n } catch (error) {\n // No console output here: the SDK logger is the only sanctioned sink, and\n // the raw payload must not leak into output the consumer cannot route or\n // silence. The caller (_processMqttMessage) logs through the Logger.\n // The cause is attached via assignment because the project's TS lib\n // predates the ES2022 Error options constructor; Node supports it.\n const parseError = new Error('Failed to parse MQTT payload');\n (parseError as Error & { cause?: unknown }).cause = error;\n throw parseError;\n }\n}\n\n/**\n * Serializes an InPayload object into binary data for MQTT transmission.\n *\n * Converts a typed TypeScript payload object into the binary ArrayBuffer format required for sending commands and\n * requests to Mysa devices via MQTT.\n *\n * @typeParam T - The specific InPayload type being serialized\n * @param payload - The typed payload object to serialize\n * @returns The serialized payload as ArrayBuffer ready for MQTT transmission\n */\nexport function serializeMqttPayload<T extends InPayload>(payload: T): Uint8Array<ArrayBuffer> {\n const jsonString = JSON.stringify(payload);\n const encoder = new TextEncoder();\n return encoder.encode(jsonString);\n}\n","import { MsgOutPayload } from '@/types/mqtt/MsgOutPayload';\nimport { MsgTypeOutPayload } from '@/types/mqtt/MsgTypeOutPayload';\nimport { OutPayload } from '@/types/mqtt/OutPayload';\n\n/**\n * Type guard function to determine if an OutPayload is a MsgType-based payload.\n *\n * Checks whether the payload uses the legacy MsgType field format for message type identification. This is used to\n * differentiate between different payload structures and ensure proper type narrowing in TypeScript.\n *\n * @param payload - The OutPayload to check\n * @returns True if the payload is a MsgTypeOutPayload, false otherwise\n */\nexport function isMsgTypeOutPayload(payload: OutPayload): payload is MsgTypeOutPayload {\n return 'MsgType' in payload;\n}\n\n/**\n * Type guard function to determine if an OutPayload is a message-based payload.\n *\n * Checks whether the payload uses the newer msg field format for message type identification. This is used to\n * differentiate between different payload structures and ensure proper type narrowing in TypeScript.\n *\n * @param payload - The OutPayload to check\n * @returns True if the payload is a MsgOutPayload, false otherwise\n */\nexport function isMsgOutPayload(payload: OutPayload): payload is MsgOutPayload {\n return 'msg' in payload;\n}\n","/**\n * Enumeration of message types for incoming MQTT messages from clients to devices.\n *\n * These message types determine how commands and requests are interpreted by Mysa devices. The enum values correspond\n * to specific numeric identifiers used in the MQTT protocol.\n */\nexport enum InMessageType {\n //\n // When the message type is reported in the `MsgType` field of the payload.\n //\n\n /** Request to check and retrieve current device settings */\n CHECK_DEVICE_SETTINGS = 6,\n\n /** Command to start publishing periodic device status updates */\n START_PUBLISHING_DEVICE_STATUS = 11,\n\n //\n // When the message type is reported in the `msg` field of the payload.\n //\n\n /** Command to change the current state of a device (temperature, mode, etc.) */\n CHANGE_DEVICE_STATE = 44\n}\n","/**\n * Enumeration of message types for outgoing MQTT messages from devices to clients.\n *\n * These message types identify different kinds of status updates, notifications, and data reports that Mysa devices can\n * send via MQTT. The enum values correspond to specific numeric identifiers used in the MQTT protocol.\n */\nexport enum OutMessageType {\n //\n // When the message type is reported in the `MsgType` field of the payload.\n //\n\n /** Version 1 device status report with basic device information */\n DEVICE_V1_STATUS = 0,\n\n /** Notification that a device's temperature setpoint has been changed */\n DEVICE_SETPOINT_CHANGE = 1,\n\n /** Device log entry or diagnostic information */\n DEVICE_LOG = 4,\n\n /** Notification sent when a device completes its boot sequence */\n DEVICE_POST_BOOT = 10,\n\n //\n // When the message type is reported in the `msg` field of the payload.\n //\n\n /** Version 2 device status report with enhanced device information */\n DEVICE_V2_STATUS = 40,\n\n /** AC device status report with temperature, humidity, setpoint, and AC-specific fields */\n DEVICE_AC_STATUS = 30,\n\n /** Notification that a device's operational state has changed */\n DEVICE_STATE_CHANGE = 44\n}\n","import { MysaCredentials } from '@/api/MysaCredentials';\nimport { EventEmitter } from '@/lib/EventEmitter';\nimport { parseMqttPayload, serializeMqttPayload } from '@/lib/PayloadParser';\nimport { isMsgOutPayload, isMsgTypeOutPayload } from '@/lib/PayloadTypeGuards';\nimport { ChangeDeviceState } from '@/types/mqtt/in/ChangeDeviceState';\nimport { InMessageType } from '@/types/mqtt/in/InMessageType';\nimport { StartPublishingDeviceStatus } from '@/types/mqtt/in/StartPublishingDeviceStatus';\nimport { OutMessageType } from '@/types/mqtt/out/OutMessageType';\nimport { Devices, DeviceStates, Firmwares, Homes } from '@/types/rest';\nimport { DescribeThingCommand, IoTClient } from '@aws-sdk/client-iot';\nimport { fromCognitoIdentityPool } from '@aws-sdk/credential-providers';\nimport { AuthenticationDetails, CognitoUser, CognitoUserPool, CognitoUserSession } from 'amazon-cognito-identity-js';\nimport { auth, iot, mqtt } from 'aws-iot-device-sdk-v2';\nimport { hash } from 'crypto';\nimport dayjs, { Dayjs } from 'dayjs';\nimport duration from 'dayjs/plugin/duration.js';\nimport { customAlphabet } from 'nanoid';\nimport { MqttPublishError, MysaApiError, UnauthenticatedError, UnknownDeviceError } from './Errors';\nimport { Logger, VoidLogger } from './Logger';\nimport { MysaApiClientEventTypes } from './MysaApiClientEventTypes';\nimport { MysaApiClientOptions } from './MysaApiClientOptions';\nimport { MysaDeviceMode, MysaFanSpeedMode } from './MysaDeviceMode';\n\ndayjs.extend(duration);\n\nconst getRandomClientId = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 8);\n\n/** Options for MQTT publish operations. */\nexport interface MqttPublishOptions {\n /** Maximum number of publish attempts before failing (default: 5). */\n maxAttempts?: number;\n /** Base delay in milliseconds used for exponential backoff calculation (default: 500). */\n baseDelayMs?: number;\n}\n\nconst AwsRegion = 'us-east-1';\nconst CognitoUserPoolId = 'us-east-1_GUFWfhI7g';\nconst CognitoClientId = '19efs8tgqe942atbqmot5m36t3';\nconst CognitoIdentityPoolId = 'us-east-1:ebd95d52-9995-45da-b059-56b865a18379';\nconst CognitoLoginKey = `cognito-idp.${AwsRegion}.amazonaws.com/${CognitoUserPoolId}`;\nconst MqttEndpoint = 'a3q27gia9qg3zy-ats.iot.us-east-1.amazonaws.com';\nconst MysaApiBaseUrl = 'https://app-prod.mysa.cloud';\nconst RealtimeKeepAliveInterval = dayjs.duration(5, 'minutes');\nconst PublishAckTimeout = dayjs.duration(30, 'seconds');\n\n/**\n * Main client for interacting with the Mysa API and real-time device communication.\n *\n * The MysaApiClient provides a comprehensive interface for authenticating with Mysa services, managing device data, and\n * receiving real-time updates from Mysa thermostats and heating devices. It handles both REST API calls for device\n * management and MQTT connections for live status updates and control commands.\n *\n * @example\n *\n * ```typescript\n * const client = new MysaApiClient({ username: 'user@example.com', password: 'password' });\n *\n * await client.login();\n * const devices = await client.getDevices();\n *\n * client.emitter.on('statusChanged', (status) => {\n * console.log(`Device ${status.deviceId} temperature: ${status.temperature}°C`);\n * });\n *\n * for (const device of Object.entries(devices.DevicesObj)) {\n * await client.startRealtimeUpdates(device[0]);\n * }\n * ```\n */\nexport class MysaApiClient {\n /** The credentials of the Mysa account this client authenticates as. */\n private _credentials: MysaCredentials;\n\n /** The current session object, if any. */\n private _cognitoUserSession?: CognitoUserSession;\n\n /** The current user object, if any. */\n private _cognitoUser?: CognitoUser;\n\n /** The in-flight session acquisition, if any, so that concurrent callers share a single refresh or login. */\n private _freshSessionPromise?: Promise<CognitoUserSession>;\n\n /** The logger instance used by the client. */\n private _logger: Logger;\n\n /** The fetcher function used by the client. */\n private _fetcher: typeof fetch;\n\n /** A promise that resolves to the MQTT connection used for real-time updates. */\n private _mqttConnectionPromise?: Promise<mqtt.MqttClientConnection>;\n\n /** Stable per-process MQTT client id (prevents collisions between multiple processes). */\n private _mqttClientId?: string;\n\n /** Expiration time of the credentials currently in use by the MQTT client. */\n private _mqttCredentialsExpiration?: Dayjs;\n\n /** Interrupt timestamps for storm / collision detection. */\n private _mqttInterrupts: number[] = [];\n\n /** Whether a forced MQTT reset is currently in progress (guards against re-entrancy). */\n private _mqttResetInProgress = false;\n\n /** The device IDs that are currently being updated in real-time, mapped to their respective timeouts. */\n private _realtimeDeviceIds: Map<string, NodeJS.Timeout> = new Map();\n\n /** The cached devices object, if any. */\n private _cachedDevices?: Devices;\n\n /**\n * Event emitter for client events.\n *\n * @see {@link MysaApiClientEventTypes} for the possible events and their payloads.\n */\n readonly emitter = new EventEmitter<MysaApiClientEventTypes>();\n\n /**\n * Constructs a new instance of the MysaApiClient.\n *\n * @param credentials - The credentials of the Mysa account to authenticate as.\n * @param options - The options for the client.\n */\n constructor(credentials: MysaCredentials, options?: MysaApiClientOptions) {\n this._credentials = credentials;\n this._logger = options?.logger || new VoidLogger();\n this._fetcher = options?.fetcher || fetch;\n }\n\n /**\n * Ensures the client has a usable session, logging in with the credentials it was constructed with if needed.\n *\n * Calling this method is optional: the client authenticates on demand before its first API call, and re-authenticates\n * on its own whenever its session can no longer be refreshed. Call it explicitly at startup to fail fast on invalid\n * credentials instead of on the first API call. It is a no-op when the current session is still usable.\n *\n * @example\n *\n * ```typescript\n * try {\n * await client.login();\n * console.log('Login successful!');\n * } catch (error) {\n * console.error('Login failed:', error.message);\n * }\n * ```\n *\n * @throws {@link UnauthenticatedError} When authentication fails due to invalid credentials or network issues.\n */\n async login(): Promise<void> {\n // Goes through _getFreshSession so that an explicit login shares any acquisition already in flight instead of\n // racing a concurrent API call into a second Cognito login.\n await this._getFreshSession();\n }\n\n /**\n * Authenticates with Mysa's Cognito user pool and replaces the current session, if any.\n *\n * @returns A promise that resolves to the newly established session.\n * @throws {@link Error} When authentication fails due to invalid credentials or network issues.\n */\n private _login(): Promise<CognitoUserSession> {\n this._cognitoUser = undefined;\n this._cognitoUserSession = undefined;\n this._mqttClientId = undefined;\n this._mqttInterrupts = [];\n\n const { username, password } = this._credentials;\n\n return new Promise((resolve, reject) => {\n const user = new CognitoUser({\n Username: username,\n Pool: new CognitoUserPool({ UserPoolId: CognitoUserPoolId, ClientId: CognitoClientId })\n });\n\n user.authenticateUser(new AuthenticationDetails({ Username: username, Password: password }), {\n onSuccess: (session) => {\n this._cognitoUser = user;\n this._cognitoUserSession = session;\n\n resolve(session);\n },\n onFailure: (err) => {\n reject(err);\n }\n });\n });\n }\n\n /**\n * Retrieves the list of devices associated with the user.\n *\n * This method fetches all Mysa devices linked to the authenticated user's account, including device information such\n * as models, locations, and configuration details.\n *\n * @example\n *\n * ```typescript\n * const devices = await client.getDevices();\n * for (const [deviceId, device] of Object.entries(devices.DevicesObj)) {\n * console.log(`Device: ${device.DisplayName} (${device.Model})`);\n * }\n * ```\n *\n * @returns A promise that resolves to the list of devices.\n * @throws {@link MysaApiError} When the API request fails.\n * @throws {@link UnauthenticatedError} When the client cannot authenticate with its credentials.\n */\n async getDevices(): Promise<Devices> {\n this._logger.debug(`Fetching devices...`);\n\n const session = await this._getFreshSession();\n\n const response = await this._fetcher(`${MysaApiBaseUrl}/devices`, {\n headers: {\n Authorization: `${session.getIdToken().getJwtToken()}`\n }\n });\n\n if (!response.ok) {\n throw new MysaApiError(response);\n }\n\n return response.json();\n }\n\n /**\n * Retrieves the serial number for a specific device.\n *\n * This method uses AWS IoT's DescribeThing API to fetch the serial number attribute for the specified device. This\n * requires additional AWS IoT permissions and may not be available for all devices.\n *\n * @example\n *\n * ```typescript\n * const serialNumber = await client.getDeviceSerialNumber('device123');\n * if (serialNumber) {\n * console.log(`Device serial: ${serialNumber}`);\n * } else {\n * console.log('Serial number not available');\n * }\n * ```\n *\n * @param deviceId - The ID of the device to get the serial number for.\n * @returns A promise that resolves to the serial number, or undefined if not found.\n * @throws {@link UnauthenticatedError} When the client cannot authenticate with its credentials.\n */\n async getDeviceSerialNumber(deviceId: string): Promise<string | undefined> {\n this._logger.debug(`Fetching serial number for device ${deviceId}...`);\n\n const session = await this._getFreshSession();\n\n // Get AWS credentials for IoT client\n const credentialsProvider = fromCognitoIdentityPool({\n clientConfig: {\n region: AwsRegion\n },\n identityPoolId: CognitoIdentityPoolId,\n logins: {\n [CognitoLoginKey]: session.getIdToken().getJwtToken()\n }\n });\n\n const credentials = await credentialsProvider();\n const iotClient = new IoTClient({\n region: AwsRegion,\n credentials: {\n accessKeyId: credentials.accessKeyId,\n secretAccessKey: credentials.secretAccessKey,\n sessionToken: credentials.sessionToken\n }\n });\n\n try {\n const command = new DescribeThingCommand({ thingName: deviceId });\n const response = await iotClient.send(command);\n return response.attributes?.['Serial'];\n } catch (error) {\n this._logger.warn(`Could not get serial number for device ${deviceId}:`, error);\n return undefined;\n }\n }\n\n /**\n * Retrieves firmware information for all devices.\n *\n * @returns A promise that resolves to the firmware information for all devices.\n * @throws {@link MysaApiError} When the API request fails.\n * @throws {@link UnauthenticatedError} When the client cannot authenticate with its credentials.\n */\n async getDeviceFirmwares(): Promise<Firmwares> {\n this._logger.debug(`Fetching device firmwares...`);\n\n const session = await this._getFreshSession();\n\n const response = await this._fetcher(`${MysaApiBaseUrl}/devices/firmware`, {\n headers: {\n Authorization: `${session.getIdToken().getJwtToken()}`\n }\n });\n\n if (!response.ok) {\n throw new MysaApiError(response);\n }\n\n return response.json();\n }\n\n /**\n * Retrieves the current state information for all devices.\n *\n * @returns A promise that resolves to the current state of all devices.\n * @throws {@link MysaApiError} When the API request fails.\n * @throws {@link UnauthenticatedError} When the client cannot authenticate with its credentials.\n */\n async getDeviceStates(): Promise<DeviceStates> {\n this._logger.debug(`Fetching device states...`);\n\n const session = await this._getFreshSession();\n\n const response = await this._fetcher(`${MysaApiBaseUrl}/devices/state`, {\n headers: {\n Authorization: `${session.getIdToken().getJwtToken()}`\n }\n });\n\n if (!response.ok) {\n throw new MysaApiError(response);\n }\n\n return response.json();\n }\n\n /**\n * Retrieves information about all homes associated with the user.\n *\n * @returns A promise that resolves to the homes information.\n * @throws {@link MysaApiError} When the API request fails.\n * @throws {@link UnauthenticatedError} When the client cannot authenticate with its credentials.\n */\n async getHomes(): Promise<Homes> {\n this._logger.debug(`Fetching homes...`);\n\n const session = await this._getFreshSession();\n\n const response = await this._fetcher(`${MysaApiBaseUrl}/homes`, {\n headers: {\n Authorization: `${session.getIdToken().getJwtToken()}`\n }\n });\n\n if (!response.ok) {\n throw new MysaApiError(response);\n }\n\n return response.json();\n }\n\n /**\n * Sets the state of a specific device by sending commands via MQTT.\n *\n * This method allows you to change the temperature set point and/or operating mode of a Mysa device. The command is\n * sent through the MQTT connection for real-time device control.\n *\n * @example\n *\n * ```typescript\n * // Set temperature to 22°C\n * await client.setDeviceState('device123', 22);\n *\n * // Turn device off\n * await client.setDeviceState('device123', undefined, 'off');\n *\n * // Set temperature and mode\n * await client.setDeviceState('device123', 20, 'heat');\n *\n * // Set fan speed\n * await client.setDeviceState('device123', undefined, undefined, 'auto');\n * ```\n *\n * @param deviceId - The ID of the device to control.\n * @param setPoint - The target temperature set point (optional).\n * @param mode - The operating mode to set (one of MysaDeviceMode values, or undefined to leave unchanged).\n * @param fanSpeed - The fan speed mode to set ('low', 'medium', 'high', 'max', 'auto', or undefined to leave\n * unchanged).\n * @throws {@link UnauthenticatedError} When the client cannot authenticate with its credentials.\n * @throws {@link UnknownDeviceError} When the device id does not match any device on the account.\n * @throws {@link Error} When MQTT connection or command sending fails.\n */\n async setDeviceState(deviceId: string, setPoint?: number, mode?: MysaDeviceMode, fanSpeed?: MysaFanSpeedMode) {\n this._logger.debug(`Setting device state for '${deviceId}'`);\n\n if (!this._cachedDevices) {\n this._cachedDevices = await this.getDevices();\n }\n\n // Own-property check: an inherited key such as 'constructor' would otherwise pass the guard below and fail later\n // with a TypeError on device.Model instead of UnknownDeviceError.\n if (!Object.prototype.hasOwnProperty.call(this._cachedDevices.DevicesObj, deviceId)) {\n throw new UnknownDeviceError(deviceId);\n }\n\n const device = this._cachedDevices.DevicesObj[deviceId];\n\n // Validate the session before reaching for a possibly cached MQTT connection. _getMqttConnection reuses\n // _mqttConnectionPromise without checking auth, so without this a command could be published on a still-open\n // connection after the session died — with no source ref to attribute it to.\n await this._getFreshSession();\n\n this._logger.debug(`Initializing MQTT connection...`);\n const mqttConnection = await this._getMqttConnection();\n\n const now = dayjs();\n\n this._logger.debug(`Sending request to set device state for '${deviceId}'...`);\n const modeMap = { off: 1, auto: 2, heat: 3, cool: 4, fan_only: 5, dry: 6 };\n const fanSpeedMap = { auto: 1, low: 3, medium: 5, high: 7, max: 8 };\n\n const payload = serializeMqttPayload<ChangeDeviceState>({\n msg: InMessageType.CHANGE_DEVICE_STATE,\n id: now.valueOf(),\n time: now.unix(),\n ver: '1.0',\n src: {\n ref: this._cognitoUser!.getUsername(),\n type: 100\n },\n dest: {\n ref: deviceId,\n type: 1\n },\n resp: 2,\n body: {\n ver: 1,\n type:\n device.Model.startsWith('BB-V1') || device.Model.startsWith('v1')\n ? 1\n : device.Model.startsWith('AC-V1')\n ? 2\n : device.Model.startsWith('BB-V2')\n ? device.Model.endsWith('-L')\n ? 5\n : 4\n : 0,\n cmd: [\n {\n tm: -1,\n sp: setPoint,\n md: mode ? modeMap[mode] : undefined,\n fn: fanSpeed ? fanSpeedMap[fanSpeed] : undefined\n }\n ]\n }\n });\n\n try {\n await this._publishWithRetry(mqttConnection, `/v1/dev/${deviceId}/in`, payload, mqtt.QoS.AtLeastOnce);\n this._logger.debug(`Device state publish succeeded for '${deviceId}'`);\n } catch (error) {\n this._logger.error(`Failed to set device state for '${deviceId}'`, error);\n throw error;\n }\n }\n\n /**\n * Starts receiving real-time updates for the specified device.\n *\n * This method establishes an MQTT subscription to receive live status updates from the device, including temperature,\n * humidity, set point changes, and other state information. The client will automatically send keep-alive messages to\n * maintain the connection.\n *\n * @example\n *\n * ```typescript\n * // Start receiving updates and listen for events\n * await client.startRealtimeUpdates('device123');\n *\n * client.emitter.on('statusChanged', (status) => {\n * console.log(`Temperature: ${status.temperature}°C`);\n * });\n * ```\n *\n * @param deviceId - The ID of the device to start receiving updates for.\n * @throws {@link Error} When MQTT connection or subscription fails.\n */\n async startRealtimeUpdates(deviceId: string) {\n this._logger.info(`Starting real-time updates for device '${deviceId}'`);\n\n if (this._realtimeDeviceIds.has(deviceId)) {\n this._logger.debug(`Real-time updates for device '${deviceId}' already started`);\n return;\n }\n\n this._logger.debug(`Initializing MQTT connection...`);\n const mqttConnection = await this._getMqttConnection();\n\n this._logger.debug(`Subscribing to MQTT topic '/v1/dev/${deviceId}/out'...`);\n await mqttConnection.subscribe(`/v1/dev/${deviceId}/out`, mqtt.QoS.AtLeastOnce, (_, payload) => {\n this._processMqttMessage(payload);\n });\n\n this._logger.debug(`Sending request to start publishing device status for '${deviceId}'...`);\n const payload = serializeMqttPayload<StartPublishingDeviceStatus>({\n Device: deviceId,\n MsgType: InMessageType.START_PUBLISHING_DEVICE_STATUS,\n Timestamp: dayjs().unix(),\n Timeout: RealtimeKeepAliveInterval.asSeconds()\n });\n\n // A failed publish request must not abort startup (or, in the keep-alive\n // below, crash the process via an unhandled rejection): the subscription\n // above still delivers the device's autonomous periodic status reports\n // even when the request-driven status stream is unavailable.\n try {\n await this._publishWithRetry(mqttConnection, `/v1/dev/${deviceId}/in`, payload, mqtt.QoS.AtLeastOnce);\n } catch (error) {\n this._logger.warn(`Failed to request status publishing for '${deviceId}'; relying on periodic reports`, error);\n }\n\n const timer = setInterval(async () => {\n this._logger.debug(`Sending request to keep-alive publishing device status for '${deviceId}'...`);\n\n try {\n const connection = await this._getMqttConnection();\n const payload = serializeMqttPayload<StartPublishingDeviceStatus>({\n Device: deviceId,\n MsgType: InMessageType.START_PUBLISHING_DEVICE_STATUS,\n Timestamp: dayjs().unix(),\n Timeout: RealtimeKeepAliveInterval.asSeconds()\n });\n await this._publishWithRetry(connection, `/v1/dev/${deviceId}/in`, payload, mqtt.QoS.AtLeastOnce);\n } catch (error) {\n this._logger.warn(`Failed to keep-alive status publishing for '${deviceId}'`, error);\n }\n }, RealtimeKeepAliveInterval.subtract(10, 'seconds').asMilliseconds());\n\n this._realtimeDeviceIds.set(deviceId, timer);\n }\n\n /**\n * Stops receiving real-time updates for the specified device.\n *\n * This method unsubscribes from the MQTT topic for the specified device and clears any associated timers to stop the\n * keep-alive messages.\n *\n * @param deviceId - The ID of the device to stop receiving real-time updates for.\n * @throws {@link Error} When MQTT unsubscription fails.\n */\n async stopRealtimeUpdates(deviceId: string) {\n this._logger.info(`Stopping real-time updates for device '${deviceId}'`);\n\n const timer = this._realtimeDeviceIds.get(deviceId);\n if (!timer) {\n this._logger.warn(`No real-time updates are running for device '${deviceId}'`);\n return;\n }\n\n this._logger.debug(`Initializing MQTT connection...`);\n const mqttConnection = await this._getMqttConnection();\n\n this._logger.debug(`Unsubscribing to MQTT topic '/v1/dev/${deviceId}/out'...`);\n await mqttConnection.unsubscribe(`/v1/dev/${deviceId}/out`);\n\n clearInterval(timer);\n this._realtimeDeviceIds.delete(deviceId);\n }\n\n /**\n * Ensures a valid, non-expired session is available.\n *\n * This method checks if the current session is valid and not expired. If the session is expired, it automatically\n * refreshes it using the refresh token. If there is no session yet, or if the refresh token has itself expired or\n * been revoked, the client logs back in with its credentials.\n *\n * Concurrent callers share a single in-flight acquisition, so a burst of API calls never triggers more than one\n * refresh or login.\n *\n * @returns A promise that resolves to a valid CognitoUserSession.\n * @throws {@link UnauthenticatedError} When neither refreshing nor logging back in succeeds.\n */\n private async _getFreshSession(): Promise<CognitoUserSession> {\n if (\n this._cognitoUser &&\n this._cognitoUserSession?.isValid() &&\n dayjs.unix(this._cognitoUserSession.getIdToken().getExpiration()).isAfter()\n ) {\n this._logger.debug('Session is valid, no need to refresh');\n return this._cognitoUserSession;\n }\n\n this._freshSessionPromise ??= this._acquireFreshSession().finally(() => {\n this._freshSessionPromise = undefined;\n });\n\n return this._freshSessionPromise;\n }\n\n /**\n * Refreshes the current session, falling back to a full login when it cannot be refreshed.\n *\n * @returns A promise that resolves to a valid CognitoUserSession.\n * @throws {@link UnauthenticatedError} When logging back in fails.\n */\n private async _acquireFreshSession(): Promise<CognitoUserSession> {\n if (this._cognitoUser && this._cognitoUserSession) {\n this._logger.debug('Session is not valid or expired, refreshing...');\n\n try {\n return await new Promise<CognitoUserSession>((resolve, reject) => {\n this._cognitoUser!.refreshSession(this._cognitoUserSession!.getRefreshToken(), (error, session) => {\n if (error) {\n reject(error);\n } else {\n this._logger.debug('Session refreshed successfully');\n this._cognitoUserSession = session;\n resolve(session);\n }\n });\n });\n } catch (error) {\n // The refresh token itself expired, was revoked, or was rotated away. Fall through to a full login.\n this._logger.warn('Failed to refresh session, logging back in:', error);\n }\n }\n\n try {\n this._logger.info('Logging in...');\n return await this._login();\n } catch (error) {\n this._logger.error('Failed to log in:', error);\n throw new UnauthenticatedError('Unable to establish an authentication session.', error);\n }\n }\n\n /**\n * Establishes and returns an MQTT connection for real-time communication.\n *\n * This method creates a new MQTT connection if one doesn't exist, using AWS IoT WebSocket connections with Cognito\n * credentials. The connection is cached and reused for subsequent calls.\n *\n * @returns A promise that resolves to an active MQTT connection.\n * @throws {@link Error} When connection establishment fails.\n */\n private _getMqttConnection(): Promise<mqtt.MqttClientConnection> {\n if (!this._mqttConnectionPromise) {\n this._mqttConnectionPromise = this._createMqttConnection().catch((err) => {\n this._mqttConnectionPromise = undefined;\n throw err;\n });\n }\n\n return this._mqttConnectionPromise;\n }\n\n /**\n * Determines whether an MQTT-related error is considered transient and worth retrying.\n *\n * Transient errors include timeouts, cancelled operations due to clean sessions, temporary connectivity loss, and\n * other recoverable network issues. Fatal errors (auth, permission, configuration) should not be retried at this\n * layer.\n *\n * @param err - The error object thrown by the underlying MQTT operation.\n * @returns True if the error appears transient and a retry should be attempted; false otherwise.\n */\n private _isTransientMqttError(err: unknown): boolean {\n if (!err || typeof err !== 'object') {\n return false;\n }\n\n const anyErr = err as { error_code?: unknown; error_name?: unknown; error?: unknown; message?: unknown };\n const code = anyErr.error_code || anyErr.error_name || anyErr.error;\n const msg = (anyErr.message || anyErr.error || '').toString();\n\n const transientMarkers = [\n 'AWS_ERROR_MQTT_TIMEOUT',\n 'AWS_ERROR_MQTT_NO_CONNECTION',\n 'AWS_ERROR_MQTT_UNEXPECTED_HANGUP',\n 'UNEXPECTED_HANGUP',\n 'AWS_ERROR_MQTT_CONNECTION_DESTROYED',\n 'Time limit between request and response',\n 'timeout'\n ];\n\n return transientMarkers.some((m) => (code && String(code).includes(m)) || msg.includes(m));\n }\n\n /**\n * Publishes an MQTT message with exponential backoff retries for transient failures.\n *\n * Retries occur for errors classified by `_isTransientMqttError`. Between attempts the delay grows exponentially with\n * jitter to avoid thundering herds after broker recovery. If the connection is not currently marked as connected, a\n * reconnect is attempted; if that fails, the connection is rebuilt (fresh credentials) before the next retry.\n *\n * On final failure (after maxAttempts) a {@link MqttPublishError} is thrown including the number of attempts and\n * original error for higher-level handling.\n *\n * @remarks\n * Retry options fields:\n *\n * - MaxAttempts: Maximum number of publish attempts before failing (default: 5).\n * - BaseDelayMs: Base delay in milliseconds used for exponential backoff calculation (default: 500).\n *\n * @param connection - The active MQTT client connection used to send the publish.\n * @param topic - The MQTT topic to publish to.\n * @param payload - The serialized payload (binary buffer or Uint8Array).\n * @param qos - The desired MQTT QoS level for the publish.\n * @param opts - Retry options (defaults: maxAttempts=5, baseDelayMs=500).\n * @returns A promise that resolves when the publish succeeds, or rejects with {@link MqttPublishError}.\n */\n private async _publishWithRetry(\n connection: mqtt.MqttClientConnection,\n topic: string,\n payload: ArrayBuffer | Uint8Array,\n qos: mqtt.QoS,\n opts: MqttPublishOptions = {}\n ): Promise<void> {\n const maxAttempts = opts.maxAttempts ?? 5;\n const baseDelayMs = opts.baseDelayMs ?? 500;\n\n let attempt = 0;\n\n while (true) {\n attempt++;\n try {\n // Guard against publishes that never settle: a QoS1 publish the\n // broker silently ignores (e.g. an unauthorized topic) produces no\n // PUBACK and, in practice, no protocol-timeout rejection either —\n // the pending await would otherwise hang this call chain forever.\n await new Promise<void>((resolve, reject) => {\n const timer = setTimeout(\n () =>\n reject(new Error(`MQTT publish timeout: no acknowledgement within ${PublishAckTimeout.asSeconds()}s`)),\n PublishAckTimeout.asMilliseconds()\n );\n connection.publish(topic, payload, qos).then(\n () => {\n clearTimeout(timer);\n resolve();\n },\n (err) => {\n clearTimeout(timer);\n reject(err);\n }\n );\n });\n return;\n } catch (err) {\n const isTransient = this._isTransientMqttError(err);\n\n if (!isTransient || attempt >= maxAttempts) {\n throw new MqttPublishError(`MQTT publish failed after ${attempt} attempts`, attempt, err);\n }\n\n // Apply jitter: delay is randomized between 75% and 125% of the base exponential backoff\n const JITTER_MIN_FACTOR = 0.75;\n const JITTER_RANGE = 0.5;\n const delay = baseDelayMs * Math.pow(2, attempt - 1) * (JITTER_MIN_FACTOR + Math.random() * JITTER_RANGE);\n\n this._logger.warn(\n `Transient MQTT publish error on '${topic}' (attempt ${attempt}/${maxAttempts}). Retrying in ${Math.round(\n delay\n )}ms`\n );\n\n await new Promise((r) => setTimeout(r, delay));\n }\n }\n }\n\n /**\n * Creates a new MQTT connection using AWS IoT WebSocket connections with Cognito credentials.\n *\n * @returns A promise that resolves to an active MQTT connection.\n * @throws {@link Error} When connection establishment fails.\n */\n private async _createMqttConnection(): Promise<mqtt.MqttClientConnection> {\n const session = await this._getFreshSession();\n const credentialsProvider = fromCognitoIdentityPool({\n clientConfig: {\n region: AwsRegion\n },\n identityPoolId: CognitoIdentityPoolId,\n logins: {\n [CognitoLoginKey]: session.getIdToken().getJwtToken()\n },\n logger: this._logger\n });\n const credentials = await credentialsProvider();\n\n if (!credentials.expiration) {\n throw new Error('MQTT credentials do not have an expiration time.');\n }\n\n this._mqttCredentialsExpiration = dayjs(credentials.expiration);\n\n this._logger.debug(`MQTT credentials expiration: ${this._mqttCredentialsExpiration.format()}`);\n\n if (!this._mqttCredentialsExpiration.isAfter(dayjs())) {\n this._mqttCredentialsExpiration = undefined;\n throw new Error('MQTT credentials are already expired.');\n }\n\n // Per-process stable client id. Random suffix avoids collisions with other running processes.\n if (!this._mqttClientId) {\n const username = this._cognitoUser?.getUsername() ?? 'anon';\n const usernameHash = hash('sha1', username);\n this._mqttClientId = `mysa-js-sdk-${usernameHash}-${process.pid}-${getRandomClientId()}`;\n }\n\n const builder = iot.AwsIotMqttConnectionConfigBuilder.new_with_websockets()\n .with_credentials(AwsRegion, credentials.accessKeyId, credentials.secretAccessKey, credentials.sessionToken)\n .with_endpoint(MqttEndpoint)\n .with_client_id(this._mqttClientId)\n .with_clean_session(false)\n .with_keep_alive_seconds(30)\n .with_ping_timeout_ms(3000)\n .with_protocol_operation_timeout_ms(60000)\n .with_reconnect_min_sec(1)\n .with_reconnect_max_sec(30);\n\n const config = builder.build();\n\n // `with_credentials` bakes the credentials fetched above into a static\n // signer. Cognito credentials expire after ~1 hour; if the connection\n // drops and the native reconnect loop is still retrying when they lapse,\n // every subsequent handshake fails with a stale signature and no JS event\n // is ever emitted (`interrupt` only fires when an *established* connection\n // drops) — the client wedges silently, forever. Override the handshake\n // transform so every (re)connect attempt signs with freshly refreshed\n // credentials instead.\n config.websocket_handshake_transform = async (request, done) => {\n try {\n const freshSession = await this._getFreshSession();\n const freshCredentialsProvider = fromCognitoIdentityPool({\n clientConfig: {\n region: AwsRegion\n },\n identityPoolId: CognitoIdentityPoolId,\n logins: {\n [CognitoLoginKey]: freshSession.getIdToken().getJwtToken()\n },\n logger: this._logger\n });\n const freshCredentials = await freshCredentialsProvider();\n if (freshCredentials.expiration) {\n this._mqttCredentialsExpiration = dayjs(freshCredentials.expiration);\n }\n\n await auth.aws_sign_request(request, {\n algorithm: auth.AwsSigningAlgorithm.SigV4,\n signature_type: auth.AwsSignatureType.HttpRequestViaQueryParams,\n provider: auth.AwsCredentialsProvider.newStatic(\n freshCredentials.accessKeyId,\n freshCredentials.secretAccessKey,\n freshCredentials.sessionToken\n ),\n region: AwsRegion,\n service: 'iotdevicegateway',\n signed_body_value: auth.AwsSignedBodyValue.EmptySha256,\n omit_session_token: true\n });\n done();\n } catch (error) {\n this._logger.error('Failed to sign MQTT websocket handshake with fresh credentials', error);\n done(3 /* AWS_ERROR_UNKNOWN: fail this attempt; the reconnect loop retries */);\n }\n };\n\n const client = new mqtt.MqttClient();\n const connection = client.new_connection(config);\n\n connection.on('connect', () => {\n this._logger.debug(`MQTT connect (clientId=${this._mqttClientId})`);\n });\n\n connection.on('connection_success', () => {\n this._logger.debug(`MQTT connection_success (clientId=${this._mqttClientId})`);\n });\n\n connection.on('connection_failure', (e) => {\n this._logger.error(`MQTT connection_failure (clientId=${this._mqttClientId})`, e);\n });\n\n connection.on('interrupt', async (e) => {\n this._logger.warn(`MQTT interrupt (clientId=${this._mqttClientId})`, e);\n\n // Track recent interrupts\n const now = Date.now();\n\n // Keep only last 60s\n this._mqttInterrupts = this._mqttInterrupts.filter((t) => now - t < 60000);\n this._mqttInterrupts.push(now);\n\n const areCredentialsExpired = !(this._mqttCredentialsExpiration?.isAfter(dayjs()) ?? false);\n\n if ((this._mqttInterrupts.length > 5 || areCredentialsExpired) && !this._mqttResetInProgress) {\n this._mqttResetInProgress = true;\n\n if (this._mqttInterrupts.length > 5) {\n this._logger.warn(\n `High interrupt rate (${this._mqttInterrupts.length}/60s). Possible clientId collision. Regenerating clientId and resetting connection...`\n );\n } else {\n this._logger.warn(`Credentials expired. Regenerating clientId and resetting connection...`);\n }\n\n // Force new client id to escape collision; close current connection\n this._mqttClientId = undefined;\n\n this._mqttCredentialsExpiration = undefined;\n\n // Clear interrupts\n this._mqttInterrupts = [];\n\n // Explicitly clear promise first to prevent reuse while disconnecting\n // (publishers calling _getMqttConnection() will create a new one)\n this._mqttConnectionPromise = undefined;\n\n try {\n // Strip listeners before disconnect. Without this, the 6 event-\n // handler closures registered above (`connect`,\n // `connection_success`, `connection_failure`, `interrupt`,\n // `resume`, `error`, `closed`) remain attached to the discarded\n // connection object. Each closure captures `this` and `_logger`,\n // and aws-crt's native binding retains the JS object via its\n // internal callback refs — the dead connection never gets GC'd.\n // After enough interrupt-storms the process heap grows until OOM.\n connection.removeAllListeners();\n await connection.disconnect();\n\n try {\n this._logger.debug('Old MQTT connection disconnected; establishing new connection...');\n const newConnection = await this._getMqttConnection();\n\n for (const deviceId of Array.from(this._realtimeDeviceIds.keys())) {\n const topic = `/v1/dev/${deviceId}/out`;\n this._logger.debug(`Re-subscribing to ${topic}`);\n await newConnection.subscribe(topic, mqtt.QoS.AtLeastOnce, (_topic, payload) => {\n this._processMqttMessage(payload);\n });\n }\n\n this._logger.info('MQTT connection rebuilt successfully after interrupt storm or credentials expiration');\n } catch (err) {\n this._logger.error('Failed to re-subscribe after interrupt storm or credentials expiration', err);\n }\n } catch (error) {\n this._logger.error('Error during MQTT reset', error);\n } finally {\n this._mqttResetInProgress = false;\n }\n }\n });\n\n connection.on('resume', async (returnCode, sessionPresent) => {\n this._logger.info(\n `MQTT resume returnCode=${returnCode} sessionPresent=${sessionPresent} clientId=${this._mqttClientId}`\n );\n\n if (!sessionPresent) {\n this._logger.info('No session present, re-subscribing each device');\n try {\n for (const deviceId of Array.from(this._realtimeDeviceIds.keys())) {\n const topic = `/v1/dev/${deviceId}/out`;\n this._logger.debug(`Re-subscribing to ${topic}`);\n await connection.subscribe(topic, mqtt.QoS.AtLeastOnce, (_topic, payload) => {\n this._processMqttMessage(payload);\n });\n }\n } catch (err) {\n this._logger.error('Failed to re-subscribe after resume', err);\n }\n }\n });\n\n connection.on('error', (e) => {\n this._logger.error(`MQTT error (clientId=${this._mqttClientId})`, e);\n });\n\n connection.on('closed', () => {\n this._logger.info('MQTT connection closed');\n this._mqttConnectionPromise = undefined;\n this._mqttCredentialsExpiration = undefined;\n });\n\n await connection.connect();\n\n return connection;\n }\n\n /**\n * Processes incoming MQTT messages and emits appropriate events.\n *\n * This method parses MQTT payloads and converts them into typed events that can be listened to via the client's event\n * emitter. It handles both v1 and v2 device message formats and emits events like 'statusChanged', 'setPointChanged',\n * and 'stateChanged'.\n *\n * @param payload - The raw MQTT message payload to process.\n */\n private _processMqttMessage(payload: ArrayBuffer) {\n try {\n const parsedPayload = parseMqttPayload(payload);\n\n this.emitter.emit('rawRealtimeMessageReceived', parsedPayload);\n\n if (isMsgTypeOutPayload(parsedPayload)) {\n switch (parsedPayload.MsgType) {\n case OutMessageType.DEVICE_V1_STATUS:\n this.emitter.emit('statusChanged', {\n deviceId: parsedPayload.Device,\n temperature: parsedPayload.MainTemp,\n humidity: parsedPayload.Humidity,\n setPoint: parsedPayload.SetPoint,\n current: parsedPayload.Current\n });\n break;\n\n case OutMessageType.DEVICE_SETPOINT_CHANGE:\n this.emitter.emit('setPointChanged', {\n deviceId: parsedPayload.Device,\n newSetPoint: parsedPayload.Next,\n previousSetPoint: parsedPayload.Prev\n });\n break;\n }\n } else if (isMsgOutPayload(parsedPayload)) {\n switch (parsedPayload.msg) {\n case OutMessageType.DEVICE_AC_STATUS:\n case OutMessageType.DEVICE_V2_STATUS:\n this.emitter.emit('statusChanged', {\n deviceId: parsedPayload.src.ref,\n temperature: parsedPayload.body.ambTemp,\n humidity: parsedPayload.body.hum,\n setPoint: parsedPayload.body.stpt,\n dutyCycle: parsedPayload.body.dtyCycle\n });\n break;\n\n case OutMessageType.DEVICE_STATE_CHANGE: {\n const modeMap: Record<number, MysaDeviceMode> = {\n 1: 'off',\n 2: 'auto',\n 3: 'heat',\n 4: 'cool',\n 5: 'fan_only',\n 6: 'dry'\n };\n const fanSpeedMap: Record<number, MysaFanSpeedMode> = {\n 1: 'auto',\n 3: 'low',\n 5: 'medium',\n 7: 'high',\n 8: 'max'\n };\n\n this.emitter.emit('stateChanged', {\n deviceId: parsedPayload.src.ref,\n mode: parsedPayload.body.state.md ? modeMap[parsedPayload.body.state.md] : undefined,\n setPoint: parsedPayload.body.state.sp,\n fanSpeed: parsedPayload.body.state.fn !== undefined ? fanSpeedMap[parsedPayload.body.state.fn] : undefined\n });\n break;\n }\n }\n }\n } catch (error) {\n this._logger.error('Error handling MQTT message:', error);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AACO,IAAM,uBAAN,cAAmC,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQT,YAAY,SAAiB,OAAiB;AAC5C,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,QAAQ;AAAA,EACf;AACF;AAGO,IAAM,eAAN,cAA2B,MAAM;AAAA;AAAA,EAE7B;AAAA;AAAA,EAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOT,YAAY,aAAuB;AACjC;AAAA,MACE,uBAAuB,YAAY,GAAG,8DAA8D,YAAY,MAAM,KAAK,YAAY,UAAU;AAAA,IACnJ;AACA,SAAK,OAAO;AACZ,SAAK,SAAS,YAAY;AAC1B,SAAK,aAAa,YAAY;AAAA,EAChC;AACF;AAGO,IAAM,qBAAN,cAAiC,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM5C,YAA4B,UAAkB;AAC5C,UAAM,sBAAsB,QAAQ,oCAAoC;AAD9C;AAE1B,SAAK,OAAO;AAAA,EACd;AAAA,EAH4B;AAI9B;AAGO,IAAM,mBAAN,cAA+B,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ1C,YACE,SACO,UACA,UACP;AACA,UAAM,OAAO;AAHN;AACA;AAGP,SAAK,OAAO;AAAA,EACd;AAAA,EALS;AAAA,EACA;AAKX;;;AC3DO,IAAM,aAAN,MAAmC;AAAA,EACxC,MAAM,YAAoB,MAAuB;AAAA,EAAC;AAAA,EAClD,KAAK,YAAoB,MAAuB;AAAA,EAAC;AAAA,EACjD,KAAK,YAAoB,MAAuB;AAAA,EAAC;AAAA,EACjD,MAAM,YAAoB,MAAuB;AAAA,EAAC;AACpD;;;ACtBA,SAAS,gBAAgB,wBAAwB;AAS1C,IAAM,eAAN,MAAuF;AAAA,EACpF,WAAW,IAAI,iBAAiB;AAAA,EAExC,KAAgD,cAA0B,UAA+B;AACvG,WAAO,KAAK,SAAS,KAAK,WAAW,GAAI,QAAe;AAAA,EAC1D;AAAA,EAEA,GACE,WACA,SACA;AACA,SAAK,SAAS,GAAG,WAAW,OAAc;AAC1C,WAAO;AAAA,EACT;AAAA,EAEA,KACE,WACA,SACA;AACA,SAAK,SAAS,KAAK,WAAW,OAAc;AAC5C,WAAO;AAAA,EACT;AAAA,EAEA,IACE,WACA,SACA;AACA,SAAK,SAAS,IAAI,WAAW,OAAc;AAC3C,WAAO;AAAA,EACT;AAAA,EAEA,YACE,WACA,UACA;AACA,SAAK,SAAS,YAAY,WAAW,QAAQ;AAC7C,WAAO;AAAA,EACT;AAAA,EAEA,eACE,WACA,UACA;AACA,SAAK,SAAS,eAAe,WAAW,QAAQ;AAChD,WAAO;AAAA,EACT;AAAA,EAEA,mBAA8D,WAAoC;AAChG,SAAK,SAAS,mBAAmB,SAAS;AAC1C,WAAO;AAAA,EACT;AAAA,EAEA,gBAAgB,GAAW;AACzB,SAAK,SAAS,gBAAgB,CAAC;AAC/B,WAAO;AAAA,EACT;AAAA,EAEA,kBAA0B;AACxB,WAAO,KAAK,SAAS,gBAAgB;AAAA,EACvC;AAAA,EAEA,UAAqD,WAAuB;AAC1E,WAAO,KAAK,SAAS,UAAU,SAAS;AAAA,EAC1C;AAAA,EAEA,aAAwD,WAAuB;AAC7E,WAAO,KAAK,SAAS,aAAa,SAAS;AAAA,EAC7C;AAAA,EAEA,cACE,WACA,UACA;AACA,WAAO,KAAK,SAAS,cAAc,WAAW,QAAQ;AAAA,EACxD;AAAA,EAEA,gBACE,WACA,UACA;AACA,SAAK,SAAS,gBAAgB,WAAW,QAAQ;AACjD,WAAO;AAAA,EACT;AAAA,EAEA,oBACE,WACA,UACA;AACA,SAAK,SAAS,oBAAoB,WAAW,QAAQ;AACrD,WAAO;AAAA,EACT;AAAA,EAEA,aAAa;AACX,WAAO,KAAK,SAAS,WAAW;AAAA,EAClC;AACF;;;AC3FO,SAAS,iBAAiB,SAAkC;AACjE,MAAI;AACF,UAAM,UAAU,IAAI,YAAY,OAAO;AACvC,UAAM,aAAa,QAAQ,OAAO,OAAO;AACzC,WAAO,KAAK,MAAM,UAAU;AAAA,EAC9B,SAAS,OAAO;AAMd,UAAM,aAAa,IAAI,MAAM,8BAA8B;AAC3D,IAAC,WAA2C,QAAQ;AACpD,UAAM;AAAA,EACR;AACF;AAYO,SAAS,qBAA0C,SAAqC;AAC7F,QAAM,aAAa,KAAK,UAAU,OAAO;AACzC,QAAM,UAAU,IAAI,YAAY;AAChC,SAAO,QAAQ,OAAO,UAAU;AAClC;;;AC/BO,SAAS,oBAAoB,SAAmD;AACrF,SAAO,aAAa;AACtB;AAWO,SAAS,gBAAgB,SAA+C;AAC7E,SAAO,SAAS;AAClB;;;ACtBO,IAAK,gBAAL,kBAAKA,mBAAL;AAML,EAAAA,8BAAA,2BAAwB,KAAxB;AAGA,EAAAA,8BAAA,oCAAiC,MAAjC;AAOA,EAAAA,8BAAA,yBAAsB,MAAtB;AAhBU,SAAAA;AAAA,GAAA;;;ACAL,IAAK,iBAAL,kBAAKC,oBAAL;AAML,EAAAA,gCAAA,sBAAmB,KAAnB;AAGA,EAAAA,gCAAA,4BAAyB,KAAzB;AAGA,EAAAA,gCAAA,gBAAa,KAAb;AAGA,EAAAA,gCAAA,sBAAmB,MAAnB;AAOA,EAAAA,gCAAA,sBAAmB,MAAnB;AAGA,EAAAA,gCAAA,sBAAmB,MAAnB;AAGA,EAAAA,gCAAA,yBAAsB,MAAtB;AA5BU,SAAAA;AAAA,GAAA;;;ACGZ,SAAS,sBAAsB,iBAAiB;AAChD,SAAS,+BAA+B;AACxC,SAAS,uBAAuB,aAAa,uBAA2C;AACxF,SAAS,MAAM,KAAK,YAAY;AAChC,SAAS,YAAY;AACrB,OAAO,WAAsB;AAC7B,OAAO,cAAc;AACrB,SAAS,sBAAsB;AAO/B,MAAM,OAAO,QAAQ;AAErB,IAAM,oBAAoB,eAAe,wCAAwC,CAAC;AAUlF,IAAM,YAAY;AAClB,IAAM,oBAAoB;AAC1B,IAAM,kBAAkB;AACxB,IAAM,wBAAwB;AAC9B,IAAM,kBAAkB,eAAe,SAAS,kBAAkB,iBAAiB;AACnF,IAAM,eAAe;AACrB,IAAM,iBAAiB;AACvB,IAAM,4BAA4B,MAAM,SAAS,GAAG,SAAS;AAC7D,IAAM,oBAAoB,MAAM,SAAS,IAAI,SAAS;AA0B/C,IAAM,gBAAN,MAAoB;AAAA;AAAA,EAEjB;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA,kBAA4B,CAAC;AAAA;AAAA,EAG7B,uBAAuB;AAAA;AAAA,EAGvB,qBAAkD,oBAAI,IAAI;AAAA;AAAA,EAG1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOC,UAAU,IAAI,aAAsC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ7D,YAAY,aAA8B,SAAgC;AACxE,SAAK,eAAe;AACpB,SAAK,WAAU,mCAAS,WAAU,IAAI,WAAW;AACjD,SAAK,YAAW,mCAAS,YAAW;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBA,MAAM,QAAuB;AAG3B,UAAM,KAAK,iBAAiB;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,SAAsC;AAC5C,SAAK,eAAe;AACpB,SAAK,sBAAsB;AAC3B,SAAK,gBAAgB;AACrB,SAAK,kBAAkB,CAAC;AAExB,UAAM,EAAE,UAAU,SAAS,IAAI,KAAK;AAEpC,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,OAAO,IAAI,YAAY;AAAA,QAC3B,UAAU;AAAA,QACV,MAAM,IAAI,gBAAgB,EAAE,YAAY,mBAAmB,UAAU,gBAAgB,CAAC;AAAA,MACxF,CAAC;AAED,WAAK,iBAAiB,IAAI,sBAAsB,EAAE,UAAU,UAAU,UAAU,SAAS,CAAC,GAAG;AAAA,QAC3F,WAAW,CAAC,YAAY;AACtB,eAAK,eAAe;AACpB,eAAK,sBAAsB;AAE3B,kBAAQ,OAAO;AAAA,QACjB;AAAA,QACA,WAAW,CAAC,QAAQ;AAClB,iBAAO,GAAG;AAAA,QACZ;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,MAAM,aAA+B;AACnC,SAAK,QAAQ,MAAM,qBAAqB;AAExC,UAAM,UAAU,MAAM,KAAK,iBAAiB;AAE5C,UAAM,WAAW,MAAM,KAAK,SAAS,GAAG,cAAc,YAAY;AAAA,MAChE,SAAS;AAAA,QACP,eAAe,GAAG,QAAQ,WAAW,EAAE,YAAY,CAAC;AAAA,MACtD;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,aAAa,QAAQ;AAAA,IACjC;AAEA,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBA,MAAM,sBAAsB,UAA+C;AAtP7E;AAuPI,SAAK,QAAQ,MAAM,qCAAqC,QAAQ,KAAK;AAErE,UAAM,UAAU,MAAM,KAAK,iBAAiB;AAG5C,UAAM,sBAAsB,wBAAwB;AAAA,MAClD,cAAc;AAAA,QACZ,QAAQ;AAAA,MACV;AAAA,MACA,gBAAgB;AAAA,MAChB,QAAQ;AAAA,QACN,CAAC,eAAe,GAAG,QAAQ,WAAW,EAAE,YAAY;AAAA,MACtD;AAAA,IACF,CAAC;AAED,UAAM,cAAc,MAAM,oBAAoB;AAC9C,UAAM,YAAY,IAAI,UAAU;AAAA,MAC9B,QAAQ;AAAA,MACR,aAAa;AAAA,QACX,aAAa,YAAY;AAAA,QACzB,iBAAiB,YAAY;AAAA,QAC7B,cAAc,YAAY;AAAA,MAC5B;AAAA,IACF,CAAC;AAED,QAAI;AACF,YAAM,UAAU,IAAI,qBAAqB,EAAE,WAAW,SAAS,CAAC;AAChE,YAAM,WAAW,MAAM,UAAU,KAAK,OAAO;AAC7C,cAAO,cAAS,eAAT,mBAAsB;AAAA,IAC/B,SAAS,OAAO;AACd,WAAK,QAAQ,KAAK,0CAA0C,QAAQ,KAAK,KAAK;AAC9E,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,qBAAyC;AAC7C,SAAK,QAAQ,MAAM,8BAA8B;AAEjD,UAAM,UAAU,MAAM,KAAK,iBAAiB;AAE5C,UAAM,WAAW,MAAM,KAAK,SAAS,GAAG,cAAc,qBAAqB;AAAA,MACzE,SAAS;AAAA,QACP,eAAe,GAAG,QAAQ,WAAW,EAAE,YAAY,CAAC;AAAA,MACtD;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,aAAa,QAAQ;AAAA,IACjC;AAEA,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,kBAAyC;AAC7C,SAAK,QAAQ,MAAM,2BAA2B;AAE9C,UAAM,UAAU,MAAM,KAAK,iBAAiB;AAE5C,UAAM,WAAW,MAAM,KAAK,SAAS,GAAG,cAAc,kBAAkB;AAAA,MACtE,SAAS;AAAA,QACP,eAAe,GAAG,QAAQ,WAAW,EAAE,YAAY,CAAC;AAAA,MACtD;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,aAAa,QAAQ;AAAA,IACjC;AAEA,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,WAA2B;AAC/B,SAAK,QAAQ,MAAM,mBAAmB;AAEtC,UAAM,UAAU,MAAM,KAAK,iBAAiB;AAE5C,UAAM,WAAW,MAAM,KAAK,SAAS,GAAG,cAAc,UAAU;AAAA,MAC9D,SAAS;AAAA,QACP,eAAe,GAAG,QAAQ,WAAW,EAAE,YAAY,CAAC;AAAA,MACtD;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,aAAa,QAAQ;AAAA,IACjC;AAEA,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiCA,MAAM,eAAe,UAAkB,UAAmB,MAAuB,UAA6B;AAC5G,SAAK,QAAQ,MAAM,6BAA6B,QAAQ,GAAG;AAE3D,QAAI,CAAC,KAAK,gBAAgB;AACxB,WAAK,iBAAiB,MAAM,KAAK,WAAW;AAAA,IAC9C;AAIA,QAAI,CAAC,OAAO,UAAU,eAAe,KAAK,KAAK,eAAe,YAAY,QAAQ,GAAG;AACnF,YAAM,IAAI,mBAAmB,QAAQ;AAAA,IACvC;AAEA,UAAM,SAAS,KAAK,eAAe,WAAW,QAAQ;AAKtD,UAAM,KAAK,iBAAiB;AAE5B,SAAK,QAAQ,MAAM,iCAAiC;AACpD,UAAM,iBAAiB,MAAM,KAAK,mBAAmB;AAErD,UAAM,MAAM,MAAM;AAElB,SAAK,QAAQ,MAAM,4CAA4C,QAAQ,MAAM;AAC7E,UAAM,UAAU,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,KAAK,EAAE;AACzE,UAAM,cAAc,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,EAAE;AAElE,UAAM,UAAU,qBAAwC;AAAA,MACtD;AAAA,MACA,IAAI,IAAI,QAAQ;AAAA,MAChB,MAAM,IAAI,KAAK;AAAA,MACf,KAAK;AAAA,MACL,KAAK;AAAA,QACH,KAAK,KAAK,aAAc,YAAY;AAAA,QACpC,MAAM;AAAA,MACR;AAAA,MACA,MAAM;AAAA,QACJ,KAAK;AAAA,QACL,MAAM;AAAA,MACR;AAAA,MACA,MAAM;AAAA,MACN,MAAM;AAAA,QACJ,KAAK;AAAA,QACL,MACE,OAAO,MAAM,WAAW,OAAO,KAAK,OAAO,MAAM,WAAW,IAAI,IAC5D,IACA,OAAO,MAAM,WAAW,OAAO,IAC7B,IACA,OAAO,MAAM,WAAW,OAAO,IAC7B,OAAO,MAAM,SAAS,IAAI,IACxB,IACA,IACF;AAAA,QACV,KAAK;AAAA,UACH;AAAA,YACE,IAAI;AAAA,YACJ,IAAI;AAAA,YACJ,IAAI,OAAO,QAAQ,IAAI,IAAI;AAAA,YAC3B,IAAI,WAAW,YAAY,QAAQ,IAAI;AAAA,UACzC;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAED,QAAI;AACF,YAAM,KAAK,kBAAkB,gBAAgB,WAAW,QAAQ,OAAO,SAAS,KAAK,IAAI,WAAW;AACpG,WAAK,QAAQ,MAAM,uCAAuC,QAAQ,GAAG;AAAA,IACvE,SAAS,OAAO;AACd,WAAK,QAAQ,MAAM,mCAAmC,QAAQ,KAAK,KAAK;AACxE,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBA,MAAM,qBAAqB,UAAkB;AAC3C,SAAK,QAAQ,KAAK,0CAA0C,QAAQ,GAAG;AAEvE,QAAI,KAAK,mBAAmB,IAAI,QAAQ,GAAG;AACzC,WAAK,QAAQ,MAAM,iCAAiC,QAAQ,mBAAmB;AAC/E;AAAA,IACF;AAEA,SAAK,QAAQ,MAAM,iCAAiC;AACpD,UAAM,iBAAiB,MAAM,KAAK,mBAAmB;AAErD,SAAK,QAAQ,MAAM,sCAAsC,QAAQ,UAAU;AAC3E,UAAM,eAAe,UAAU,WAAW,QAAQ,QAAQ,KAAK,IAAI,aAAa,CAAC,GAAGC,aAAY;AAC9F,WAAK,oBAAoBA,QAAO;AAAA,IAClC,CAAC;AAED,SAAK,QAAQ,MAAM,0DAA0D,QAAQ,MAAM;AAC3F,UAAM,UAAU,qBAAkD;AAAA,MAChE,QAAQ;AAAA,MACR;AAAA,MACA,WAAW,MAAM,EAAE,KAAK;AAAA,MACxB,SAAS,0BAA0B,UAAU;AAAA,IAC/C,CAAC;AAMD,QAAI;AACF,YAAM,KAAK,kBAAkB,gBAAgB,WAAW,QAAQ,OAAO,SAAS,KAAK,IAAI,WAAW;AAAA,IACtG,SAAS,OAAO;AACd,WAAK,QAAQ,KAAK,4CAA4C,QAAQ,kCAAkC,KAAK;AAAA,IAC/G;AAEA,UAAM,QAAQ,YAAY,YAAY;AACpC,WAAK,QAAQ,MAAM,+DAA+D,QAAQ,MAAM;AAEhG,UAAI;AACF,cAAM,aAAa,MAAM,KAAK,mBAAmB;AACjD,cAAMA,WAAU,qBAAkD;AAAA,UAChE,QAAQ;AAAA,UACR;AAAA,UACA,WAAW,MAAM,EAAE,KAAK;AAAA,UACxB,SAAS,0BAA0B,UAAU;AAAA,QAC/C,CAAC;AACD,cAAM,KAAK,kBAAkB,YAAY,WAAW,QAAQ,OAAOA,UAAS,KAAK,IAAI,WAAW;AAAA,MAClG,SAAS,OAAO;AACd,aAAK,QAAQ,KAAK,+CAA+C,QAAQ,KAAK,KAAK;AAAA,MACrF;AAAA,IACF,GAAG,0BAA0B,SAAS,IAAI,SAAS,EAAE,eAAe,CAAC;AAErE,SAAK,mBAAmB,IAAI,UAAU,KAAK;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,oBAAoB,UAAkB;AAC1C,SAAK,QAAQ,KAAK,0CAA0C,QAAQ,GAAG;AAEvE,UAAM,QAAQ,KAAK,mBAAmB,IAAI,QAAQ;AAClD,QAAI,CAAC,OAAO;AACV,WAAK,QAAQ,KAAK,gDAAgD,QAAQ,GAAG;AAC7E;AAAA,IACF;AAEA,SAAK,QAAQ,MAAM,iCAAiC;AACpD,UAAM,iBAAiB,MAAM,KAAK,mBAAmB;AAErD,SAAK,QAAQ,MAAM,wCAAwC,QAAQ,UAAU;AAC7E,UAAM,eAAe,YAAY,WAAW,QAAQ,MAAM;AAE1D,kBAAc,KAAK;AACnB,SAAK,mBAAmB,OAAO,QAAQ;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAc,mBAAgD;AAnkBhE;AAokBI,QACE,KAAK,kBACL,UAAK,wBAAL,mBAA0B,cAC1B,MAAM,KAAK,KAAK,oBAAoB,WAAW,EAAE,cAAc,CAAC,EAAE,QAAQ,GAC1E;AACA,WAAK,QAAQ,MAAM,sCAAsC;AACzD,aAAO,KAAK;AAAA,IACd;AAEA,SAAK,yBAAyB,KAAK,qBAAqB,EAAE,QAAQ,MAAM;AACtE,WAAK,uBAAuB;AAAA,IAC9B,CAAC;AAED,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,uBAAoD;AAChE,QAAI,KAAK,gBAAgB,KAAK,qBAAqB;AACjD,WAAK,QAAQ,MAAM,gDAAgD;AAEnE,UAAI;AACF,eAAO,MAAM,IAAI,QAA4B,CAAC,SAAS,WAAW;AAChE,eAAK,aAAc,eAAe,KAAK,oBAAqB,gBAAgB,GAAG,CAAC,OAAO,YAAY;AACjG,gBAAI,OAAO;AACT,qBAAO,KAAK;AAAA,YACd,OAAO;AACL,mBAAK,QAAQ,MAAM,gCAAgC;AACnD,mBAAK,sBAAsB;AAC3B,sBAAQ,OAAO;AAAA,YACjB;AAAA,UACF,CAAC;AAAA,QACH,CAAC;AAAA,MACH,SAAS,OAAO;AAEd,aAAK,QAAQ,KAAK,+CAA+C,KAAK;AAAA,MACxE;AAAA,IACF;AAEA,QAAI;AACF,WAAK,QAAQ,KAAK,eAAe;AACjC,aAAO,MAAM,KAAK,OAAO;AAAA,IAC3B,SAAS,OAAO;AACd,WAAK,QAAQ,MAAM,qBAAqB,KAAK;AAC7C,YAAM,IAAI,qBAAqB,kDAAkD,KAAK;AAAA,IACxF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,qBAAyD;AAC/D,QAAI,CAAC,KAAK,wBAAwB;AAChC,WAAK,yBAAyB,KAAK,sBAAsB,EAAE,MAAM,CAAC,QAAQ;AACxE,aAAK,yBAAyB;AAC9B,cAAM;AAAA,MACR,CAAC;AAAA,IACH;AAEA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYQ,sBAAsB,KAAuB;AACnD,QAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;AACnC,aAAO;AAAA,IACT;AAEA,UAAM,SAAS;AACf,UAAM,OAAO,OAAO,cAAc,OAAO,cAAc,OAAO;AAC9D,UAAM,OAAO,OAAO,WAAW,OAAO,SAAS,IAAI,SAAS;AAE5D,UAAM,mBAAmB;AAAA,MACvB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,WAAO,iBAAiB,KAAK,CAAC,MAAO,QAAQ,OAAO,IAAI,EAAE,SAAS,CAAC,KAAM,IAAI,SAAS,CAAC,CAAC;AAAA,EAC3F;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyBA,MAAc,kBACZ,YACA,OACA,SACA,KACA,OAA2B,CAAC,GACb;AACf,UAAM,cAAc,KAAK,eAAe;AACxC,UAAM,cAAc,KAAK,eAAe;AAExC,QAAI,UAAU;AAEd,WAAO,MAAM;AACX;AACA,UAAI;AAKF,cAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,gBAAM,QAAQ;AAAA,YACZ,MACE,OAAO,IAAI,MAAM,mDAAmD,kBAAkB,UAAU,CAAC,GAAG,CAAC;AAAA,YACvG,kBAAkB,eAAe;AAAA,UACnC;AACA,qBAAW,QAAQ,OAAO,SAAS,GAAG,EAAE;AAAA,YACtC,MAAM;AACJ,2BAAa,KAAK;AAClB,sBAAQ;AAAA,YACV;AAAA,YACA,CAAC,QAAQ;AACP,2BAAa,KAAK;AAClB,qBAAO,GAAG;AAAA,YACZ;AAAA,UACF;AAAA,QACF,CAAC;AACD;AAAA,MACF,SAAS,KAAK;AACZ,cAAM,cAAc,KAAK,sBAAsB,GAAG;AAElD,YAAI,CAAC,eAAe,WAAW,aAAa;AAC1C,gBAAM,IAAI,iBAAiB,6BAA6B,OAAO,aAAa,SAAS,GAAG;AAAA,QAC1F;AAGA,cAAM,oBAAoB;AAC1B,cAAM,eAAe;AACrB,cAAM,QAAQ,cAAc,KAAK,IAAI,GAAG,UAAU,CAAC,KAAK,oBAAoB,KAAK,OAAO,IAAI;AAE5F,aAAK,QAAQ;AAAA,UACX,oCAAoC,KAAK,cAAc,OAAO,IAAI,WAAW,kBAAkB,KAAK;AAAA,YAClG;AAAA,UACF,CAAC;AAAA,QACH;AAEA,cAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,KAAK,CAAC;AAAA,MAC/C;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,wBAA4D;AAtwB5E;AAuwBI,UAAM,UAAU,MAAM,KAAK,iBAAiB;AAC5C,UAAM,sBAAsB,wBAAwB;AAAA,MAClD,cAAc;AAAA,QACZ,QAAQ;AAAA,MACV;AAAA,MACA,gBAAgB;AAAA,MAChB,QAAQ;AAAA,QACN,CAAC,eAAe,GAAG,QAAQ,WAAW,EAAE,YAAY;AAAA,MACtD;AAAA,MACA,QAAQ,KAAK;AAAA,IACf,CAAC;AACD,UAAM,cAAc,MAAM,oBAAoB;AAE9C,QAAI,CAAC,YAAY,YAAY;AAC3B,YAAM,IAAI,MAAM,kDAAkD;AAAA,IACpE;AAEA,SAAK,6BAA6B,MAAM,YAAY,UAAU;AAE9D,SAAK,QAAQ,MAAM,gCAAgC,KAAK,2BAA2B,OAAO,CAAC,EAAE;AAE7F,QAAI,CAAC,KAAK,2BAA2B,QAAQ,MAAM,CAAC,GAAG;AACrD,WAAK,6BAA6B;AAClC,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAGA,QAAI,CAAC,KAAK,eAAe;AACvB,YAAM,aAAW,UAAK,iBAAL,mBAAmB,kBAAiB;AACrD,YAAM,eAAe,KAAK,QAAQ,QAAQ;AAC1C,WAAK,gBAAgB,eAAe,YAAY,IAAI,QAAQ,GAAG,IAAI,kBAAkB,CAAC;AAAA,IACxF;AAEA,UAAM,UAAU,IAAI,kCAAkC,oBAAoB,EACvE,iBAAiB,WAAW,YAAY,aAAa,YAAY,iBAAiB,YAAY,YAAY,EAC1G,cAAc,YAAY,EAC1B,eAAe,KAAK,aAAa,EACjC,mBAAmB,KAAK,EACxB,wBAAwB,EAAE,EAC1B,qBAAqB,GAAI,EACzB,mCAAmC,GAAK,EACxC,uBAAuB,CAAC,EACxB,uBAAuB,EAAE;AAE5B,UAAM,SAAS,QAAQ,MAAM;AAU7B,WAAO,gCAAgC,OAAO,SAAS,SAAS;AAC9D,UAAI;AACF,cAAM,eAAe,MAAM,KAAK,iBAAiB;AACjD,cAAM,2BAA2B,wBAAwB;AAAA,UACvD,cAAc;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,gBAAgB;AAAA,UAChB,QAAQ;AAAA,YACN,CAAC,eAAe,GAAG,aAAa,WAAW,EAAE,YAAY;AAAA,UAC3D;AAAA,UACA,QAAQ,KAAK;AAAA,QACf,CAAC;AACD,cAAM,mBAAmB,MAAM,yBAAyB;AACxD,YAAI,iBAAiB,YAAY;AAC/B,eAAK,6BAA6B,MAAM,iBAAiB,UAAU;AAAA,QACrE;AAEA,cAAM,KAAK,iBAAiB,SAAS;AAAA,UACnC,WAAW,KAAK,oBAAoB;AAAA,UACpC,gBAAgB,KAAK,iBAAiB;AAAA,UACtC,UAAU,KAAK,uBAAuB;AAAA,YACpC,iBAAiB;AAAA,YACjB,iBAAiB;AAAA,YACjB,iBAAiB;AAAA,UACnB;AAAA,UACA,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,mBAAmB,KAAK,mBAAmB;AAAA,UAC3C,oBAAoB;AAAA,QACtB,CAAC;AACD,aAAK;AAAA,MACP,SAAS,OAAO;AACd,aAAK,QAAQ,MAAM,kEAAkE,KAAK;AAC1F;AAAA,UAAK;AAAA;AAAA,QAAwE;AAAA,MAC/E;AAAA,IACF;AAEA,UAAM,SAAS,IAAI,KAAK,WAAW;AACnC,UAAM,aAAa,OAAO,eAAe,MAAM;AAE/C,eAAW,GAAG,WAAW,MAAM;AAC7B,WAAK,QAAQ,MAAM,0BAA0B,KAAK,aAAa,GAAG;AAAA,IACpE,CAAC;AAED,eAAW,GAAG,sBAAsB,MAAM;AACxC,WAAK,QAAQ,MAAM,qCAAqC,KAAK,aAAa,GAAG;AAAA,IAC/E,CAAC;AAED,eAAW,GAAG,sBAAsB,CAAC,MAAM;AACzC,WAAK,QAAQ,MAAM,qCAAqC,KAAK,aAAa,KAAK,CAAC;AAAA,IAClF,CAAC;AAED,eAAW,GAAG,aAAa,OAAO,MAAM;AAl3B5C,UAAAC;AAm3BM,WAAK,QAAQ,KAAK,4BAA4B,KAAK,aAAa,KAAK,CAAC;AAGtE,YAAM,MAAM,KAAK,IAAI;AAGrB,WAAK,kBAAkB,KAAK,gBAAgB,OAAO,CAAC,MAAM,MAAM,IAAI,GAAK;AACzE,WAAK,gBAAgB,KAAK,GAAG;AAE7B,YAAM,wBAAwB,IAAEA,MAAA,KAAK,+BAAL,gBAAAA,IAAiC,QAAQ,MAAM,OAAM;AAErF,WAAK,KAAK,gBAAgB,SAAS,KAAK,0BAA0B,CAAC,KAAK,sBAAsB;AAC5F,aAAK,uBAAuB;AAE5B,YAAI,KAAK,gBAAgB,SAAS,GAAG;AACnC,eAAK,QAAQ;AAAA,YACX,wBAAwB,KAAK,gBAAgB,MAAM;AAAA,UACrD;AAAA,QACF,OAAO;AACL,eAAK,QAAQ,KAAK,wEAAwE;AAAA,QAC5F;AAGA,aAAK,gBAAgB;AAErB,aAAK,6BAA6B;AAGlC,aAAK,kBAAkB,CAAC;AAIxB,aAAK,yBAAyB;AAE9B,YAAI;AASF,qBAAW,mBAAmB;AAC9B,gBAAM,WAAW,WAAW;AAE5B,cAAI;AACF,iBAAK,QAAQ,MAAM,kEAAkE;AACrF,kBAAM,gBAAgB,MAAM,KAAK,mBAAmB;AAEpD,uBAAW,YAAY,MAAM,KAAK,KAAK,mBAAmB,KAAK,CAAC,GAAG;AACjE,oBAAM,QAAQ,WAAW,QAAQ;AACjC,mBAAK,QAAQ,MAAM,qBAAqB,KAAK,EAAE;AAC/C,oBAAM,cAAc,UAAU,OAAO,KAAK,IAAI,aAAa,CAAC,QAAQ,YAAY;AAC9E,qBAAK,oBAAoB,OAAO;AAAA,cAClC,CAAC;AAAA,YACH;AAEA,iBAAK,QAAQ,KAAK,sFAAsF;AAAA,UAC1G,SAAS,KAAK;AACZ,iBAAK,QAAQ,MAAM,0EAA0E,GAAG;AAAA,UAClG;AAAA,QACF,SAAS,OAAO;AACd,eAAK,QAAQ,MAAM,2BAA2B,KAAK;AAAA,QACrD,UAAE;AACA,eAAK,uBAAuB;AAAA,QAC9B;AAAA,MACF;AAAA,IACF,CAAC;AAED,eAAW,GAAG,UAAU,OAAO,YAAY,mBAAmB;AAC5D,WAAK,QAAQ;AAAA,QACX,0BAA0B,UAAU,mBAAmB,cAAc,aAAa,KAAK,aAAa;AAAA,MACtG;AAEA,UAAI,CAAC,gBAAgB;AACnB,aAAK,QAAQ,KAAK,gDAAgD;AAClE,YAAI;AACF,qBAAW,YAAY,MAAM,KAAK,KAAK,mBAAmB,KAAK,CAAC,GAAG;AACjE,kBAAM,QAAQ,WAAW,QAAQ;AACjC,iBAAK,QAAQ,MAAM,qBAAqB,KAAK,EAAE;AAC/C,kBAAM,WAAW,UAAU,OAAO,KAAK,IAAI,aAAa,CAAC,QAAQ,YAAY;AAC3E,mBAAK,oBAAoB,OAAO;AAAA,YAClC,CAAC;AAAA,UACH;AAAA,QACF,SAAS,KAAK;AACZ,eAAK,QAAQ,MAAM,uCAAuC,GAAG;AAAA,QAC/D;AAAA,MACF;AAAA,IACF,CAAC;AAED,eAAW,GAAG,SAAS,CAAC,MAAM;AAC5B,WAAK,QAAQ,MAAM,wBAAwB,KAAK,aAAa,KAAK,CAAC;AAAA,IACrE,CAAC;AAED,eAAW,GAAG,UAAU,MAAM;AAC5B,WAAK,QAAQ,KAAK,wBAAwB;AAC1C,WAAK,yBAAyB;AAC9B,WAAK,6BAA6B;AAAA,IACpC,CAAC;AAED,UAAM,WAAW,QAAQ;AAEzB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,oBAAoB,SAAsB;AAChD,QAAI;AACF,YAAM,gBAAgB,iBAAiB,OAAO;AAE9C,WAAK,QAAQ,KAAK,8BAA8B,aAAa;AAE7D,UAAI,oBAAoB,aAAa,GAAG;AACtC,gBAAQ,cAAc,SAAS;AAAA,UAC7B;AACE,iBAAK,QAAQ,KAAK,iBAAiB;AAAA,cACjC,UAAU,cAAc;AAAA,cACxB,aAAa,cAAc;AAAA,cAC3B,UAAU,cAAc;AAAA,cACxB,UAAU,cAAc;AAAA,cACxB,SAAS,cAAc;AAAA,YACzB,CAAC;AACD;AAAA,UAEF;AACE,iBAAK,QAAQ,KAAK,mBAAmB;AAAA,cACnC,UAAU,cAAc;AAAA,cACxB,aAAa,cAAc;AAAA,cAC3B,kBAAkB,cAAc;AAAA,YAClC,CAAC;AACD;AAAA,QACJ;AAAA,MACF,WAAW,gBAAgB,aAAa,GAAG;AACzC,gBAAQ,cAAc,KAAK;AAAA,UACzB;AAAA,UACA;AACE,iBAAK,QAAQ,KAAK,iBAAiB;AAAA,cACjC,UAAU,cAAc,IAAI;AAAA,cAC5B,aAAa,cAAc,KAAK;AAAA,cAChC,UAAU,cAAc,KAAK;AAAA,cAC7B,UAAU,cAAc,KAAK;AAAA,cAC7B,WAAW,cAAc,KAAK;AAAA,YAChC,CAAC;AACD;AAAA,UAEF,mCAAyC;AACvC,kBAAM,UAA0C;AAAA,cAC9C,GAAG;AAAA,cACH,GAAG;AAAA,cACH,GAAG;AAAA,cACH,GAAG;AAAA,cACH,GAAG;AAAA,cACH,GAAG;AAAA,YACL;AACA,kBAAM,cAAgD;AAAA,cACpD,GAAG;AAAA,cACH,GAAG;AAAA,cACH,GAAG;AAAA,cACH,GAAG;AAAA,cACH,GAAG;AAAA,YACL;AAEA,iBAAK,QAAQ,KAAK,gBAAgB;AAAA,cAChC,UAAU,cAAc,IAAI;AAAA,cAC5B,MAAM,cAAc,KAAK,MAAM,KAAK,QAAQ,cAAc,KAAK,MAAM,EAAE,IAAI;AAAA,cAC3E,UAAU,cAAc,KAAK,MAAM;AAAA,cACnC,UAAU,cAAc,KAAK,MAAM,OAAO,SAAY,YAAY,cAAc,KAAK,MAAM,EAAE,IAAI;AAAA,YACnG,CAAC;AACD;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,WAAK,QAAQ,MAAM,gCAAgC,KAAK;AAAA,IAC1D;AAAA,EACF;AACF;","names":["InMessageType","OutMessageType","payload","_a"]}
|
|
1
|
+
{"version":3,"sources":["../src/api/Errors.ts","../src/api/Logger.ts","../src/lib/EventEmitter.ts","../src/lib/PayloadParser.ts","../src/lib/PayloadTypeGuards.ts","../src/types/mqtt/in/InMessageType.ts","../src/types/mqtt/out/OutMessageType.ts","../src/api/MysaApiClient.ts"],"sourcesContent":["/** Error thrown when attempting to access the Mysa API without proper authentication. */\nexport class UnauthenticatedError extends Error {\n /**\n * The underlying failure that prevented authentication.\n *\n * Callers need it to tell a credential rejection apart from a transport or service failure, since both surface as\n * this error. Declared explicitly rather than passed to `super` so the SDK keeps compiling against its current lib,\n * which predates the two-argument `Error` constructor.\n */\n readonly cause?: unknown;\n\n /**\n * Creates a new UnauthenticatedError instance.\n *\n * @param message - The error message\n * @param cause - The underlying failure that prevented authentication, if any.\n */\n constructor(message: string, cause?: unknown) {\n super(message);\n this.name = 'UnauthenticatedError';\n this.cause = cause;\n }\n}\n\n/** Error thrown when a Mysa API request fails. */\nexport class MysaApiError extends Error {\n /** The HTTP status code returned by the API */\n readonly status: number;\n /** The HTTP status text returned by the API */\n readonly statusText: string;\n\n /**\n * Creates a new MysaApiError instance.\n *\n * @param apiResponse - The failed Response object from the API call\n */\n constructor(apiResponse: Response) {\n super(\n `Failed to call the '${apiResponse.url}' Mysa API endpoint. The server responded with a status of ${apiResponse.status} (${apiResponse.statusText}).`\n );\n this.name = 'MysaApiError';\n this.status = apiResponse.status;\n this.statusText = apiResponse.statusText;\n }\n}\n\n/** Error thrown when a device id does not match any device on the account. */\nexport class UnknownDeviceError extends Error {\n /**\n * Creates a new UnknownDeviceError instance.\n *\n * @param deviceId - The device id that could not be resolved\n */\n constructor(public readonly deviceId: string) {\n super(`Unknown device id '${deviceId}': no such device on this account.`);\n this.name = 'UnknownDeviceError';\n }\n}\n\n/** Error thrown when a requested fan speed is not supported by the target device. */\nexport class UnsupportedFanSpeedError extends Error {\n /**\n * Creates a new UnsupportedFanSpeedError instance.\n *\n * @param deviceId - The id of the device the command was aimed at.\n * @param fanSpeed - The requested fan speed that the device does not support.\n * @param supportedFanSpeeds - The fan speeds the device does support.\n */\n constructor(\n public readonly deviceId: string,\n public readonly fanSpeed: string,\n public readonly supportedFanSpeeds: string[]\n ) {\n super(\n `Device '${deviceId}' does not support the '${fanSpeed}' fan speed. Supported fan speeds: ${supportedFanSpeeds.join(', ') || '(none)'}.`\n );\n this.name = 'UnsupportedFanSpeedError';\n }\n}\n\n/** Error thrown when an MQTT publish ultimately fails after retry attempts. */\nexport class MqttPublishError extends Error {\n /**\n * Creates a new MqttPublishError instance.\n *\n * @param message - A human-readable description of the publish failure.\n * @param attempts - The number of attempts that were made before giving up.\n * @param original - The original error object thrown by the underlying MQTT library (optional).\n */\n constructor(\n message: string,\n public attempts: number,\n public original?: unknown\n ) {\n super(message);\n this.name = 'MqttPublishError';\n }\n}\n","/** Interface for logging operations at different severity levels */\nexport interface Logger {\n /** Logs a debug message with optional metadata */\n debug(message: string, ...meta: unknown[]): void;\n\n /** Logs an info message with optional metadata */\n info(message: string, ...meta: unknown[]): void;\n\n /** Logs a warning message with optional metadata */\n warn(message: string, ...meta: unknown[]): void;\n\n /** Logs an error message with optional metadata */\n error(message: string, ...meta: unknown[]): void;\n}\n\n/** Logger implementation that silently discards all log messages. */\n/* eslint-disable @typescript-eslint/no-unused-vars */\nexport class VoidLogger implements Logger {\n debug(message: string, ...meta: unknown[]): void {}\n info(message: string, ...meta: unknown[]): void {}\n warn(message: string, ...meta: unknown[]): void {}\n error(message: string, ...meta: unknown[]): void {}\n}\n/* eslint-enable @typescript-eslint/no-unused-vars */\n","import { EventEmitter as NodeEventEmitter } from 'node:events';\n\n/**\n * Typed wrapper around Node's `EventEmitter` class.\n *\n * @remarks\n * Source: {@link https://blog.makerx.com.au/a-type-safe-event-emitter-in-node-js}\n */\n/* eslint-disable @typescript-eslint/no-explicit-any */\nexport class EventEmitter<TEvents extends Record<string, any>> implements NodeJS.EventEmitter {\n private _emitter = new NodeEventEmitter();\n\n emit<TEventName extends keyof TEvents & string>(eventName: TEventName, ...eventArg: TEvents[TEventName]) {\n return this._emitter.emit(eventName, ...(eventArg as []));\n }\n\n on<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n handler: (...eventArg: TEvents[TEventName]) => void\n ) {\n this._emitter.on(eventName, handler as any);\n return this;\n }\n\n once<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n handler: (...eventArg: TEvents[TEventName]) => void\n ) {\n this._emitter.once(eventName, handler as any);\n return this;\n }\n\n off<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n handler: (...eventArg: TEvents[TEventName]) => void\n ) {\n this._emitter.off(eventName, handler as any);\n return this;\n }\n\n addListener<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n listener: (...args: TEvents[TEventName]) => void\n ) {\n this._emitter.addListener(eventName, listener);\n return this;\n }\n\n removeListener<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n listener: (...args: TEvents[TEventName]) => void\n ) {\n this._emitter.removeListener(eventName, listener);\n return this;\n }\n\n removeAllListeners<TEventName extends keyof TEvents & string>(eventName?: TEventName | undefined) {\n this._emitter.removeAllListeners(eventName);\n return this;\n }\n\n setMaxListeners(n: number) {\n this._emitter.setMaxListeners(n);\n return this;\n }\n\n getMaxListeners(): number {\n return this._emitter.getMaxListeners();\n }\n\n listeners<TEventName extends keyof TEvents & string>(eventName: TEventName) {\n return this._emitter.listeners(eventName);\n }\n\n rawListeners<TEventName extends keyof TEvents & string>(eventName: TEventName) {\n return this._emitter.rawListeners(eventName);\n }\n\n listenerCount<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n listener?: (...args: TEvents[TEventName]) => void\n ) {\n return this._emitter.listenerCount(eventName, listener);\n }\n\n prependListener<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n listener: (...args: TEvents[TEventName]) => void\n ) {\n this._emitter.prependListener(eventName, listener);\n return this;\n }\n\n prependOnceListener<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n listener: (...args: TEvents[TEventName]) => void\n ) {\n this._emitter.prependOnceListener(eventName, listener);\n return this;\n }\n\n eventNames() {\n return this._emitter.eventNames();\n }\n}\n/* eslint-enable @typescript-eslint/no-explicit-any */\n","import { InPayload } from '@/types/mqtt/InPayload';\nimport { OutPayload } from '@/types/mqtt/OutPayload';\n\n/**\n * Parses an MQTT payload from binary data into a typed OutPayload object.\n *\n * Converts the raw ArrayBuffer received from MQTT messages into a structured TypeScript object representing device\n * status, state changes, or other outgoing message types from Mysa devices.\n *\n * @param payload - The raw binary MQTT message payload as ArrayBuffer\n * @returns The parsed payload as a typed OutPayload object\n * @throws Error if the payload cannot be decoded or parsed as valid JSON\n */\nexport function parseMqttPayload(payload: ArrayBuffer): OutPayload {\n try {\n const decoder = new TextDecoder('utf-8');\n const jsonString = decoder.decode(payload);\n return JSON.parse(jsonString);\n } catch (error) {\n // No console output here: the SDK logger is the only sanctioned sink, and\n // the raw payload must not leak into output the consumer cannot route or\n // silence. The caller (_processMqttMessage) logs through the Logger.\n // The cause is attached via assignment because the project's TS lib\n // predates the ES2022 Error options constructor; Node supports it.\n const parseError = new Error('Failed to parse MQTT payload');\n (parseError as Error & { cause?: unknown }).cause = error;\n throw parseError;\n }\n}\n\n/**\n * Serializes an InPayload object into binary data for MQTT transmission.\n *\n * Converts a typed TypeScript payload object into the binary ArrayBuffer format required for sending commands and\n * requests to Mysa devices via MQTT.\n *\n * @typeParam T - The specific InPayload type being serialized\n * @param payload - The typed payload object to serialize\n * @returns The serialized payload as ArrayBuffer ready for MQTT transmission\n */\nexport function serializeMqttPayload<T extends InPayload>(payload: T): Uint8Array<ArrayBuffer> {\n const jsonString = JSON.stringify(payload);\n const encoder = new TextEncoder();\n return encoder.encode(jsonString);\n}\n","import { MsgOutPayload } from '@/types/mqtt/MsgOutPayload';\nimport { MsgTypeOutPayload } from '@/types/mqtt/MsgTypeOutPayload';\nimport { OutPayload } from '@/types/mqtt/OutPayload';\n\n/**\n * Type guard function to determine if an OutPayload is a MsgType-based payload.\n *\n * Checks whether the payload uses the legacy MsgType field format for message type identification. This is used to\n * differentiate between different payload structures and ensure proper type narrowing in TypeScript.\n *\n * @param payload - The OutPayload to check\n * @returns True if the payload is a MsgTypeOutPayload, false otherwise\n */\nexport function isMsgTypeOutPayload(payload: OutPayload): payload is MsgTypeOutPayload {\n return 'MsgType' in payload;\n}\n\n/**\n * Type guard function to determine if an OutPayload is a message-based payload.\n *\n * Checks whether the payload uses the newer msg field format for message type identification. This is used to\n * differentiate between different payload structures and ensure proper type narrowing in TypeScript.\n *\n * @param payload - The OutPayload to check\n * @returns True if the payload is a MsgOutPayload, false otherwise\n */\nexport function isMsgOutPayload(payload: OutPayload): payload is MsgOutPayload {\n return 'msg' in payload;\n}\n","/**\n * Enumeration of message types for incoming MQTT messages from clients to devices.\n *\n * These message types determine how commands and requests are interpreted by Mysa devices. The enum values correspond\n * to specific numeric identifiers used in the MQTT protocol.\n */\nexport enum InMessageType {\n //\n // When the message type is reported in the `MsgType` field of the payload.\n //\n\n /** Request to check and retrieve current device settings */\n CHECK_DEVICE_SETTINGS = 6,\n\n /** Command to start publishing periodic device status updates */\n START_PUBLISHING_DEVICE_STATUS = 11,\n\n //\n // When the message type is reported in the `msg` field of the payload.\n //\n\n /** Command to change the current state of a device (temperature, mode, etc.) */\n CHANGE_DEVICE_STATE = 44\n}\n","/**\n * Enumeration of message types for outgoing MQTT messages from devices to clients.\n *\n * These message types identify different kinds of status updates, notifications, and data reports that Mysa devices can\n * send via MQTT. The enum values correspond to specific numeric identifiers used in the MQTT protocol.\n */\nexport enum OutMessageType {\n //\n // When the message type is reported in the `MsgType` field of the payload.\n //\n\n /** Version 1 device status report with basic device information */\n DEVICE_V1_STATUS = 0,\n\n /** Notification that a device's temperature setpoint has been changed */\n DEVICE_SETPOINT_CHANGE = 1,\n\n /** Device log entry or diagnostic information */\n DEVICE_LOG = 4,\n\n /** Notification sent when a device completes its boot sequence */\n DEVICE_POST_BOOT = 10,\n\n //\n // When the message type is reported in the `msg` field of the payload.\n //\n\n /** Version 2 device status report with enhanced device information */\n DEVICE_V2_STATUS = 40,\n\n /** AC device status report with temperature, humidity, setpoint, and AC-specific fields */\n DEVICE_AC_STATUS = 30,\n\n /** Notification that a device's operational state has changed */\n DEVICE_STATE_CHANGE = 44\n}\n","import { MysaCredentials } from '@/api/MysaCredentials';\nimport { EventEmitter } from '@/lib/EventEmitter';\nimport { parseMqttPayload, serializeMqttPayload } from '@/lib/PayloadParser';\nimport { isMsgOutPayload, isMsgTypeOutPayload } from '@/lib/PayloadTypeGuards';\nimport { ChangeDeviceState } from '@/types/mqtt/in/ChangeDeviceState';\nimport { InMessageType } from '@/types/mqtt/in/InMessageType';\nimport { StartPublishingDeviceStatus } from '@/types/mqtt/in/StartPublishingDeviceStatus';\nimport { OutMessageType } from '@/types/mqtt/out/OutMessageType';\nimport { DeviceBase, Devices, DeviceStates, Firmwares, Homes } from '@/types/rest';\nimport { DescribeThingCommand, IoTClient } from '@aws-sdk/client-iot';\nimport { fromCognitoIdentityPool } from '@aws-sdk/credential-providers';\nimport { AuthenticationDetails, CognitoUser, CognitoUserPool, CognitoUserSession } from 'amazon-cognito-identity-js';\nimport { auth, iot, mqtt } from 'aws-iot-device-sdk-v2';\nimport { hash } from 'crypto';\nimport dayjs, { Dayjs } from 'dayjs';\nimport duration from 'dayjs/plugin/duration.js';\nimport { customAlphabet } from 'nanoid';\nimport {\n MqttPublishError,\n MysaApiError,\n UnauthenticatedError,\n UnknownDeviceError,\n UnsupportedFanSpeedError\n} from './Errors';\nimport { Logger, VoidLogger } from './Logger';\nimport { MysaApiClientEventTypes } from './MysaApiClientEventTypes';\nimport { MysaApiClientOptions } from './MysaApiClientOptions';\nimport { MysaDeviceMode, MysaFanSpeedMode } from './MysaDeviceMode';\n\ndayjs.extend(duration);\n\nconst getRandomClientId = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 8);\n\n/** Options for MQTT publish operations. */\nexport interface MqttPublishOptions {\n /** Maximum number of publish attempts before failing (default: 5). */\n maxAttempts?: number;\n /** Base delay in milliseconds used for exponential backoff calculation (default: 500). */\n baseDelayMs?: number;\n}\n\nconst AwsRegion = 'us-east-1';\nconst CognitoUserPoolId = 'us-east-1_GUFWfhI7g';\nconst CognitoClientId = '19efs8tgqe942atbqmot5m36t3';\nconst CognitoIdentityPoolId = 'us-east-1:ebd95d52-9995-45da-b059-56b865a18379';\nconst CognitoLoginKey = `cognito-idp.${AwsRegion}.amazonaws.com/${CognitoUserPoolId}`;\nconst MqttEndpoint = 'a3q27gia9qg3zy-ats.iot.us-east-1.amazonaws.com';\nconst MysaApiBaseUrl = 'https://app-prod.mysa.cloud';\nconst RealtimeKeepAliveInterval = dayjs.duration(5, 'minutes');\nconst PublishAckTimeout = dayjs.duration(30, 'seconds');\n\n/** Rolling window over which MQTT interrupts are counted for storm detection. */\nconst MqttInterruptWindow = dayjs.duration(30, 'seconds');\n/** Number of interrupts within {@link MqttInterruptWindow} that triggers a forced connection reset. */\nconst MqttInterruptThreshold = 3;\n/** Base delay for exponential backoff between consecutive forced MQTT resets. */\nconst MqttResetBaseDelay = dayjs.duration(1, 'second');\n/** Maximum delay between consecutive forced MQTT resets. */\nconst MqttResetMaxDelay = dayjs.duration(30, 'seconds');\n/** How long a connection must stay interrupt-free before the consecutive-reset counter is cleared. */\nconst MqttStabilityWindow = dayjs.duration(60, 'seconds');\n\n/** Canonical fan-speed order, matching the positional layout of a device's `SupportedCaps.fanSpeeds`. */\nconst CanonicalFanSpeedOrder: MysaFanSpeedMode[] = ['auto', 'low', 'medium', 'high', 'max'];\n\n/** Universal fan-speed `fn` mapping used when a device does not report its own `SupportedCaps.fanSpeeds`. */\nconst LegacyFanSpeedSendMap: Record<MysaFanSpeedMode, number> = { auto: 1, low: 3, medium: 5, high: 7, max: 8 };\n\n/**\n * Receive-side `fn`-to-fan-speed mapping. Includes both the legacy universal values (3/5/7) and the AC-V1-X\n * CodeNum=1117 canonical values (2/4/6); the latter are unused by legacy devices, so there is no conflict.\n */\nconst FanSpeedReceiveMap: Record<number, MysaFanSpeedMode> = {\n 1: 'auto',\n 2: 'low', // CodeNum=1117 canonical low\n 3: 'low', // legacy\n 4: 'medium', // CodeNum=1117 canonical medium\n 5: 'medium', // legacy\n 6: 'high', // CodeNum=1117 canonical high\n 7: 'high', // legacy\n 8: 'max'\n};\n\n/**\n * Builds the send-side fan-speed `fn` mapping for a device.\n *\n * When the device reports `SupportedCaps.fanSpeeds`, its values are zipped positionally with\n * {@link CanonicalFanSpeedOrder} (e.g. `[1, 2, 4, 6]` → `{ auto: 1, low: 2, medium: 4, high: 6 }`). Otherwise the\n * {@link LegacyFanSpeedSendMap} is used, preserving backward compatibility.\n *\n * @param device - The device to build the mapping for.\n * @returns A partial map from fan-speed mode to the device-specific `fn` value.\n */\nfunction buildFanSpeedSendMap(device: DeviceBase): Partial<Record<MysaFanSpeedMode, number>> {\n const fanSpeeds = device.SupportedCaps?.fanSpeeds;\n if (!fanSpeeds || fanSpeeds.length === 0) {\n return LegacyFanSpeedSendMap;\n }\n\n const map: Partial<Record<MysaFanSpeedMode, number>> = {};\n CanonicalFanSpeedOrder.forEach((name, index) => {\n if (index < fanSpeeds.length) {\n map[name] = fanSpeeds[index];\n }\n });\n return map;\n}\n\n/**\n * Main client for interacting with the Mysa API and real-time device communication.\n *\n * The MysaApiClient provides a comprehensive interface for authenticating with Mysa services, managing device data, and\n * receiving real-time updates from Mysa thermostats and heating devices. It handles both REST API calls for device\n * management and MQTT connections for live status updates and control commands.\n *\n * @example\n *\n * ```typescript\n * const client = new MysaApiClient({ username: 'user@example.com', password: 'password' });\n *\n * await client.login();\n * const devices = await client.getDevices();\n *\n * client.emitter.on('statusChanged', (status) => {\n * console.log(`Device ${status.deviceId} temperature: ${status.temperature}°C`);\n * });\n *\n * for (const device of Object.entries(devices.DevicesObj)) {\n * await client.startRealtimeUpdates(device[0]);\n * }\n * ```\n */\nexport class MysaApiClient {\n /** The credentials of the Mysa account this client authenticates as. */\n private _credentials: MysaCredentials;\n\n /** The current session object, if any. */\n private _cognitoUserSession?: CognitoUserSession;\n\n /** The current user object, if any. */\n private _cognitoUser?: CognitoUser;\n\n /** The in-flight session acquisition, if any, so that concurrent callers share a single refresh or login. */\n private _freshSessionPromise?: Promise<CognitoUserSession>;\n\n /** The logger instance used by the client. */\n private _logger: Logger;\n\n /** The fetcher function used by the client. */\n private _fetcher: typeof fetch;\n\n /** A promise that resolves to the MQTT connection used for real-time updates. */\n private _mqttConnectionPromise?: Promise<mqtt.MqttClientConnection>;\n\n /** Stable per-process MQTT client id (prevents collisions between multiple processes). */\n private _mqttClientId?: string;\n\n /** Expiration time of the credentials currently in use by the MQTT client. */\n private _mqttCredentialsExpiration?: Dayjs;\n\n /** Interrupt timestamps for storm / collision detection. */\n private _mqttInterrupts: number[] = [];\n\n /** Whether a forced MQTT reset is currently in progress (guards against re-entrancy). */\n private _mqttResetInProgress = false;\n\n /** Monotonic id of the current MQTT connection; used to ignore events from discarded connections. */\n private _mqttGeneration = 0;\n\n /** Consecutive forced resets without an intervening stable period; drives reset backoff. */\n private _mqttConsecutiveResets = 0;\n\n /** Timestamp (ms) of the most recent successful MQTT (re)connect, for interrupt dwell-time diagnostics. */\n private _mqttLastConnectionSuccessAt?: number;\n\n /** Timer that clears the consecutive-reset counter once a connection stays healthy. */\n private _mqttStabilityTimer?: NodeJS.Timeout;\n\n /** The device IDs that are currently being updated in real-time, mapped to their respective timeouts. */\n private _realtimeDeviceIds: Map<string, NodeJS.Timeout> = new Map();\n\n /**\n * Raw topic filters registered via {@link startRawTopicCapture}, mapped to their message handlers. Re-subscribed on\n * every reconnect so a debug capture survives connection resets.\n */\n private _rawTopicCaptures: Map<string, (topic: string, payload: string) => void> = new Map();\n\n /** The cached devices object, if any. */\n private _cachedDevices?: Devices;\n\n /**\n * Event emitter for client events.\n *\n * @see {@link MysaApiClientEventTypes} for the possible events and their payloads.\n */\n readonly emitter = new EventEmitter<MysaApiClientEventTypes>();\n\n /**\n * Constructs a new instance of the MysaApiClient.\n *\n * @param credentials - The credentials of the Mysa account to authenticate as.\n * @param options - The options for the client.\n */\n constructor(credentials: MysaCredentials, options?: MysaApiClientOptions) {\n this._credentials = credentials;\n this._logger = options?.logger || new VoidLogger();\n this._fetcher = options?.fetcher || fetch;\n }\n\n /**\n * Ensures the client has a usable session, logging in with the credentials it was constructed with if needed.\n *\n * Calling this method is optional: the client authenticates on demand before its first API call, and re-authenticates\n * on its own whenever its session can no longer be refreshed. Call it explicitly at startup to fail fast on invalid\n * credentials instead of on the first API call. It is a no-op when the current session is still usable.\n *\n * @example\n *\n * ```typescript\n * try {\n * await client.login();\n * console.log('Login successful!');\n * } catch (error) {\n * console.error('Login failed:', error.message);\n * }\n * ```\n *\n * @throws {@link UnauthenticatedError} When authentication fails due to invalid credentials or network issues.\n */\n async login(): Promise<void> {\n // Goes through _getFreshSession so that an explicit login shares any acquisition already in flight instead of\n // racing a concurrent API call into a second Cognito login.\n await this._getFreshSession();\n }\n\n /**\n * Authenticates with Mysa's Cognito user pool and replaces the current session, if any.\n *\n * @returns A promise that resolves to the newly established session.\n * @throws {@link Error} When authentication fails due to invalid credentials or network issues.\n */\n private _login(): Promise<CognitoUserSession> {\n this._cognitoUser = undefined;\n this._cognitoUserSession = undefined;\n this._mqttClientId = undefined;\n this._mqttInterrupts = [];\n this._mqttConsecutiveResets = 0;\n\n const { username, password } = this._credentials;\n\n return new Promise((resolve, reject) => {\n const user = new CognitoUser({\n Username: username,\n Pool: new CognitoUserPool({ UserPoolId: CognitoUserPoolId, ClientId: CognitoClientId })\n });\n\n user.authenticateUser(new AuthenticationDetails({ Username: username, Password: password }), {\n onSuccess: (session) => {\n this._cognitoUser = user;\n this._cognitoUserSession = session;\n\n resolve(session);\n },\n onFailure: (err) => {\n reject(err);\n }\n });\n });\n }\n\n /**\n * Retrieves the list of devices associated with the user.\n *\n * This method fetches all Mysa devices linked to the authenticated user's account, including device information such\n * as models, locations, and configuration details.\n *\n * @example\n *\n * ```typescript\n * const devices = await client.getDevices();\n * for (const [deviceId, device] of Object.entries(devices.DevicesObj)) {\n * console.log(`Device: ${device.DisplayName} (${device.Model})`);\n * }\n * ```\n *\n * @returns A promise that resolves to the list of devices.\n * @throws {@link MysaApiError} When the API request fails.\n * @throws {@link UnauthenticatedError} When the client cannot authenticate with its credentials.\n */\n async getDevices(): Promise<Devices> {\n this._logger.debug(`Fetching devices...`);\n\n const session = await this._getFreshSession();\n\n const response = await this._fetcher(`${MysaApiBaseUrl}/devices`, {\n headers: {\n Authorization: `${session.getIdToken().getJwtToken()}`\n }\n });\n\n if (!response.ok) {\n throw new MysaApiError(response);\n }\n\n return response.json();\n }\n\n /**\n * Retrieves the serial number for a specific device.\n *\n * This method uses AWS IoT's DescribeThing API to fetch the serial number attribute for the specified device. This\n * requires additional AWS IoT permissions and may not be available for all devices.\n *\n * @example\n *\n * ```typescript\n * const serialNumber = await client.getDeviceSerialNumber('device123');\n * if (serialNumber) {\n * console.log(`Device serial: ${serialNumber}`);\n * } else {\n * console.log('Serial number not available');\n * }\n * ```\n *\n * @param deviceId - The ID of the device to get the serial number for.\n * @returns A promise that resolves to the serial number, or undefined if not found.\n * @throws {@link UnauthenticatedError} When the client cannot authenticate with its credentials.\n */\n async getDeviceSerialNumber(deviceId: string): Promise<string | undefined> {\n this._logger.debug(`Fetching serial number for device ${deviceId}...`);\n\n const session = await this._getFreshSession();\n\n // Get AWS credentials for IoT client\n const credentialsProvider = fromCognitoIdentityPool({\n clientConfig: {\n region: AwsRegion\n },\n identityPoolId: CognitoIdentityPoolId,\n logins: {\n [CognitoLoginKey]: session.getIdToken().getJwtToken()\n }\n });\n\n const credentials = await credentialsProvider();\n const iotClient = new IoTClient({\n region: AwsRegion,\n credentials: {\n accessKeyId: credentials.accessKeyId,\n secretAccessKey: credentials.secretAccessKey,\n sessionToken: credentials.sessionToken\n }\n });\n\n try {\n const command = new DescribeThingCommand({ thingName: deviceId });\n const response = await iotClient.send(command);\n return response.attributes?.['Serial'];\n } catch (error) {\n this._logger.warn(`Could not get serial number for device ${deviceId}:`, error);\n return undefined;\n }\n }\n\n /**\n * Retrieves firmware information for all devices.\n *\n * @returns A promise that resolves to the firmware information for all devices.\n * @throws {@link MysaApiError} When the API request fails.\n * @throws {@link UnauthenticatedError} When the client cannot authenticate with its credentials.\n */\n async getDeviceFirmwares(): Promise<Firmwares> {\n this._logger.debug(`Fetching device firmwares...`);\n\n const session = await this._getFreshSession();\n\n const response = await this._fetcher(`${MysaApiBaseUrl}/devices/firmware`, {\n headers: {\n Authorization: `${session.getIdToken().getJwtToken()}`\n }\n });\n\n if (!response.ok) {\n throw new MysaApiError(response);\n }\n\n return response.json();\n }\n\n /**\n * Retrieves the current state information for all devices.\n *\n * @returns A promise that resolves to the current state of all devices.\n * @throws {@link MysaApiError} When the API request fails.\n * @throws {@link UnauthenticatedError} When the client cannot authenticate with its credentials.\n */\n async getDeviceStates(): Promise<DeviceStates> {\n this._logger.debug(`Fetching device states...`);\n\n const session = await this._getFreshSession();\n\n const response = await this._fetcher(`${MysaApiBaseUrl}/devices/state`, {\n headers: {\n Authorization: `${session.getIdToken().getJwtToken()}`\n }\n });\n\n if (!response.ok) {\n throw new MysaApiError(response);\n }\n\n return response.json();\n }\n\n /**\n * Retrieves information about all homes associated with the user.\n *\n * @returns A promise that resolves to the homes information.\n * @throws {@link MysaApiError} When the API request fails.\n * @throws {@link UnauthenticatedError} When the client cannot authenticate with its credentials.\n */\n async getHomes(): Promise<Homes> {\n this._logger.debug(`Fetching homes...`);\n\n const session = await this._getFreshSession();\n\n const response = await this._fetcher(`${MysaApiBaseUrl}/homes`, {\n headers: {\n Authorization: `${session.getIdToken().getJwtToken()}`\n }\n });\n\n if (!response.ok) {\n throw new MysaApiError(response);\n }\n\n return response.json();\n }\n\n /**\n * Sets the state of a specific device by sending commands via MQTT.\n *\n * This method allows you to change the temperature set point and/or operating mode of a Mysa device. The command is\n * sent through the MQTT connection for real-time device control.\n *\n * @example\n *\n * ```typescript\n * // Set temperature to 22°C\n * await client.setDeviceState('device123', 22);\n *\n * // Turn device off\n * await client.setDeviceState('device123', undefined, 'off');\n *\n * // Set temperature and mode\n * await client.setDeviceState('device123', 20, 'heat');\n *\n * // Set fan speed\n * await client.setDeviceState('device123', undefined, undefined, 'auto');\n * ```\n *\n * @param deviceId - The ID of the device to control.\n * @param setPoint - The target temperature set point (optional).\n * @param mode - The operating mode to set (one of MysaDeviceMode values, or undefined to leave unchanged).\n * @param fanSpeed - The fan speed mode to set ('low', 'medium', 'high', 'max', 'auto', or undefined to leave\n * unchanged).\n * @throws {@link UnauthenticatedError} When the client cannot authenticate with its credentials.\n * @throws {@link UnknownDeviceError} When the device id does not match any device on the account.\n * @throws {@link UnsupportedFanSpeedError} When the requested fan speed is not supported by the device.\n * @throws {@link Error} When MQTT connection or command sending fails.\n */\n async setDeviceState(deviceId: string, setPoint?: number, mode?: MysaDeviceMode, fanSpeed?: MysaFanSpeedMode) {\n this._logger.debug(`Setting device state for '${deviceId}'`);\n\n if (!this._cachedDevices) {\n this._cachedDevices = await this.getDevices();\n }\n\n // Own-property check: an inherited key such as 'constructor' would otherwise pass the guard below and fail later\n // with a TypeError on device.Model instead of UnknownDeviceError.\n if (!Object.prototype.hasOwnProperty.call(this._cachedDevices.DevicesObj, deviceId)) {\n throw new UnknownDeviceError(deviceId);\n }\n\n const device = this._cachedDevices.DevicesObj[deviceId];\n\n // Validate the session before reaching for a possibly cached MQTT connection. _getMqttConnection reuses\n // _mqttConnectionPromise without checking auth, so without this a command could be published on a still-open\n // connection after the session died — with no source ref to attribute it to.\n await this._getFreshSession();\n\n this._logger.debug(`Initializing MQTT connection...`);\n const mqttConnection = await this._getMqttConnection();\n\n const now = dayjs();\n\n this._logger.debug(`Sending request to set device state for '${deviceId}'...`);\n const modeMap = { off: 1, auto: 2, heat: 3, cool: 4, fan_only: 5, dry: 6 };\n const fanSpeedMap = buildFanSpeedSendMap(device);\n\n // Reject an unsupported fan speed (e.g. 'max' on a device whose SupportedCaps.fanSpeeds only covers auto/low/\n // medium/high) rather than silently publishing fn: undefined, which the caller would perceive as a no-op.\n if (fanSpeed !== undefined && fanSpeedMap[fanSpeed] === undefined) {\n throw new UnsupportedFanSpeedError(deviceId, fanSpeed, Object.keys(fanSpeedMap));\n }\n\n const payload = serializeMqttPayload<ChangeDeviceState>({\n msg: InMessageType.CHANGE_DEVICE_STATE,\n id: now.valueOf(),\n time: now.unix(),\n ver: '1.0',\n src: {\n ref: this._cognitoUser!.getUsername(),\n type: 100\n },\n dest: {\n ref: deviceId,\n type: 1\n },\n resp: 2,\n body: {\n ver: 1,\n type:\n device.Model.startsWith('BB-V1') || device.Model.startsWith('v1')\n ? 1\n : device.Model.startsWith('AC-V1')\n ? 2\n : device.Model.startsWith('INF-V1')\n ? 3\n : device.Model.startsWith('BB-V2')\n ? device.Model.endsWith('-L')\n ? 5\n : 4\n : 0,\n cmd: [\n {\n tm: -1,\n sp: setPoint,\n md: mode ? modeMap[mode] : undefined,\n fn: fanSpeed ? fanSpeedMap[fanSpeed] : undefined\n }\n ]\n }\n });\n\n try {\n await this._publishWithRetry(mqttConnection, `/v1/dev/${deviceId}/in`, payload, mqtt.QoS.AtLeastOnce);\n this._logger.debug(`Device state publish succeeded for '${deviceId}'`);\n } catch (error) {\n this._logger.error(`Failed to set device state for '${deviceId}'`, error);\n throw error;\n }\n }\n\n /**\n * Starts receiving real-time updates for the specified device.\n *\n * This method establishes an MQTT subscription to receive live status updates from the device, including temperature,\n * humidity, set point changes, and other state information. The client will automatically send keep-alive messages to\n * maintain the connection.\n *\n * @example\n *\n * ```typescript\n * // Start receiving updates and listen for events\n * await client.startRealtimeUpdates('device123');\n *\n * client.emitter.on('statusChanged', (status) => {\n * console.log(`Temperature: ${status.temperature}°C`);\n * });\n * ```\n *\n * @param deviceId - The ID of the device to start receiving updates for.\n * @throws {@link Error} When MQTT connection or subscription fails.\n */\n async startRealtimeUpdates(deviceId: string) {\n this._logger.info(`Starting real-time updates for device '${deviceId}'`);\n\n if (this._realtimeDeviceIds.has(deviceId)) {\n this._logger.debug(`Real-time updates for device '${deviceId}' already started`);\n return;\n }\n\n this._logger.debug(`Initializing MQTT connection...`);\n const mqttConnection = await this._getMqttConnection();\n\n this._logger.debug(`Subscribing to MQTT topic '/v1/dev/${deviceId}/out'...`);\n await mqttConnection.subscribe(`/v1/dev/${deviceId}/out`, mqtt.QoS.AtLeastOnce, (_, payload) => {\n this._processMqttMessage(payload);\n });\n\n this._logger.debug(`Sending request to start publishing device status for '${deviceId}'...`);\n const payload = serializeMqttPayload<StartPublishingDeviceStatus>({\n Device: deviceId,\n MsgType: InMessageType.START_PUBLISHING_DEVICE_STATUS,\n Timestamp: dayjs().unix(),\n Timeout: RealtimeKeepAliveInterval.asSeconds()\n });\n\n // A failed publish request must not abort startup (or, in the keep-alive\n // below, crash the process via an unhandled rejection): the subscription\n // above still delivers the device's autonomous periodic status reports\n // even when the request-driven status stream is unavailable.\n try {\n await this._publishWithRetry(mqttConnection, `/v1/dev/${deviceId}/in`, payload, mqtt.QoS.AtLeastOnce);\n } catch (error) {\n this._logger.warn(`Failed to request status publishing for '${deviceId}'; relying on periodic reports`, error);\n }\n\n const timer = setInterval(async () => {\n this._logger.debug(`Sending request to keep-alive publishing device status for '${deviceId}'...`);\n\n try {\n const connection = await this._getMqttConnection();\n const payload = serializeMqttPayload<StartPublishingDeviceStatus>({\n Device: deviceId,\n MsgType: InMessageType.START_PUBLISHING_DEVICE_STATUS,\n Timestamp: dayjs().unix(),\n Timeout: RealtimeKeepAliveInterval.asSeconds()\n });\n await this._publishWithRetry(connection, `/v1/dev/${deviceId}/in`, payload, mqtt.QoS.AtLeastOnce);\n } catch (error) {\n this._logger.warn(`Failed to keep-alive status publishing for '${deviceId}'`, error);\n }\n }, RealtimeKeepAliveInterval.subtract(10, 'seconds').asMilliseconds());\n\n this._realtimeDeviceIds.set(deviceId, timer);\n }\n\n /**\n * Stops receiving real-time updates for the specified device.\n *\n * This method unsubscribes from the MQTT topic for the specified device and clears any associated timers to stop the\n * keep-alive messages.\n *\n * @param deviceId - The ID of the device to stop receiving real-time updates for.\n * @throws {@link Error} When MQTT unsubscription fails.\n */\n async stopRealtimeUpdates(deviceId: string) {\n this._logger.info(`Stopping real-time updates for device '${deviceId}'`);\n\n const timer = this._realtimeDeviceIds.get(deviceId);\n if (!timer) {\n this._logger.warn(`No real-time updates are running for device '${deviceId}'`);\n return;\n }\n\n this._logger.debug(`Initializing MQTT connection...`);\n const mqttConnection = await this._getMqttConnection();\n\n this._logger.debug(`Unsubscribing to MQTT topic '/v1/dev/${deviceId}/out'...`);\n await mqttConnection.unsubscribe(`/v1/dev/${deviceId}/out`);\n\n clearInterval(timer);\n this._realtimeDeviceIds.delete(deviceId);\n }\n\n /**\n * Subscribes to raw MQTT topic filters and relays every message verbatim.\n *\n * Unlike {@link startRealtimeUpdates}, this performs no parsing, emits no typed events and sends no \"start publishing\"\n * request to the device — it simply forwards the full message topic and the decoded UTF-8 payload of everything that\n * arrives on the given filters. It exists to reverse-engineer device families the SDK does not model yet, most\n * notably the AWS IoT Device Shadow protocol used by the central-HVAC ST-V1 thermostats, where both the topic (which\n * shadow, and `accepted`/`rejected`/`delta`/`documents`) and the raw JSON body carry the information a new\n * implementation needs.\n *\n * The capture is passive: the device only publishes to its shadow topics when something drives a change (the Mysa\n * mobile app, a schedule, or the device itself), so exercise the thermostat while a capture is running.\n *\n * Registered filters are re-subscribed automatically after a reconnect.\n *\n * @param topicFilters - MQTT topic filters to subscribe to. Wildcards (`+`, `#`) are allowed, subject to the AWS IoT\n * policy attached to the account's Cognito identity — a filter the policy forbids resolves with a non-zero\n * `error_code`, which is logged rather than thrown so the remaining filters still subscribe.\n * @param handler - Invoked with the full message topic and the decoded UTF-8 payload for every message received.\n * @throws {@link Error} When the MQTT connection cannot be established.\n */\n async startRawTopicCapture(topicFilters: string[], handler: (topic: string, payload: string) => void): Promise<void> {\n this._logger.info(`Starting raw topic capture for ${topicFilters.length} filter(s)`);\n\n const connection = await this._getMqttConnection();\n const decoder = new TextDecoder('utf-8');\n\n for (const filter of topicFilters) {\n this._rawTopicCaptures.set(filter, handler);\n this._logger.debug(`Subscribing to raw topic filter '${filter}'...`);\n try {\n const result = await connection.subscribe(filter, mqtt.QoS.AtLeastOnce, (topic, payload) => {\n handler(topic, decoder.decode(payload));\n });\n this._logger.debug(\n `Raw subscribe to '${filter}' granted (topic='${result.topic}', qos=${result.qos}, ` +\n `error_code=${result.error_code ?? 0})`\n );\n } catch (error) {\n // A rejected filter (e.g. denied by the IoT policy) must not abort the whole capture: keep the\n // registration so a reconnect retries it, and let the remaining filters subscribe.\n this._logger.warn(`Failed to subscribe to raw topic filter '${filter}'`, error);\n }\n }\n }\n\n /**\n * Ensures a valid, non-expired session is available.\n *\n * This method checks if the current session is valid and not expired. If the session is expired, it automatically\n * refreshes it using the refresh token. If there is no session yet, or if the refresh token has itself expired or\n * been revoked, the client logs back in with its credentials.\n *\n * Concurrent callers share a single in-flight acquisition, so a burst of API calls never triggers more than one\n * refresh or login.\n *\n * @returns A promise that resolves to a valid CognitoUserSession.\n * @throws {@link UnauthenticatedError} When neither refreshing nor logging back in succeeds.\n */\n private async _getFreshSession(): Promise<CognitoUserSession> {\n if (\n this._cognitoUser &&\n this._cognitoUserSession?.isValid() &&\n dayjs.unix(this._cognitoUserSession.getIdToken().getExpiration()).isAfter()\n ) {\n this._logger.debug('Session is valid, no need to refresh');\n return this._cognitoUserSession;\n }\n\n this._freshSessionPromise ??= this._acquireFreshSession().finally(() => {\n this._freshSessionPromise = undefined;\n });\n\n return this._freshSessionPromise;\n }\n\n /**\n * Refreshes the current session, falling back to a full login when it cannot be refreshed.\n *\n * @returns A promise that resolves to a valid CognitoUserSession.\n * @throws {@link UnauthenticatedError} When logging back in fails.\n */\n private async _acquireFreshSession(): Promise<CognitoUserSession> {\n if (this._cognitoUser && this._cognitoUserSession) {\n this._logger.debug('Session is not valid or expired, refreshing...');\n\n try {\n return await new Promise<CognitoUserSession>((resolve, reject) => {\n this._cognitoUser!.refreshSession(this._cognitoUserSession!.getRefreshToken(), (error, session) => {\n if (error) {\n reject(error);\n } else {\n this._logger.debug('Session refreshed successfully');\n this._cognitoUserSession = session;\n resolve(session);\n }\n });\n });\n } catch (error) {\n // The refresh token itself expired, was revoked, or was rotated away. Fall through to a full login.\n this._logger.warn('Failed to refresh session, logging back in:', error);\n }\n }\n\n try {\n this._logger.info('Logging in...');\n return await this._login();\n } catch (error) {\n this._logger.error('Failed to log in:', error);\n throw new UnauthenticatedError('Unable to establish an authentication session.', error);\n }\n }\n\n /**\n * Establishes and returns an MQTT connection for real-time communication.\n *\n * This method creates a new MQTT connection if one doesn't exist, using AWS IoT WebSocket connections with Cognito\n * credentials. The connection is cached and reused for subsequent calls.\n *\n * @returns A promise that resolves to an active MQTT connection.\n * @throws {@link Error} When connection establishment fails.\n */\n private _getMqttConnection(): Promise<mqtt.MqttClientConnection> {\n if (!this._mqttConnectionPromise) {\n this._mqttConnectionPromise = this._createMqttConnection().catch((err) => {\n this._mqttConnectionPromise = undefined;\n throw err;\n });\n }\n\n return this._mqttConnectionPromise;\n }\n\n /**\n * Determines whether an MQTT-related error is considered transient and worth retrying.\n *\n * Transient errors include timeouts, cancelled operations due to clean sessions, temporary connectivity loss, and\n * other recoverable network issues. Fatal errors (auth, permission, configuration) should not be retried at this\n * layer.\n *\n * @param err - The error object thrown by the underlying MQTT operation.\n * @returns True if the error appears transient and a retry should be attempted; false otherwise.\n */\n private _isTransientMqttError(err: unknown): boolean {\n if (!err || typeof err !== 'object') {\n return false;\n }\n\n const anyErr = err as { error_code?: unknown; error_name?: unknown; error?: unknown; message?: unknown };\n const code = anyErr.error_code || anyErr.error_name || anyErr.error;\n const msg = (anyErr.message || anyErr.error || '').toString();\n\n const transientMarkers = [\n 'AWS_ERROR_MQTT_TIMEOUT',\n 'AWS_ERROR_MQTT_NO_CONNECTION',\n 'AWS_ERROR_MQTT_UNEXPECTED_HANGUP',\n 'UNEXPECTED_HANGUP',\n 'AWS_ERROR_MQTT_CONNECTION_DESTROYED',\n 'Time limit between request and response',\n 'timeout'\n ];\n\n return transientMarkers.some((m) => (code && String(code).includes(m)) || msg.includes(m));\n }\n\n /**\n * Publishes an MQTT message with exponential backoff retries for transient failures.\n *\n * Retries occur for errors classified by `_isTransientMqttError`. Between attempts the delay grows exponentially with\n * jitter to avoid thundering herds after broker recovery. If the connection is not currently marked as connected, a\n * reconnect is attempted; if that fails, the connection is rebuilt (fresh credentials) before the next retry.\n *\n * On final failure (after maxAttempts) a {@link MqttPublishError} is thrown including the number of attempts and\n * original error for higher-level handling.\n *\n * @remarks\n * Retry options fields:\n *\n * - MaxAttempts: Maximum number of publish attempts before failing (default: 5).\n * - BaseDelayMs: Base delay in milliseconds used for exponential backoff calculation (default: 500).\n *\n * @param connection - The active MQTT client connection used to send the publish.\n * @param topic - The MQTT topic to publish to.\n * @param payload - The serialized payload (binary buffer or Uint8Array).\n * @param qos - The desired MQTT QoS level for the publish.\n * @param opts - Retry options (defaults: maxAttempts=5, baseDelayMs=500).\n * @returns A promise that resolves when the publish succeeds, or rejects with {@link MqttPublishError}.\n */\n private async _publishWithRetry(\n connection: mqtt.MqttClientConnection,\n topic: string,\n payload: ArrayBuffer | Uint8Array,\n qos: mqtt.QoS,\n opts: MqttPublishOptions = {}\n ): Promise<void> {\n const maxAttempts = opts.maxAttempts ?? 5;\n const baseDelayMs = opts.baseDelayMs ?? 500;\n\n let attempt = 0;\n\n while (true) {\n attempt++;\n try {\n // Guard against publishes that never settle: a QoS1 publish the\n // broker silently ignores (e.g. an unauthorized topic) produces no\n // PUBACK and, in practice, no protocol-timeout rejection either —\n // the pending await would otherwise hang this call chain forever.\n await new Promise<void>((resolve, reject) => {\n const timer = setTimeout(\n () =>\n reject(new Error(`MQTT publish timeout: no acknowledgement within ${PublishAckTimeout.asSeconds()}s`)),\n PublishAckTimeout.asMilliseconds()\n );\n connection.publish(topic, payload, qos).then(\n () => {\n clearTimeout(timer);\n resolve();\n },\n (err) => {\n clearTimeout(timer);\n reject(err);\n }\n );\n });\n return;\n } catch (err) {\n const isTransient = this._isTransientMqttError(err);\n\n if (!isTransient || attempt >= maxAttempts) {\n throw new MqttPublishError(`MQTT publish failed after ${attempt} attempts`, attempt, err);\n }\n\n // Apply jitter: delay is randomized between 75% and 125% of the base exponential backoff\n const JITTER_MIN_FACTOR = 0.75;\n const JITTER_RANGE = 0.5;\n const delay = baseDelayMs * Math.pow(2, attempt - 1) * (JITTER_MIN_FACTOR + Math.random() * JITTER_RANGE);\n\n this._logger.warn(\n `Transient MQTT publish error on '${topic}' (attempt ${attempt}/${maxAttempts}). Retrying in ${Math.round(\n delay\n )}ms`\n );\n\n await new Promise((r) => setTimeout(r, delay));\n }\n }\n }\n\n /**\n * Creates a new MQTT connection using AWS IoT WebSocket connections with Cognito credentials.\n *\n * @returns A promise that resolves to an active MQTT connection.\n * @throws {@link Error} When connection establishment fails.\n */\n private async _createMqttConnection(): Promise<mqtt.MqttClientConnection> {\n const session = await this._getFreshSession();\n const credentialsProvider = fromCognitoIdentityPool({\n clientConfig: {\n region: AwsRegion\n },\n identityPoolId: CognitoIdentityPoolId,\n logins: {\n [CognitoLoginKey]: session.getIdToken().getJwtToken()\n },\n logger: this._logger\n });\n const credentials = await credentialsProvider();\n\n if (!credentials.expiration) {\n throw new Error('MQTT credentials do not have an expiration time.');\n }\n\n this._mqttCredentialsExpiration = dayjs(credentials.expiration);\n\n this._logger.debug(`MQTT credentials expiration: ${this._mqttCredentialsExpiration.format()}`);\n\n if (!this._mqttCredentialsExpiration.isAfter(dayjs())) {\n this._mqttCredentialsExpiration = undefined;\n throw new Error('MQTT credentials are already expired.');\n }\n\n // Per-process stable client id. Random suffix avoids collisions with other running processes.\n if (!this._mqttClientId) {\n const username = this._cognitoUser?.getUsername() ?? 'anon';\n const usernameHash = hash('sha1', username);\n this._mqttClientId = `mysa-js-sdk-${usernameHash}-${process.pid}-${getRandomClientId()}`;\n }\n\n const builder = iot.AwsIotMqttConnectionConfigBuilder.new_with_websockets()\n .with_credentials(AwsRegion, credentials.accessKeyId, credentials.secretAccessKey, credentials.sessionToken)\n .with_endpoint(MqttEndpoint)\n .with_client_id(this._mqttClientId)\n // Clean sessions: the broker never persists our subscriptions or queues QoS1 messages while we\n // are disconnected. This makes every (re)connect re-subscribe deterministically (see the\n // `resume` handler) and — crucially — prevents the broker from dumping a backlog of queued\n // messages on reconnect, which during an interrupt storm compounded into the ~1000x message /\n // DB-growth reports. It also stops each forced reset from leaving an orphaned broker session.\n .with_clean_session(true)\n .with_keep_alive_seconds(30)\n .with_ping_timeout_ms(3000)\n .with_protocol_operation_timeout_ms(60000)\n // A less-aggressive minimum backoff keeps the native reconnect loop from hammering the broker\n // during a storm (a likely contributor to the self-collision hangups); the stable connection\n // only pings every 30s, so nothing depends on 1s reconnects.\n .with_reconnect_min_sec(3)\n .with_reconnect_max_sec(30);\n\n const config = builder.build();\n\n // `with_credentials` bakes the credentials fetched above into a static\n // signer. Cognito credentials expire after ~1 hour; if the connection\n // drops and the native reconnect loop is still retrying when they lapse,\n // every subsequent handshake fails with a stale signature and no JS event\n // is ever emitted (`interrupt` only fires when an *established* connection\n // drops) — the client wedges silently, forever. Override the handshake\n // transform so every (re)connect attempt signs with freshly refreshed\n // credentials instead.\n config.websocket_handshake_transform = async (request, done) => {\n try {\n const freshSession = await this._getFreshSession();\n const freshCredentialsProvider = fromCognitoIdentityPool({\n clientConfig: {\n region: AwsRegion\n },\n identityPoolId: CognitoIdentityPoolId,\n logins: {\n [CognitoLoginKey]: freshSession.getIdToken().getJwtToken()\n },\n logger: this._logger\n });\n const freshCredentials = await freshCredentialsProvider();\n if (freshCredentials.expiration) {\n this._mqttCredentialsExpiration = dayjs(freshCredentials.expiration);\n }\n\n await auth.aws_sign_request(request, {\n algorithm: auth.AwsSigningAlgorithm.SigV4,\n signature_type: auth.AwsSignatureType.HttpRequestViaQueryParams,\n provider: auth.AwsCredentialsProvider.newStatic(\n freshCredentials.accessKeyId,\n freshCredentials.secretAccessKey,\n freshCredentials.sessionToken\n ),\n region: AwsRegion,\n service: 'iotdevicegateway',\n signed_body_value: auth.AwsSignedBodyValue.EmptySha256,\n omit_session_token: true\n });\n done();\n } catch (error) {\n this._logger.error('Failed to sign MQTT websocket handshake with fresh credentials', error);\n done(3 /* AWS_ERROR_UNKNOWN: fail this attempt; the reconnect loop retries */);\n }\n };\n\n const client = new mqtt.MqttClient();\n const connection = client.new_connection(config);\n\n // Tag this connection with a generation. Events from a discarded connection (e.g. one we\n // disconnected during a forced reset, or whose native reconnect loop is still winding down)\n // are ignored, so a dying connection can never mutate current state or trigger a second reset.\n const generation = ++this._mqttGeneration;\n\n connection.on('connect', (sessionPresent) => {\n if (generation !== this._mqttGeneration) return;\n this._logger.debug(`MQTT connect (clientId=${this._mqttClientId}, sessionPresent=${sessionPresent})`);\n });\n\n connection.on('connection_success', (result) => {\n if (generation !== this._mqttGeneration) return;\n this._mqttLastConnectionSuccessAt = Date.now();\n this._logger.debug(\n `MQTT connection_success (clientId=${this._mqttClientId}, sessionPresent=${result?.session_present}, ` +\n `reasonCode=${result?.reason_code})`\n );\n // A connection that stays up long enough is considered recovered; arm the timer that clears\n // the reset backoff counter.\n this._armMqttStabilityTimer(generation);\n });\n\n connection.on('connection_failure', (e) => {\n if (generation !== this._mqttGeneration) return;\n this._logger.error(`MQTT connection_failure (clientId=${this._mqttClientId})`, e);\n });\n\n connection.on('interrupt', (e) => {\n if (generation !== this._mqttGeneration) return;\n\n // Dwell time since the last successful connect. A tiny value (tens of ms) is the signature of\n // an immediate server-side close (the storm) rather than a genuine network drop — logged to\n // help pin down the still-undiagnosed server-side trigger from live runs.\n const dwellMs =\n this._mqttLastConnectionSuccessAt !== undefined ? Date.now() - this._mqttLastConnectionSuccessAt : undefined;\n this._logger.warn(\n `MQTT interrupt (clientId=${this._mqttClientId}, dwellMs=${dwellMs ?? 'n/a'}, generation=${generation})`,\n e\n );\n\n // A storm keeps interrupting; don't let the stability timer clear the backoff counter mid-storm.\n this._clearMqttStabilityTimer();\n\n const now = Date.now();\n this._mqttInterrupts = this._mqttInterrupts.filter((t) => now - t < MqttInterruptWindow.asMilliseconds());\n this._mqttInterrupts.push(now);\n\n const areCredentialsExpired = !(this._mqttCredentialsExpiration?.isAfter(dayjs()) ?? false);\n const isStorm = this._mqttInterrupts.length >= MqttInterruptThreshold;\n\n if (isStorm || areCredentialsExpired) {\n const reason = isStorm\n ? `High interrupt rate (${this._mqttInterrupts.length} in ${MqttInterruptWindow.asSeconds()}s)`\n : 'Credentials expired';\n // Fire-and-forget: _resetMqttConnection is fully self-contained and never rejects, so an\n // unhandled rejection can't escape this event handler and crash the process.\n void this._resetMqttConnection(reason);\n }\n });\n\n connection.on('resume', async (returnCode, sessionPresent) => {\n if (generation !== this._mqttGeneration) return;\n this._logger.info(\n `MQTT resume returnCode=${returnCode} sessionPresent=${sessionPresent} clientId=${this._mqttClientId}`\n );\n\n // With clean sessions the broker never restores our subscriptions, so re-subscribe on every\n // resume. (sessionPresent is expected to always be false now; the guard is kept for safety.)\n if (!sessionPresent) {\n try {\n await this._resubscribeAll(connection);\n } catch (err) {\n this._logger.error('Failed to re-subscribe after resume', err);\n }\n }\n });\n\n connection.on('error', (e) => {\n if (generation !== this._mqttGeneration) return;\n this._logger.error(`MQTT error (clientId=${this._mqttClientId})`, e);\n });\n\n connection.on('closed', () => {\n if (generation !== this._mqttGeneration) return;\n this._logger.info('MQTT connection closed');\n this._clearMqttStabilityTimer();\n this._mqttConnectionPromise = undefined;\n this._mqttCredentialsExpiration = undefined;\n });\n\n await connection.connect();\n\n return connection;\n }\n\n /**\n * Re-subscribes every device currently receiving real-time updates on the given connection.\n *\n * Safe to call on every (re)connect: aws-crt replaces the per-topic callback on a duplicate subscribe, so this never\n * registers a message handler more than once.\n *\n * @param connection - The connection to (re-)establish subscriptions on.\n */\n private async _resubscribeAll(connection: mqtt.MqttClientConnection): Promise<void> {\n for (const deviceId of Array.from(this._realtimeDeviceIds.keys())) {\n const topic = `/v1/dev/${deviceId}/out`;\n this._logger.debug(`Re-subscribing to ${topic}`);\n await connection.subscribe(topic, mqtt.QoS.AtLeastOnce, (_topic, payload) => {\n this._processMqttMessage(payload);\n });\n }\n\n // Restore any raw debug-capture subscriptions (see startRawTopicCapture) so a capture keeps\n // flowing across reconnects.\n const decoder = new TextDecoder('utf-8');\n for (const [filter, handler] of Array.from(this._rawTopicCaptures.entries())) {\n this._logger.debug(`Re-subscribing to raw topic filter '${filter}'`);\n try {\n await connection.subscribe(filter, mqtt.QoS.AtLeastOnce, (topic, payload) => {\n handler(topic, decoder.decode(payload));\n });\n } catch (error) {\n // One rejected filter must not abort the remaining resubscriptions (matches startRawTopicCapture).\n this._logger.warn(`Failed to re-subscribe to raw topic filter '${filter}'`, error);\n }\n }\n }\n\n /**\n * Arms (or re-arms) the stability timer. When a connection stays interrupt-free for {@link MqttStabilityWindow}, the\n * consecutive-reset backoff counter is cleared so a future isolated storm still gets a fast first reset.\n *\n * @param generation - The connection generation that armed the timer; the callback is a no-op if the connection has\n * since been replaced.\n */\n private _armMqttStabilityTimer(generation: number): void {\n this._clearMqttStabilityTimer();\n this._mqttStabilityTimer = setTimeout(() => {\n if (generation !== this._mqttGeneration) return;\n if (this._mqttConsecutiveResets > 0) {\n this._logger.debug('MQTT connection stable; clearing consecutive-reset counter');\n this._mqttConsecutiveResets = 0;\n }\n }, MqttStabilityWindow.asMilliseconds());\n // Don't keep the event loop alive solely for this bookkeeping timer.\n this._mqttStabilityTimer.unref?.();\n }\n\n /** Clears the stability timer, if armed. */\n private _clearMqttStabilityTimer(): void {\n if (this._mqttStabilityTimer) {\n clearTimeout(this._mqttStabilityTimer);\n this._mqttStabilityTimer = undefined;\n }\n }\n\n /**\n * Forcefully tears down the current MQTT connection and rebuilds it with a fresh client id and fresh credentials,\n * escaping interrupt storms that a plain native reconnect cannot.\n *\n * Repeatable and self-contained: only one reset runs at a time, consecutive resets without an intervening stable\n * period back off exponentially (up to {@link MqttResetMaxDelay}), and it never rejects — so it is safe to invoke\n * fire-and-forget from an event handler.\n *\n * @param reason - Human-readable reason for the reset, used in logs.\n */\n private async _resetMqttConnection(reason: string): Promise<void> {\n if (this._mqttResetInProgress) {\n return;\n }\n this._mqttResetInProgress = true;\n\n // Capture the connection to tear down before we clear the cached promise below.\n const connectionToClose = this._mqttConnectionPromise;\n\n try {\n this._mqttConsecutiveResets++;\n\n const delayMs = Math.min(\n MqttResetBaseDelay.asMilliseconds() * Math.pow(2, this._mqttConsecutiveResets - 1),\n MqttResetMaxDelay.asMilliseconds()\n );\n\n this._logger.warn(\n `${reason}. Forcing MQTT reset #${this._mqttConsecutiveResets} ` +\n `(new clientId, fresh credentials) after ${Math.round(delayMs)}ms...`\n );\n\n // Invalidate the current client id, credentials and interrupt history before rebuilding.\n this._mqttClientId = undefined;\n this._mqttCredentialsExpiration = undefined;\n this._mqttInterrupts = [];\n this._clearMqttStabilityTimer();\n\n // Clear the cached promise first so publishers calling _getMqttConnection() build a new\n // connection instead of reusing the one we're about to destroy.\n this._mqttConnectionPromise = undefined;\n\n // Tear down the old connection. Strip listeners so its native binding can be GC'd and so no\n // late event from it mutates current state (belt-and-suspenders with the generation guard).\n // Any in-flight publish rejecting with AWS_ERROR_MQTT_CONNECTION_DESTROYED is swallowed here.\n if (connectionToClose) {\n try {\n const connection = await connectionToClose;\n connection.removeAllListeners();\n await connection.disconnect();\n } catch (err) {\n this._logger.debug('Error tearing down old MQTT connection during reset (ignored)', err);\n }\n }\n\n if (delayMs > 0) {\n await new Promise((r) => setTimeout(r, delayMs));\n }\n\n const newConnection = await this._getMqttConnection();\n await this._resubscribeAll(newConnection);\n\n this._logger.info(`MQTT connection rebuilt successfully after reset #${this._mqttConsecutiveResets} (${reason})`);\n } catch (err) {\n this._logger.error('Failed to rebuild MQTT connection after reset', err);\n } finally {\n this._mqttResetInProgress = false;\n }\n }\n\n /**\n * Processes incoming MQTT messages and emits appropriate events.\n *\n * This method parses MQTT payloads and converts them into typed events that can be listened to via the client's event\n * emitter. It handles both v1 and v2 device message formats and emits events like 'statusChanged', 'setPointChanged',\n * and 'stateChanged'.\n *\n * @param payload - The raw MQTT message payload to process.\n */\n private _processMqttMessage(payload: ArrayBuffer) {\n try {\n const parsedPayload = parseMqttPayload(payload);\n\n this.emitter.emit('rawRealtimeMessageReceived', parsedPayload);\n\n if (isMsgTypeOutPayload(parsedPayload)) {\n switch (parsedPayload.MsgType) {\n case OutMessageType.DEVICE_V1_STATUS:\n this.emitter.emit('statusChanged', {\n deviceId: parsedPayload.Device,\n temperature: parsedPayload.MainTemp,\n humidity: parsedPayload.Humidity,\n setPoint: parsedPayload.SetPoint,\n current: parsedPayload.Current\n });\n break;\n\n case OutMessageType.DEVICE_SETPOINT_CHANGE:\n this.emitter.emit('setPointChanged', {\n deviceId: parsedPayload.Device,\n newSetPoint: parsedPayload.Next,\n previousSetPoint: parsedPayload.Prev\n });\n break;\n }\n } else if (isMsgOutPayload(parsedPayload)) {\n switch (parsedPayload.msg) {\n case OutMessageType.DEVICE_AC_STATUS:\n this.emitter.emit('statusChanged', {\n deviceId: parsedPayload.src.ref,\n temperature: parsedPayload.body.ambTemp,\n humidity: parsedPayload.body.hum,\n setPoint: parsedPayload.body.stpt,\n dutyCycle: parsedPayload.body.dtyCycle\n });\n break;\n\n case OutMessageType.DEVICE_V2_STATUS:\n // In-floor heating thermostats (INF-V1-0) share this message type but report a binary heating-relay state\n // (`heatStat`) instead of a fractional `dtyCycle`, plus a floor-probe temperature. `heatStat` (0 or 1) is a\n // valid 0.0-1.0 duty fraction, so it maps straight onto `dutyCycle`.\n this.emitter.emit('statusChanged', {\n deviceId: parsedPayload.src.ref,\n temperature: parsedPayload.body.ambTemp,\n humidity: parsedPayload.body.hum,\n setPoint: parsedPayload.body.stpt,\n dutyCycle: parsedPayload.body.dtyCycle ?? parsedPayload.body.heatStat,\n floorTemperature: parsedPayload.body.flrSnsrTemp\n });\n break;\n\n case OutMessageType.DEVICE_STATE_CHANGE: {\n const modeMap: Record<number, MysaDeviceMode> = {\n 1: 'off',\n 2: 'auto',\n 3: 'heat',\n 4: 'cool',\n 5: 'fan_only',\n 6: 'dry'\n };\n this.emitter.emit('stateChanged', {\n deviceId: parsedPayload.src.ref,\n mode: parsedPayload.body.state.md ? modeMap[parsedPayload.body.state.md] : undefined,\n setPoint: parsedPayload.body.state.sp,\n fanSpeed:\n parsedPayload.body.state.fn !== undefined ? FanSpeedReceiveMap[parsedPayload.body.state.fn] : undefined\n });\n break;\n }\n }\n }\n } catch (error) {\n this._logger.error('Error handling MQTT message:', error);\n }\n }\n}\n"],"mappings":"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","names":["InMessageType","OutMessageType","payload","_a"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mysa-js-sdk",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "A JavaScript SDK for accessing Mysa smart thermostats.",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"build:docs": "typedoc"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@aws-sdk/client-iot": "3.
|
|
54
|
-
"@aws-sdk/credential-providers": "3.
|
|
53
|
+
"@aws-sdk/client-iot": "3.1094.0",
|
|
54
|
+
"@aws-sdk/credential-providers": "3.1094.0",
|
|
55
55
|
"amazon-cognito-identity-js": "6.3.20",
|
|
56
56
|
"aws-iot-device-sdk-v2": "1.27.0",
|
|
57
57
|
"dayjs": "1.11.21",
|