edilkamin 1.4.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +3 -2
- package/.github/workflows/tests.yml +8 -1
- package/README.md +1 -0
- package/dist/esm/cli.js +3 -2
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/library.d.ts +16 -2
- package/dist/esm/library.js +42 -16
- package/dist/esm/library.test.js +125 -33
- package/eslint.config.mjs +35 -0
- package/package.json +25 -5
- package/src/cli.ts +4 -2
- package/src/index.ts +1 -3
- package/src/library.test.ts +136 -35
- package/src/library.ts +46 -16
|
@@ -2,8 +2,7 @@ name: Publish
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
|
-
|
|
6
|
-
- "*"
|
|
5
|
+
pull_request:
|
|
7
6
|
|
|
8
7
|
jobs:
|
|
9
8
|
build:
|
|
@@ -17,6 +16,8 @@ jobs:
|
|
|
17
16
|
registry-url: "https://registry.npmjs.org"
|
|
18
17
|
- run: yarn install
|
|
19
18
|
- run: yarn build
|
|
19
|
+
- run: npm publish --dry-run
|
|
20
20
|
- run: npm publish
|
|
21
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
21
22
|
env:
|
|
22
23
|
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
|
|
@@ -19,4 +19,11 @@ jobs:
|
|
|
19
19
|
- run: yarn install
|
|
20
20
|
- run: yarn lint
|
|
21
21
|
- run: yarn build
|
|
22
|
-
- run:
|
|
22
|
+
- run: yarn test
|
|
23
|
+
- uses: codecov/codecov-action@v5
|
|
24
|
+
with:
|
|
25
|
+
files: ./coverage/lcov.info
|
|
26
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
27
|
+
fail_ci_if_error: true
|
|
28
|
+
env:
|
|
29
|
+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
package/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/AndreMiras/edilkamin.js/actions/workflows/tests.yml)
|
|
4
4
|
[](https://github.com/AndreMiras/edilkamin.js/actions/workflows/cli-tests.yml)
|
|
5
|
+
[](https://app.codecov.io/gh/AndreMiras/edilkamin.js/tree/main)
|
|
5
6
|
[](https://github.com/AndreMiras/edilkamin.js/actions/workflows/documentation.yml)
|
|
6
7
|
[](https://badge.fury.io/js/edilkamin)
|
|
7
8
|
|
package/dist/esm/cli.js
CHANGED
|
@@ -8,10 +8,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
-
import { signIn, configure } from "./library";
|
|
12
11
|
import { Command } from "commander";
|
|
13
12
|
import readline from "readline";
|
|
14
13
|
import { version } from "../package.json";
|
|
14
|
+
import { configure, signIn } from "./library";
|
|
15
15
|
const promptPassword = () => {
|
|
16
16
|
const rl = readline.createInterface({
|
|
17
17
|
input: process.stdin,
|
|
@@ -61,10 +61,11 @@ const createProgram = () => {
|
|
|
61
61
|
.description("Retrieve device info for a specific MAC address")
|
|
62
62
|
.requiredOption("-m, --mac <macAddress>", "MAC address of the device")).action((options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
63
63
|
const { username, password, mac } = options;
|
|
64
|
+
const normalizedMac = mac.replace(/:/g, "");
|
|
64
65
|
const pwd = password || (yield promptPassword());
|
|
65
66
|
const jwtToken = yield signIn(username, pwd);
|
|
66
67
|
const api = configure(); // Use the default API configuration
|
|
67
|
-
const deviceInfo = yield api.deviceInfo(jwtToken,
|
|
68
|
+
const deviceInfo = yield api.deviceInfo(jwtToken, normalizedMac);
|
|
68
69
|
console.log("Device Info:", deviceInfo.data);
|
|
69
70
|
}));
|
|
70
71
|
return program;
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { API_URL } from "./constants";
|
|
2
|
+
export { configure, signIn } from "./library";
|
|
2
3
|
export { CommandsType, DeviceInfoType, StatusType, TemperaturesType, UserParametersType, } from "./types";
|
|
3
|
-
export { signIn, configure } from "./library";
|
|
4
4
|
export declare const deviceInfo: (jwtToken: string, macAddress: string) => Promise<import("axios").AxiosResponse<import("./types").DeviceInfoType, any>>, 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>>;
|
package/dist/esm/index.js
CHANGED
package/dist/esm/library.d.ts
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
|
+
import * as amplifyAuth from "aws-amplify/auth";
|
|
1
2
|
import { DeviceInfoType } from "./types";
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
+
* Generates headers with a JWT token for authenticated requests.
|
|
5
|
+
* @param {string} jwtToken - The JWT token for authorization.
|
|
6
|
+
* @returns {object} - The headers object with the Authorization field.
|
|
4
7
|
*/
|
|
8
|
+
declare const headers: (jwtToken: string) => {
|
|
9
|
+
Authorization: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Creates an authentication service with sign-in functionality.
|
|
13
|
+
* @param {typeof amplifyAuth} auth - The authentication module to use.
|
|
14
|
+
* @returns {object} - An object containing authentication-related methods.
|
|
15
|
+
*/
|
|
16
|
+
declare const createAuthService: (auth: typeof amplifyAuth) => {
|
|
17
|
+
signIn: (username: string, password: string) => Promise<string>;
|
|
18
|
+
};
|
|
5
19
|
declare const signIn: (username: string, password: string) => Promise<string>;
|
|
6
20
|
declare const configure: (baseURL?: string) => {
|
|
7
21
|
deviceInfo: (jwtToken: string, macAddress: string) => Promise<import("axios").AxiosResponse<DeviceInfoType, any>>;
|
|
@@ -9,4 +23,4 @@ declare const configure: (baseURL?: string) => {
|
|
|
9
23
|
setPowerOff: (jwtToken: string, macAddress: string) => Promise<import("axios").AxiosResponse<any, any>>;
|
|
10
24
|
setPowerOn: (jwtToken: string, macAddress: string) => Promise<import("axios").AxiosResponse<any, any>>;
|
|
11
25
|
};
|
|
12
|
-
export {
|
|
26
|
+
export { configure, createAuthService, headers, signIn };
|
package/dist/esm/library.js
CHANGED
|
@@ -17,28 +17,54 @@ const amplifyconfiguration = {
|
|
|
17
17
|
aws_user_pools_id: "eu-central-1_BYmQ2VBlo",
|
|
18
18
|
aws_user_pools_web_client_id: "7sc1qltkqobo3ddqsk4542dg2h",
|
|
19
19
|
};
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Generates headers with a JWT token for authenticated requests.
|
|
22
|
+
* @param {string} jwtToken - The JWT token for authorization.
|
|
23
|
+
* @returns {object} - The headers object with the Authorization field.
|
|
24
|
+
*/
|
|
21
25
|
const headers = (jwtToken) => ({ Authorization: `Bearer ${jwtToken}` });
|
|
22
26
|
/**
|
|
23
|
-
*
|
|
27
|
+
* Configures Amplify if not already configured.
|
|
28
|
+
* Ensures the configuration is only applied once.
|
|
24
29
|
*/
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
const configureAmplify = () => {
|
|
31
|
+
const currentConfig = Amplify.getConfig();
|
|
32
|
+
if (Object.keys(currentConfig).length !== 0)
|
|
33
|
+
return;
|
|
34
|
+
Amplify.configure(amplifyconfiguration);
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Creates an authentication service with sign-in functionality.
|
|
38
|
+
* @param {typeof amplifyAuth} auth - The authentication module to use.
|
|
39
|
+
* @returns {object} - An object containing authentication-related methods.
|
|
40
|
+
*/
|
|
41
|
+
const createAuthService = (auth) => {
|
|
42
|
+
/**
|
|
43
|
+
* Signs in a user with the provided credentials.
|
|
44
|
+
* @param {string} username - The username of the user.
|
|
45
|
+
* @param {string} password - The password of the user.
|
|
46
|
+
* @returns {Promise<string>} - The JWT token of the signed-in user.
|
|
47
|
+
* @throws {Error} - If sign-in fails or no tokens are retrieved.
|
|
48
|
+
*/
|
|
49
|
+
const signIn = (username, password) => __awaiter(void 0, void 0, void 0, function* () {
|
|
50
|
+
configureAmplify();
|
|
51
|
+
yield auth.signOut(); // Ensure the user is signed out first
|
|
52
|
+
const { isSignedIn } = yield auth.signIn({ username, password });
|
|
53
|
+
assert.ok(isSignedIn, "Sign-in failed");
|
|
54
|
+
const { tokens } = yield auth.fetchAuthSession();
|
|
55
|
+
assert.ok(tokens, "No tokens found");
|
|
56
|
+
return tokens.accessToken.toString();
|
|
32
57
|
});
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
});
|
|
58
|
+
return { signIn };
|
|
59
|
+
};
|
|
60
|
+
// Create the default auth service using amplifyAuth
|
|
61
|
+
const { signIn } = createAuthService(amplifyAuth);
|
|
38
62
|
const deviceInfo = (axiosInstance) => (jwtToken, macAddress) => axiosInstance.get(`device/${macAddress}/info`, {
|
|
39
63
|
headers: headers(jwtToken),
|
|
40
64
|
});
|
|
41
|
-
const mqttCommand = (axiosInstance) =>
|
|
65
|
+
const mqttCommand = (axiosInstance) =>
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
67
|
+
(jwtToken, macAddress, payload) => axiosInstance.put("mqtt/command", Object.assign({ mac_address: macAddress }, payload), { headers: headers(jwtToken) });
|
|
42
68
|
const setPower = (axiosInstance) => (jwtToken, macAddress, value) => mqttCommand(axiosInstance)(jwtToken, macAddress, { name: "power", value });
|
|
43
69
|
const setPowerOn = (axiosInstance) => (jwtToken, macAddress) => setPower(axiosInstance)(jwtToken, macAddress, 1);
|
|
44
70
|
const setPowerOff = (axiosInstance) => (jwtToken, macAddress) => setPower(axiosInstance)(jwtToken, macAddress, 0);
|
|
@@ -55,4 +81,4 @@ const configure = (baseURL = API_URL) => {
|
|
|
55
81
|
setPowerOn: setPowerOnInstance,
|
|
56
82
|
};
|
|
57
83
|
};
|
|
58
|
-
export {
|
|
84
|
+
export { configure, createAuthService, headers, signIn };
|
package/dist/esm/library.test.js
CHANGED
|
@@ -8,26 +8,99 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { strict as assert } from "assert";
|
|
11
|
-
import sinon from "sinon";
|
|
12
11
|
import axios from "axios";
|
|
13
|
-
import
|
|
12
|
+
import sinon from "sinon";
|
|
13
|
+
import { configure, createAuthService } from "../src/library";
|
|
14
|
+
import { API_URL } from "./constants";
|
|
14
15
|
describe("library", () => {
|
|
15
16
|
let axiosStub;
|
|
16
17
|
beforeEach(() => {
|
|
17
18
|
axiosStub = sinon.stub(axios, "create").returns({
|
|
18
19
|
get: sinon.stub(),
|
|
19
20
|
put: sinon.stub(),
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
22
|
});
|
|
21
23
|
});
|
|
22
24
|
afterEach(() => {
|
|
23
25
|
sinon.restore();
|
|
24
26
|
});
|
|
27
|
+
describe("signIn", () => {
|
|
28
|
+
it("should sign in and return the JWT token", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
29
|
+
const expectedUsername = "testuser";
|
|
30
|
+
const expectedPassword = "testpassword";
|
|
31
|
+
const expectedToken = "mockJwtToken";
|
|
32
|
+
const signIn = sinon.stub().resolves({ isSignedIn: true });
|
|
33
|
+
const signOut = sinon.stub();
|
|
34
|
+
const fetchAuthSession = sinon.stub().resolves({
|
|
35
|
+
tokens: {
|
|
36
|
+
accessToken: { toString: () => expectedToken },
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
const authStub = {
|
|
40
|
+
signIn,
|
|
41
|
+
signOut,
|
|
42
|
+
fetchAuthSession,
|
|
43
|
+
};
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
|
+
const authService = createAuthService(authStub);
|
|
46
|
+
const token = yield authService.signIn(expectedUsername, expectedPassword);
|
|
47
|
+
assert.deepEqual(authStub.signOut.args, [[]]);
|
|
48
|
+
assert.deepEqual(signIn.args, [
|
|
49
|
+
[{ username: expectedUsername, password: expectedPassword }],
|
|
50
|
+
]);
|
|
51
|
+
assert.equal(token, expectedToken);
|
|
52
|
+
}));
|
|
53
|
+
it("should throw an error if sign-in fails", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
54
|
+
const expectedUsername = "testuser";
|
|
55
|
+
const expectedPassword = "testpassword";
|
|
56
|
+
const expectedToken = "mockJwtToken";
|
|
57
|
+
const signIn = sinon.stub().resolves({ isSignedIn: false });
|
|
58
|
+
const signOut = sinon.stub();
|
|
59
|
+
const fetchAuthSession = sinon.stub().resolves({
|
|
60
|
+
tokens: {
|
|
61
|
+
accessToken: { toString: () => expectedToken },
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
const authStub = {
|
|
65
|
+
signIn,
|
|
66
|
+
signOut,
|
|
67
|
+
fetchAuthSession,
|
|
68
|
+
};
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
|
+
const authService = createAuthService(authStub);
|
|
71
|
+
yield assert.rejects(() => __awaiter(void 0, void 0, void 0, function* () { return authService.signIn(expectedUsername, expectedPassword); }), {
|
|
72
|
+
name: "AssertionError",
|
|
73
|
+
message: "Sign-in failed",
|
|
74
|
+
});
|
|
75
|
+
}));
|
|
76
|
+
});
|
|
25
77
|
describe("configure", () => {
|
|
26
78
|
it("should create API methods with the correct baseURL", () => {
|
|
27
79
|
const baseURL = "https://example.com/api";
|
|
28
80
|
const api = configure(baseURL);
|
|
29
|
-
assert.
|
|
30
|
-
|
|
81
|
+
assert.deepEqual(axiosStub.args, [
|
|
82
|
+
[
|
|
83
|
+
{
|
|
84
|
+
baseURL,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
]);
|
|
88
|
+
assert.deepEqual(Object.keys(api), [
|
|
89
|
+
"deviceInfo",
|
|
90
|
+
"setPower",
|
|
91
|
+
"setPowerOff",
|
|
92
|
+
"setPowerOn",
|
|
93
|
+
]);
|
|
94
|
+
});
|
|
95
|
+
it("should create API methods with the default baseURL", () => {
|
|
96
|
+
const api = configure();
|
|
97
|
+
assert.deepEqual(axiosStub.args, [
|
|
98
|
+
[
|
|
99
|
+
{
|
|
100
|
+
baseURL: API_URL,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
]);
|
|
31
104
|
assert.deepEqual(Object.keys(api), [
|
|
32
105
|
"deviceInfo",
|
|
33
106
|
"setPower",
|
|
@@ -38,39 +111,58 @@ describe("library", () => {
|
|
|
38
111
|
});
|
|
39
112
|
describe("API Methods", () => {
|
|
40
113
|
it("should call axios for deviceInfo", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
114
|
+
const expectedDevice = { id: "123", name: "Mock Device" };
|
|
115
|
+
const expectedToken = "mockToken";
|
|
41
116
|
const mockAxios = {
|
|
42
|
-
get: sinon
|
|
43
|
-
.stub()
|
|
44
|
-
.resolves({ data: { id: "123", name: "Mock Device" } }),
|
|
117
|
+
get: sinon.stub().resolves({ data: expectedDevice }),
|
|
45
118
|
};
|
|
46
119
|
axiosStub.returns(mockAxios);
|
|
47
120
|
const api = configure("https://example.com/api");
|
|
48
|
-
const result = yield api.deviceInfo(
|
|
49
|
-
assert.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
it("should call axios for setPowerOn", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
57
|
-
const mockAxios = {
|
|
58
|
-
put: sinon.stub().resolves({ status: 200 }),
|
|
59
|
-
};
|
|
60
|
-
axiosStub.returns(mockAxios);
|
|
61
|
-
const api = configure("https://example.com/api");
|
|
62
|
-
const result = yield api.setPowerOn("mockToken", "mockMacAddress");
|
|
63
|
-
assert.ok(mockAxios.put.calledOnce);
|
|
64
|
-
assert.equal(mockAxios.put.firstCall.args[0], "mqtt/command");
|
|
65
|
-
assert.deepEqual(mockAxios.put.firstCall.args[1], {
|
|
66
|
-
mac_address: "mockMacAddress",
|
|
67
|
-
name: "power",
|
|
68
|
-
value: 1,
|
|
69
|
-
});
|
|
70
|
-
assert.deepEqual(mockAxios.put.firstCall.args[2], {
|
|
71
|
-
headers: { Authorization: "Bearer mockToken" },
|
|
72
|
-
});
|
|
73
|
-
assert.equal(result.status, 200);
|
|
121
|
+
const result = yield api.deviceInfo(expectedToken, "mockMacAddress");
|
|
122
|
+
assert.deepEqual(mockAxios.get.args, [
|
|
123
|
+
[
|
|
124
|
+
"device/mockMacAddress/info",
|
|
125
|
+
{ headers: { Authorization: `Bearer ${expectedToken}` } },
|
|
126
|
+
],
|
|
127
|
+
]);
|
|
128
|
+
assert.deepEqual(result.data, expectedDevice);
|
|
74
129
|
}));
|
|
130
|
+
// Tests for setPowerOn and setPowerOff
|
|
131
|
+
[
|
|
132
|
+
{
|
|
133
|
+
method: "setPowerOn",
|
|
134
|
+
call: (api) => api.setPowerOn("mockToken", "mockMacAddress"),
|
|
135
|
+
expectedValue: 1,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
method: "setPowerOff",
|
|
139
|
+
call: (api) => api.setPowerOff("mockToken", "mockMacAddress"),
|
|
140
|
+
expectedValue: 0,
|
|
141
|
+
},
|
|
142
|
+
].forEach(({ method, call, expectedValue }) => {
|
|
143
|
+
it(`should call axios for ${method}`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
144
|
+
const mockAxios = {
|
|
145
|
+
put: sinon.stub().resolves({ status: 200 }),
|
|
146
|
+
};
|
|
147
|
+
axiosStub.returns(mockAxios);
|
|
148
|
+
const api = configure("https://example.com/api");
|
|
149
|
+
// Invoke the method using the mapped call function
|
|
150
|
+
const result = yield call(api);
|
|
151
|
+
assert.deepEqual(mockAxios.put.args, [
|
|
152
|
+
[
|
|
153
|
+
"mqtt/command",
|
|
154
|
+
{
|
|
155
|
+
mac_address: "mockMacAddress",
|
|
156
|
+
name: "power",
|
|
157
|
+
value: expectedValue,
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
headers: { Authorization: "Bearer mockToken" },
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
]);
|
|
164
|
+
assert.equal(result.status, 200);
|
|
165
|
+
}));
|
|
166
|
+
});
|
|
75
167
|
});
|
|
76
168
|
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import typescriptEslint from "@typescript-eslint/eslint-plugin";
|
|
2
|
+
import simpleImportSort from "eslint-plugin-simple-import-sort";
|
|
3
|
+
import globals from "globals";
|
|
4
|
+
import tsParser from "@typescript-eslint/parser";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import js from "@eslint/js";
|
|
8
|
+
import { FlatCompat } from "@eslint/eslintrc";
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
|
|
13
|
+
const compat = new FlatCompat({
|
|
14
|
+
baseDirectory: __dirname,
|
|
15
|
+
recommendedConfig: js.configs.recommended,
|
|
16
|
+
allConfig: js.configs.all,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export default [
|
|
20
|
+
...compat.extends(
|
|
21
|
+
"eslint:recommended",
|
|
22
|
+
"plugin:@typescript-eslint/recommended"
|
|
23
|
+
),
|
|
24
|
+
{
|
|
25
|
+
plugins: {
|
|
26
|
+
"@typescript-eslint": typescriptEslint,
|
|
27
|
+
"simple-import-sort": simpleImportSort,
|
|
28
|
+
},
|
|
29
|
+
rules: {
|
|
30
|
+
// Sorting imports and exports
|
|
31
|
+
"simple-import-sort/imports": "error",
|
|
32
|
+
"simple-import-sort/exports": "error",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "edilkamin",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -8,10 +8,14 @@
|
|
|
8
8
|
"scripts": {
|
|
9
9
|
"cli": "ts-node src/cli.ts",
|
|
10
10
|
"cli:debug": "node --inspect --require ts-node/register/transpile-only src/cli.ts",
|
|
11
|
-
"test": "mocha --require ts-node/register src/*.test.ts",
|
|
12
|
-
"test:debug": "mocha --require ts-node/register/transpile-only --inspect src/*.test.ts",
|
|
13
|
-
"lint": "prettier --check src docs .github *.md *.
|
|
14
|
-
"format": "prettier --write src docs .github *.md *.
|
|
11
|
+
"test": "nyc mocha --require ts-node/register src/*.test.ts",
|
|
12
|
+
"test:debug": "nyc mocha --require ts-node/register/transpile-only --inspect src/*.test.ts",
|
|
13
|
+
"lint:prettier": "prettier --check src docs .github *.json *.md *.mjs",
|
|
14
|
+
"format:prettier": "prettier --write src docs .github *.json *.md *.mjs",
|
|
15
|
+
"lint:eslint": "eslint src",
|
|
16
|
+
"format:eslint": "eslint --fix src",
|
|
17
|
+
"lint": "yarn lint:prettier && yarn lint:eslint",
|
|
18
|
+
"format": "yarn format:prettier && yarn format:eslint",
|
|
15
19
|
"build:cjs": "tsc -p tsconfig.cjs.json",
|
|
16
20
|
"build:esm": "tsc -p tsconfig.esm.json",
|
|
17
21
|
"build": "npm run build:cjs && npm run build:esm"
|
|
@@ -29,15 +33,31 @@
|
|
|
29
33
|
"bin": {
|
|
30
34
|
"edilkamin": "dist/cjs/cli.js"
|
|
31
35
|
},
|
|
36
|
+
"nyc": {
|
|
37
|
+
"reporter": [
|
|
38
|
+
"html",
|
|
39
|
+
"lcov",
|
|
40
|
+
"text"
|
|
41
|
+
]
|
|
42
|
+
},
|
|
32
43
|
"dependencies": {
|
|
33
44
|
"aws-amplify": "^6.10.0",
|
|
34
45
|
"axios": "^0.26.0"
|
|
35
46
|
},
|
|
36
47
|
"devDependencies": {
|
|
37
48
|
"@aws-amplify/cli": "^7.6.21",
|
|
49
|
+
"@eslint/eslintrc": "^3.2.0",
|
|
50
|
+
"@eslint/js": "^9.16.0",
|
|
38
51
|
"@types/mocha": "^10.0.10",
|
|
39
52
|
"@types/sinon": "^17.0.3",
|
|
53
|
+
"@typescript-eslint/eslint-plugin": "^8.17.0",
|
|
54
|
+
"@typescript-eslint/parser": "^8.17.0",
|
|
55
|
+
"eslint": "^9.16.0",
|
|
56
|
+
"eslint-config-prettier": "^9.1.0",
|
|
57
|
+
"eslint-plugin-prettier": "^5.2.1",
|
|
58
|
+
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
40
59
|
"mocha": "^10.8.2",
|
|
60
|
+
"nyc": "^17.1.0",
|
|
41
61
|
"prettier": "^2.5.1",
|
|
42
62
|
"sinon": "^19.0.2",
|
|
43
63
|
"ts-node": "^10.9.1",
|
package/src/cli.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { signIn, configure } from "./library";
|
|
3
2
|
import { Command } from "commander";
|
|
4
3
|
import readline from "readline";
|
|
4
|
+
|
|
5
5
|
import { version } from "../package.json";
|
|
6
|
+
import { configure, signIn } from "./library";
|
|
6
7
|
|
|
7
8
|
const promptPassword = (): Promise<string> => {
|
|
8
9
|
const rl = readline.createInterface({
|
|
@@ -59,10 +60,11 @@ const createProgram = (): Command => {
|
|
|
59
60
|
.requiredOption("-m, --mac <macAddress>", "MAC address of the device")
|
|
60
61
|
).action(async (options) => {
|
|
61
62
|
const { username, password, mac } = options;
|
|
63
|
+
const normalizedMac = mac.replace(/:/g, "");
|
|
62
64
|
const pwd = password || (await promptPassword());
|
|
63
65
|
const jwtToken = await signIn(username, pwd);
|
|
64
66
|
const api = configure(); // Use the default API configuration
|
|
65
|
-
const deviceInfo = await api.deviceInfo(jwtToken,
|
|
67
|
+
const deviceInfo = await api.deviceInfo(jwtToken, normalizedMac);
|
|
66
68
|
console.log("Device Info:", deviceInfo.data);
|
|
67
69
|
});
|
|
68
70
|
return program;
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { configure } from "./library";
|
|
2
2
|
|
|
3
3
|
export { API_URL } from "./constants";
|
|
4
|
-
|
|
4
|
+
export { configure, signIn } from "./library";
|
|
5
5
|
export {
|
|
6
6
|
CommandsType,
|
|
7
7
|
DeviceInfoType,
|
|
@@ -10,6 +10,4 @@ export {
|
|
|
10
10
|
UserParametersType,
|
|
11
11
|
} from "./types";
|
|
12
12
|
|
|
13
|
-
export { signIn, configure } from "./library";
|
|
14
|
-
|
|
15
13
|
export const { deviceInfo, setPower, setPowerOff, setPowerOn } = configure();
|
package/src/library.test.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { strict as assert } from "assert";
|
|
2
|
-
import sinon from "sinon";
|
|
3
2
|
import axios from "axios";
|
|
4
|
-
import
|
|
3
|
+
import sinon from "sinon";
|
|
4
|
+
|
|
5
|
+
import { configure, createAuthService } from "../src/library";
|
|
6
|
+
import { API_URL } from "./constants";
|
|
5
7
|
|
|
6
8
|
describe("library", () => {
|
|
7
9
|
let axiosStub: sinon.SinonStub;
|
|
@@ -10,6 +12,7 @@ describe("library", () => {
|
|
|
10
12
|
axiosStub = sinon.stub(axios, "create").returns({
|
|
11
13
|
get: sinon.stub(),
|
|
12
14
|
put: sinon.stub(),
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
16
|
} as any);
|
|
14
17
|
});
|
|
15
18
|
|
|
@@ -17,12 +20,91 @@ describe("library", () => {
|
|
|
17
20
|
sinon.restore();
|
|
18
21
|
});
|
|
19
22
|
|
|
23
|
+
describe("signIn", () => {
|
|
24
|
+
it("should sign in and return the JWT token", async () => {
|
|
25
|
+
const expectedUsername = "testuser";
|
|
26
|
+
const expectedPassword = "testpassword";
|
|
27
|
+
const expectedToken = "mockJwtToken";
|
|
28
|
+
const signIn = sinon.stub().resolves({ isSignedIn: true });
|
|
29
|
+
const signOut = sinon.stub();
|
|
30
|
+
const fetchAuthSession = sinon.stub().resolves({
|
|
31
|
+
tokens: {
|
|
32
|
+
accessToken: { toString: () => expectedToken },
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
const authStub = {
|
|
36
|
+
signIn,
|
|
37
|
+
signOut,
|
|
38
|
+
fetchAuthSession,
|
|
39
|
+
};
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
|
+
const authService = createAuthService(authStub as any);
|
|
42
|
+
const token = await authService.signIn(
|
|
43
|
+
expectedUsername,
|
|
44
|
+
expectedPassword
|
|
45
|
+
);
|
|
46
|
+
assert.deepEqual(authStub.signOut.args, [[]]);
|
|
47
|
+
assert.deepEqual(signIn.args, [
|
|
48
|
+
[{ username: expectedUsername, password: expectedPassword }],
|
|
49
|
+
]);
|
|
50
|
+
assert.equal(token, expectedToken);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should throw an error if sign-in fails", async () => {
|
|
54
|
+
const expectedUsername = "testuser";
|
|
55
|
+
const expectedPassword = "testpassword";
|
|
56
|
+
const expectedToken = "mockJwtToken";
|
|
57
|
+
const signIn = sinon.stub().resolves({ isSignedIn: false });
|
|
58
|
+
const signOut = sinon.stub();
|
|
59
|
+
const fetchAuthSession = sinon.stub().resolves({
|
|
60
|
+
tokens: {
|
|
61
|
+
accessToken: { toString: () => expectedToken },
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
const authStub = {
|
|
65
|
+
signIn,
|
|
66
|
+
signOut,
|
|
67
|
+
fetchAuthSession,
|
|
68
|
+
};
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
|
+
const authService = createAuthService(authStub as any);
|
|
71
|
+
await assert.rejects(
|
|
72
|
+
async () => authService.signIn(expectedUsername, expectedPassword),
|
|
73
|
+
{
|
|
74
|
+
name: "AssertionError",
|
|
75
|
+
message: "Sign-in failed",
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
20
81
|
describe("configure", () => {
|
|
21
82
|
it("should create API methods with the correct baseURL", () => {
|
|
22
83
|
const baseURL = "https://example.com/api";
|
|
23
84
|
const api = configure(baseURL);
|
|
24
|
-
assert.
|
|
25
|
-
|
|
85
|
+
assert.deepEqual(axiosStub.args, [
|
|
86
|
+
[
|
|
87
|
+
{
|
|
88
|
+
baseURL,
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
]);
|
|
92
|
+
assert.deepEqual(Object.keys(api), [
|
|
93
|
+
"deviceInfo",
|
|
94
|
+
"setPower",
|
|
95
|
+
"setPowerOff",
|
|
96
|
+
"setPowerOn",
|
|
97
|
+
]);
|
|
98
|
+
});
|
|
99
|
+
it("should create API methods with the default baseURL", () => {
|
|
100
|
+
const api = configure();
|
|
101
|
+
assert.deepEqual(axiosStub.args, [
|
|
102
|
+
[
|
|
103
|
+
{
|
|
104
|
+
baseURL: API_URL,
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
]);
|
|
26
108
|
assert.deepEqual(Object.keys(api), [
|
|
27
109
|
"deviceInfo",
|
|
28
110
|
"setPower",
|
|
@@ -34,43 +116,62 @@ describe("library", () => {
|
|
|
34
116
|
|
|
35
117
|
describe("API Methods", () => {
|
|
36
118
|
it("should call axios for deviceInfo", async () => {
|
|
119
|
+
const expectedDevice = { id: "123", name: "Mock Device" };
|
|
120
|
+
const expectedToken = "mockToken";
|
|
37
121
|
const mockAxios = {
|
|
38
|
-
get: sinon
|
|
39
|
-
.stub()
|
|
40
|
-
.resolves({ data: { id: "123", name: "Mock Device" } }),
|
|
122
|
+
get: sinon.stub().resolves({ data: expectedDevice }),
|
|
41
123
|
};
|
|
42
|
-
axiosStub.returns(mockAxios
|
|
124
|
+
axiosStub.returns(mockAxios);
|
|
43
125
|
const api = configure("https://example.com/api");
|
|
44
|
-
const result = await api.deviceInfo(
|
|
45
|
-
assert.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
});
|
|
53
|
-
assert.deepEqual(result.data, { id: "123", name: "Mock Device" });
|
|
126
|
+
const result = await api.deviceInfo(expectedToken, "mockMacAddress");
|
|
127
|
+
assert.deepEqual(mockAxios.get.args, [
|
|
128
|
+
[
|
|
129
|
+
"device/mockMacAddress/info",
|
|
130
|
+
{ headers: { Authorization: `Bearer ${expectedToken}` } },
|
|
131
|
+
],
|
|
132
|
+
]);
|
|
133
|
+
assert.deepEqual(result.data, expectedDevice);
|
|
54
134
|
});
|
|
55
135
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
136
|
+
// Tests for setPowerOn and setPowerOff
|
|
137
|
+
[
|
|
138
|
+
{
|
|
139
|
+
method: "setPowerOn",
|
|
140
|
+
call: (api: ReturnType<typeof configure>) =>
|
|
141
|
+
api.setPowerOn("mockToken", "mockMacAddress"),
|
|
142
|
+
expectedValue: 1,
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
method: "setPowerOff",
|
|
146
|
+
call: (api: ReturnType<typeof configure>) =>
|
|
147
|
+
api.setPowerOff("mockToken", "mockMacAddress"),
|
|
148
|
+
expectedValue: 0,
|
|
149
|
+
},
|
|
150
|
+
].forEach(({ method, call, expectedValue }) => {
|
|
151
|
+
it(`should call axios for ${method}`, async () => {
|
|
152
|
+
const mockAxios = {
|
|
153
|
+
put: sinon.stub().resolves({ status: 200 }),
|
|
154
|
+
};
|
|
155
|
+
axiosStub.returns(mockAxios);
|
|
156
|
+
const api = configure("https://example.com/api");
|
|
157
|
+
|
|
158
|
+
// Invoke the method using the mapped call function
|
|
159
|
+
const result = await call(api);
|
|
160
|
+
assert.deepEqual(mockAxios.put.args, [
|
|
161
|
+
[
|
|
162
|
+
"mqtt/command",
|
|
163
|
+
{
|
|
164
|
+
mac_address: "mockMacAddress",
|
|
165
|
+
name: "power",
|
|
166
|
+
value: expectedValue,
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
headers: { Authorization: "Bearer mockToken" },
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
]);
|
|
173
|
+
assert.equal(result.status, 200);
|
|
72
174
|
});
|
|
73
|
-
assert.equal(result.status, 200);
|
|
74
175
|
});
|
|
75
176
|
});
|
|
76
177
|
});
|
package/src/library.ts
CHANGED
|
@@ -2,35 +2,64 @@ import { strict as assert } from "assert";
|
|
|
2
2
|
import { Amplify } from "aws-amplify";
|
|
3
3
|
import * as amplifyAuth from "aws-amplify/auth";
|
|
4
4
|
import axios, { AxiosInstance } from "axios";
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
import { API_URL } from "./constants";
|
|
7
|
+
import { DeviceInfoType } from "./types";
|
|
7
8
|
|
|
8
9
|
const amplifyconfiguration = {
|
|
9
10
|
aws_project_region: "eu-central-1",
|
|
10
11
|
aws_user_pools_id: "eu-central-1_BYmQ2VBlo",
|
|
11
12
|
aws_user_pools_web_client_id: "7sc1qltkqobo3ddqsk4542dg2h",
|
|
12
13
|
};
|
|
13
|
-
Amplify.configure(amplifyconfiguration);
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Generates headers with a JWT token for authenticated requests.
|
|
17
|
+
* @param {string} jwtToken - The JWT token for authorization.
|
|
18
|
+
* @returns {object} - The headers object with the Authorization field.
|
|
19
|
+
*/
|
|
15
20
|
const headers = (jwtToken: string) => ({ Authorization: `Bearer ${jwtToken}` });
|
|
16
21
|
|
|
17
22
|
/**
|
|
18
|
-
*
|
|
23
|
+
* Configures Amplify if not already configured.
|
|
24
|
+
* Ensures the configuration is only applied once.
|
|
19
25
|
*/
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const { isSignedIn } = await amplifyAuth.signIn({
|
|
25
|
-
username,
|
|
26
|
-
password,
|
|
27
|
-
});
|
|
28
|
-
assert.ok(isSignedIn);
|
|
29
|
-
const { tokens } = await amplifyAuth.fetchAuthSession();
|
|
30
|
-
assert.ok(tokens);
|
|
31
|
-
return tokens.accessToken.toString();
|
|
26
|
+
const configureAmplify = () => {
|
|
27
|
+
const currentConfig = Amplify.getConfig();
|
|
28
|
+
if (Object.keys(currentConfig).length !== 0) return;
|
|
29
|
+
Amplify.configure(amplifyconfiguration);
|
|
32
30
|
};
|
|
33
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Creates an authentication service with sign-in functionality.
|
|
34
|
+
* @param {typeof amplifyAuth} auth - The authentication module to use.
|
|
35
|
+
* @returns {object} - An object containing authentication-related methods.
|
|
36
|
+
*/
|
|
37
|
+
const createAuthService = (auth: typeof amplifyAuth) => {
|
|
38
|
+
/**
|
|
39
|
+
* Signs in a user with the provided credentials.
|
|
40
|
+
* @param {string} username - The username of the user.
|
|
41
|
+
* @param {string} password - The password of the user.
|
|
42
|
+
* @returns {Promise<string>} - The JWT token of the signed-in user.
|
|
43
|
+
* @throws {Error} - If sign-in fails or no tokens are retrieved.
|
|
44
|
+
*/
|
|
45
|
+
const signIn = async (
|
|
46
|
+
username: string,
|
|
47
|
+
password: string
|
|
48
|
+
): Promise<string> => {
|
|
49
|
+
configureAmplify();
|
|
50
|
+
await auth.signOut(); // Ensure the user is signed out first
|
|
51
|
+
const { isSignedIn } = await auth.signIn({ username, password });
|
|
52
|
+
assert.ok(isSignedIn, "Sign-in failed");
|
|
53
|
+
const { tokens } = await auth.fetchAuthSession();
|
|
54
|
+
assert.ok(tokens, "No tokens found");
|
|
55
|
+
return tokens.accessToken.toString();
|
|
56
|
+
};
|
|
57
|
+
return { signIn };
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Create the default auth service using amplifyAuth
|
|
61
|
+
const { signIn } = createAuthService(amplifyAuth);
|
|
62
|
+
|
|
34
63
|
const deviceInfo =
|
|
35
64
|
(axiosInstance: AxiosInstance) => (jwtToken: string, macAddress: string) =>
|
|
36
65
|
axiosInstance.get<DeviceInfoType>(`device/${macAddress}/info`, {
|
|
@@ -39,6 +68,7 @@ const deviceInfo =
|
|
|
39
68
|
|
|
40
69
|
const mqttCommand =
|
|
41
70
|
(axiosInstance: AxiosInstance) =>
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
42
72
|
(jwtToken: string, macAddress: string, payload: any) =>
|
|
43
73
|
axiosInstance.put(
|
|
44
74
|
"mqtt/command",
|
|
@@ -72,4 +102,4 @@ const configure = (baseURL: string = API_URL) => {
|
|
|
72
102
|
};
|
|
73
103
|
};
|
|
74
104
|
|
|
75
|
-
export {
|
|
105
|
+
export { configure, createAuthService, headers, signIn };
|