edilkamin 1.6.2 → 1.7.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.
@@ -10,7 +10,7 @@ jobs:
10
10
  timeout-minutes: 5
11
11
  strategy:
12
12
  matrix:
13
- node-version: [20.x, 22.x]
13
+ node-version: [20.x, 22.x, 24.x]
14
14
  steps:
15
15
  - uses: actions/checkout@v6
16
16
  - uses: actions/setup-node@v6
@@ -12,7 +12,7 @@ jobs:
12
12
  - uses: actions/checkout@v6
13
13
  - uses: actions/setup-node@v6
14
14
  with:
15
- node-version: "22.x"
15
+ node-version: "24.x"
16
16
  - name: git config
17
17
  run: |
18
18
  git config user.name documentation-deploy-action
@@ -4,6 +4,10 @@ on:
4
4
  push:
5
5
  pull_request:
6
6
 
7
+ permissions:
8
+ id-token: write
9
+ contents: read
10
+
7
11
  jobs:
8
12
  build:
9
13
  runs-on: ubuntu-latest
@@ -12,12 +16,10 @@ jobs:
12
16
  - uses: actions/checkout@v6
13
17
  - uses: actions/setup-node@v6
14
18
  with:
15
- node-version: "22.x"
19
+ node-version: "24.x"
16
20
  registry-url: "https://registry.npmjs.org"
17
21
  - run: yarn install
18
22
  - run: yarn build
19
23
  - run: npm publish --dry-run
20
24
  - run: npm publish
21
25
  if: startsWith(github.ref, 'refs/tags/')
22
- env:
23
- NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
@@ -10,7 +10,7 @@ jobs:
10
10
  timeout-minutes: 5
11
11
  strategy:
12
12
  matrix:
13
- node-version: [20.x, 22.x]
13
+ node-version: [20.x, 22.x, 24.x]
14
14
  steps:
15
15
  - uses: actions/checkout@v6
16
16
  - uses: actions/setup-node@v6
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # Edilkamin.js
2
2
 
