mysa-js-sdk 2.0.2 → 2.0.3

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/dist/index.cjs CHANGED
@@ -231,6 +231,7 @@ var import_client_iot = require("@aws-sdk/client-iot");
231
231
  var import_credential_providers = require("@aws-sdk/credential-providers");
232
232
  var import_amazon_cognito_identity_js = require("amazon-cognito-identity-js");
233
233
  var import_aws_iot_device_sdk_v2 = require("aws-iot-device-sdk-v2");
234
+ var import_crypto = require("crypto");
234
235
  var import_dayjs = __toESM(require("dayjs"), 1);
235
236
  var import_duration = __toESM(require("dayjs/plugin/duration.js"), 1);
236
237
  var import_nanoid = require("nanoid");
@@ -257,8 +258,12 @@ var MysaApiClient = class {
257
258
  _mqttConnectionPromise;
258
259
  /** Stable per-process MQTT client id (prevents collisions between multiple processes). */
259
260
  _mqttClientId;
261
+ /** Expiration time of the credentials currently in use by the MQTT client. */
262
+ _mqttCredentialsExpiration;
260
263
  /** Interrupt timestamps for storm / collision detection. */
261
264
  _mqttInterrupts = [];
265
+ /** Whether a forced MQTT reset is currently in progress (guards against re-entrancy). */
266
+ _mqttResetInProgress = false;
262
267
  /** The device IDs that are currently being updated in real-time, mapped to their respective timeouts. */
263
268
  _realtimeDeviceIds = /* @__PURE__ */ new Map();
264
269
  /** The cached devices object, if any. */
@@ -604,13 +609,14 @@ var MysaApiClient = class {
604
609
  await this._publishWithRetry(mqttConnection, `/v1/dev/${deviceId}/in`, payload, import_aws_iot_device_sdk_v2.mqtt.QoS.AtLeastOnce);
605
610
  const timer = setInterval(async () => {
606
611
  this._logger.debug(`Sending request to keep-alive publishing device status for '${deviceId}'...`);
612
+ const connection = await this._getMqttConnection();
607
613
  const payload2 = serializeMqttPayload({
608
614
  Device: deviceId,
609
615
  MsgType: 11 /* START_PUBLISHING_DEVICE_STATUS */,
610
616
  Timestamp: (0, import_dayjs.default)().unix(),
611
617
  Timeout: RealtimeKeepAliveInterval.asSeconds()
612
618
  });
613
- await this._publishWithRetry(mqttConnection, `/v1/dev/${deviceId}/in`, payload2, import_aws_iot_device_sdk_v2.mqtt.QoS.AtLeastOnce);
619
+ await this._publishWithRetry(connection, `/v1/dev/${deviceId}/in`, payload2, import_aws_iot_device_sdk_v2.mqtt.QoS.AtLeastOnce);
614
620
  }, RealtimeKeepAliveInterval.subtract(10, "seconds").asMilliseconds());
615
621
  this._realtimeDeviceIds.set(deviceId, timer);
616
622
  }
@@ -709,6 +715,7 @@ var MysaApiClient = class {
709
715
  "AWS_ERROR_MQTT_NO_CONNECTION",
710
716
  "AWS_ERROR_MQTT_UNEXPECTED_HANGUP",
711
717
  "UNEXPECTED_HANGUP",
718
+ "AWS_ERROR_MQTT_CONNECTION_DESTROYED",
712
719
  "Time limit between request and response",
713
720
  "timeout"
714
721
  ];
@@ -783,46 +790,80 @@ var MysaApiClient = class {
783
790
  logger: this._logger
784
791
  });
785
792
  const credentials = await credentialsProvider();
793
+ if (!credentials.expiration) {
794
+ throw new Error("MQTT credentials do not have an expiration time.");
795
+ }
796
+ this._mqttCredentialsExpiration = (0, import_dayjs.default)(credentials.expiration);
797
+ this._logger.debug(`MQTT credentials expiration: ${this._mqttCredentialsExpiration.format()}`);
798
+ if (!this._mqttCredentialsExpiration.isAfter((0, import_dayjs.default)())) {
799
+ this._mqttCredentialsExpiration = void 0;
800
+ throw new Error("MQTT credentials are already expired.");
801
+ }
786
802
  if (!this._mqttClientId) {
787
- this._mqttClientId = `mysa-js-sdk-${((_a = this.session) == null ? void 0 : _a.username) ?? "anon"}-${getRandomClientId()}`;
803
+ const username = ((_a = this.session) == null ? void 0 : _a.username) ?? "anon";
804
+ const usernameHash = (0, import_crypto.hash)("sha1", username);
805
+ this._mqttClientId = `mysa-js-sdk-${usernameHash}-${process.pid}-${getRandomClientId()}`;
788
806
  }
789
807
  const builder = import_aws_iot_device_sdk_v2.iot.AwsIotMqttConnectionConfigBuilder.new_with_websockets().with_credentials(AwsRegion, credentials.accessKeyId, credentials.secretAccessKey, credentials.sessionToken).with_endpoint(MqttEndpoint).with_client_id(this._mqttClientId).with_clean_session(false).with_keep_alive_seconds(30).with_ping_timeout_ms(3e3).with_protocol_operation_timeout_ms(6e4).with_reconnect_min_sec(1).with_reconnect_max_sec(30);
790
808
  const config = builder.build();
791
809
  const client = new import_aws_iot_device_sdk_v2.mqtt.MqttClient();
792
810
  const connection = client.new_connection(config);
793
811
  connection.on("connect", () => {
794
- this._logger.debug("MQTT connect");
812
+ this._logger.debug(`MQTT connect (clientId=${this._mqttClientId})`);
795
813
  });
796
814
  connection.on("connection_success", () => {
797
- this._logger.debug("MQTT connection_success");
815
+ this._logger.debug(`MQTT connection_success (clientId=${this._mqttClientId})`);
798
816
  });
799
817
  connection.on("connection_failure", (e) => {
800
- this._logger.error("MQTT connection_failure", e);
818
+ this._logger.error(`MQTT connection_failure (clientId=${this._mqttClientId})`, e);
801
819
  });
