edilkamin 1.6.1 → 1.6.2

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/README.md CHANGED
@@ -61,6 +61,36 @@ Or with `npx` once the library is installed:
61
61
  npx edilkamin deviceInfo --mac $MAC --username $USERNAME --password $PASSWORD
62
62
  ```
63
63
 
64
+ ## API Versions
65
+
66
+ This library supports both the new and legacy Edilkamin API endpoints.
67
+
68
+ ### CLI Usage
69
+
70
+ ```sh
71
+ # New API (default)
72
+ yarn cli deviceInfo --mac $MAC --username $USERNAME --password $PASSWORD
73
+
74
+ # Legacy API
75
+ yarn cli deviceInfo --mac $MAC --username $USERNAME --password $PASSWORD --legacy
76
+ ```
77
+
78
+ ### Library Usage
79
+
80
+ ```js
81
+ import { configure, signIn, OLD_API_URL, NEW_API_URL } from "edilkamin";
82
+
83
+ // New API (default)
84
+ const token = await signIn(username, password);
85
+ const api = configure();
86
+
87
+ // Legacy API
88
+ const legacyToken = await signIn(username, password, true);
89
+ const legacyApi = configure(OLD_API_URL);
90
+ ```
91
+
92
+ > **Note**: The legacy API uses AWS API Gateway and may be deprecated in the future.
93
+
64
94
  ## Motivations
65
95
 
66
96
  - providing an open source web alternative
@@ -0,0 +1,25 @@
1
+ import { BufferEncodedType } from "./types";
2
+ /**
3
+ * Type guard to check if a value is a serialized Node.js Buffer.
4
+ * Node.js Buffers serialize to JSON as: {type: "Buffer", data: [...]}
5
+ *
6
+ * @param value - The value to check
7
+ * @returns True if the value is a Buffer-encoded object
8
+ */
9
+ declare const isBuffer: (value: unknown) => value is BufferEncodedType;
10
+ /**
11
+ * Decompresses a Buffer-encoded gzip object and parses the resulting JSON.
12
+ *
13
+ * @param bufferObj - A serialized Buffer object containing gzip data
14
+ * @returns The decompressed and parsed JSON data, or the original object on failure
15
+ */
16
+ declare const decompressBuffer: (bufferObj: BufferEncodedType) => unknown;
17
+ /**
18
+ * Recursively processes an API response to decompress any Buffer-encoded fields.
19
+ * Handles nested objects and arrays, preserving structure while decompressing.
20
+ *
21
+ * @param data - The API response data to process
22
+ * @returns The processed data with all Buffer fields decompressed
23
+ */
24
+ declare const processResponse: <T>(data: T) => T;
25
+ export { decompressBuffer, isBuffer, processResponse };
@@ -0,0 +1,70 @@
1
+ import pako from "pako";
2
+ /**
3
+ * Type guard to check if a value is a serialized Node.js Buffer.
4
+ * Node.js Buffers serialize to JSON as: {type: "Buffer", data: [...]}
5
+ *
6
+ * @param value - The value to check
7
+ * @returns True if the value is a Buffer-encoded object
8
+ */
9
+ const isBuffer = (value) => {
10
+ return (typeof value === "object" &&
11
+ value !== null &&
12
+ "type" in value &&
13
+ value.type === "Buffer" &&
14
+ "data" in value &&
15
+ Array.isArray(value.data));
16
+ };
17
+ /**
18
+ * Decompresses a Buffer-encoded gzip object and parses the resulting JSON.
19
+ *
20
+ * @param bufferObj - A serialized Buffer object containing gzip data
21
+ * @returns The decompressed and parsed JSON data, or the original object on failure
22
+ */
23
+ const decompressBuffer = (bufferObj) => {
24
+ try {
25
+ // Convert data array to Uint8Array for pako
26
+ const compressed = new Uint8Array(bufferObj.data);
27
+ // Decompress with gzip
28
+ const decompressed = pako.ungzip(compressed, { to: "string" });
29
+ // Parse JSON
30
+ return JSON.parse(decompressed);
31
+ }
32
+ catch (error) {
33
+ // Log warning but return original to maintain backward compatibility
34
+ console.warn("Failed to decompress buffer:", error);
35
+ return bufferObj;
36
+ }
37
+ };
38
+ /**
39
+ * Recursively processes an API response to decompress any Buffer-encoded fields.
40
+ * Handles nested objects and arrays, preserving structure while decompressing.
41
+ *
42
+ * @param data - The API response data to process
43
+ * @returns The processed data with all Buffer fields decompressed
44
+ */
45
+ const processResponse = (data) => {
46
+ if (data === null || data === undefined) {
47
+ return data;
48
+ }
49
+ // Check if this is a Buffer object
50
+ if (isBuffer(data)) {
51
+ const decompressed = decompressBuffer(data);
52
+ // Recursively process the decompressed result (may contain nested buffers)
53
+ return processResponse(decompressed);
54
+ }
55
+ // Recursively process arrays
56
+ if (Array.isArray(data)) {
57
+ return data.map((item) => processResponse(item));
58
+ }
59
+ // Recursively process objects
60
+ if (typeof data === "object") {
61
+ const processed = {};
62
+ for (const [key, value] of Object.entries(data)) {
63
+ processed[key] = processResponse(value);
64
+ }
65
+ return processed;
66
+ }
67
+ // Primitive value, return as-is
68
+ return data;
69
+ };
70
+ export { decompressBuffer, isBuffer, processResponse };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,181 @@
1
+ import { strict as assert } from "assert";
2
+ import pako from "pako";
3
+ import sinon from "sinon";
4
+ import { decompressBuffer, isBuffer, processResponse } from "./buffer-utils";
5
+ /**
6
+ * Helper to create a gzip-compressed Buffer object for testing.
7
+ */
8
+ const createGzippedBuffer = (data) => {
9
+ const json = JSON.stringify(data);
10
+ const compressed = pako.gzip(json);
11
+ return {
12
+ type: "Buffer",
13
+ data: Array.from(compressed),
14
+ };
15
+ };
16
+ describe("buffer-utils", () => {
17
+ afterEach(() => {
18
+ sinon.restore();
19
+ });
20
+ describe("isBuffer", () => {
21
+ it("should detect valid Buffer objects", () => {
22
+ const buffer = { type: "Buffer", data: [31, 139, 8, 0] };
23
+ assert.ok(isBuffer(buffer));
24
+ });
25
+ it("should detect empty Buffer objects", () => {
26
+ const buffer = { type: "Buffer", data: [] };
27
+ assert.ok(isBuffer(buffer));
28
+ });
29
+ it("should reject non-Buffer objects with wrong type", () => {
30
+ assert.ok(!isBuffer({ type: "NotBuffer", data: [] }));
31
+ });
32
+ it("should reject objects without type field", () => {
33
+ assert.ok(!isBuffer({ data: [1, 2, 3] }));
34
+ });
35
+ it("should reject objects without data field", () => {
36
+ assert.ok(!isBuffer({ type: "Buffer" }));
37
+ });
38
+ it("should reject objects with non-array data", () => {
39
+ assert.ok(!isBuffer({ type: "Buffer", data: "not an array" }));
40
+ });
41
+ it("should reject null", () => {
42
+ assert.ok(!isBuffer(null));
43
+ });
44
+ it("should reject undefined", () => {
45
+ assert.ok(!isBuffer(undefined));
46
+ });
47
+ it("should reject primitives", () => {
48
+ assert.ok(!isBuffer("string"));
49
+ assert.ok(!isBuffer(123));
50
+ assert.ok(!isBuffer(true));
51
+ });
52
+ });
53
+ describe("decompressBuffer", () => {
54
+ it("should decompress gzipped JSON buffer", () => {
55
+ const originalData = { test: "value", nested: { key: 123 } };
56
+ const bufferObj = createGzippedBuffer(originalData);
57
+ const result = decompressBuffer(bufferObj);
58
+ assert.deepEqual(result, originalData);
59
+ });
60
+ it("should handle gzipped arrays", () => {
61
+ const originalData = [1, 2, 3, "test"];
62
+ const bufferObj = createGzippedBuffer(originalData);
63
+ const result = decompressBuffer(bufferObj);
64
+ assert.deepEqual(result, originalData);
65
+ });
66
+ it("should handle gzipped strings", () => {
67
+ const originalData = "test string";
68
+ const bufferObj = createGzippedBuffer(originalData);
69
+ const result = decompressBuffer(bufferObj);
70
+ assert.equal(result, originalData);
71
+ });
72
+ it("should return original value if decompression fails", () => {
73
+ const consoleWarnStub = sinon.stub(console, "warn");
74
+ const invalidBuffer = { type: "Buffer", data: [1, 2, 3] };
75
+ const result = decompressBuffer(invalidBuffer);
76
+ assert.deepEqual(result, invalidBuffer);
77
+ assert.ok(consoleWarnStub.calledOnce);
78
+ });
79
+ it("should return original value if JSON parsing fails", () => {
80
+ const consoleWarnStub = sinon.stub(console, "warn");
81
+ // Create valid gzip but invalid JSON
82
+ const invalidJson = "not valid json {";
83
+ const compressed = pako.gzip(invalidJson);
84
+ const bufferObj = {
85
+ type: "Buffer",
86
+ data: Array.from(compressed),
87
+ };
88
+ const result = decompressBuffer(bufferObj);
89
+ assert.deepEqual(result, bufferObj);
90
+ assert.ok(consoleWarnStub.calledOnce);
91
+ });
92
+ });
93
+ describe("processResponse", () => {
94
+ it("should pass through null", () => {
95
+ assert.equal(processResponse(null), null);
96
+ });
97
+ it("should pass through undefined", () => {
98
+ assert.equal(processResponse(undefined), undefined);
99
+ });
100
+ it("should pass through primitives", () => {
101
+ assert.equal(processResponse("string"), "string");
102
+ assert.equal(processResponse(123), 123);
103
+ assert.equal(processResponse(true), true);
104
+ });
105
+ it("should pass through plain objects", () => {
106
+ const obj = { key: "value", nested: { num: 42 } };
107
+ assert.deepEqual(processResponse(obj), obj);
108
+ });
109
+ it("should pass through plain arrays", () => {
110
+ const arr = [1, "two", { three: 3 }];
111
+ assert.deepEqual(processResponse(arr), arr);
112
+ });
113
+ it("should decompress Buffer at root level", () => {
114
+ const originalData = { decompressed: true };
115
+ const buffer = createGzippedBuffer(originalData);
116
+ const result = processResponse(buffer);
117
+ assert.deepEqual(result, originalData);
118
+ });
119
+ it("should decompress nested Buffer fields", () => {
120
+ const statusData = { commands: { power: true } };
121
+ const response = {
122
+ plain: "data",
123
+ status: createGzippedBuffer(statusData),
124
+ };
125
+ const result = processResponse(response);
126
+ assert.equal(result.plain, "data");
127
+ assert.deepEqual(result.status, statusData);
128
+ });
129
+ it("should recursively decompress deeply nested Buffers", () => {
130
+ const innerData = { value: 42 };
131
+ const middleData = { inner: createGzippedBuffer(innerData) };
132
+ const response = {
133
+ outer: createGzippedBuffer(middleData),
134
+ };
135
+ const result = processResponse(response);
136
+ assert.deepEqual(result, { outer: { inner: { value: 42 } } });
137
+ });
138
+ it("should handle arrays containing Buffers", () => {
139
+ const itemData = { id: 1 };
140
+ const response = {
141
+ items: [createGzippedBuffer(itemData), { id: 2 }],
142
+ };
143
+ const result = processResponse(response);
144
+ assert.deepEqual(result.items, [{ id: 1 }, { id: 2 }]);
145
+ });
146
+ it("should handle mixed compressed and uncompressed fields", () => {
147
+ const compressedStatus = { commands: { power: true } };
148
+ const response = {
149
+ status: createGzippedBuffer(compressedStatus),
150
+ nvm: { user_parameters: { temperature: 22 } },
151
+ plain_field: "unchanged",
152
+ };
153
+ const result = processResponse(response);
154
+ assert.deepEqual(result.status, compressedStatus);
155
+ assert.deepEqual(result.nvm, { user_parameters: { temperature: 22 } });
156
+ assert.equal(result.plain_field, "unchanged");
157
+ });
158
+ it("should handle real-world DeviceInfo structure with compressed status", () => {
159
+ const statusData = {
160
+ commands: { power: true },
161
+ temperatures: { board: 25, enviroment: 20 },
162
+ };
163
+ const nvmData = {
164
+ user_parameters: {
165
+ enviroment_1_temperature: 22,
166
+ enviroment_2_temperature: 0,
167
+ enviroment_3_temperature: 0,
168
+ is_auto: false,
169
+ is_sound_active: true,
170
+ },
171
+ };
172
+ const response = {
173
+ status: createGzippedBuffer(statusData),
174
+ nvm: createGzippedBuffer(nvmData),
175
+ };
176
+ const result = processResponse(response);
177
+ assert.deepEqual(result.status, statusData);
178
+ assert.deepEqual(result.nvm, nvmData);
179
+ });
180
+ });
181
+ });
package/dist/esm/cli.js CHANGED
@@ -11,6 +11,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  import { Command } from "commander";
12
12
  import readline from "readline";
13
13
  import { version } from "../package.json";
14
+ import { NEW_API_URL, OLD_API_URL } from "./constants";
14
15
  import { configure, signIn } from "./library";
15
16
  const promptPassword = () => {
16
17
  const rl = readline.createInterface({
@@ -48,17 +49,24 @@ const addAuthOptions = (command) => command
48
49
  * @returns The command with the MAC address option added.
49
50
  */
50
51
  const addMacOption = (command) => command.requiredOption("-m, --mac <macAddress>", "MAC address of the device");
52
+ /**
53
+ * Adds legacy API option to a command.
54
+ * @param command The command to which the legacy option should be added.
55
+ * @returns The command with the legacy option added.
56
+ */
57
+ const addLegacyOption = (command) => command.option("--legacy", "Use legacy API endpoint (old AWS Gateway)");
51
58
  /**
52
59
  * Handles common authentication and API initialization logic.
53
60
  * @param options The options passed from the CLI command.
54
61
  * @returns An object containing the normalized MAC, JWT token, and configured API instance.
55
62
  */
56
63
  const initializeCommand = (options) => __awaiter(void 0, void 0, void 0, function* () {
57
- const { username, password, mac } = options;
64
+ const { username, password, mac, legacy = false } = options;
58
65
  const normalizedMac = mac.replace(/:/g, "");
59
66
  const pwd = password || (yield promptPassword());
60
- const jwtToken = yield signIn(username, pwd);
61
- const api = configure();
67
+ const jwtToken = yield signIn(username, pwd, legacy);
68
+ const apiUrl = legacy ? OLD_API_URL : NEW_API_URL;
69
+ const api = configure(apiUrl);
62
70
  return { normalizedMac, jwtToken, api };
63
71
  });
64
72
  /**
@@ -117,7 +125,7 @@ const createProgram = () => {
117
125
  getter: (api, jwtToken, mac) => api.getTargetTemperature(jwtToken, mac),
118
126
  },
119
127
  ].forEach(({ commandName, description, getter }) => {
120
- addMacOption(addAuthOptions(program.command(commandName).description(description))).action((options) => executeGetter(options, getter));
128
+ addLegacyOption(addMacOption(addAuthOptions(program.command(commandName).description(description)))).action((options) => executeGetter(options, getter));
121
129
  });
122
130
  // Generic setter commands
123
131
  [
@@ -132,8 +140,42 @@ const createProgram = () => {
132
140
  setter: (api, jwtToken, mac, value) => api.setTargetTemperature(jwtToken, mac, value),
133
141
  },
134
142
  ].forEach(({ commandName, description, setter }) => {
135
- addMacOption(addAuthOptions(program.command(commandName).description(description)).requiredOption("-v, --value <number>", "Value to set", parseFloat)).action((options) => executeSetter(options, setter));
143
+ addLegacyOption(addMacOption(addAuthOptions(program.command(commandName).description(description)).requiredOption("-v, --value <number>", "Value to set", parseFloat))).action((options) => executeSetter(options, setter));
136
144
  });
145
+ // Command: register
146
+ addLegacyOption(addAuthOptions(program
147
+ .command("register")
148
+ .description("Register a device with your account")))
149
+ .requiredOption("-m, --mac <macAddress>", "MAC address of the device")
150
+ .requiredOption("-s, --serial <serialNumber>", "Device serial number")
151
+ .requiredOption("-n, --name <deviceName>", "Device name")
152
+ .requiredOption("-r, --room <deviceRoom>", "Room name")
153
+ .action((options) => __awaiter(void 0, void 0, void 0, function* () {
154
+ const { username, password, mac, serial, name, room, legacy = false, } = options;
155
+ const normalizedMac = mac.replace(/:/g, "");
156
+ const pwd = password || (yield promptPassword());
157
+ const jwtToken = yield signIn(username, pwd, legacy);
158
+ const apiUrl = legacy ? OLD_API_URL : NEW_API_URL;
159
+ const api = configure(apiUrl);
160
+ const result = yield api.registerDevice(jwtToken, normalizedMac, serial, name, room);
161
+ console.log("Device registered successfully:");
162
+ console.log(JSON.stringify(result, null, 2));
163
+ }));
164
+ // Command: editDevice
165
+ addLegacyOption(addMacOption(addAuthOptions(program.command("editDevice").description("Update device name and room"))))
166
+ .requiredOption("-n, --name <deviceName>", "Device name")
167
+ .requiredOption("-r, --room <deviceRoom>", "Room name")
168
+ .action((options) => __awaiter(void 0, void 0, void 0, function* () {
169
+ const { username, password, mac, name, room, legacy = false } = options;
170
+ const normalizedMac = mac.replace(/:/g, "");
171
+ const pwd = password || (yield promptPassword());
172
+ const jwtToken = yield signIn(username, pwd, legacy);
173
+ const apiUrl = legacy ? OLD_API_URL : NEW_API_URL;
174
+ const api = configure(apiUrl);
175
+ const result = yield api.editDevice(jwtToken, normalizedMac, name, room);
176
+ console.log("Device updated successfully:");
177
+ console.log(JSON.stringify(result, null, 2));
178
+ }));
137
179
  return program;
138
180
  };
139
181
  const main = () => {
@@ -1,2 +1,4 @@
1
+ declare const OLD_API_URL = "https://fxtj7xkgc6.execute-api.eu-central-1.amazonaws.com/prod/";
2
+ declare const NEW_API_URL = "https://the-mind-api.edilkamin.com/";
1
3
  declare const API_URL = "https://the-mind-api.edilkamin.com/";
2
- export { API_URL };
4
+ export { API_URL, NEW_API_URL, OLD_API_URL };
@@ -1,2 +1,4 @@
1
- const API_URL = "https://the-mind-api.edilkamin.com/";
2
- export { API_URL };
1
+ const OLD_API_URL = "https://fxtj7xkgc6.execute-api.eu-central-1.amazonaws.com/prod/";
2
+ const NEW_API_URL = "https://the-mind-api.edilkamin.com/";
3
+ const API_URL = NEW_API_URL;
4
+ export { API_URL, NEW_API_URL, OLD_API_URL };
@@ -1,4 +1,6 @@
1
- export { API_URL } from "./constants";
1
+ export { decompressBuffer, isBuffer, processResponse } from "./buffer-utils";
2
+ export { API_URL, NEW_API_URL, OLD_API_URL } from "./constants";
2
3
  export { configure, signIn } from "./library";
3
- export { CommandsType, DeviceInfoType, StatusType, TemperaturesType, UserParametersType, } from "./types";
4
- export declare const deviceInfo: (jwtToken: string, macAddress: string) => Promise<import("./types").DeviceInfoType>, setPower: (jwtToken: string, macAddress: string, value: number) => Promise<import("axios").AxiosResponse<any, any, {}>>, setPowerOff: (jwtToken: string, macAddress: string) => Promise<import("axios").AxiosResponse<any, any, {}>>, setPowerOn: (jwtToken: string, macAddress: string) => Promise<import("axios").AxiosResponse<any, any, {}>>, getPower: (jwtToken: string, macAddress: string) => Promise<boolean>, getEnvironmentTemperature: (jwtToken: string, macAddress: string) => Promise<number>, getTargetTemperature: (jwtToken: string, macAddress: string) => Promise<number>, setTargetTemperature: (jwtToken: string, macAddress: string, temperature: number) => Promise<import("axios").AxiosResponse<any, any, {}>>;
4
+ export { serialNumberDisplay, serialNumberFromHex, serialNumberToHex, } from "./serial-utils";
5
+ export { BufferEncodedType, CommandsType, DeviceAssociationBody, DeviceAssociationResponse, DeviceInfoRawType, DeviceInfoType, EditDeviceAssociationBody, StatusType, TemperaturesType, UserParametersType, } from "./types";
6
+ export declare const deviceInfo: (jwtToken: string, macAddress: string) => Promise<import("./types").DeviceInfoType>, registerDevice: (jwtToken: string, macAddress: string, serialNumber: string, deviceName?: string, deviceRoom?: string) => Promise<import("./types").DeviceAssociationResponse>, editDevice: (jwtToken: string, macAddress: string, deviceName?: string, deviceRoom?: string) => Promise<import("./types").DeviceAssociationResponse>, setPower: (jwtToken: string, macAddress: string, value: number) => Promise<import("axios").AxiosResponse<any, any, {}>>, setPowerOff: (jwtToken: string, macAddress: string) => Promise<import("axios").AxiosResponse<any, any, {}>>, setPowerOn: (jwtToken: string, macAddress: string) => Promise<import("axios").AxiosResponse<any, any, {}>>, getPower: (jwtToken: string, macAddress: string) => Promise<boolean>, getEnvironmentTemperature: (jwtToken: string, macAddress: string) => Promise<number>, getTargetTemperature: (jwtToken: string, macAddress: string) => Promise<number>, setTargetTemperature: (jwtToken: string, macAddress: string, temperature: number) => Promise<import("axios").AxiosResponse<any, any, {}>>;
package/dist/esm/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { configure } from "./library";
2
- export { API_URL } from "./constants";
2
+ export { decompressBuffer, isBuffer, processResponse } from "./buffer-utils";
3
+ export { API_URL, NEW_API_URL, OLD_API_URL } from "./constants";
3
4
  export { configure, signIn } from "./library";
4
- export const { deviceInfo, setPower, setPowerOff, setPowerOn, getPower, getEnvironmentTemperature, getTargetTemperature, setTargetTemperature, } = configure();
5
+ export { serialNumberDisplay, serialNumberFromHex, serialNumberToHex, } from "./serial-utils";
6
+ export const { deviceInfo, registerDevice, editDevice, setPower, setPowerOff, setPowerOn, getPower, getEnvironmentTemperature, getTargetTemperature, setTargetTemperature, } = configure();
@@ -1,5 +1,5 @@
1
1
  import * as amplifyAuth from "aws-amplify/auth";
2
- import { DeviceInfoType } from "./types";
2
+ import { DeviceAssociationResponse, DeviceInfoType } from "./types";
3
3
  /**
4
4
  * Generates headers with a JWT token for authenticated requests.
5
5
  * @param {string} jwtToken - The JWT token for authorization.
@@ -14,9 +14,9 @@ declare const headers: (jwtToken: string) => {
14
14
  * @returns {object} - An object containing authentication-related methods.
15
15
  */
16
16
  declare const createAuthService: (auth: typeof amplifyAuth) => {
17
- signIn: (username: string, password: string) => Promise<string>;
17
+ signIn: (username: string, password: string, legacy?: boolean) => Promise<string>;
18
18
  };
19
- declare const signIn: (username: string, password: string) => Promise<string>;
19
+ declare const signIn: (username: string, password: string, legacy?: boolean) => Promise<string>;
20
20
  /**
21
21
  * Configures the library for API interactions.
22
22
  * Initializes API methods with a specified base URL.
@@ -30,6 +30,8 @@ declare const signIn: (username: string, password: string) => Promise<string>;
30
30
  */
31
31
  declare const configure: (baseURL?: string) => {
32
32
  deviceInfo: (jwtToken: string, macAddress: string) => Promise<DeviceInfoType>;
33
+ registerDevice: (jwtToken: string, macAddress: string, serialNumber: string, deviceName?: string, deviceRoom?: string) => Promise<DeviceAssociationResponse>;
34
+ editDevice: (jwtToken: string, macAddress: string, deviceName?: string, deviceRoom?: string) => Promise<DeviceAssociationResponse>;
33
35
  setPower: (jwtToken: string, macAddress: string, value: number) => Promise<import("axios").AxiosResponse<any, any, {}>>;
34
36
  setPowerOff: (jwtToken: string, macAddress: string) => Promise<import("axios").AxiosResponse<any, any, {}>>;
35
37
  setPowerOn: (jwtToken: string, macAddress: string) => Promise<import("axios").AxiosResponse<any, any, {}>>;
@@ -11,6 +11,7 @@ import { strict as assert } from "assert";
11
11
  import { Amplify } from "aws-amplify";
12
12
  import * as amplifyAuth from "aws-amplify/auth";
13
13
  import axios from "axios";
14
+ import { processResponse } from "./buffer-utils";
14
15
  import { API_URL } from "./constants";
15
16
  const amplifyconfiguration = {
16
17
  aws_project_region: "eu-central-1",
@@ -23,15 +24,16 @@ const amplifyconfiguration = {
23
24
  * @returns {object} - The headers object with the Authorization field.
24
25
  */
25
26
  const headers = (jwtToken) => ({ Authorization: `Bearer ${jwtToken}` });
27
+ let amplifyConfigured = false;
26
28
  /**
27
29
  * Configures Amplify if not already configured.
28
- * Ensures the configuration is only applied once.
30
+ * Uses a local flag to avoid calling getConfig() which prints a warning.
29
31
  */
30
32
  const configureAmplify = () => {
31
- const currentConfig = Amplify.getConfig();
32
- if (Object.keys(currentConfig).length !== 0)
33
+ if (amplifyConfigured)
33
34
  return;
34
35
  Amplify.configure(amplifyconfiguration);
36
+ amplifyConfigured = true;
35
37
  };
36
38
  /**
37
39
  * Creates an authentication service with sign-in functionality.
@@ -43,16 +45,21 @@ const createAuthService = (auth) => {
43
45
  * Signs in a user with the provided credentials.
44
46
  * @param {string} username - The username of the user.
45
47
  * @param {string} password - The password of the user.
48
+ * @param {boolean} [legacy=false] - If true, returns accessToken for legacy API.
46
49
  * @returns {Promise<string>} - The JWT token of the signed-in user.
47
50
  * @throws {Error} - If sign-in fails or no tokens are retrieved.
48
51
  */
49
- const signIn = (username, password) => __awaiter(void 0, void 0, void 0, function* () {
52
+ const signIn = (username_1, password_1, ...args_1) => __awaiter(void 0, [username_1, password_1, ...args_1], void 0, function* (username, password, legacy = false) {
50
53
  configureAmplify();
51
54
  yield auth.signOut(); // Ensure the user is signed out first
52
55
  const { isSignedIn } = yield auth.signIn({ username, password });
53
56
  assert.ok(isSignedIn, "Sign-in failed");
54
57
  const { tokens } = yield auth.fetchAuthSession();
55
58
  assert.ok(tokens, "No tokens found");
59
+ if (legacy) {
60
+ assert.ok(tokens.accessToken, "No access token found");
61
+ return tokens.accessToken.toString();
62
+ }
56
63
  assert.ok(tokens.idToken, "No ID token found");
57
64
  return tokens.idToken.toString();
58
65
  });
@@ -63,6 +70,7 @@ const { signIn } = createAuthService(amplifyAuth);
63
70
  const deviceInfo = (axiosInstance) =>
64
71
  /**
65
72
  * Retrieves information about a device by its MAC address.
73
+ * Automatically decompresses any gzip-compressed Buffer fields in the response.
66
74
  *
67
75
  * @param {string} jwtToken - The JWT token for authentication.
68
76
  * @param {string} macAddress - The MAC address of the device.
@@ -72,7 +80,8 @@ const deviceInfo = (axiosInstance) =>
72
80
  const response = yield axiosInstance.get(`device/${macAddress}/info`, {
73
81
  headers: headers(jwtToken),
74
82
  });
75
- return response.data;
83
+ // Process response to decompress any gzipped Buffer fields
84
+ return processResponse(response.data);
76
85
  });
77
86
  const mqttCommand = (axiosInstance) =>
78
87
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -162,6 +171,47 @@ const setTargetTemperature = (axiosInstance) =>
162
171
  name: "enviroment_1_temperature",
163
172
  value: temperature,
164
173
  });
174
+ const registerDevice = (axiosInstance) =>
175
+ /**
176
+ * Registers a device with the user's account.
177
+ * This must be called before other device operations will work on the new API.
178
+ *
179
+ * @param {string} jwtToken - The JWT token for authentication.
180
+ * @param {string} macAddress - The MAC address of the device (colons optional).
181
+ * @param {string} serialNumber - The device serial number.
182
+ * @param {string} deviceName - User-friendly name for the device (default: empty string).
183
+ * @param {string} deviceRoom - Room name for the device (default: empty string).
184
+ * @returns {Promise<DeviceAssociationResponse>} - A promise that resolves to the registration response.
185
+ */
186
+ (jwtToken_1, macAddress_1, serialNumber_1, ...args_1) => __awaiter(void 0, [jwtToken_1, macAddress_1, serialNumber_1, ...args_1], void 0, function* (jwtToken, macAddress, serialNumber, deviceName = "", deviceRoom = "") {
187
+ const body = {
188
+ macAddress: macAddress.replace(/:/g, ""),
189
+ deviceName,
190
+ deviceRoom,
191
+ serialNumber,
192
+ };
193
+ const response = yield axiosInstance.post("device", body, { headers: headers(jwtToken) });
194
+ return response.data;
195
+ });
196
+ const editDevice = (axiosInstance) =>
197
+ /**
198
+ * Updates a device's name and room.
199
+ *
200
+ * @param {string} jwtToken - The JWT token for authentication.
201
+ * @param {string} macAddress - The MAC address of the device (colons optional).
202
+ * @param {string} deviceName - New name for the device (default: empty string).
203
+ * @param {string} deviceRoom - New room for the device (default: empty string).
204
+ * @returns {Promise<DeviceAssociationResponse>} - A promise that resolves to the update response.
205
+ */
206
+ (jwtToken_1, macAddress_1, ...args_1) => __awaiter(void 0, [jwtToken_1, macAddress_1, ...args_1], void 0, function* (jwtToken, macAddress, deviceName = "", deviceRoom = "") {
207
+ const normalizedMac = macAddress.replace(/:/g, "");
208
+ const body = {
209
+ deviceName,
210
+ deviceRoom,
211
+ };
212
+ const response = yield axiosInstance.put(`device/${normalizedMac}`, body, { headers: headers(jwtToken) });
213
+ return response.data;
214
+ });
165
215
  /**
166
216
  * Configures the library for API interactions.
167
217
  * Initializes API methods with a specified base URL.
@@ -176,6 +226,8 @@ const setTargetTemperature = (axiosInstance) =>
176
226
  const configure = (baseURL = API_URL) => {
177
227
  const axiosInstance = axios.create({ baseURL });
178
228
  const deviceInfoInstance = deviceInfo(axiosInstance);
229
+ const registerDeviceInstance = registerDevice(axiosInstance);
230
+ const editDeviceInstance = editDevice(axiosInstance);
179
231
  const setPowerInstance = setPower(axiosInstance);
180
232
  const setPowerOffInstance = setPowerOff(axiosInstance);
181
233
  const setPowerOnInstance = setPowerOn(axiosInstance);
@@ -185,6 +237,8 @@ const configure = (baseURL = API_URL) => {
185
237
  const setTargetTemperatureInstance = setTargetTemperature(axiosInstance);
186
238
  return {
187
239
  deviceInfo: deviceInfoInstance,
240
+ registerDevice: registerDeviceInstance,
241
+ editDevice: editDeviceInstance,
188
242
  setPower: setPowerInstance,
189
243
  setPowerOff: setPowerOffInstance,
190
244
  setPowerOn: setPowerOnInstance,