3
- [![Tests](https://github.com/AndreMiras/edilkamin.js/workflows/Tests/badge.svg)](https://github.com/AndreMiras/edilkamin.js/actions/workflows/tests.yml)
3
+ [![Tests](htts://github.com/AndreMiras/edilkamin.js/actions/workflows/tests.yml/badge.svg)](https://github.com/AndreMiras/edilkamin.js/actions/workflows/tests.yml)
4
4
  [![CLI Tests](https://github.com/AndreMiras/edilkamin.js/actions/workflows/cli-tests.yml/badge.svg)](https://github.com/AndreMiras/edilkamin.js/actions/workflows/cli-tests.yml)
5
5
  [![codecov](https://codecov.io/gh/AndreMiras/edilkamin.js/graph/badge.svg?token=YG3LKXNZWU)](https://app.codecov.io/gh/AndreMiras/edilkamin.js/tree/main)
6
- [![Documentation](https://github.com/AndreMiras/edilkamin.js/workflows/Documentation/badge.svg)](https://github.com/AndreMiras/edilkamin.js/actions/workflows/documentation.yml)
6
+ [![Documentation](https://github.com/AndreMiras/edilkamin.js/actions/workflows/documentation.yml/badge.svg)](https://github.com/AndreMiras/edilkamin.js/actions/workflows/documentation.yml)
7
7
  [![npm version](https://badge.fury.io/js/edilkamin.svg)](https://badge.fury.io/js/edilkamin)
8
8
 
9
9
  This is a library for the [Reverse Engineered](docs/ReverseEngineering.md) "The Mind" Edilkamin API.
@@ -31,11 +31,26 @@ Basic usage:
31
31
  import { signIn, deviceInfo, setPowerOff } from "edilkamin";
32
32
 
33
33
  const macAddress = "aabbccddeeff";
34
- const token = signIn(username, password);
35
- deviceInfo(token, macAddress).then(console.log);
36
- setPowerOff(token, macAddress).then(console.log);
34
+ const token = await signIn(username, password);
35
+ console.log(await deviceInfo(token, macAddress));
36
+ console.log(await setPowerOff(token, macAddress));
37
37
  ```
38
38
 
39
+ For long-running applications, use `getSession()` to automatically refresh tokens:
40
+
41
+ ```js
42
+ import { signIn, getSession, deviceInfo } from "edilkamin";
43
+
44
+ // Authenticate once
45
+ await signIn(username, password);
46
+
47
+ // Get current session (auto-refreshes if expired)
48
+ const token = await getSession();
49
+ console.log(await deviceInfo(token, macAddress));
50
+ ```
51
+
52
+ Sessions persist for ~30 days without re-authentication. Call `getSession()` to retrieve fresh tokens as needed.
53
+
39
54
  It's also possible to change the default backend URL:
40
55
 
41
56
  ```js
@@ -43,8 +58,8 @@ import { signIn, configure } from "edilkamin";
43
58
 
44
59
  const baseUrl = "https://my-proxy.com/";
45
60
  const { deviceInfo, setPower } = configure(baseUrl);
46
- deviceInfo(token, macAddress).then(console.log);
47
- setPower(token, macAddress, 0).then(console.log);
61
+ console.log(await deviceInfo(token, macAddress));
62
+ console.log(await setPower(token, macAddress, 0));
48
63
  ```
49
64
 
50
65
  ## CLI
@@ -52,7 +67,14 @@ setPower(token, macAddress, 0).then(console.log);
52
67
  The library includes a CLI tool that is useful for debugging.
53
68
 
54
69
  ```sh
70
+ # First time: provide credentials to authenticate
55
71
  yarn cli deviceInfo --mac $MAC --username $USERNAME --password $PASSWORD
72
+
73
+ # Subsequent calls: session persists, credentials optional
74
+ yarn cli deviceInfo --mac $MAC
75
+
76
+ # Clear stored session
77
+ yarn cli logout
56
78
  ```
57
79
 
58
80
  Or with `npx` once the library is installed:
@@ -61,6 +83,8 @@ Or with `npx` once the library is installed:
61
83
  npx edilkamin deviceInfo --mac $MAC --username $USERNAME --password $PASSWORD
62
84
  ```
63
85
 
86
+ The CLI stores session data in `~/.edilkamin/session.json` and automatically refreshes tokens when needed. Sessions remain valid for ~30 days.
87
+
64
88
  ## API Versions
65
89
 
66
90
  This library supports both the new and legacy Edilkamin API endpoints.
package/dist/esm/cli.js CHANGED
@@ -12,7 +12,8 @@ import { Command } from "commander";
12
12
  import readline from "readline";
13
13
  import { version } from "../package.json";
14
14
  import { NEW_API_URL, OLD_API_URL } from "./constants";
15
- import { configure, signIn } from "./library";
15
+ import { configure, configureAmplify, getSession, signIn } from "./library";
16
+ import { clearSession, createFileStorage } from "./token-storage";
16
17
  const promptPassword = () => {
17
18
  const rl = readline.createInterface({
18
19
  input: process.stdin,
@@ -37,11 +38,12 @@ const promptPassword = () => {
37
38
  };
38
39
  /**
39
40
  * Adds common options (username and password) to a command.
41
+ * Username is optional if a session already exists.
40
42
  * @param command The command to which options should be added.
41
43
  * @returns The command with options added.
42
44
  */
43
45
  const addAuthOptions = (command) => command
44
- .requiredOption("-u, --username <username>", "Username")
46
+ .option("-u, --username <username>", "Username (optional if session exists)")
45
47
  .option("-p, --password <password>", "Password");
46
48
  /**
47
49
  * Adds MAC address option to a command.
@@ -57,14 +59,29 @@ const addMacOption = (command) => command.requiredOption("-m, --mac <macAddress>
57
59
  const addLegacyOption = (command) => command.option("--legacy", "Use legacy API endpoint (old AWS Gateway)");
58
60
  /**
59
61
  * Handles common authentication and API initialization logic.
62
+ * Tries to use existing session first, falls back to sign-in if needed.
60
63
  * @param options The options passed from the CLI command.
61
64
  * @returns An object containing the normalized MAC, JWT token, and configured API instance.
62
65
  */
63
66
  const initializeCommand = (options) => __awaiter(void 0, void 0, void 0, function* () {
64
67
  const { username, password, mac, legacy = false } = options;
65
68
  const normalizedMac = mac.replace(/:/g, "");
66
- const pwd = password || (yield promptPassword());
67
- const jwtToken = yield signIn(username, pwd, legacy);
69
+ // Initialize file storage for session persistence
70
+ const storage = createFileStorage();
71
+ configureAmplify(storage);
72
+ let jwtToken;
73
+ try {
74
+ // Try to get existing session first
75
+ jwtToken = yield getSession(false, legacy);
76
+ }
77
+ catch (_a) {
78
+ // No session, need to sign in
79
+ if (!username) {
80
+ throw new Error("No session found. Please provide --username to sign in.");
81
+ }
82
+ const pwd = password || (yield promptPassword());
83
+ jwtToken = yield signIn(username, pwd, legacy);
84
+ }
68
85
  const apiUrl = legacy ? OLD_API_URL : NEW_API_URL;
69
86
  const api = configure(apiUrl);
70
87
  return { normalizedMac, jwtToken, api };
@@ -96,12 +113,28 @@ const createProgram = () => {
96
113
  .description("CLI tool for interacting with the Edilkamin API")
97
114
  .version(version);
98
115
  // Command: signIn
99
- addAuthOptions(program.command("signIn").description("Sign in and retrieve a JWT token")).action((options) => __awaiter(void 0, void 0, void 0, function* () {
116
+ program
117
+ .command("signIn")
118
+ .description("Sign in and retrieve a JWT token")
119
+ .requiredOption("-u, --username <username>", "Username")
120
+ .option("-p, --password <password>", "Password")
121
+ .action((options) => __awaiter(void 0, void 0, void 0, function* () {
100
122
  const { username, password } = options;
123
+ // Initialize file storage for session persistence
124
+ const storage = createFileStorage();
125
+ configureAmplify(storage);
101
126
  const pwd = password || (yield promptPassword());
102
127
  const jwtToken = yield signIn(username, pwd);
103
128
  console.log("JWT Token:", jwtToken);
104
129
  }));
130
+ // Command: logout
131
+ program
132
+ .command("logout")
133
+ .description("Clear stored session")
134
+ .action(() => __awaiter(void 0, void 0, void 0, function* () {
135
+ yield clearSession();
136
+ console.log("Session cleared successfully");
137
+ }));
105
138
  // Generic getter commands
106
139
  [
107
140
  {
@@ -153,8 +186,20 @@ const createProgram = () => {
153
186
  .action((options) => __awaiter(void 0, void 0, void 0, function* () {
154
187
  const { username, password, mac, serial, name, room, legacy = false, } = options;
155
188
  const normalizedMac = mac.replace(/:/g, "");
156
- const pwd = password || (yield promptPassword());
157
- const jwtToken = yield signIn(username, pwd, legacy);
189
+ // Initialize file storage for session persistence
190
+ const storage = createFileStorage();
191
+ configureAmplify(storage);
192
+ let jwtToken;
193
+ try {
194
+ jwtToken = yield getSession(false, legacy);
195
+ }
196
+ catch (_a) {
197
+ if (!username) {
198
+ throw new Error("No session found. Please provide --username to sign in.");
199
+ }
200
+ const pwd = password || (yield promptPassword());
201
+ jwtToken = yield signIn(username, pwd, legacy);
202
+ }
158
203
  const apiUrl = legacy ? OLD_API_URL : NEW_API_URL;
159
204
  const api = configure(apiUrl);
160
205
  const result = yield api.registerDevice(jwtToken, normalizedMac, serial, name, room);
@@ -162,14 +207,28 @@ const createProgram = () => {
162
207
  console.log(JSON.stringify(result, null, 2));
163
208
  }));
164
209
  // Command: editDevice
165
- addLegacyOption(addMacOption(addAuthOptions(program.command("editDevice").description("Update device name and room"))))
210
+ addLegacyOption(addMacOption(addAuthOptions(program
211
+ .command("editDevice")
212
+ .description("Update device name and room"))))
166
213
  .requiredOption("-n, --name <deviceName>", "Device name")
167
214
  .requiredOption("-r, --room <deviceRoom>", "Room name")
168
215
  .action((options) => __awaiter(void 0, void 0, void 0, function* () {
169
216
  const { username, password, mac, name, room, legacy = false } = options;
170
217
  const normalizedMac = mac.replace(/:/g, "");
171
- const pwd = password || (yield promptPassword());
172
- const jwtToken = yield signIn(username, pwd, legacy);
218
+ // Initialize file storage for session persistence
219
+ const storage = createFileStorage();
220
+ configureAmplify(storage);
221
+ let jwtToken;
222
+ try {
223
+ jwtToken = yield getSession(false, legacy);
224
+ }
225
+ catch (_a) {
226
+ if (!username) {
227
+ throw new Error("No session found. Please provide --username to sign in.");
228
+ }
229
+ const pwd = password || (yield promptPassword());
230
+ jwtToken = yield signIn(username, pwd, legacy);
231
+ }
173
232
  const apiUrl = legacy ? OLD_API_URL : NEW_API_URL;
174
233
  const api = configure(apiUrl);
175
234
  const result = yield api.editDevice(jwtToken, normalizedMac, name, room);
@@ -1,6 +1,7 @@
1
1
  export { decompressBuffer, isBuffer, processResponse } from "./buffer-utils";
2
2
  export { API_URL, NEW_API_URL, OLD_API_URL } from "./constants";
3
- export { configure, signIn } from "./library";
3
+ export { configure, getSession, signIn } from "./library";
4
4
  export { serialNumberDisplay, serialNumberFromHex, serialNumberToHex, } from "./serial-utils";
5
+ export { clearSession } from "./token-storage";
5
6
  export { BufferEncodedType, CommandsType, DeviceAssociationBody, DeviceAssociationResponse, DeviceInfoRawType, DeviceInfoType, EditDeviceAssociationBody, StatusType, TemperaturesType, UserParametersType, } from "./types";
6
7
  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,6 +1,7 @@
1
1
  import { configure } from "./library";
2
2
  export { decompressBuffer, isBuffer, processResponse } from "./buffer-utils";
3
3
  export { API_URL, NEW_API_URL, OLD_API_URL } from "./constants";
4
- export { configure, signIn } from "./library";
4
+ export { configure, getSession, signIn } from "./library";
5
5
  export { serialNumberDisplay, serialNumberFromHex, serialNumberToHex, } from "./serial-utils";
6
+ export { clearSession } from "./token-storage";
6
7
  export const { deviceInfo, registerDevice, editDevice, setPower, setPowerOff, setPowerOn, getPower, getEnvironmentTemperature, getTargetTemperature, setTargetTemperature, } = configure();
@@ -8,6 +8,17 @@ import { DeviceAssociationResponse, DeviceInfoType } from "./types";
8
8
  declare const headers: (jwtToken: string) => {
9
9
  Authorization: string;
10
10
  };
11
+ /**
12
+ * Configures Amplify if not already configured.
13
+ * Uses a local flag to avoid calling getConfig() which prints a warning.
14
+ * @param {object} [storage] - Optional custom storage adapter for token persistence
15
+ */
16
+ declare const configureAmplify: (storage?: {
17
+ setItem: (key: string, value: string) => Promise<void>;
18
+ getItem: (key: string) => Promise<string | null>;
19
+ removeItem: (key: string) => Promise<void>;
20
+ clear: () => Promise<void>;
21
+ }) => void;
11
22
  /**
12
23
  * Creates an authentication service with sign-in functionality.
13
24
  * @param {typeof amplifyAuth} auth - The authentication module to use.
@@ -15,8 +26,9 @@ declare const headers: (jwtToken: string) => {
15
26
  */
16
27
  declare const createAuthService: (auth: typeof amplifyAuth) => {
17
28
  signIn: (username: string, password: string, legacy?: boolean) => Promise<string>;
29
+ getSession: (forceRefresh?: boolean, legacy?: boolean) => Promise<string>;
18
30
  };
19
- declare const signIn: (username: string, password: string, legacy?: boolean) => Promise<string>;
31
+ declare const signIn: (username: string, password: string, legacy?: boolean) => Promise<string>, getSession: (forceRefresh?: boolean, legacy?: boolean) => Promise<string>;
20
32
  /**
21
33
  * Configures the library for API interactions.
22
34
  * Initializes API methods with a specified base URL.
@@ -40,4 +52,4 @@ declare const configure: (baseURL?: string) => {
40
52
  getTargetTemperature: (jwtToken: string, macAddress: string) => Promise<number>;
41
53
  setTargetTemperature: (jwtToken: string, macAddress: string, temperature: number) => Promise<import("axios").AxiosResponse<any, any, {}>>;
42
54
  };
43
- export { configure, createAuthService, headers, signIn };
55
+ export { configure, configureAmplify, createAuthService, getSession, headers, signIn, };
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import { strict as assert } from "assert";
11
11
  import { Amplify } from "aws-amplify";
12
12
  import * as amplifyAuth from "aws-amplify/auth";
13
+ import { cognitoUserPoolsTokenProvider } from "aws-amplify/auth/cognito";
13
14
  import axios from "axios";
14
15
  import { processResponse } from "./buffer-utils";
15
16
  import { API_URL } from "./constants";
@@ -28,11 +29,15 @@ let amplifyConfigured = false;
28
29
  /**
29
30
  * Configures Amplify if not already configured.
30
31
  * Uses a local flag to avoid calling getConfig() which prints a warning.
32
+ * @param {object} [storage] - Optional custom storage adapter for token persistence
31
33
  */
32
- const configureAmplify = () => {
34
+ const configureAmplify = (storage) => {
33
35
  if (amplifyConfigured)
34
36
  return;
35
37
  Amplify.configure(amplifyconfiguration);
38
+ if (storage) {
39
+ cognitoUserPoolsTokenProvider.setKeyValueStorage(storage);
40
+ }
36
41
  amplifyConfigured = true;
37
42
  };
38
43
  /**
@@ -63,10 +68,29 @@ const createAuthService = (auth) => {
63
68
  assert.ok(tokens.idToken, "No ID token found");
64
69
  return tokens.idToken.toString();
65
70
  });
66
- return { signIn };
71
+ /**
72
+ * Retrieves the current session, refreshing tokens if necessary.
73
+ * Requires a prior successful signIn() call.
74
+ * @param {boolean} [forceRefresh=false] - Force token refresh even if valid
75
+ * @param {boolean} [legacy=false] - If true, returns accessToken for legacy API
76
+ * @returns {Promise<string>} - The JWT token (idToken or accessToken)
77
+ * @throws {Error} - If no session exists (user needs to sign in)
78
+ */
79
+ const getSession = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (forceRefresh = false, legacy = false) {
80
+ configureAmplify();
81
+ const { tokens } = yield auth.fetchAuthSession({ forceRefresh });
82
+ assert.ok(tokens, "No session found - please sign in first");
83
+ if (legacy) {
84
+ assert.ok(tokens.accessToken, "No access token found");
85
+ return tokens.accessToken.toString();
86
+ }
87
+ assert.ok(tokens.idToken, "No ID token found");
88
+ return tokens.idToken.toString();
89
+ });
90
+ return { signIn, getSession };
67
91
  };
68
92
  // Create the default auth service using amplifyAuth
69
- const { signIn } = createAuthService(amplifyAuth);
93
+ const { signIn, getSession } = createAuthService(amplifyAuth);
70
94
  const deviceInfo = (axiosInstance) =>
71
95
  /**
72
96
  * Retrieves information about a device by its MAC address.
@@ -248,4 +272,4 @@ const configure = (baseURL = API_URL) => {
248
272
  setTargetTemperature: setTargetTemperatureInstance,
249
273
  };
250
274
  };
251
- export { configure, createAuthService, headers, signIn };
275
+ export { configure, configureAmplify, createAuthService, getSession, headers, signIn, };
@@ -81,8 +81,7 @@ describe("library", () => {
81
81
  };
82
82
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
83
83
  const authService = createAuthService(authStub);
84
- const token = yield authService.signIn(expectedUsername, expectedPassword, true // legacy mode
85
- );
84
+ const token = yield authService.signIn(expectedUsername, expectedPassword, true);
86
85
  assert.equal(token, expectedToken);
87
86
  }));
88
87
  it("should throw an error if sign-in fails", () => __awaiter(void 0, void 0, void 0, function* () {
@@ -108,6 +107,68 @@ describe("library", () => {
108
107
  });
109
108
  }));
110
109
  });
110
+ describe("getSession", () => {
111
+ it("should return idToken by default", () => __awaiter(void 0, void 0, void 0, function* () {
112
+ const mockAuth = {
113
+ signIn: sinon.stub().resolves({ isSignedIn: true }),
114
+ signOut: sinon.stub().resolves(),
115
+ fetchAuthSession: sinon.stub().resolves({
116
+ tokens: {
117
+ idToken: { toString: () => "mock-id-token" },
118
+ accessToken: { toString: () => "mock-access-token" },
119
+ },
120
+ }),
121
+ };
122
+ const { getSession, signIn } = createAuthService(mockAuth);
123
+ yield signIn("user", "pass");
124
+ const token = yield getSession();
125
+ assert.equal(token, "mock-id-token");
126
+ }));
127
+ it("should return accessToken when legacy=true", () => __awaiter(void 0, void 0, void 0, function* () {
128
+ const mockAuth = {
129
+ signIn: sinon.stub().resolves({ isSignedIn: true }),
130
+ signOut: sinon.stub().resolves(),
131
+ fetchAuthSession: sinon.stub().resolves({
132
+ tokens: {
133
+ idToken: { toString: () => "mock-id-token" },
134
+ accessToken: { toString: () => "mock-access-token" },
135
+ },
136
+ }),
137
+ };
138
+ const { getSession, signIn } = createAuthService(mockAuth);
139
+ yield signIn("user", "pass");
140
+ const token = yield getSession(false, true);
141
+ assert.equal(token, "mock-access-token");
142
+ }));
143
+ it("should throw error when no session exists", () => __awaiter(void 0, void 0, void 0, function* () {
144
+ const mockAuth = {
145
+ signIn: sinon.stub().resolves({ isSignedIn: true }),
146
+ signOut: sinon.stub().resolves(),
147
+ fetchAuthSession: sinon.stub().resolves({ tokens: null }),
148
+ };
149
+ const { getSession } = createAuthService(mockAuth);
150
+ yield assert.rejects(() => __awaiter(void 0, void 0, void 0, function* () { return getSession(); }), {
151
+ name: "AssertionError",
152
+ message: "No session found - please sign in first",
153
+ });
154
+ }));
155
+ it("should pass forceRefresh to fetchAuthSession", () => __awaiter(void 0, void 0, void 0, function* () {
156
+ const mockAuth = {
157
+ signIn: sinon.stub().resolves({ isSignedIn: true }),
158
+ signOut: sinon.stub().resolves(),
159
+ fetchAuthSession: sinon.stub().resolves({
160
+ tokens: {
161
+ idToken: { toString: () => "mock-id-token" },
162
+ accessToken: { toString: () => "mock-access-token" },
163
+ },
164
+ }),
165
+ };
166
+ const { getSession, signIn } = createAuthService(mockAuth);
167
+ yield signIn("user", "pass");
168
+ yield getSession(true);
169
+ assert.ok(mockAuth.fetchAuthSession.calledWith({ forceRefresh: true }));
170
+ }));
171
+ });
111
172
  describe("configure", () => {
112
173
  const expectedApi = [
113
174
  "deviceInfo",
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Custom storage adapter for AWS Amplify that persists to file system.
3
+ * Used for CLI to maintain sessions between invocations.
4
+ */
5
+ export declare const createFileStorage: () => {
6
+ setItem: (key: string, value: string) => Promise<void>;
7
+ getItem: (key: string) => Promise<string | null>;
8
+ removeItem: (key: string) => Promise<void>;
9
+ clear: () => Promise<void>;
10
+ };
11
+ /**
12
+ * Clears all stored session data.
13
+ */
14
+ export declare const clearSession: () => Promise<void>;
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "edilkamin",
3
- "version": "1.6.2",
3
+ "version": "1.7.2",
4
4
  "description": "",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -60,7 +60,7 @@
60
60
  "eslint-plugin-simple-import-sort": "^12.1.1",
61
61
  "mocha": "^11.7.5",
62
62
  "nyc": "^17.1.0",
63
- "prettier": "^2.5.1",
63
+ "prettier": "^3.7.4",
64
64
  "sinon": "^19.0.2",
65
65
  "ts-node": "^10.9.1",
66
66
  "typedoc": "^0.28.15",
@@ -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);