edilkamin 1.6.2 → 1.7.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/.github/workflows/cli-tests.yml +1 -1
- package/.github/workflows/documentation.yml +1 -1
- package/.github/workflows/publish.yml +6 -4
- package/.github/workflows/tests.yml +1 -1
- package/README.md +31 -7
- package/dist/esm/browser-bundle.test.d.ts +1 -0
- package/dist/esm/browser-bundle.test.js +29 -0
- package/dist/esm/cli.js +69 -10
- package/dist/esm/configureAmplify.test.d.ts +1 -0
- package/dist/esm/configureAmplify.test.js +37 -0
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/library.d.ts +18 -6
- package/dist/esm/library.js +87 -55
- package/dist/esm/library.test.js +230 -192
- package/dist/esm/token-storage.d.ts +14 -0
- package/dist/esm/token-storage.js +81 -0
- package/eslint.config.mjs +12 -1
- package/package.json +15 -3
- package/src/browser-bundle.test.ts +21 -0
- package/src/buffer-utils.test.ts +1 -1
- package/src/cli.ts +113 -40
- package/src/configureAmplify.test.ts +47 -0
- package/src/index.ts +1 -1
- package/src/library.test.ts +279 -206
- package/src/library.ts +125 -70
- package/src/token-storage.ts +78 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { promises as fs } from "fs";
|
|
11
|
+
import * as os from "os";
|
|
12
|
+
import * as path from "path";
|
|
13
|
+
const TOKEN_DIR = path.join(os.homedir(), ".edilkamin");
|
|
14
|
+
const TOKEN_FILE = path.join(TOKEN_DIR, "session.json");
|
|
15
|
+
/**
|
|
16
|
+
* Custom storage adapter for AWS Amplify that persists to file system.
|
|
17
|
+
* Used for CLI to maintain sessions between invocations.
|
|
18
|
+
*/
|
|
19
|
+
export const createFileStorage = () => {
|
|
20
|
+
let cache = {};
|
|
21
|
+
let loaded = false;
|
|
22
|
+
const ensureDir = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
23
|
+
try {
|
|
24
|
+
yield fs.mkdir(TOKEN_DIR, { recursive: true, mode: 0o700 });
|
|
25
|
+
}
|
|
26
|
+
catch (_a) {
|
|
27
|
+
// Directory may already exist
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
const load = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
31
|
+
if (loaded)
|
|
32
|
+
return;
|
|
33
|
+
try {
|
|
34
|
+
const data = yield fs.readFile(TOKEN_FILE, "utf-8");
|
|
35
|
+
cache = JSON.parse(data);
|
|
36
|
+
}
|
|
37
|
+
catch (_a) {
|
|
38
|
+
cache = {};
|
|
39
|
+
}
|
|
40
|
+
loaded = true;
|
|
41
|
+
});
|
|
42
|
+
const save = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
43
|
+
yield ensureDir();
|
|
44
|
+
yield fs.writeFile(TOKEN_FILE, JSON.stringify(cache), {
|
|
45
|
+
encoding: "utf-8",
|
|
46
|
+
mode: 0o600,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
setItem: (key, value) => __awaiter(void 0, void 0, void 0, function* () {
|
|
51
|
+
yield load();
|
|
52
|
+
cache[key] = value;
|
|
53
|
+
yield save();
|
|
54
|
+
}),
|
|
55
|
+
getItem: (key) => __awaiter(void 0, void 0, void 0, function* () {
|
|
56
|
+
var _a;
|
|
57
|
+
yield load();
|
|
58
|
+
return (_a = cache[key]) !== null && _a !== void 0 ? _a : null;
|
|
59
|
+
}),
|
|
60
|
+
removeItem: (key) => __awaiter(void 0, void 0, void 0, function* () {
|
|
61
|
+
yield load();
|
|
62
|
+
delete cache[key];
|
|
63
|
+
yield save();
|
|
64
|
+
}),
|
|
65
|
+
clear: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
66
|
+
cache = {};
|
|
67
|
+
yield save();
|
|
68
|
+
}),
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Clears all stored session data.
|
|
73
|
+
*/
|
|
74
|
+
export const clearSession = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
75
|
+
try {
|
|
76
|
+
yield fs.unlink(TOKEN_FILE);
|
|
77
|
+
}
|
|
78
|
+
catch (_a) {
|
|
79
|
+
// File may not exist
|
|
80
|
+
}
|
|
81
|
+
});
|
package/eslint.config.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import typescriptEslint from "@typescript-eslint/eslint-plugin";
|
|
2
|
+
import prettierConfig from "eslint-config-prettier";
|
|
3
|
+
import prettierPlugin from "eslint-plugin-prettier";
|
|
2
4
|
import simpleImportSort from "eslint-plugin-simple-import-sort";
|
|
3
5
|
import globals from "globals";
|
|
4
6
|
import tsParser from "@typescript-eslint/parser";
|
|
@@ -19,7 +21,7 @@ const compat = new FlatCompat({
|
|
|
19
21
|
export default [
|
|
20
22
|
...compat.extends(
|
|
21
23
|
"eslint:recommended",
|
|
22
|
-
"plugin:@typescript-eslint/recommended"
|
|
24
|
+
"plugin:@typescript-eslint/recommended",
|
|
23
25
|
),
|
|
24
26
|
{
|
|
25
27
|
plugins: {
|
|
@@ -32,4 +34,13 @@ export default [
|
|
|
32
34
|
"simple-import-sort/exports": "error",
|
|
33
35
|
},
|
|
34
36
|
},
|
|
37
|
+
prettierConfig,
|
|
38
|
+
{
|
|
39
|
+
plugins: {
|
|
40
|
+
prettier: prettierPlugin,
|
|
41
|
+
},
|
|
42
|
+
rules: {
|
|
43
|
+
"prettier/prettier": "error",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
35
46
|
];
|
package/package.json
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "edilkamin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
7
7
|
"types": "dist/esm/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/esm/index.d.ts",
|
|
12
|
+
"default": "./dist/esm/index.js"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/cjs/index.d.ts",
|
|
16
|
+
"default": "./dist/cjs/index.js"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
8
20
|
"scripts": {
|
|
9
21
|
"cli": "ts-node src/cli.ts",
|
|
10
22
|
"cli:debug": "node --inspect --require ts-node/register/transpile-only src/cli.ts",
|
|
@@ -42,7 +54,6 @@
|
|
|
42
54
|
},
|
|
43
55
|
"dependencies": {
|
|
44
56
|
"aws-amplify": "^6.10.0",
|
|
45
|
-
"axios": "^1.13.2",
|
|
46
57
|
"pako": "^2.1.0"
|
|
47
58
|
},
|
|
48
59
|
"devDependencies": {
|
|
@@ -54,13 +65,14 @@
|
|
|
54
65
|
"@types/sinon": "^17.0.3",
|
|
55
66
|
"@typescript-eslint/eslint-plugin": "^8.17.0",
|
|
56
67
|
"@typescript-eslint/parser": "^8.17.0",
|
|
68
|
+
"esbuild": "^0.27.1",
|
|
57
69
|
"eslint": "^9.16.0",
|
|
58
70
|
"eslint-config-prettier": "^10.1.8",
|
|
59
71
|
"eslint-plugin-prettier": "^5.2.1",
|
|
60
72
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
61
73
|
"mocha": "^11.7.5",
|
|
62
74
|
"nyc": "^17.1.0",
|
|
63
|
-
"prettier": "^
|
|
75
|
+
"prettier": "^3.7.4",
|
|
64
76
|
"sinon": "^19.0.2",
|
|
65
77
|
"ts-node": "^10.9.1",
|
|
66
78
|
"typedoc": "^0.28.15",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { strict as assert } from "assert";
|
|
2
|
+
import * as esbuild from "esbuild";
|
|
3
|
+
|
|
4
|
+
describe("browser-bundle", () => {
|
|
5
|
+
it("should bundle for browser without Node.js built-ins", async () => {
|
|
6
|
+
// This test verifies that the library can be bundled for browser environments
|
|
7
|
+
// without requiring Node.js built-in modules (fs, os, path).
|
|
8
|
+
// If this test fails, it means Node.js-only code has leaked into the main exports.
|
|
9
|
+
const result = await esbuild.build({
|
|
10
|
+
entryPoints: ["dist/esm/index.js"],
|
|
11
|
+
platform: "browser",
|
|
12
|
+
bundle: true,
|
|
13
|
+
write: false,
|
|
14
|
+
// External dependencies that are expected (real deps + assert which is used for validation)
|
|
15
|
+
external: ["aws-amplify", "aws-amplify/*", "pako", "assert"],
|
|
16
|
+
logLevel: "silent",
|
|
17
|
+
});
|
|
18
|
+
// If we get here without error, the bundle succeeded
|
|
19
|
+
assert.ok(result.outputFiles.length > 0, "Bundle should produce output");
|
|
20
|
+
});
|
|
21
|
+
});
|
package/src/buffer-utils.test.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { decompressBuffer, isBuffer, processResponse } from "./buffer-utils";
|
|
|
8
8
|
* Helper to create a gzip-compressed Buffer object for testing.
|
|
9
9
|
*/
|
|
10
10
|
const createGzippedBuffer = (
|
|
11
|
-
data: unknown
|
|
11
|
+
data: unknown,
|
|
12
12
|
): { type: "Buffer"; data: number[] } => {
|
|
13
13
|
const json = JSON.stringify(data);
|
|
14
14
|
const compressed = pako.gzip(json);
|
package/src/cli.ts
CHANGED
|
@@ -4,7 +4,8 @@ import readline from "readline";
|
|
|
4
4
|
|
|
5
5
|
import { version } from "../package.json";
|
|
6
6
|
import { NEW_API_URL, OLD_API_URL } from "./constants";
|
|
7
|
-
import { configure, signIn } from "./library";
|
|
7
|
+
import { configure, configureAmplify, getSession, signIn } from "./library";
|
|
8
|
+
import { clearSession, createFileStorage } from "./token-storage";
|
|
8
9
|
|
|
9
10
|
const promptPassword = (): Promise<string> => {
|
|
10
11
|
const rl = readline.createInterface({
|
|
@@ -30,12 +31,16 @@ const promptPassword = (): Promise<string> => {
|
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* Adds common options (username and password) to a command.
|
|
34
|
+
* Username is optional if a session already exists.
|
|
33
35
|
* @param command The command to which options should be added.
|
|
34
36
|
* @returns The command with options added.
|
|
35
37
|
*/
|
|
36
38
|
const addAuthOptions = (command: Command): Command =>
|
|
37
39
|
command
|
|
38
|
-
.
|
|
40
|
+
.option(
|
|
41
|
+
"-u, --username <username>",
|
|
42
|
+
"Username (optional if session exists)",
|
|
43
|
+
)
|
|
39
44
|
.option("-p, --password <password>", "Password");
|
|
40
45
|
|
|
41
46
|
/**
|
|
@@ -56,11 +61,12 @@ const addLegacyOption = (command: Command): Command =>
|
|
|
56
61
|
|
|
57
62
|
/**
|
|
58
63
|
* Handles common authentication and API initialization logic.
|
|
64
|
+
* Tries to use existing session first, falls back to sign-in if needed.
|
|
59
65
|
* @param options The options passed from the CLI command.
|
|
60
66
|
* @returns An object containing the normalized MAC, JWT token, and configured API instance.
|
|
61
67
|
*/
|
|
62
68
|
const initializeCommand = async (options: {
|
|
63
|
-
username
|
|
69
|
+
username?: string;
|
|
64
70
|
password?: string;
|
|
65
71
|
mac: string;
|
|
66
72
|
legacy?: boolean;
|
|
@@ -71,8 +77,26 @@ const initializeCommand = async (options: {
|
|
|
71
77
|
}> => {
|
|
72
78
|
const { username, password, mac, legacy = false } = options;
|
|
73
79
|
const normalizedMac = mac.replace(/:/g, "");
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
|
|
81
|
+
// Initialize file storage for session persistence
|
|
82
|
+
const storage = createFileStorage();
|
|
83
|
+
configureAmplify(storage);
|
|
84
|
+
|
|
85
|
+
let jwtToken: string;
|
|
86
|
+
try {
|
|
87
|
+
// Try to get existing session first
|
|
88
|
+
jwtToken = await getSession(false, legacy);
|
|
89
|
+
} catch {
|
|
90
|
+
// No session, need to sign in
|
|
91
|
+
if (!username) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
"No session found. Please provide --username to sign in.",
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const pwd = password || (await promptPassword());
|
|
97
|
+
jwtToken = await signIn(username, pwd, legacy);
|
|
98
|
+
}
|
|
99
|
+
|
|
76
100
|
const apiUrl = legacy ? OLD_API_URL : NEW_API_URL;
|
|
77
101
|
const api = configure(apiUrl);
|
|
78
102
|
return { normalizedMac, jwtToken, api };
|
|
@@ -85,7 +109,7 @@ const initializeCommand = async (options: {
|
|
|
85
109
|
*/
|
|
86
110
|
const executeGetter = async (
|
|
87
111
|
options: {
|
|
88
|
-
username
|
|
112
|
+
username?: string;
|
|
89
113
|
password?: string;
|
|
90
114
|
mac: string;
|
|
91
115
|
legacy?: boolean;
|
|
@@ -93,8 +117,8 @@ const executeGetter = async (
|
|
|
93
117
|
getter: (
|
|
94
118
|
api: ReturnType<typeof configure>,
|
|
95
119
|
jwtToken: string,
|
|
96
|
-
mac: string
|
|
97
|
-
) => Promise<unknown
|
|
120
|
+
mac: string,
|
|
121
|
+
) => Promise<unknown>,
|
|
98
122
|
): Promise<void> => {
|
|
99
123
|
const { normalizedMac, jwtToken, api } = await initializeCommand(options);
|
|
100
124
|
const result = await getter(api, jwtToken, normalizedMac);
|
|
@@ -108,7 +132,7 @@ const executeGetter = async (
|
|
|
108
132
|
*/
|
|
109
133
|
const executeSetter = async (
|
|
110
134
|
options: {
|
|
111
|
-
username
|
|
135
|
+
username?: string;
|
|
112
136
|
password?: string;
|
|
113
137
|
mac: string;
|
|
114
138
|
value: number;
|
|
@@ -118,8 +142,8 @@ const executeSetter = async (
|
|
|
118
142
|
api: ReturnType<typeof configure>,
|
|
119
143
|
jwtToken: string,
|
|
120
144
|
mac: string,
|
|
121
|
-
value: number
|
|
122
|
-
) => Promise<unknown
|
|
145
|
+
value: number,
|
|
146
|
+
) => Promise<unknown>,
|
|
123
147
|
): Promise<void> => {
|
|
124
148
|
const { normalizedMac, jwtToken, api } = await initializeCommand(options);
|
|
125
149
|
const result = await setter(api, jwtToken, normalizedMac, options.value);
|
|
@@ -133,14 +157,29 @@ const createProgram = (): Command => {
|
|
|
133
157
|
.description("CLI tool for interacting with the Edilkamin API")
|
|
134
158
|
.version(version);
|
|
135
159
|
// Command: signIn
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
160
|
+
program
|
|
161
|
+
.command("signIn")
|
|
162
|
+
.description("Sign in and retrieve a JWT token")
|
|
163
|
+
.requiredOption("-u, --username <username>", "Username")
|
|
164
|
+
.option("-p, --password <password>", "Password")
|
|
165
|
+
.action(async (options) => {
|
|
166
|
+
const { username, password } = options;
|
|
167
|
+
// Initialize file storage for session persistence
|
|
168
|
+
const storage = createFileStorage();
|
|
169
|
+
configureAmplify(storage);
|
|
170
|
+
const pwd = password || (await promptPassword());
|
|
171
|
+
const jwtToken = await signIn(username, pwd);
|
|
172
|
+
console.log("JWT Token:", jwtToken);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Command: logout
|
|
176
|
+
program
|
|
177
|
+
.command("logout")
|
|
178
|
+
.description("Clear stored session")
|
|
179
|
+
.action(async () => {
|
|
180
|
+
await clearSession();
|
|
181
|
+
console.log("Session cleared successfully");
|
|
182
|
+
});
|
|
144
183
|
// Generic getter commands
|
|
145
184
|
[
|
|
146
185
|
{
|
|
@@ -149,7 +188,7 @@ const createProgram = (): Command => {
|
|
|
149
188
|
getter: (
|
|
150
189
|
api: ReturnType<typeof configure>,
|
|
151
190
|
jwtToken: string,
|
|
152
|
-
mac: string
|
|
191
|
+
mac: string,
|
|
153
192
|
) => api.deviceInfo(jwtToken, mac),
|
|
154
193
|
},
|
|
155
194
|
{
|
|
@@ -158,7 +197,7 @@ const createProgram = (): Command => {
|
|
|
158
197
|
getter: (
|
|
159
198
|
api: ReturnType<typeof configure>,
|
|
160
199
|
jwtToken: string,
|
|
161
|
-
mac: string
|
|
200
|
+
mac: string,
|
|
162
201
|
) => api.getPower(jwtToken, mac),
|
|
163
202
|
},
|
|
164
203
|
{
|
|
@@ -167,7 +206,7 @@ const createProgram = (): Command => {
|
|
|
167
206
|
getter: (
|
|
168
207
|
api: ReturnType<typeof configure>,
|
|
169
208
|
jwtToken: string,
|
|
170
|
-
mac: string
|
|
209
|
+
mac: string,
|
|
171
210
|
) => api.getEnvironmentTemperature(jwtToken, mac),
|
|
172
211
|
},
|
|
173
212
|
{
|
|
@@ -176,14 +215,14 @@ const createProgram = (): Command => {
|
|
|
176
215
|
getter: (
|
|
177
216
|
api: ReturnType<typeof configure>,
|
|
178
217
|
jwtToken: string,
|
|
179
|
-
mac: string
|
|
218
|
+
mac: string,
|
|
180
219
|
) => api.getTargetTemperature(jwtToken, mac),
|
|
181
220
|
},
|
|
182
221
|
].forEach(({ commandName, description, getter }) => {
|
|
183
222
|
addLegacyOption(
|
|
184
223
|
addMacOption(
|
|
185
|
-
addAuthOptions(program.command(commandName).description(description))
|
|
186
|
-
)
|
|
224
|
+
addAuthOptions(program.command(commandName).description(description)),
|
|
225
|
+
),
|
|
187
226
|
).action((options) => executeGetter(options, getter));
|
|
188
227
|
});
|
|
189
228
|
// Generic setter commands
|
|
@@ -195,7 +234,7 @@ const createProgram = (): Command => {
|
|
|
195
234
|
api: ReturnType<typeof configure>,
|
|
196
235
|
jwtToken: string,
|
|
197
236
|
mac: string,
|
|
198
|
-
value: number
|
|
237
|
+
value: number,
|
|
199
238
|
) => api.setPower(jwtToken, mac, value),
|
|
200
239
|
},
|
|
201
240
|
{
|
|
@@ -205,16 +244,16 @@ const createProgram = (): Command => {
|
|
|
205
244
|
api: ReturnType<typeof configure>,
|
|
206
245
|
jwtToken: string,
|
|
207
246
|
mac: string,
|
|
208
|
-
value: number
|
|
247
|
+
value: number,
|
|
209
248
|
) => api.setTargetTemperature(jwtToken, mac, value),
|
|
210
249
|
},
|
|
211
250
|
].forEach(({ commandName, description, setter }) => {
|
|
212
251
|
addLegacyOption(
|
|
213
252
|
addMacOption(
|
|
214
253
|
addAuthOptions(
|
|
215
|
-
program.command(commandName).description(description)
|
|
216
|
-
).requiredOption("-v, --value <number>", "Value to set", parseFloat)
|
|
217
|
-
)
|
|
254
|
+
program.command(commandName).description(description),
|
|
255
|
+
).requiredOption("-v, --value <number>", "Value to set", parseFloat),
|
|
256
|
+
),
|
|
218
257
|
).action((options) => executeSetter(options, setter));
|
|
219
258
|
});
|
|
220
259
|
|
|
@@ -223,8 +262,8 @@ const createProgram = (): Command => {
|
|
|
223
262
|
addAuthOptions(
|
|
224
263
|
program
|
|
225
264
|
.command("register")
|
|
226
|
-
.description("Register a device with your account")
|
|
227
|
-
)
|
|
265
|
+
.description("Register a device with your account"),
|
|
266
|
+
),
|
|
228
267
|
)
|
|
229
268
|
.requiredOption("-m, --mac <macAddress>", "MAC address of the device")
|
|
230
269
|
.requiredOption("-s, --serial <serialNumber>", "Device serial number")
|
|
@@ -241,8 +280,24 @@ const createProgram = (): Command => {
|
|
|
241
280
|
legacy = false,
|
|
242
281
|
} = options;
|
|
243
282
|
const normalizedMac = mac.replace(/:/g, "");
|
|
244
|
-
|
|
245
|
-
|
|
283
|
+
|
|
284
|
+
// Initialize file storage for session persistence
|
|
285
|
+
const storage = createFileStorage();
|
|
286
|
+
configureAmplify(storage);
|
|
287
|
+
|
|
288
|
+
let jwtToken: string;
|
|
289
|
+
try {
|
|
290
|
+
jwtToken = await getSession(false, legacy);
|
|
291
|
+
} catch {
|
|
292
|
+
if (!username) {
|
|
293
|
+
throw new Error(
|
|
294
|
+
"No session found. Please provide --username to sign in.",
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
const pwd = password || (await promptPassword());
|
|
298
|
+
jwtToken = await signIn(username, pwd, legacy);
|
|
299
|
+
}
|
|
300
|
+
|
|
246
301
|
const apiUrl = legacy ? OLD_API_URL : NEW_API_URL;
|
|
247
302
|
const api = configure(apiUrl);
|
|
248
303
|
const result = await api.registerDevice(
|
|
@@ -250,7 +305,7 @@ const createProgram = (): Command => {
|
|
|
250
305
|
normalizedMac,
|
|
251
306
|
serial,
|
|
252
307
|
name,
|
|
253
|
-
room
|
|
308
|
+
room,
|
|
254
309
|
);
|
|
255
310
|
console.log("Device registered successfully:");
|
|
256
311
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -260,17 +315,35 @@ const createProgram = (): Command => {
|
|
|
260
315
|
addLegacyOption(
|
|
261
316
|
addMacOption(
|
|
262
317
|
addAuthOptions(
|
|
263
|
-
program
|
|
264
|
-
|
|
265
|
-
|
|
318
|
+
program
|
|
319
|
+
.command("editDevice")
|
|
320
|
+
.description("Update device name and room"),
|
|
321
|
+
),
|
|
322
|
+
),
|
|
266
323
|
)
|
|
267
324
|
.requiredOption("-n, --name <deviceName>", "Device name")
|
|
268
325
|
.requiredOption("-r, --room <deviceRoom>", "Room name")
|
|
269
326
|
.action(async (options) => {
|
|
270
327
|
const { username, password, mac, name, room, legacy = false } = options;
|
|
271
328
|
const normalizedMac = mac.replace(/:/g, "");
|
|
272
|
-
|
|
273
|
-
|
|
329
|
+
|
|
330
|
+
// Initialize file storage for session persistence
|
|
331
|
+
const storage = createFileStorage();
|
|
332
|
+
configureAmplify(storage);
|
|
333
|
+
|
|
334
|
+
let jwtToken: string;
|
|
335
|
+
try {
|
|
336
|
+
jwtToken = await getSession(false, legacy);
|
|
337
|
+
} catch {
|
|
338
|
+
if (!username) {
|
|
339
|
+
throw new Error(
|
|
340
|
+
"No session found. Please provide --username to sign in.",
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
const pwd = password || (await promptPassword());
|
|
344
|
+
jwtToken = await signIn(username, pwd, legacy);
|
|
345
|
+
}
|
|
346
|
+
|
|
274
347
|
const apiUrl = legacy ? OLD_API_URL : NEW_API_URL;
|
|
275
348
|
const api = configure(apiUrl);
|
|
276
349
|
const result = await api.editDevice(jwtToken, normalizedMac, name, room);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { strict as assert } from "assert";
|
|
2
|
+
import sinon from "sinon";
|
|
3
|
+
|
|
4
|
+
import { configureAmplify } from "../src/library";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This test file specifically tests the configureAmplify function with custom storage.
|
|
8
|
+
* It tests line 61 in library.ts:
|
|
9
|
+
* cognitoUserPoolsTokenProvider.setKeyValueStorage(storage)
|
|
10
|
+
*
|
|
11
|
+
* IMPORTANT: This file is named to run BEFORE library.test.ts (alphabetically)
|
|
12
|
+
* to ensure amplifyConfigured is still false when these tests run.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
describe("configureAmplify", () => {
|
|
16
|
+
it("should configure Amplify with custom storage", () => {
|
|
17
|
+
const mockStorage = {
|
|
18
|
+
setItem: sinon.stub().resolves(),
|
|
19
|
+
getItem: sinon.stub().resolves(null),
|
|
20
|
+
removeItem: sinon.stub().resolves(),
|
|
21
|
+
clear: sinon.stub().resolves(),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Call configureAmplify with custom storage
|
|
25
|
+
// This is the first call in the test suite, so amplifyConfigured is false
|
|
26
|
+
// This should trigger line 61 in library.ts
|
|
27
|
+
configureAmplify(mockStorage);
|
|
28
|
+
|
|
29
|
+
// The test passes if no error is thrown
|
|
30
|
+
// Coverage confirms line 61 is executed
|
|
31
|
+
assert.ok(true, "configureAmplify with storage completed without error");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should only configure Amplify once (idempotent)", () => {
|
|
35
|
+
// Call configureAmplify multiple times without storage
|
|
36
|
+
configureAmplify();
|
|
37
|
+
configureAmplify();
|
|
38
|
+
configureAmplify();
|
|
39
|
+
|
|
40
|
+
// Should not throw or have any side effects
|
|
41
|
+
// The function returns early if already configured (line 58)
|
|
42
|
+
assert.ok(
|
|
43
|
+
true,
|
|
44
|
+
"Multiple calls to configureAmplify completed without error",
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { configure } from "./library";
|
|
|
2
2
|
|
|
3
3
|
export { decompressBuffer, isBuffer, processResponse } from "./buffer-utils";
|
|
4
4
|
export { API_URL, NEW_API_URL, OLD_API_URL } from "./constants";
|
|
5
|
-
export { configure, signIn } from "./library";
|
|
5
|
+
export { configure, getSession, signIn } from "./library";
|
|
6
6
|
export {
|
|
7
7
|
serialNumberDisplay,
|
|
8
8
|
serialNumberFromHex,
|