802
820
  connection.on("interrupt", async (e) => {
803
- this._logger.warn("MQTT interrupt", e);
821
+ var _a2;
822
+ this._logger.warn(`MQTT interrupt (clientId=${this._mqttClientId})`, e);
804
823
  const now = Date.now();
805
824
  this._mqttInterrupts = this._mqttInterrupts.filter((t) => now - t < 6e4);
806
825
  this._mqttInterrupts.push(now);
807
- if (this._mqttInterrupts.length > 5) {
808
- this._logger.warn(
809
- `High interrupt rate (${this._mqttInterrupts.length}/60s). Possible clientId collision. Regenerating clientId and resetting connection...`
810
- );
826
+ const areCredentialsExpired = !(((_a2 = this._mqttCredentialsExpiration) == null ? void 0 : _a2.isAfter((0, import_dayjs.default)())) ?? false);
827
+ if ((this._mqttInterrupts.length > 5 || areCredentialsExpired) && !this._mqttResetInProgress) {
828
+ this._mqttResetInProgress = true;
829
+ if (this._mqttInterrupts.length > 5) {
830
+ this._logger.warn(
831
+ `High interrupt rate (${this._mqttInterrupts.length}/60s). Possible clientId collision. Regenerating clientId and resetting connection...`
832
+ );
833
+ } else {
834
+ this._logger.warn(`Credentials expired. Regenerating clientId and resetting connection...`);
835
+ }
811
836
  this._mqttClientId = void 0;
837
+ this._mqttCredentialsExpiration = void 0;
812
838
  this._mqttInterrupts = [];
839
+ this._mqttConnectionPromise = void 0;
813
840
  try {
814
841
  await connection.disconnect();
815
- if (this._mqttConnectionPromise) {
816
- this._logger.warn("MQTT connection promise still defined after disconnect; expected it to be cleared.");
817
- this._mqttConnectionPromise = void 0;
842
+ try {
843
+ this._logger.debug("Old MQTT connection disconnected; establishing new connection...");
844
+ const newConnection = await this._getMqttConnection();
845
+ for (const deviceId of Array.from(this._realtimeDeviceIds.keys())) {
846
+ const topic = `/v1/dev/${deviceId}/out`;
847
+ this._logger.debug(`Re-subscribing to ${topic}`);
848
+ await newConnection.subscribe(topic, import_aws_iot_device_sdk_v2.mqtt.QoS.AtLeastOnce, (_topic, payload) => {
849
+ this._processMqttMessage(payload);
850
+ });
851
+ }
852
+ this._logger.info("MQTT connection rebuilt successfully after interrupt storm or credentials expiration");
853
+ } catch (err) {
854
+ this._logger.error("Failed to re-subscribe after interrupt storm or credentials expiration", err);
818
855
  }
819
856
  } catch (error) {
820
- this._logger.error("Failed to disconnect MQTT connection", error);
857
+ this._logger.error("Error during MQTT reset", error);
858
+ } finally {
859
+ this._mqttResetInProgress = false;
821
860
  }
822
861
  }
823
862
  });
824
863
  connection.on("resume", async (returnCode, sessionPresent) => {
825
- this._logger.info(`MQTT resume returnCode=${returnCode} sessionPresent=${sessionPresent}`);
864
+ this._logger.info(
865
+ `MQTT resume returnCode=${returnCode} sessionPresent=${sessionPresent} clientId=${this._mqttClientId}`
866
+ );
826
867
  if (!sessionPresent) {
827
868
  this._logger.info("No session present, re-subscribing each device");
828
869
  try {
@@ -839,11 +880,12 @@ var MysaApiClient = class {
839
880
  }
840
881
  });
841
882
  connection.on("error", (e) => {
842
- this._logger.error("MQTT error", e);
883
+ this._logger.error(`MQTT error (clientId=${this._mqttClientId})`, e);
843
884
  });
844
885
  connection.on("closed", () => {
845
886
  this._logger.info("MQTT connection closed");
846
887
  this._mqttConnectionPromise = void 0;
888
+ this._mqttCredentialsExpiration = void 0;
847
889
  });
848
890
  await connection.connect();
849
891
  return connection;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../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":["export * from './api';\nexport * from './types';\n","/** Error thrown when attempting to access the Mysa API without proper authentication. */\nexport class UnauthenticatedError extends Error {\n /**\n * Creates a new UnauthenticatedError instance.\n *\n * @param message - The error message\n */\n constructor(message: string) {\n super(message);\n this.name = 'UnauthenticatedError';\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 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 console.error('Error parsing MQTT payload:', error);\n throw new Error('Failed to parse MQTT payload');\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 /** Notification that a device's operational state has changed */\n DEVICE_STATE_CHANGE = 44\n}\n","import { MysaSession } from '@/api/MysaSession';\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 } from '@/types/rest';\nimport { DescribeThingCommand, IoTClient } from '@aws-sdk/client-iot';\nimport { fromCognitoIdentityPool } from '@aws-sdk/credential-providers';\nimport {\n AuthenticationDetails,\n CognitoAccessToken,\n CognitoIdToken,\n CognitoRefreshToken,\n CognitoUser,\n CognitoUserPool,\n CognitoUserSession\n} from 'amazon-cognito-identity-js';\nimport { iot, mqtt } from 'aws-iot-device-sdk-v2';\nimport dayjs from 'dayjs';\nimport duration from 'dayjs/plugin/duration.js';\nimport { customAlphabet } from 'nanoid';\nimport { MqttPublishError, MysaApiError, UnauthenticatedError } 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');\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();\n *\n * await client.login('user@example.com', 'password');\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 current session object, if any. */\n private _cognitoUserSession?: CognitoUserSession;\n\n /** The current user object, if any. */\n private _cognitoUser?: CognitoUser;\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 /** Interrupt timestamps for storm / collision detection. */\n private _mqttInterrupts: number[] = [];\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 * Gets the persistable session object.\n *\n * @returns The current persistable session object, if any.\n */\n get session(): MysaSession | undefined {\n if (!this._cognitoUserSession || !this._cognitoUser) {\n return undefined;\n }\n\n return {\n username: this._cognitoUser.getUsername(),\n idToken: this._cognitoUserSession.getIdToken().getJwtToken(),\n accessToken: this._cognitoUserSession.getAccessToken().getJwtToken(),\n refreshToken: this._cognitoUserSession.getRefreshToken().getToken()\n };\n }\n\n /**\n * Returns whether the client currently has an active session.\n *\n * @returns True if the client has an active session, false otherwise.\n */\n get isAuthenticated(): boolean {\n return !!this.session;\n }\n\n /**\n * Constructs a new instance of the MysaApiClient.\n *\n * @param session - The persistable session object, if any.\n * @param options - The options for the client.\n */\n constructor(session?: MysaSession, options?: MysaApiClientOptions) {\n this._logger = options?.logger || new VoidLogger();\n this._fetcher = options?.fetcher || fetch;\n\n if (session) {\n this._cognitoUser = new CognitoUser({\n Username: session.username,\n Pool: new CognitoUserPool({ UserPoolId: CognitoUserPoolId, ClientId: CognitoClientId })\n });\n this._cognitoUserSession = new CognitoUserSession({\n IdToken: new CognitoIdToken({ IdToken: session.idToken }),\n AccessToken: new CognitoAccessToken({ AccessToken: session.accessToken }),\n RefreshToken: new CognitoRefreshToken({ RefreshToken: session.refreshToken })\n });\n }\n }\n\n /**\n * Logs in the user with the given email address and password.\n *\n * This method authenticates the user with Mysa's Cognito user pool and establishes a session that can be used for\n * subsequent API calls. Upon successful login, a 'sessionChanged' event is emitted.\n *\n * @example\n *\n * ```typescript\n * try {\n * await client.login('user@example.com', 'password123');\n * console.log('Login successful!');\n * } catch (error) {\n * console.error('Login failed:', error.message);\n * }\n * ```\n *\n * @param emailAddress - The email address of the user.\n * @param password - The password of the user.\n * @throws {@link Error} When authentication fails due to invalid credentials or network issues.\n */\n async login(emailAddress: string, password: string): Promise<void> {\n this._cognitoUser = undefined;\n this._cognitoUserSession = undefined;\n this._mqttClientId = undefined;\n this._mqttInterrupts = [];\n\n this.emitter.emit('sessionChanged', this.session);\n\n return new Promise((resolve, reject) => {\n const user = new CognitoUser({\n Username: emailAddress,\n Pool: new CognitoUserPool({ UserPoolId: CognitoUserPoolId, ClientId: CognitoClientId })\n });\n\n user.authenticateUser(new AuthenticationDetails({ Username: emailAddress, Password: password }), {\n onSuccess: (session) => {\n this._cognitoUser = user;\n this._cognitoUserSession = session;\n this.emitter.emit('sessionChanged', this.session);\n\n resolve();\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 user is not authenticated.\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 user is not authenticated.\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 user is not authenticated.\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 user is not authenticated.\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 * 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 user is not authenticated.\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 const device = this._cachedDevices.DevicesObj[deviceId];\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.session?.username ?? '',\n type: 100\n },\n dest: {\n ref: deviceId,\n type: 1\n },\n resp: 2,\n body: {\n ver: 1,\n type: device.Model.startsWith('BB-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 await this._publishWithRetry(mqttConnection, `/v1/dev/${deviceId}/in`, payload, mqtt.QoS.AtLeastOnce);\n\n const timer = setInterval(async () => {\n this._logger.debug(`Sending request to keep-alive 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 await this._publishWithRetry(mqttConnection, `/v1/dev/${deviceId}/in`, payload, mqtt.QoS.AtLeastOnce);\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.\n *\n * @returns A promise that resolves to a valid CognitoUserSession.\n * @throws {@link UnauthenticatedError} When no session exists or refresh fails.\n */\n private async _getFreshSession(): Promise<CognitoUserSession> {\n if (!this._cognitoUser || !this._cognitoUserSession) {\n throw new UnauthenticatedError('An attempt was made to access a resource without a valid session.');\n }\n\n if (\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 Promise.resolve(this._cognitoUserSession);\n }\n\n this._logger.debug('Session is not valid or expired, refreshing...');\n return new Promise<CognitoUserSession>((resolve, reject) => {\n this._cognitoUser!.refreshSession(this._cognitoUserSession!.getRefreshToken(), (error, session) => {\n if (error) {\n this._logger.error('Failed to refresh session:', error);\n reject(new UnauthenticatedError('Unable to refresh the authentication session.'));\n } else {\n this._logger.debug('Session refreshed successfully');\n this._cognitoUserSession = session;\n this.emitter.emit('sessionChanged', this.session);\n resolve(session);\n }\n });\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 '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 await connection.publish(topic, payload, qos);\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 // Per-process stable client id. Random suffix avoids collisions with other running processes.\n if (!this._mqttClientId) {\n this._mqttClientId = `mysa-js-sdk-${this.session?.username ?? 'anon'}-${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 const client = new mqtt.MqttClient();\n const connection = client.new_connection(config);\n\n connection.on('connect', () => {\n this._logger.debug('MQTT connect');\n });\n\n connection.on('connection_success', () => {\n this._logger.debug('MQTT connection_success');\n });\n\n connection.on('connection_failure', (e) => {\n this._logger.error('MQTT connection_failure', e);\n });\n\n connection.on('interrupt', async (e) => {\n this._logger.warn('MQTT interrupt', 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 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\n // Force new client id to escape collision; close current connection\n this._mqttClientId = undefined;\n\n // Clear interrupts\n this._mqttInterrupts = [];\n\n try {\n await connection.disconnect();\n\n if (this._mqttConnectionPromise) {\n this._logger.warn('MQTT connection promise still defined after disconnect; expected it to be cleared.');\n this._mqttConnectionPromise = undefined;\n }\n } catch (error) {\n this._logger.error('Failed to disconnect MQTT connection', error);\n }\n }\n });\n\n connection.on('resume', async (returnCode, sessionPresent) => {\n this._logger.info(`MQTT resume returnCode=${returnCode} sessionPresent=${sessionPresent}`);\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', e);\n });\n\n connection.on('closed', () => {\n this._logger.info('MQTT connection closed');\n this._mqttConnectionPromise = 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_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":"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","names":["NodeEventEmitter","InMessageType","OutMessageType","dayjs","duration","payload"]}
1
+ {"version":3,"sources":["../src/index.ts","../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":["export * from './api';\nexport * from './types';\n","/** Error thrown when attempting to access the Mysa API without proper authentication. */\nexport class UnauthenticatedError extends Error {\n /**\n * Creates a new UnauthenticatedError instance.\n *\n * @param message - The error message\n */\n constructor(message: string) {\n super(message);\n this.name = 'UnauthenticatedError';\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 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 console.error('Error parsing MQTT payload:', error);\n throw new Error('Failed to parse MQTT payload');\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 /** Notification that a device's operational state has changed */\n DEVICE_STATE_CHANGE = 44\n}\n","import { MysaSession } from '@/api/MysaSession';\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 } from '@/types/rest';\nimport { DescribeThingCommand, IoTClient } from '@aws-sdk/client-iot';\nimport { fromCognitoIdentityPool } from '@aws-sdk/credential-providers';\nimport {\n AuthenticationDetails,\n CognitoAccessToken,\n CognitoIdToken,\n CognitoRefreshToken,\n CognitoUser,\n CognitoUserPool,\n CognitoUserSession\n} from 'amazon-cognito-identity-js';\nimport { 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 } 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');\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();\n *\n * await client.login('user@example.com', 'password');\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 current session object, if any. */\n private _cognitoUserSession?: CognitoUserSession;\n\n /** The current user object, if any. */\n private _cognitoUser?: CognitoUser;\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 * Gets the persistable session object.\n *\n * @returns The current persistable session object, if any.\n */\n get session(): MysaSession | undefined {\n if (!this._cognitoUserSession || !this._cognitoUser) {\n return undefined;\n }\n\n return {\n username: this._cognitoUser.getUsername(),\n idToken: this._cognitoUserSession.getIdToken().getJwtToken(),\n accessToken: this._cognitoUserSession.getAccessToken().getJwtToken(),\n refreshToken: this._cognitoUserSession.getRefreshToken().getToken()\n };\n }\n\n /**\n * Returns whether the client currently has an active session.\n *\n * @returns True if the client has an active session, false otherwise.\n */\n get isAuthenticated(): boolean {\n return !!this.session;\n }\n\n /**\n * Constructs a new instance of the MysaApiClient.\n *\n * @param session - The persistable session object, if any.\n * @param options - The options for the client.\n */\n constructor(session?: MysaSession, options?: MysaApiClientOptions) {\n this._logger = options?.logger || new VoidLogger();\n this._fetcher = options?.fetcher || fetch;\n\n if (session) {\n this._cognitoUser = new CognitoUser({\n Username: session.username,\n Pool: new CognitoUserPool({ UserPoolId: CognitoUserPoolId, ClientId: CognitoClientId })\n });\n this._cognitoUserSession = new CognitoUserSession({\n IdToken: new CognitoIdToken({ IdToken: session.idToken }),\n AccessToken: new CognitoAccessToken({ AccessToken: session.accessToken }),\n RefreshToken: new CognitoRefreshToken({ RefreshToken: session.refreshToken })\n });\n }\n }\n\n /**\n * Logs in the user with the given email address and password.\n *\n * This method authenticates the user with Mysa's Cognito user pool and establishes a session that can be used for\n * subsequent API calls. Upon successful login, a 'sessionChanged' event is emitted.\n *\n * @example\n *\n * ```typescript\n * try {\n * await client.login('user@example.com', 'password123');\n * console.log('Login successful!');\n * } catch (error) {\n * console.error('Login failed:', error.message);\n * }\n * ```\n *\n * @param emailAddress - The email address of the user.\n * @param password - The password of the user.\n * @throws {@link Error} When authentication fails due to invalid credentials or network issues.\n */\n async login(emailAddress: string, password: string): Promise<void> {\n this._cognitoUser = undefined;\n this._cognitoUserSession = undefined;\n this._mqttClientId = undefined;\n this._mqttInterrupts = [];\n\n this.emitter.emit('sessionChanged', this.session);\n\n return new Promise((resolve, reject) => {\n const user = new CognitoUser({\n Username: emailAddress,\n Pool: new CognitoUserPool({ UserPoolId: CognitoUserPoolId, ClientId: CognitoClientId })\n });\n\n user.authenticateUser(new AuthenticationDetails({ Username: emailAddress, Password: password }), {\n onSuccess: (session) => {\n this._cognitoUser = user;\n this._cognitoUserSession = session;\n this.emitter.emit('sessionChanged', this.session);\n\n resolve();\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 user is not authenticated.\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 user is not authenticated.\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 user is not authenticated.\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 user is not authenticated.\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 * 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 user is not authenticated.\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 const device = this._cachedDevices.DevicesObj[deviceId];\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.session?.username ?? '',\n type: 100\n },\n dest: {\n ref: deviceId,\n type: 1\n },\n resp: 2,\n body: {\n ver: 1,\n type: device.Model.startsWith('BB-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 await this._publishWithRetry(mqttConnection, `/v1/dev/${deviceId}/in`, payload, mqtt.QoS.AtLeastOnce);\n\n const timer = setInterval(async () => {\n this._logger.debug(`Sending request to keep-alive publishing device status for '${deviceId}'...`);\n\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 }, 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.\n *\n * @returns A promise that resolves to a valid CognitoUserSession.\n * @throws {@link UnauthenticatedError} When no session exists or refresh fails.\n */\n private async _getFreshSession(): Promise<CognitoUserSession> {\n if (!this._cognitoUser || !this._cognitoUserSession) {\n throw new UnauthenticatedError('An attempt was made to access a resource without a valid session.');\n }\n\n if (\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 Promise.resolve(this._cognitoUserSession);\n }\n\n this._logger.debug('Session is not valid or expired, refreshing...');\n return new Promise<CognitoUserSession>((resolve, reject) => {\n this._cognitoUser!.refreshSession(this._cognitoUserSession!.getRefreshToken(), (error, session) => {\n if (error) {\n this._logger.error('Failed to refresh session:', error);\n reject(new UnauthenticatedError('Unable to refresh the authentication session.'));\n } else {\n this._logger.debug('Session refreshed successfully');\n this._cognitoUserSession = session;\n this.emitter.emit('sessionChanged', this.session);\n resolve(session);\n }\n });\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 await connection.publish(topic, payload, qos);\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.session?.username ?? '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 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 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_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":"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","names":["NodeEventEmitter","InMessageType","OutMessageType","dayjs","duration","payload","_a"]}
package/dist/index.d.cts CHANGED
@@ -739,8 +739,12 @@ declare class MysaApiClient {
739
739
  private _mqttConnectionPromise?;
740
740
  /** Stable per-process MQTT client id (prevents collisions between multiple processes). */
741
741
  private _mqttClientId?;
742
+ /** Expiration time of the credentials currently in use by the MQTT client. */
743
+ private _mqttCredentialsExpiration?;
742
744
  /** Interrupt timestamps for storm / collision detection. */
743
745
  private _mqttInterrupts;
746
+ /** Whether a forced MQTT reset is currently in progress (guards against re-entrancy). */
747
+ private _mqttResetInProgress;
744
748
  /** The device IDs that are currently being updated in real-time, mapped to their respective timeouts. */
745
749
  private _realtimeDeviceIds;
746
750
  /** The cached devices object, if any. */
package/dist/index.d.ts CHANGED
@@ -739,8 +739,12 @@ declare class MysaApiClient {
739
739
  private _mqttConnectionPromise?;
740
740
  /** Stable per-process MQTT client id (prevents collisions between multiple processes). */
741
741
  private _mqttClientId?;
742
+ /** Expiration time of the credentials currently in use by the MQTT client. */
743
+ private _mqttCredentialsExpiration?;
742
744
  /** Interrupt timestamps for storm / collision detection. */
743
745
  private _mqttInterrupts;
746
+ /** Whether a forced MQTT reset is currently in progress (guards against re-entrancy). */
747
+ private _mqttResetInProgress;
744
748
  /** The device IDs that are currently being updated in real-time, mapped to their respective timeouts. */
745
749
  private _realtimeDeviceIds;
746
750
  /** The cached devices object, if any. */
package/dist/index.js CHANGED
@@ -198,6 +198,7 @@ import {
198
198
  CognitoUserSession
199
199
  } from "amazon-cognito-identity-js";
200
200
  import { iot, mqtt } from "aws-iot-device-sdk-v2";
201
+ import { hash } from "crypto";
201
202
  import dayjs from "dayjs";
202
203
  import duration from "dayjs/plugin/duration.js";
203
204
  import { customAlphabet } from "nanoid";
@@ -224,8 +225,12 @@ var MysaApiClient = class {
224
225
  _mqttConnectionPromise;
225
226
  /** Stable per-process MQTT client id (prevents collisions between multiple processes). */
226
227
  _mqttClientId;
228
+ /** Expiration time of the credentials currently in use by the MQTT client. */
229
+ _mqttCredentialsExpiration;
227
230
  /** Interrupt timestamps for storm / collision detection. */
228
231
  _mqttInterrupts = [];
232
+ /** Whether a forced MQTT reset is currently in progress (guards against re-entrancy). */
233
+ _mqttResetInProgress = false;
229
234
  /** The device IDs that are currently being updated in real-time, mapped to their respective timeouts. */
230
235
  _realtimeDeviceIds = /* @__PURE__ */ new Map();
231
236
  /** The cached devices object, if any. */
@@ -571,13 +576,14 @@ var MysaApiClient = class {
571
576
  await this._publishWithRetry(mqttConnection, `/v1/dev/${deviceId}/in`, payload, mqtt.QoS.AtLeastOnce);
572
577
  const timer = setInterval(async () => {
573
578
  this._logger.debug(`Sending request to keep-alive publishing device status for '${deviceId}'...`);
579
+ const connection = await this._getMqttConnection();
574
580
  const payload2 = serializeMqttPayload({
575
581
  Device: deviceId,
576
582
  MsgType: 11 /* START_PUBLISHING_DEVICE_STATUS */,
577
583
  Timestamp: dayjs().unix(),
578
584
  Timeout: RealtimeKeepAliveInterval.asSeconds()
579
585
  });
580
- await this._publishWithRetry(mqttConnection, `/v1/dev/${deviceId}/in`, payload2, mqtt.QoS.AtLeastOnce);
586
+ await this._publishWithRetry(connection, `/v1/dev/${deviceId}/in`, payload2, mqtt.QoS.AtLeastOnce);
581
587
  }, RealtimeKeepAliveInterval.subtract(10, "seconds").asMilliseconds());
582
588
  this._realtimeDeviceIds.set(deviceId, timer);
583
589
  }
@@ -676,6 +682,7 @@ var MysaApiClient = class {
676
682
  "AWS_ERROR_MQTT_NO_CONNECTION",
677
683
  "AWS_ERROR_MQTT_UNEXPECTED_HANGUP",
678
684
  "UNEXPECTED_HANGUP",
685
+ "AWS_ERROR_MQTT_CONNECTION_DESTROYED",
679
686
  "Time limit between request and response",
680
687
  "timeout"
681
688
  ];
@@ -750,46 +757,80 @@ var MysaApiClient = class {
750
757
  logger: this._logger
751
758
  });
752
759
  const credentials = await credentialsProvider();
760
+ if (!credentials.expiration) {
761
+ throw new Error("MQTT credentials do not have an expiration time.");
762
+ }
763
+ this._mqttCredentialsExpiration = dayjs(credentials.expiration);
764
+ this._logger.debug(`MQTT credentials expiration: ${this._mqttCredentialsExpiration.format()}`);
765
+ if (!this._mqttCredentialsExpiration.isAfter(dayjs())) {
766
+ this._mqttCredentialsExpiration = void 0;
767
+ throw new Error("MQTT credentials are already expired.");
768
+ }
753
769
  if (!this._mqttClientId) {
754
- this._mqttClientId = `mysa-js-sdk-${((_a = this.session) == null ? void 0 : _a.username) ?? "anon"}-${getRandomClientId()}`;
770
+ const username = ((_a = this.session) == null ? void 0 : _a.username) ?? "anon";
771
+ const usernameHash = hash("sha1", username);
772
+ this._mqttClientId = `mysa-js-sdk-${usernameHash}-${process.pid}-${getRandomClientId()}`;
755
773
  }
756
774
  const builder = iot.AwsIotMqttConnectionConfigBuilder.new_with_websockets().with_credentials(AwsRegion, credentials.accessKeyId, credentials.secretAccessKey, credentials.sessionToken).with_endpoint(MqttEndpoint).with_client_id(this._mqttClientId).with_clean_session(false).with_keep_alive_seconds(30).with_ping_timeout_ms(3e3).with_protocol_operation_timeout_ms(6e4).with_reconnect_min_sec(1).with_reconnect_max_sec(30);
757
775
  const config = builder.build();
758
776
  const client = new mqtt.MqttClient();
759
777
  const connection = client.new_connection(config);
760
778
  connection.on("connect", () => {
761
- this._logger.debug("MQTT connect");
779
+ this._logger.debug(`MQTT connect (clientId=${this._mqttClientId})`);
762
780
  });
763
781
  connection.on("connection_success", () => {
764
- this._logger.debug("MQTT connection_success");
782
+ this._logger.debug(`MQTT connection_success (clientId=${this._mqttClientId})`);
765
783
  });
766
784
  connection.on("connection_failure", (e) => {
767
- this._logger.error("MQTT connection_failure", e);
785
+ this._logger.error(`MQTT connection_failure (clientId=${this._mqttClientId})`, e);
768
786
  });
769
787
  connection.on("interrupt", async (e) => {
770
- this._logger.warn("MQTT interrupt", e);
788
+ var _a2;
789
+ this._logger.warn(`MQTT interrupt (clientId=${this._mqttClientId})`, e);
771
790
  const now = Date.now();
772
791
  this._mqttInterrupts = this._mqttInterrupts.filter((t) => now - t < 6e4);
773
792
  this._mqttInterrupts.push(now);
774
- if (this._mqttInterrupts.length > 5) {
775
- this._logger.warn(
776
- `High interrupt rate (${this._mqttInterrupts.length}/60s). Possible clientId collision. Regenerating clientId and resetting connection...`
777
- );
793
+ const areCredentialsExpired = !(((_a2 = this._mqttCredentialsExpiration) == null ? void 0 : _a2.isAfter(dayjs())) ?? false);
794
+ if ((this._mqttInterrupts.length > 5 || areCredentialsExpired) && !this._mqttResetInProgress) {
795
+ this._mqttResetInProgress = true;
796
+ if (this._mqttInterrupts.length > 5) {
797
+ this._logger.warn(
798
+ `High interrupt rate (${this._mqttInterrupts.length}/60s). Possible clientId collision. Regenerating clientId and resetting connection...`
799
+ );
800
+ } else {
801
+ this._logger.warn(`Credentials expired. Regenerating clientId and resetting connection...`);
802
+ }
778
803
  this._mqttClientId = void 0;
804
+ this._mqttCredentialsExpiration = void 0;
779
805
  this._mqttInterrupts = [];
806
+ this._mqttConnectionPromise = void 0;
780
807
  try {
781
808
  await connection.disconnect();
782
- if (this._mqttConnectionPromise) {
783
- this._logger.warn("MQTT connection promise still defined after disconnect; expected it to be cleared.");
784
- this._mqttConnectionPromise = void 0;
809
+ try {
810
+ this._logger.debug("Old MQTT connection disconnected; establishing new connection...");
811
+ const newConnection = await this._getMqttConnection();
812
+ for (const deviceId of Array.from(this._realtimeDeviceIds.keys())) {
813
+ const topic = `/v1/dev/${deviceId}/out`;
814
+ this._logger.debug(`Re-subscribing to ${topic}`);
815
+ await newConnection.subscribe(topic, mqtt.QoS.AtLeastOnce, (_topic, payload) => {
816
+ this._processMqttMessage(payload);
817
+ });
818
+ }
819
+ this._logger.info("MQTT connection rebuilt successfully after interrupt storm or credentials expiration");
820
+ } catch (err) {
821
+ this._logger.error("Failed to re-subscribe after interrupt storm or credentials expiration", err);
785
822
  }
786
823
  } catch (error) {
787
- this._logger.error("Failed to disconnect MQTT connection", error);
824
+ this._logger.error("Error during MQTT reset", error);
825
+ } finally {
826
+ this._mqttResetInProgress = false;
788
827
  }
789
828
  }
790
829
  });
791
830
  connection.on("resume", async (returnCode, sessionPresent) => {
792
- this._logger.info(`MQTT resume returnCode=${returnCode} sessionPresent=${sessionPresent}`);
831
+ this._logger.info(
832
+ `MQTT resume returnCode=${returnCode} sessionPresent=${sessionPresent} clientId=${this._mqttClientId}`
833
+ );
793
834
  if (!sessionPresent) {
794
835
  this._logger.info("No session present, re-subscribing each device");
795
836
  try {
@@ -806,11 +847,12 @@ var MysaApiClient = class {
806
847
  }
807
848
  });
808
849
  connection.on("error", (e) => {
809
- this._logger.error("MQTT error", e);
850
+ this._logger.error(`MQTT error (clientId=${this._mqttClientId})`, e);
810
851
  });
811
852
  connection.on("closed", () => {
812
853
  this._logger.info("MQTT connection closed");
813
854
  this._mqttConnectionPromise = void 0;
855
+ this._mqttCredentialsExpiration = void 0;
814
856
  });
815
857
  await connection.connect();
816
858
  return connection;
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 * Creates a new UnauthenticatedError instance.\n *\n * @param message - The error message\n */\n constructor(message: string) {\n super(message);\n this.name = 'UnauthenticatedError';\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 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 console.error('Error parsing MQTT payload:', error);\n throw new Error('Failed to parse MQTT payload');\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 /** Notification that a device's operational state has changed */\n DEVICE_STATE_CHANGE = 44\n}\n","import { MysaSession } from '@/api/MysaSession';\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 } from '@/types/rest';\nimport { DescribeThingCommand, IoTClient } from '@aws-sdk/client-iot';\nimport { fromCognitoIdentityPool } from '@aws-sdk/credential-providers';\nimport {\n AuthenticationDetails,\n CognitoAccessToken,\n CognitoIdToken,\n CognitoRefreshToken,\n CognitoUser,\n CognitoUserPool,\n CognitoUserSession\n} from 'amazon-cognito-identity-js';\nimport { iot, mqtt } from 'aws-iot-device-sdk-v2';\nimport dayjs from 'dayjs';\nimport duration from 'dayjs/plugin/duration.js';\nimport { customAlphabet } from 'nanoid';\nimport { MqttPublishError, MysaApiError, UnauthenticatedError } 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');\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();\n *\n * await client.login('user@example.com', 'password');\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 current session object, if any. */\n private _cognitoUserSession?: CognitoUserSession;\n\n /** The current user object, if any. */\n private _cognitoUser?: CognitoUser;\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 /** Interrupt timestamps for storm / collision detection. */\n private _mqttInterrupts: number[] = [];\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 * Gets the persistable session object.\n *\n * @returns The current persistable session object, if any.\n */\n get session(): MysaSession | undefined {\n if (!this._cognitoUserSession || !this._cognitoUser) {\n return undefined;\n }\n\n return {\n username: this._cognitoUser.getUsername(),\n idToken: this._cognitoUserSession.getIdToken().getJwtToken(),\n accessToken: this._cognitoUserSession.getAccessToken().getJwtToken(),\n refreshToken: this._cognitoUserSession.getRefreshToken().getToken()\n };\n }\n\n /**\n * Returns whether the client currently has an active session.\n *\n * @returns True if the client has an active session, false otherwise.\n */\n get isAuthenticated(): boolean {\n return !!this.session;\n }\n\n /**\n * Constructs a new instance of the MysaApiClient.\n *\n * @param session - The persistable session object, if any.\n * @param options - The options for the client.\n */\n constructor(session?: MysaSession, options?: MysaApiClientOptions) {\n this._logger = options?.logger || new VoidLogger();\n this._fetcher = options?.fetcher || fetch;\n\n if (session) {\n this._cognitoUser = new CognitoUser({\n Username: session.username,\n Pool: new CognitoUserPool({ UserPoolId: CognitoUserPoolId, ClientId: CognitoClientId })\n });\n this._cognitoUserSession = new CognitoUserSession({\n IdToken: new CognitoIdToken({ IdToken: session.idToken }),\n AccessToken: new CognitoAccessToken({ AccessToken: session.accessToken }),\n RefreshToken: new CognitoRefreshToken({ RefreshToken: session.refreshToken })\n });\n }\n }\n\n /**\n * Logs in the user with the given email address and password.\n *\n * This method authenticates the user with Mysa's Cognito user pool and establishes a session that can be used for\n * subsequent API calls. Upon successful login, a 'sessionChanged' event is emitted.\n *\n * @example\n *\n * ```typescript\n * try {\n * await client.login('user@example.com', 'password123');\n * console.log('Login successful!');\n * } catch (error) {\n * console.error('Login failed:', error.message);\n * }\n * ```\n *\n * @param emailAddress - The email address of the user.\n * @param password - The password of the user.\n * @throws {@link Error} When authentication fails due to invalid credentials or network issues.\n */\n async login(emailAddress: string, password: string): Promise<void> {\n this._cognitoUser = undefined;\n this._cognitoUserSession = undefined;\n this._mqttClientId = undefined;\n this._mqttInterrupts = [];\n\n this.emitter.emit('sessionChanged', this.session);\n\n return new Promise((resolve, reject) => {\n const user = new CognitoUser({\n Username: emailAddress,\n Pool: new CognitoUserPool({ UserPoolId: CognitoUserPoolId, ClientId: CognitoClientId })\n });\n\n user.authenticateUser(new AuthenticationDetails({ Username: emailAddress, Password: password }), {\n onSuccess: (session) => {\n this._cognitoUser = user;\n this._cognitoUserSession = session;\n this.emitter.emit('sessionChanged', this.session);\n\n resolve();\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 user is not authenticated.\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 user is not authenticated.\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 user is not authenticated.\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 user is not authenticated.\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 * 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 user is not authenticated.\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 const device = this._cachedDevices.DevicesObj[deviceId];\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.session?.username ?? '',\n type: 100\n },\n dest: {\n ref: deviceId,\n type: 1\n },\n resp: 2,\n body: {\n ver: 1,\n type: device.Model.startsWith('BB-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 await this._publishWithRetry(mqttConnection, `/v1/dev/${deviceId}/in`, payload, mqtt.QoS.AtLeastOnce);\n\n const timer = setInterval(async () => {\n this._logger.debug(`Sending request to keep-alive 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 await this._publishWithRetry(mqttConnection, `/v1/dev/${deviceId}/in`, payload, mqtt.QoS.AtLeastOnce);\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.\n *\n * @returns A promise that resolves to a valid CognitoUserSession.\n * @throws {@link UnauthenticatedError} When no session exists or refresh fails.\n */\n private async _getFreshSession(): Promise<CognitoUserSession> {\n if (!this._cognitoUser || !this._cognitoUserSession) {\n throw new UnauthenticatedError('An attempt was made to access a resource without a valid session.');\n }\n\n if (\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 Promise.resolve(this._cognitoUserSession);\n }\n\n this._logger.debug('Session is not valid or expired, refreshing...');\n return new Promise<CognitoUserSession>((resolve, reject) => {\n this._cognitoUser!.refreshSession(this._cognitoUserSession!.getRefreshToken(), (error, session) => {\n if (error) {\n this._logger.error('Failed to refresh session:', error);\n reject(new UnauthenticatedError('Unable to refresh the authentication session.'));\n } else {\n this._logger.debug('Session refreshed successfully');\n this._cognitoUserSession = session;\n this.emitter.emit('sessionChanged', this.session);\n resolve(session);\n }\n });\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 '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 await connection.publish(topic, payload, qos);\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 // Per-process stable client id. Random suffix avoids collisions with other running processes.\n if (!this._mqttClientId) {\n this._mqttClientId = `mysa-js-sdk-${this.session?.username ?? 'anon'}-${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 const client = new mqtt.MqttClient();\n const connection = client.new_connection(config);\n\n connection.on('connect', () => {\n this._logger.debug('MQTT connect');\n });\n\n connection.on('connection_success', () => {\n this._logger.debug('MQTT connection_success');\n });\n\n connection.on('connection_failure', (e) => {\n this._logger.error('MQTT connection_failure', e);\n });\n\n connection.on('interrupt', async (e) => {\n this._logger.warn('MQTT interrupt', 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 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\n // Force new client id to escape collision; close current connection\n this._mqttClientId = undefined;\n\n // Clear interrupts\n this._mqttInterrupts = [];\n\n try {\n await connection.disconnect();\n\n if (this._mqttConnectionPromise) {\n this._logger.warn('MQTT connection promise still defined after disconnect; expected it to be cleared.');\n this._mqttConnectionPromise = undefined;\n }\n } catch (error) {\n this._logger.error('Failed to disconnect MQTT connection', error);\n }\n }\n });\n\n connection.on('resume', async (returnCode, sessionPresent) => {\n this._logger.info(`MQTT resume returnCode=${returnCode} sessionPresent=${sessionPresent}`);\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', e);\n });\n\n connection.on('closed', () => {\n this._logger.info('MQTT connection closed');\n this._mqttConnectionPromise = 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_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":"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","names":["InMessageType","OutMessageType","payload"]}
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 * Creates a new UnauthenticatedError instance.\n *\n * @param message - The error message\n */\n constructor(message: string) {\n super(message);\n this.name = 'UnauthenticatedError';\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 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 console.error('Error parsing MQTT payload:', error);\n throw new Error('Failed to parse MQTT payload');\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 /** Notification that a device's operational state has changed */\n DEVICE_STATE_CHANGE = 44\n}\n","import { MysaSession } from '@/api/MysaSession';\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 } from '@/types/rest';\nimport { DescribeThingCommand, IoTClient } from '@aws-sdk/client-iot';\nimport { fromCognitoIdentityPool } from '@aws-sdk/credential-providers';\nimport {\n AuthenticationDetails,\n CognitoAccessToken,\n CognitoIdToken,\n CognitoRefreshToken,\n CognitoUser,\n CognitoUserPool,\n CognitoUserSession\n} from 'amazon-cognito-identity-js';\nimport { 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 } 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');\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();\n *\n * await client.login('user@example.com', 'password');\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 current session object, if any. */\n private _cognitoUserSession?: CognitoUserSession;\n\n /** The current user object, if any. */\n private _cognitoUser?: CognitoUser;\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 * Gets the persistable session object.\n *\n * @returns The current persistable session object, if any.\n */\n get session(): MysaSession | undefined {\n if (!this._cognitoUserSession || !this._cognitoUser) {\n return undefined;\n }\n\n return {\n username: this._cognitoUser.getUsername(),\n idToken: this._cognitoUserSession.getIdToken().getJwtToken(),\n accessToken: this._cognitoUserSession.getAccessToken().getJwtToken(),\n refreshToken: this._cognitoUserSession.getRefreshToken().getToken()\n };\n }\n\n /**\n * Returns whether the client currently has an active session.\n *\n * @returns True if the client has an active session, false otherwise.\n */\n get isAuthenticated(): boolean {\n return !!this.session;\n }\n\n /**\n * Constructs a new instance of the MysaApiClient.\n *\n * @param session - The persistable session object, if any.\n * @param options - The options for the client.\n */\n constructor(session?: MysaSession, options?: MysaApiClientOptions) {\n this._logger = options?.logger || new VoidLogger();\n this._fetcher = options?.fetcher || fetch;\n\n if (session) {\n this._cognitoUser = new CognitoUser({\n Username: session.username,\n Pool: new CognitoUserPool({ UserPoolId: CognitoUserPoolId, ClientId: CognitoClientId })\n });\n this._cognitoUserSession = new CognitoUserSession({\n IdToken: new CognitoIdToken({ IdToken: session.idToken }),\n AccessToken: new CognitoAccessToken({ AccessToken: session.accessToken }),\n RefreshToken: new CognitoRefreshToken({ RefreshToken: session.refreshToken })\n });\n }\n }\n\n /**\n * Logs in the user with the given email address and password.\n *\n * This method authenticates the user with Mysa's Cognito user pool and establishes a session that can be used for\n * subsequent API calls. Upon successful login, a 'sessionChanged' event is emitted.\n *\n * @example\n *\n * ```typescript\n * try {\n * await client.login('user@example.com', 'password123');\n * console.log('Login successful!');\n * } catch (error) {\n * console.error('Login failed:', error.message);\n * }\n * ```\n *\n * @param emailAddress - The email address of the user.\n * @param password - The password of the user.\n * @throws {@link Error} When authentication fails due to invalid credentials or network issues.\n */\n async login(emailAddress: string, password: string): Promise<void> {\n this._cognitoUser = undefined;\n this._cognitoUserSession = undefined;\n this._mqttClientId = undefined;\n this._mqttInterrupts = [];\n\n this.emitter.emit('sessionChanged', this.session);\n\n return new Promise((resolve, reject) => {\n const user = new CognitoUser({\n Username: emailAddress,\n Pool: new CognitoUserPool({ UserPoolId: CognitoUserPoolId, ClientId: CognitoClientId })\n });\n\n user.authenticateUser(new AuthenticationDetails({ Username: emailAddress, Password: password }), {\n onSuccess: (session) => {\n this._cognitoUser = user;\n this._cognitoUserSession = session;\n this.emitter.emit('sessionChanged', this.session);\n\n resolve();\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 user is not authenticated.\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 user is not authenticated.\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 user is not authenticated.\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 user is not authenticated.\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 * 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 user is not authenticated.\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 const device = this._cachedDevices.DevicesObj[deviceId];\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.session?.username ?? '',\n type: 100\n },\n dest: {\n ref: deviceId,\n type: 1\n },\n resp: 2,\n body: {\n ver: 1,\n type: device.Model.startsWith('BB-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 await this._publishWithRetry(mqttConnection, `/v1/dev/${deviceId}/in`, payload, mqtt.QoS.AtLeastOnce);\n\n const timer = setInterval(async () => {\n this._logger.debug(`Sending request to keep-alive publishing device status for '${deviceId}'...`);\n\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 }, 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.\n *\n * @returns A promise that resolves to a valid CognitoUserSession.\n * @throws {@link UnauthenticatedError} When no session exists or refresh fails.\n */\n private async _getFreshSession(): Promise<CognitoUserSession> {\n if (!this._cognitoUser || !this._cognitoUserSession) {\n throw new UnauthenticatedError('An attempt was made to access a resource without a valid session.');\n }\n\n if (\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 Promise.resolve(this._cognitoUserSession);\n }\n\n this._logger.debug('Session is not valid or expired, refreshing...');\n return new Promise<CognitoUserSession>((resolve, reject) => {\n this._cognitoUser!.refreshSession(this._cognitoUserSession!.getRefreshToken(), (error, session) => {\n if (error) {\n this._logger.error('Failed to refresh session:', error);\n reject(new UnauthenticatedError('Unable to refresh the authentication session.'));\n } else {\n this._logger.debug('Session refreshed successfully');\n this._cognitoUserSession = session;\n this.emitter.emit('sessionChanged', this.session);\n resolve(session);\n }\n });\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 await connection.publish(topic, payload, qos);\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.session?.username ?? '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 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 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_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":"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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": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "description": "A JavaScript SDK for accessing Mysa smart thermostats.",
@@ -50,30 +50,30 @@
50
50
  "brace-expansion": "^2.0.2"
51
51
  },
52
52
  "dependencies": {
53
- "@aws-sdk/client-iot": "3.920.0",
54
- "@aws-sdk/credential-providers": "3.922.0",
55
- "amazon-cognito-identity-js": "6.3.15",
56
- "aws-iot-device-sdk-v2": "1.22.0",
57
- "dayjs": "1.11.18",
53
+ "@aws-sdk/client-iot": "3.936.0",
54
+ "@aws-sdk/credential-providers": "3.940.0",
55
+ "amazon-cognito-identity-js": "6.3.16",
56
+ "aws-iot-device-sdk-v2": "1.23.1",
57
+ "dayjs": "1.11.19",
58
58
  "lodash": "4.17.21",
59
59
  "nanoid": "5.1.6"
60
60
  },
61
61
  "devDependencies": {
62
- "@eslint/js": "9.38.0",
63
- "@semantic-release/npm": "13.1.1",
64
- "@types/lodash": "4.17.20",
65
- "@types/node": "24.10.0",
62
+ "@eslint/js": "9.39.1",
63
+ "@semantic-release/npm": "13.1.2",
64
+ "@types/lodash": "4.17.21",
65
+ "@types/node": "24.10.1",
66
66
  "conventional-changelog-conventionalcommits": "9.1.0",
67
67
  "dotenv": "17.2.3",
68
- "eslint": "9.39.0",
69
- "eslint-plugin-jsdoc": "61.1.11",
70
- "eslint-plugin-tsdoc": "0.4.0",
71
- "pino": "9.13.0",
72
- "pino-pretty": "13.0.0",
68
+ "eslint": "9.39.1",
69
+ "eslint-plugin-jsdoc": "61.4.1",
70
+ "eslint-plugin-tsdoc": "0.5.0",
71
+ "pino": "10.1.0",
72
+ "pino-pretty": "13.1.2",
73
73
  "prettier": "3.6.2",
74
74
  "prettier-plugin-jsdoc": "1.5.0",
75
75
  "prettier-plugin-organize-imports": "4.3.0",
76
- "semantic-release": "25.0.1",
76
+ "semantic-release": "25.0.2",
77
77
  "tsup": "8.5.0",
78
78
  "tsx": "4.20.6",
79
79
  "typedoc": "0.28.14",