mysa-js-sdk 2.0.1 → 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 +83 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +83 -10
- package/dist/index.js.map +1 -1
- package/package.json +18 -17
package/dist/index.cjs
CHANGED
|
@@ -231,9 +231,12 @@ 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);
|
|
237
|
+
var import_nanoid = require("nanoid");
|
|
236
238
|
import_dayjs.default.extend(import_duration.default);
|
|
239
|
+
var getRandomClientId = (0, import_nanoid.customAlphabet)("1234567890abcdefghijklmnopqrstuvwxyz", 8);
|
|
237
240
|
var AwsRegion = "us-east-1";
|
|
238
241
|
var CognitoUserPoolId = "us-east-1_GUFWfhI7g";
|
|
239
242
|
var CognitoClientId = "19efs8tgqe942atbqmot5m36t3";
|
|
@@ -253,6 +256,14 @@ var MysaApiClient = class {
|
|
|
253
256
|
_fetcher;
|
|
254
257
|
/** A promise that resolves to the MQTT connection used for real-time updates. */
|
|
255
258
|
_mqttConnectionPromise;
|
|
259
|
+
/** Stable per-process MQTT client id (prevents collisions between multiple processes). */
|
|
260
|
+
_mqttClientId;
|
|
261
|
+
/** Expiration time of the credentials currently in use by the MQTT client. */
|
|
262
|
+
_mqttCredentialsExpiration;
|
|
263
|
+
/** Interrupt timestamps for storm / collision detection. */
|
|
264
|
+
_mqttInterrupts = [];
|
|
265
|
+
/** Whether a forced MQTT reset is currently in progress (guards against re-entrancy). */
|
|
266
|
+
_mqttResetInProgress = false;
|
|
256
267
|
/** The device IDs that are currently being updated in real-time, mapped to their respective timeouts. */
|
|
257
268
|
_realtimeDeviceIds = /* @__PURE__ */ new Map();
|
|
258
269
|
/** The cached devices object, if any. */
|
|
@@ -332,6 +343,8 @@ var MysaApiClient = class {
|
|
|
332
343
|
async login(emailAddress, password) {
|
|
333
344
|
this._cognitoUser = void 0;
|
|
334
345
|
this._cognitoUserSession = void 0;
|
|
346
|
+
this._mqttClientId = void 0;
|
|
347
|
+
this._mqttInterrupts = [];
|
|
335
348
|
this.emitter.emit("sessionChanged", this.session);
|
|
336
349
|
return new Promise((resolve, reject) => {
|
|
337
350
|
const user = new import_amazon_cognito_identity_js.CognitoUser({
|
|
@@ -596,13 +609,14 @@ var MysaApiClient = class {
|
|
|
596
609
|
await this._publishWithRetry(mqttConnection, `/v1/dev/${deviceId}/in`, payload, import_aws_iot_device_sdk_v2.mqtt.QoS.AtLeastOnce);
|
|
597
610
|
const timer = setInterval(async () => {
|
|
598
611
|
this._logger.debug(`Sending request to keep-alive publishing device status for '${deviceId}'...`);
|
|
612
|
+
const connection = await this._getMqttConnection();
|
|
599
613
|
const payload2 = serializeMqttPayload({
|
|
600
614
|
Device: deviceId,
|
|
601
615
|
MsgType: 11 /* START_PUBLISHING_DEVICE_STATUS */,
|
|
602
616
|
Timestamp: (0, import_dayjs.default)().unix(),
|
|
603
617
|
Timeout: RealtimeKeepAliveInterval.asSeconds()
|
|
604
618
|
});
|
|
605
|
-
await this._publishWithRetry(
|
|
619
|
+
await this._publishWithRetry(connection, `/v1/dev/${deviceId}/in`, payload2, import_aws_iot_device_sdk_v2.mqtt.QoS.AtLeastOnce);
|
|
606
620
|
}, RealtimeKeepAliveInterval.subtract(10, "seconds").asMilliseconds());
|
|
607
621
|
this._realtimeDeviceIds.set(deviceId, timer);
|
|
608
622
|
}
|
|
@@ -699,6 +713,9 @@ var MysaApiClient = class {
|
|
|
699
713
|
const transientMarkers = [
|
|
700
714
|
"AWS_ERROR_MQTT_TIMEOUT",
|
|
701
715
|
"AWS_ERROR_MQTT_NO_CONNECTION",
|
|
716
|
+
"AWS_ERROR_MQTT_UNEXPECTED_HANGUP",
|
|
717
|
+
"UNEXPECTED_HANGUP",
|
|
718
|
+
"AWS_ERROR_MQTT_CONNECTION_DESTROYED",
|
|
702
719
|
"Time limit between request and response",
|
|
703
720
|
"timeout"
|
|
704
721
|
];
|
|
@@ -773,25 +790,80 @@ var MysaApiClient = class {
|
|
|
773
790
|
logger: this._logger
|
|
774
791
|
});
|
|
775
792
|
const credentials = await credentialsProvider();
|
|
776
|
-
|
|
777
|
-
|
|
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
|
+
}
|
|
802
|
+
if (!this._mqttClientId) {
|
|
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()}`;
|
|
806
|
+
}
|
|
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);
|
|
778
808
|
const config = builder.build();
|
|
779
809
|
const client = new import_aws_iot_device_sdk_v2.mqtt.MqttClient();
|
|
780
810
|
const connection = client.new_connection(config);
|
|
781
811
|
connection.on("connect", () => {
|
|
782
|
-
this._logger.debug(
|
|
812
|
+
this._logger.debug(`MQTT connect (clientId=${this._mqttClientId})`);
|
|
783
813
|
});
|
|
784
814
|
connection.on("connection_success", () => {
|
|
785
|
-
this._logger.debug(
|
|
815
|
+
this._logger.debug(`MQTT connection_success (clientId=${this._mqttClientId})`);
|
|
786
816
|
});
|
|
787
817
|
connection.on("connection_failure", (e) => {
|
|
788
|
-
this._logger.error(
|
|
818
|
+
this._logger.error(`MQTT connection_failure (clientId=${this._mqttClientId})`, e);
|
|
789
819
|
});
|
|
790
|
-
connection.on("interrupt", (e) => {
|
|
791
|
-
|
|
820
|
+
connection.on("interrupt", async (e) => {
|
|
821
|
+
var _a2;
|
|
822
|
+
this._logger.warn(`MQTT interrupt (clientId=${this._mqttClientId})`, e);
|
|
823
|
+
const now = Date.now();
|
|
824
|
+
this._mqttInterrupts = this._mqttInterrupts.filter((t) => now - t < 6e4);
|
|
825
|
+
this._mqttInterrupts.push(now);
|
|
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
|
+
}
|
|
836
|
+
this._mqttClientId = void 0;
|
|
837
|
+
this._mqttCredentialsExpiration = void 0;
|
|
838
|
+
this._mqttInterrupts = [];
|
|
839
|
+
this._mqttConnectionPromise = void 0;
|
|
840
|
+
try {
|
|
841
|
+
await connection.disconnect();
|
|
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);
|
|
855
|
+
}
|
|
856
|
+
} catch (error) {
|
|
857
|
+
this._logger.error("Error during MQTT reset", error);
|
|
858
|
+
} finally {
|
|
859
|
+
this._mqttResetInProgress = false;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
792
862
|
});
|
|
793
863
|
connection.on("resume", async (returnCode, sessionPresent) => {
|
|
794
|
-
this._logger.info(
|
|
864
|
+
this._logger.info(
|
|
865
|
+
`MQTT resume returnCode=${returnCode} sessionPresent=${sessionPresent} clientId=${this._mqttClientId}`
|
|
866
|
+
);
|
|
795
867
|
if (!sessionPresent) {
|
|
796
868
|
this._logger.info("No session present, re-subscribing each device");
|
|
797
869
|
try {
|
|
@@ -808,11 +880,12 @@ var MysaApiClient = class {
|
|
|
808
880
|
}
|
|
809
881
|
});
|
|
810
882
|
connection.on("error", (e) => {
|
|
811
|
-
this._logger.error(
|
|
883
|
+
this._logger.error(`MQTT error (clientId=${this._mqttClientId})`, e);
|
|
812
884
|
});
|
|
813
885
|
connection.on("closed", () => {
|
|
814
886
|
this._logger.info("MQTT connection closed");
|
|
815
887
|
this._mqttConnectionPromise = void 0;
|
|
888
|
+
this._mqttCredentialsExpiration = void 0;
|
|
816
889
|
});
|
|
817
890
|
await connection.connect();
|
|
818
891
|
return connection;
|
package/dist/index.cjs.map
CHANGED
|
@@ -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 { 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\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 /** 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.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 '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 // Stable client id + persistent session to retain QoS1 queue & subscriptions across reconnects.\n const stableClientId = `mysa-js-sdk-${this.session?.username ?? ''}`;\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(stableClientId)\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\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', (e) => {\n this._logger.warn('MQTT interrupt', e);\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
|
@@ -737,6 +737,14 @@ declare class MysaApiClient {
|
|
|
737
737
|
private _fetcher;
|
|
738
738
|
/** A promise that resolves to the MQTT connection used for real-time updates. */
|
|
739
739
|
private _mqttConnectionPromise?;
|
|
740
|
+
/** Stable per-process MQTT client id (prevents collisions between multiple processes). */
|
|
741
|
+
private _mqttClientId?;
|
|
742
|
+
/** Expiration time of the credentials currently in use by the MQTT client. */
|
|
743
|
+
private _mqttCredentialsExpiration?;
|
|
744
|
+
/** Interrupt timestamps for storm / collision detection. */
|
|
745
|
+
private _mqttInterrupts;
|
|
746
|
+
/** Whether a forced MQTT reset is currently in progress (guards against re-entrancy). */
|
|
747
|
+
private _mqttResetInProgress;
|
|
740
748
|
/** The device IDs that are currently being updated in real-time, mapped to their respective timeouts. */
|
|
741
749
|
private _realtimeDeviceIds;
|
|
742
750
|
/** The cached devices object, if any. */
|
package/dist/index.d.ts
CHANGED
|
@@ -737,6 +737,14 @@ declare class MysaApiClient {
|
|
|
737
737
|
private _fetcher;
|
|
738
738
|
/** A promise that resolves to the MQTT connection used for real-time updates. */
|
|
739
739
|
private _mqttConnectionPromise?;
|
|
740
|
+
/** Stable per-process MQTT client id (prevents collisions between multiple processes). */
|
|
741
|
+
private _mqttClientId?;
|
|
742
|
+
/** Expiration time of the credentials currently in use by the MQTT client. */
|
|
743
|
+
private _mqttCredentialsExpiration?;
|
|
744
|
+
/** Interrupt timestamps for storm / collision detection. */
|
|
745
|
+
private _mqttInterrupts;
|
|
746
|
+
/** Whether a forced MQTT reset is currently in progress (guards against re-entrancy). */
|
|
747
|
+
private _mqttResetInProgress;
|
|
740
748
|
/** The device IDs that are currently being updated in real-time, mapped to their respective timeouts. */
|
|
741
749
|
private _realtimeDeviceIds;
|
|
742
750
|
/** The cached devices object, if any. */
|
package/dist/index.js
CHANGED
|
@@ -198,9 +198,12 @@ 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";
|
|
204
|
+
import { customAlphabet } from "nanoid";
|
|
203
205
|
dayjs.extend(duration);
|
|
206
|
+
var getRandomClientId = customAlphabet("1234567890abcdefghijklmnopqrstuvwxyz", 8);
|
|
204
207
|
var AwsRegion = "us-east-1";
|
|
205
208
|
var CognitoUserPoolId = "us-east-1_GUFWfhI7g";
|
|
206
209
|
var CognitoClientId = "19efs8tgqe942atbqmot5m36t3";
|
|
@@ -220,6 +223,14 @@ var MysaApiClient = class {
|
|
|
220
223
|
_fetcher;
|
|
221
224
|
/** A promise that resolves to the MQTT connection used for real-time updates. */
|
|
222
225
|
_mqttConnectionPromise;
|
|
226
|
+
/** Stable per-process MQTT client id (prevents collisions between multiple processes). */
|
|
227
|
+
_mqttClientId;
|
|
228
|
+
/** Expiration time of the credentials currently in use by the MQTT client. */
|
|
229
|
+
_mqttCredentialsExpiration;
|
|
230
|
+
/** Interrupt timestamps for storm / collision detection. */
|
|
231
|
+
_mqttInterrupts = [];
|
|
232
|
+
/** Whether a forced MQTT reset is currently in progress (guards against re-entrancy). */
|
|
233
|
+
_mqttResetInProgress = false;
|
|
223
234
|
/** The device IDs that are currently being updated in real-time, mapped to their respective timeouts. */
|
|
224
235
|
_realtimeDeviceIds = /* @__PURE__ */ new Map();
|
|
225
236
|
/** The cached devices object, if any. */
|
|
@@ -299,6 +310,8 @@ var MysaApiClient = class {
|
|
|
299
310
|
async login(emailAddress, password) {
|
|
300
311
|
this._cognitoUser = void 0;
|
|
301
312
|
this._cognitoUserSession = void 0;
|
|
313
|
+
this._mqttClientId = void 0;
|
|
314
|
+
this._mqttInterrupts = [];
|
|
302
315
|
this.emitter.emit("sessionChanged", this.session);
|
|
303
316
|
return new Promise((resolve, reject) => {
|
|
304
317
|
const user = new CognitoUser({
|
|
@@ -563,13 +576,14 @@ var MysaApiClient = class {
|
|
|
563
576
|
await this._publishWithRetry(mqttConnection, `/v1/dev/${deviceId}/in`, payload, mqtt.QoS.AtLeastOnce);
|
|
564
577
|
const timer = setInterval(async () => {
|
|
565
578
|
this._logger.debug(`Sending request to keep-alive publishing device status for '${deviceId}'...`);
|
|
579
|
+
const connection = await this._getMqttConnection();
|
|
566
580
|
const payload2 = serializeMqttPayload({
|
|
567
581
|
Device: deviceId,
|
|
568
582
|
MsgType: 11 /* START_PUBLISHING_DEVICE_STATUS */,
|
|
569
583
|
Timestamp: dayjs().unix(),
|
|
570
584
|
Timeout: RealtimeKeepAliveInterval.asSeconds()
|
|
571
585
|
});
|
|
572
|
-
await this._publishWithRetry(
|
|
586
|
+
await this._publishWithRetry(connection, `/v1/dev/${deviceId}/in`, payload2, mqtt.QoS.AtLeastOnce);
|
|
573
587
|
}, RealtimeKeepAliveInterval.subtract(10, "seconds").asMilliseconds());
|
|
574
588
|
this._realtimeDeviceIds.set(deviceId, timer);
|
|
575
589
|
}
|
|
@@ -666,6 +680,9 @@ var MysaApiClient = class {
|
|
|
666
680
|
const transientMarkers = [
|
|
667
681
|
"AWS_ERROR_MQTT_TIMEOUT",
|
|
668
682
|
"AWS_ERROR_MQTT_NO_CONNECTION",
|
|
683
|
+
"AWS_ERROR_MQTT_UNEXPECTED_HANGUP",
|
|
684
|
+
"UNEXPECTED_HANGUP",
|
|
685
|
+
"AWS_ERROR_MQTT_CONNECTION_DESTROYED",
|
|
669
686
|
"Time limit between request and response",
|
|
670
687
|
"timeout"
|
|
671
688
|
];
|
|
@@ -740,25 +757,80 @@ var MysaApiClient = class {
|
|
|
740
757
|
logger: this._logger
|
|
741
758
|
});
|
|
742
759
|
const credentials = await credentialsProvider();
|
|
743
|
-
|
|
744
|
-
|
|
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
|
+
}
|
|
769
|
+
if (!this._mqttClientId) {
|
|
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()}`;
|
|
773
|
+
}
|
|
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);
|
|
745
775
|
const config = builder.build();
|
|
746
776
|
const client = new mqtt.MqttClient();
|
|
747
777
|
const connection = client.new_connection(config);
|
|
748
778
|
connection.on("connect", () => {
|
|
749
|
-
this._logger.debug(
|
|
779
|
+
this._logger.debug(`MQTT connect (clientId=${this._mqttClientId})`);
|
|
750
780
|
});
|
|
751
781
|
connection.on("connection_success", () => {
|
|
752
|
-
this._logger.debug(
|
|
782
|
+
this._logger.debug(`MQTT connection_success (clientId=${this._mqttClientId})`);
|
|
753
783
|
});
|
|
754
784
|
connection.on("connection_failure", (e) => {
|
|
755
|
-
this._logger.error(
|
|
785
|
+
this._logger.error(`MQTT connection_failure (clientId=${this._mqttClientId})`, e);
|
|
756
786
|
});
|
|
757
|
-
connection.on("interrupt", (e) => {
|
|
758
|
-
|
|
787
|
+
connection.on("interrupt", async (e) => {
|
|
788
|
+
var _a2;
|
|
789
|
+
this._logger.warn(`MQTT interrupt (clientId=${this._mqttClientId})`, e);
|
|
790
|
+
const now = Date.now();
|
|
791
|
+
this._mqttInterrupts = this._mqttInterrupts.filter((t) => now - t < 6e4);
|
|
792
|
+
this._mqttInterrupts.push(now);
|
|
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
|
+
}
|
|
803
|
+
this._mqttClientId = void 0;
|
|
804
|
+
this._mqttCredentialsExpiration = void 0;
|
|
805
|
+
this._mqttInterrupts = [];
|
|
806
|
+
this._mqttConnectionPromise = void 0;
|
|
807
|
+
try {
|
|
808
|
+
await connection.disconnect();
|
|
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);
|
|
822
|
+
}
|
|
823
|
+
} catch (error) {
|
|
824
|
+
this._logger.error("Error during MQTT reset", error);
|
|
825
|
+
} finally {
|
|
826
|
+
this._mqttResetInProgress = false;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
759
829
|
});
|
|
760
830
|
connection.on("resume", async (returnCode, sessionPresent) => {
|
|
761
|
-
this._logger.info(
|
|
831
|
+
this._logger.info(
|
|
832
|
+
`MQTT resume returnCode=${returnCode} sessionPresent=${sessionPresent} clientId=${this._mqttClientId}`
|
|
833
|
+
);
|
|
762
834
|
if (!sessionPresent) {
|
|
763
835
|
this._logger.info("No session present, re-subscribing each device");
|
|
764
836
|
try {
|
|
@@ -775,11 +847,12 @@ var MysaApiClient = class {
|
|
|
775
847
|
}
|
|
776
848
|
});
|
|
777
849
|
connection.on("error", (e) => {
|
|
778
|
-
this._logger.error(
|
|
850
|
+
this._logger.error(`MQTT error (clientId=${this._mqttClientId})`, e);
|
|
779
851
|
});
|
|
780
852
|
connection.on("closed", () => {
|
|
781
853
|
this._logger.info("MQTT connection closed");
|
|
782
854
|
this._mqttConnectionPromise = void 0;
|
|
855
|
+
this._mqttCredentialsExpiration = void 0;
|
|
783
856
|
});
|
|
784
857
|
await connection.connect();
|
|
785
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 { 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\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 /** 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.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 '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 // Stable client id + persistent session to retain QoS1 queue & subscriptions across reconnects.\n const stableClientId = `mysa-js-sdk-${this.session?.username ?? ''}`;\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(stableClientId)\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\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', (e) => {\n this._logger.warn('MQTT interrupt', e);\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.
|
|
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,29 +50,30 @@
|
|
|
50
50
|
"brace-expansion": "^2.0.2"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@aws-sdk/client-iot": "3.
|
|
54
|
-
"@aws-sdk/credential-providers": "3.
|
|
55
|
-
"amazon-cognito-identity-js": "6.3.
|
|
56
|
-
"aws-iot-device-sdk-v2": "1.
|
|
57
|
-
"dayjs": "1.11.
|
|
58
|
-
"lodash": "4.17.21"
|
|
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
|
+
"lodash": "4.17.21",
|
|
59
|
+
"nanoid": "5.1.6"
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|
|
61
|
-
"@eslint/js": "9.
|
|
62
|
-
"@semantic-release/npm": "13.1.
|
|
63
|
-
"@types/lodash": "4.17.
|
|
64
|
-
"@types/node": "24.10.
|
|
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",
|
|
65
66
|
"conventional-changelog-conventionalcommits": "9.1.0",
|
|
66
67
|
"dotenv": "17.2.3",
|
|
67
|
-
"eslint": "9.39.
|
|
68
|
-
"eslint-plugin-jsdoc": "61.1
|
|
69
|
-
"eslint-plugin-tsdoc": "0.
|
|
70
|
-
"pino": "
|
|
71
|
-
"pino-pretty": "13.
|
|
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",
|
|
72
73
|
"prettier": "3.6.2",
|
|
73
74
|
"prettier-plugin-jsdoc": "1.5.0",
|
|
74
75
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
75
|
-
"semantic-release": "25.0.
|
|
76
|
+
"semantic-release": "25.0.2",
|
|
76
77
|
"tsup": "8.5.0",
|
|
77
78
|
"tsx": "4.20.6",
|
|
78
79
|
"typedoc": "0.28.14",
|