mysa-js-sdk 2.1.2 → 3.0.0
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/CHANGELOG.md +20 -0
- package/README.md +18 -34
- package/dist/index.cjs +113 -81
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +71 -54
- package/dist/index.d.ts +71 -54
- package/dist/index.js +113 -90
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# mysa-js-sdk
|
|
2
2
|
|
|
3
|
+
## 3.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- [#199](https://github.com/bourquep/mysa2mqtt/pull/199) [`b384d79`](https://github.com/bourquep/mysa2mqtt/commit/b384d7950d757bc85af7580eaa26435190b47364) Thanks [@bourquep](https://github.com/bourquep)! - The client now takes Mysa account credentials instead of a session object, and re-authenticates on its own when its refresh token expires or is revoked, so long-running processes no longer die after about a month. `new MysaApiClient(session, options)` becomes `new MysaApiClient({ username, password }, options)`, `login()` no longer takes arguments and is optional (the client authenticates on demand), and the `MysaSession` type, the `session` and `isAuthenticated` properties, and the `sessionChanged` event are gone — there is no longer any session state to persist.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- [#201](https://github.com/bourquep/mysa2mqtt/pull/201) [`10ee91c`](https://github.com/bourquep/mysa2mqtt/commit/10ee91c1981b77ef1e9f76abd3d24ba6d9a19d77) Thanks [@bourquep](https://github.com/bourquep)! - A Mysa login rejected for a bad password now reports that a `$` in it is expanded by shells and by Docker Compose (in both `environment:` entries and default-format `env_file:` files, where it must be written `$$`, but not in an `env_file:` declared with `format: raw`), and that an unquoted `#` truncates a `.env` value, instead of surfacing a bare `Incorrect username or password.` stack trace. An unrecognized account gets username-specific guidance instead, since none of the password escaping rules apply to it. Transport and Cognito service failures keep propagating without that guidance, since `UnauthenticatedError` now carries the underlying failure as its `cause`. Debug logging also reports the length of the password that was actually received -- never the password or the account it belongs to -- so a mangled value can be spotted at a glance.
|
|
12
|
+
|
|
13
|
+
- [#190](https://github.com/bourquep/mysa2mqtt/pull/190) [`7169263`](https://github.com/bourquep/mysa2mqtt/commit/7169263fcb4b2aeb51c7eae4c112972c3e2fdb08) Thanks [@vavallee](https://github.com/vavallee)! - Payload parse failures no longer write to console.error, which bypassed the configurable SDK logger and could leak payload contents. The error is rethrown with the original failure attached as cause and logged by the message handler through the SDK logger.
|
|
14
|
+
|
|
15
|
+
- [#191](https://github.com/bourquep/mysa2mqtt/pull/191) [`527ef25`](https://github.com/bourquep/mysa2mqtt/commit/527ef25886aad766a2fd71fc92002d8de126b364) Thanks [@vavallee](https://github.com/vavallee)! - setDeviceState now throws a descriptive UnknownDeviceError when the device id does not match any device on the account, instead of failing with a raw TypeError on an undefined dereference.
|
|
16
|
+
|
|
17
|
+
- [#200](https://github.com/bourquep/mysa2mqtt/pull/200) [`61dc2a2`](https://github.com/bourquep/mysa2mqtt/commit/61dc2a2b395397e9e5245098bfd31b49b501fd7d) Thanks [@bourquep](https://github.com/bourquep)! - V2 thermostats can now report power. These devices have no current sensor and only report the duty cycle of their heating relay, which is why their **Current power** sensor has always been unavailable. Set the new `--heater-watts` option (`M2M_HEATER_WATTS`) to the rated wattage of the heaters each thermostat controls — for example `M2M_HEATER_WATTS="Kitchen=1500,<device-id>=750"`, matching devices by name or id — and power is estimated as `duty cycle × rated wattage`. V1 thermostats measure their own current and continue to work with no configuration.
|
|
18
|
+
|
|
19
|
+
The power sensor is now only created for devices that can actually report a value. If you have AC devices, or V2 thermostats for which you have not configured a wattage, their **Current power** entity is removed from Home Assistant on startup; it only ever showed as unavailable.
|
|
20
|
+
|
|
21
|
+
`mqtt2ha` gains a `Discoverable.removeConfig()` method, which clears an entity's retained discovery topic so Home Assistant drops the entity. This is what makes the removal above take effect: because `writeConfig()` retains its payload, an entity published by an earlier run persists until its topic is explicitly cleared.
|
|
22
|
+
|
|
3
23
|
## 2.1.2
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -66,17 +66,20 @@ The Mysa SDK provides a simple interface to interact with Mysa smart thermostats
|
|
|
66
66
|
```typescript
|
|
67
67
|
import { MysaApiClient } from 'mysa-js-sdk';
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
// The client holds your credentials and authenticates on demand
|
|
70
|
+
const client = new MysaApiClient({
|
|
71
|
+
username: 'your-email@example.com',
|
|
72
|
+
password: 'your-password'
|
|
73
|
+
});
|
|
73
74
|
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
console.log('Successfully authenticated!');
|
|
77
|
-
}
|
|
75
|
+
// Optional: log in eagerly to fail fast on invalid credentials
|
|
76
|
+
await client.login();
|
|
78
77
|
```
|
|
79
78
|
|
|
79
|
+
The client manages its session on its own: it refreshes the session when it needs to, and logs back in automatically
|
|
80
|
+
when the refresh token has itself expired or been revoked. There is nothing to persist, and no need to re-create the
|
|
81
|
+
client after a long run.
|
|
82
|
+
|
|
80
83
|
### Retrieving Thermostat Data
|
|
81
84
|
|
|
82
85
|
Once authenticated, you can access your thermostat data:
|
|
@@ -139,10 +142,9 @@ The SDK provides specific error types to handle API errors:
|
|
|
139
142
|
```typescript
|
|
140
143
|
import { MysaApiClient, MysaApiError, UnauthenticatedError } from 'mysa-js-sdk';
|
|
141
144
|
|
|
142
|
-
const client = new MysaApiClient();
|
|
145
|
+
const client = new MysaApiClient({ username: 'user@example.com', password: 'password' });
|
|
143
146
|
|
|
144
147
|
try {
|
|
145
|
-
await client.login('user@example.com', 'password');
|
|
146
148
|
const devices = await client.getDevices();
|
|
147
149
|
} catch (error) {
|
|
148
150
|
if (error instanceof UnauthenticatedError) {
|
|
@@ -170,31 +172,13 @@ const logger = pino({
|
|
|
170
172
|
});
|
|
171
173
|
|
|
172
174
|
// Configure client with options
|
|
173
|
-
const client = new MysaApiClient(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
// Or restore from a saved session
|
|
179
|
-
const savedSession = {
|
|
180
|
-
username: 'user@example.com',
|
|
181
|
-
idToken: 'eyJ...',
|
|
182
|
-
accessToken: 'eyJ...',
|
|
183
|
-
refreshToken: 'abc123...'
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
const clientWithSession = new MysaApiClient(savedSession, { logger });
|
|
187
|
-
|
|
188
|
-
// Listen for session changes to persist them
|
|
189
|
-
client.emitter.on('sessionChanged', (newSession) => {
|
|
190
|
-
if (newSession) {
|
|
191
|
-
// Save session to storage (file, database, etc.)
|
|
192
|
-
localStorage.setItem('mysaSession', JSON.stringify(newSession));
|
|
193
|
-
} else {
|
|
194
|
-
// Session expired or logged out
|
|
195
|
-
localStorage.removeItem('mysaSession');
|
|
175
|
+
const client = new MysaApiClient(
|
|
176
|
+
{ username: 'user@example.com', password: 'password' },
|
|
177
|
+
{
|
|
178
|
+
logger: logger,
|
|
179
|
+
fetcher: fetch // Custom fetch implementation if needed
|
|
196
180
|
}
|
|
197
|
-
|
|
181
|
+
);
|
|
198
182
|
```
|
|
199
183
|
|
|
200
184
|
### Reference documentation
|
package/dist/index.cjs
CHANGED
|
@@ -59,20 +59,31 @@ __export(index_exports, {
|
|
|
59
59
|
MysaApiError: () => MysaApiError,
|
|
60
60
|
OutMessageType: () => OutMessageType,
|
|
61
61
|
UnauthenticatedError: () => UnauthenticatedError,
|
|
62
|
+
UnknownDeviceError: () => UnknownDeviceError,
|
|
62
63
|
VoidLogger: () => VoidLogger
|
|
63
64
|
});
|
|
64
65
|
module.exports = __toCommonJS(index_exports);
|
|
65
66
|
|
|
66
67
|
// src/api/Errors.ts
|
|
67
68
|
var UnauthenticatedError = class extends Error {
|
|
69
|
+
/**
|
|
70
|
+
* The underlying failure that prevented authentication.
|
|
71
|
+
*
|
|
72
|
+
* Callers need it to tell a credential rejection apart from a transport or service failure, since both surface as
|
|
73
|
+
* this error. Declared explicitly rather than passed to `super` so the SDK keeps compiling against its current lib,
|
|
74
|
+
* which predates the two-argument `Error` constructor.
|
|
75
|
+
*/
|
|
76
|
+
cause;
|
|
68
77
|
/**
|
|
69
78
|
* Creates a new UnauthenticatedError instance.
|
|
70
79
|
*
|
|
71
80
|
* @param message - The error message
|
|
81
|
+
* @param cause - The underlying failure that prevented authentication, if any.
|
|
72
82
|
*/
|
|
73
|
-
constructor(message) {
|
|
83
|
+
constructor(message, cause) {
|
|
74
84
|
super(message);
|
|
75
85
|
this.name = "UnauthenticatedError";
|
|
86
|
+
this.cause = cause;
|
|
76
87
|
}
|
|
77
88
|
};
|
|
78
89
|
var MysaApiError = class extends Error {
|
|
@@ -94,6 +105,19 @@ var MysaApiError = class extends Error {
|
|
|
94
105
|
this.statusText = apiResponse.statusText;
|
|
95
106
|
}
|
|
96
107
|
};
|
|
108
|
+
var UnknownDeviceError = class extends Error {
|
|
109
|
+
/**
|
|
110
|
+
* Creates a new UnknownDeviceError instance.
|
|
111
|
+
*
|
|
112
|
+
* @param deviceId - The device id that could not be resolved
|
|
113
|
+
*/
|
|
114
|
+
constructor(deviceId) {
|
|
115
|
+
super(`Unknown device id '${deviceId}': no such device on this account.`);
|
|
116
|
+
this.deviceId = deviceId;
|
|
117
|
+
this.name = "UnknownDeviceError";
|
|
118
|
+
}
|
|
119
|
+
deviceId;
|
|
120
|
+
};
|
|
97
121
|
var MqttPublishError = class extends Error {
|
|
98
122
|
/**
|
|
99
123
|
* Creates a new MqttPublishError instance.
|
|
@@ -191,8 +215,9 @@ function parseMqttPayload(payload) {
|
|
|
191
215
|
const jsonString = decoder.decode(payload);
|
|
192
216
|
return JSON.parse(jsonString);
|
|
193
217
|
} catch (error) {
|
|
194
|
-
|
|
195
|
-
|
|
218
|
+
const parseError = new Error("Failed to parse MQTT payload");
|
|
219
|
+
parseError.cause = error;
|
|
220
|
+
throw parseError;
|
|
196
221
|
}
|
|
197
222
|
}
|
|
198
223
|
function serializeMqttPayload(payload) {
|
|
@@ -250,10 +275,14 @@ var MysaApiBaseUrl = "https://app-prod.mysa.cloud";
|
|
|
250
275
|
var RealtimeKeepAliveInterval = import_dayjs.default.duration(5, "minutes");
|
|
251
276
|
var PublishAckTimeout = import_dayjs.default.duration(30, "seconds");
|
|
252
277
|
var MysaApiClient = class {
|
|
278
|
+
/** The credentials of the Mysa account this client authenticates as. */
|
|
279
|
+
_credentials;
|
|
253
280
|
/** The current session object, if any. */
|
|
254
281
|
_cognitoUserSession;
|
|
255
282
|
/** The current user object, if any. */
|
|
256
283
|
_cognitoUser;
|
|
284
|
+
/** The in-flight session acquisition, if any, so that concurrent callers share a single refresh or login. */
|
|
285
|
+
_freshSessionPromise;
|
|
257
286
|
/** The logger instance used by the client. */
|
|
258
287
|
_logger;
|
|
259
288
|
/** The fetcher function used by the client. */
|
|
@@ -278,89 +307,62 @@ var MysaApiClient = class {
|
|
|
278
307
|
* @see {@link MysaApiClientEventTypes} for the possible events and their payloads.
|
|
279
308
|
*/
|
|
280
309
|
emitter = new EventEmitter();
|
|
281
|
-
/**
|
|
282
|
-
* Gets the persistable session object.
|
|
283
|
-
*
|
|
284
|
-
* @returns The current persistable session object, if any.
|
|
285
|
-
*/
|
|
286
|
-
get session() {
|
|
287
|
-
if (!this._cognitoUserSession || !this._cognitoUser) {
|
|
288
|
-
return void 0;
|
|
289
|
-
}
|
|
290
|
-
return {
|
|
291
|
-
username: this._cognitoUser.getUsername(),
|
|
292
|
-
idToken: this._cognitoUserSession.getIdToken().getJwtToken(),
|
|
293
|
-
accessToken: this._cognitoUserSession.getAccessToken().getJwtToken(),
|
|
294
|
-
refreshToken: this._cognitoUserSession.getRefreshToken().getToken()
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Returns whether the client currently has an active session.
|
|
299
|
-
*
|
|
300
|
-
* @returns True if the client has an active session, false otherwise.
|
|
301
|
-
*/
|
|
302
|
-
get isAuthenticated() {
|
|
303
|
-
return !!this.session;
|
|
304
|
-
}
|
|
305
310
|
/**
|
|
306
311
|
* Constructs a new instance of the MysaApiClient.
|
|
307
312
|
*
|
|
308
|
-
* @param
|
|
313
|
+
* @param credentials - The credentials of the Mysa account to authenticate as.
|
|
309
314
|
* @param options - The options for the client.
|
|
310
315
|
*/
|
|
311
|
-
constructor(
|
|
316
|
+
constructor(credentials, options) {
|
|
317
|
+
this._credentials = credentials;
|
|
312
318
|
this._logger = (options == null ? void 0 : options.logger) || new VoidLogger();
|
|
313
319
|
this._fetcher = (options == null ? void 0 : options.fetcher) || fetch;
|
|
314
|
-
if (session) {
|
|
315
|
-
this._cognitoUser = new import_amazon_cognito_identity_js.CognitoUser({
|
|
316
|
-
Username: session.username,
|
|
317
|
-
Pool: new import_amazon_cognito_identity_js.CognitoUserPool({ UserPoolId: CognitoUserPoolId, ClientId: CognitoClientId })
|
|
318
|
-
});
|
|
319
|
-
this._cognitoUserSession = new import_amazon_cognito_identity_js.CognitoUserSession({
|
|
320
|
-
IdToken: new import_amazon_cognito_identity_js.CognitoIdToken({ IdToken: session.idToken }),
|
|
321
|
-
AccessToken: new import_amazon_cognito_identity_js.CognitoAccessToken({ AccessToken: session.accessToken }),
|
|
322
|
-
RefreshToken: new import_amazon_cognito_identity_js.CognitoRefreshToken({ RefreshToken: session.refreshToken })
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
320
|
}
|
|
326
321
|
/**
|
|
327
|
-
*
|
|
322
|
+
* Ensures the client has a usable session, logging in with the credentials it was constructed with if needed.
|
|
328
323
|
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
324
|
+
* Calling this method is optional: the client authenticates on demand before its first API call, and re-authenticates
|
|
325
|
+
* on its own whenever its session can no longer be refreshed. Call it explicitly at startup to fail fast on invalid
|
|
326
|
+
* credentials instead of on the first API call. It is a no-op when the current session is still usable.
|
|
331
327
|
*
|
|
332
328
|
* @example
|
|
333
329
|
*
|
|
334
330
|
* ```typescript
|
|
335
331
|
* try {
|
|
336
|
-
* await client.login(
|
|
332
|
+
* await client.login();
|
|
337
333
|
* console.log('Login successful!');
|
|
338
334
|
* } catch (error) {
|
|
339
335
|
* console.error('Login failed:', error.message);
|
|
340
336
|
* }
|
|
341
337
|
* ```
|
|
342
338
|
*
|
|
343
|
-
* @
|
|
344
|
-
|
|
339
|
+
* @throws {@link UnauthenticatedError} When authentication fails due to invalid credentials or network issues.
|
|
340
|
+
*/
|
|
341
|
+
async login() {
|
|
342
|
+
await this._getFreshSession();
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Authenticates with Mysa's Cognito user pool and replaces the current session, if any.
|
|
346
|
+
*
|
|
347
|
+
* @returns A promise that resolves to the newly established session.
|
|
345
348
|
* @throws {@link Error} When authentication fails due to invalid credentials or network issues.
|
|
346
349
|
*/
|
|
347
|
-
|
|
350
|
+
_login() {
|
|
348
351
|
this._cognitoUser = void 0;
|
|
349
352
|
this._cognitoUserSession = void 0;
|
|
350
353
|
this._mqttClientId = void 0;
|
|
351
354
|
this._mqttInterrupts = [];
|
|
352
|
-
|
|
355
|
+
const { username, password } = this._credentials;
|
|
353
356
|
return new Promise((resolve, reject) => {
|
|
354
357
|
const user = new import_amazon_cognito_identity_js.CognitoUser({
|
|
355
|
-
Username:
|
|
358
|
+
Username: username,
|
|
356
359
|
Pool: new import_amazon_cognito_identity_js.CognitoUserPool({ UserPoolId: CognitoUserPoolId, ClientId: CognitoClientId })
|
|
357
360
|
});
|
|
358
|
-
user.authenticateUser(new import_amazon_cognito_identity_js.AuthenticationDetails({ Username:
|
|
361
|
+
user.authenticateUser(new import_amazon_cognito_identity_js.AuthenticationDetails({ Username: username, Password: password }), {
|
|
359
362
|
onSuccess: (session) => {
|
|
360
363
|
this._cognitoUser = user;
|
|
361
364
|
this._cognitoUserSession = session;
|
|
362
|
-
|
|
363
|
-
resolve();
|
|
365
|
+
resolve(session);
|
|
364
366
|
},
|
|
365
367
|
onFailure: (err) => {
|
|
366
368
|
reject(err);
|
|
@@ -385,7 +387,7 @@ var MysaApiClient = class {
|
|
|
385
387
|
*
|
|
386
388
|
* @returns A promise that resolves to the list of devices.
|
|
387
389
|
* @throws {@link MysaApiError} When the API request fails.
|
|
388
|
-
* @throws {@link UnauthenticatedError} When the
|
|
390
|
+
* @throws {@link UnauthenticatedError} When the client cannot authenticate with its credentials.
|
|
389
391
|
*/
|
|
390
392
|
async getDevices() {
|
|
391
393
|
this._logger.debug(`Fetching devices...`);
|
|
@@ -419,7 +421,7 @@ var MysaApiClient = class {
|
|
|
419
421
|
*
|
|
420
422
|
* @param deviceId - The ID of the device to get the serial number for.
|
|
421
423
|
* @returns A promise that resolves to the serial number, or undefined if not found.
|
|
422
|
-
* @throws {@link UnauthenticatedError} When the
|
|
424
|
+
* @throws {@link UnauthenticatedError} When the client cannot authenticate with its credentials.
|
|
423
425
|
*/
|
|
424
426
|
async getDeviceSerialNumber(deviceId) {
|
|
425
427
|
var _a;
|
|
@@ -457,7 +459,7 @@ var MysaApiClient = class {
|
|
|
457
459
|
*
|
|
458
460
|
* @returns A promise that resolves to the firmware information for all devices.
|
|
459
461
|
* @throws {@link MysaApiError} When the API request fails.
|
|
460
|
-
* @throws {@link UnauthenticatedError} When the
|
|
462
|
+
* @throws {@link UnauthenticatedError} When the client cannot authenticate with its credentials.
|
|
461
463
|
*/
|
|
462
464
|
async getDeviceFirmwares() {
|
|
463
465
|
this._logger.debug(`Fetching device firmwares...`);
|
|
@@ -477,7 +479,7 @@ var MysaApiClient = class {
|
|
|
477
479
|
*
|
|
478
480
|
* @returns A promise that resolves to the current state of all devices.
|
|
479
481
|
* @throws {@link MysaApiError} When the API request fails.
|
|
480
|
-
* @throws {@link UnauthenticatedError} When the
|
|
482
|
+
* @throws {@link UnauthenticatedError} When the client cannot authenticate with its credentials.
|
|
481
483
|
*/
|
|
482
484
|
async getDeviceStates() {
|
|
483
485
|
this._logger.debug(`Fetching device states...`);
|
|
@@ -497,7 +499,7 @@ var MysaApiClient = class {
|
|
|
497
499
|
*
|
|
498
500
|
* @returns A promise that resolves to the homes information.
|
|
499
501
|
* @throws {@link MysaApiError} When the API request fails.
|
|
500
|
-
* @throws {@link UnauthenticatedError} When the
|
|
502
|
+
* @throws {@link UnauthenticatedError} When the client cannot authenticate with its credentials.
|
|
501
503
|
*/
|
|
502
504
|
async getHomes() {
|
|
503
505
|
this._logger.debug(`Fetching homes...`);
|
|
@@ -539,16 +541,20 @@ var MysaApiClient = class {
|
|
|
539
541
|
* @param mode - The operating mode to set (one of MysaDeviceMode values, or undefined to leave unchanged).
|
|
540
542
|
* @param fanSpeed - The fan speed mode to set ('low', 'medium', 'high', 'max', 'auto', or undefined to leave
|
|
541
543
|
* unchanged).
|
|
542
|
-
* @throws {@link UnauthenticatedError} When the
|
|
544
|
+
* @throws {@link UnauthenticatedError} When the client cannot authenticate with its credentials.
|
|
545
|
+
* @throws {@link UnknownDeviceError} When the device id does not match any device on the account.
|
|
543
546
|
* @throws {@link Error} When MQTT connection or command sending fails.
|
|
544
547
|
*/
|
|
545
548
|
async setDeviceState(deviceId, setPoint, mode, fanSpeed) {
|
|
546
|
-
var _a;
|
|
547
549
|
this._logger.debug(`Setting device state for '${deviceId}'`);
|
|
548
550
|
if (!this._cachedDevices) {
|
|
549
551
|
this._cachedDevices = await this.getDevices();
|
|
550
552
|
}
|
|
553
|
+
if (!Object.prototype.hasOwnProperty.call(this._cachedDevices.DevicesObj, deviceId)) {
|
|
554
|
+
throw new UnknownDeviceError(deviceId);
|
|
555
|
+
}
|
|
551
556
|
const device = this._cachedDevices.DevicesObj[deviceId];
|
|
557
|
+
await this._getFreshSession();
|
|
552
558
|
this._logger.debug(`Initializing MQTT connection...`);
|
|
553
559
|
const mqttConnection = await this._getMqttConnection();
|
|
554
560
|
const now = (0, import_dayjs.default)();
|
|
@@ -561,7 +567,7 @@ var MysaApiClient = class {
|
|
|
561
567
|
time: now.unix(),
|
|
562
568
|
ver: "1.0",
|
|
563
569
|
src: {
|
|
564
|
-
ref:
|
|
570
|
+
ref: this._cognitoUser.getUsername(),
|
|
565
571
|
type: 100
|
|
566
572
|
},
|
|
567
573
|
dest: {
|
|
@@ -679,33 +685,58 @@ var MysaApiClient = class {
|
|
|
679
685
|
* Ensures a valid, non-expired session is available.
|
|
680
686
|
*
|
|
681
687
|
* This method checks if the current session is valid and not expired. If the session is expired, it automatically
|
|
682
|
-
* refreshes it using the refresh token.
|
|
688
|
+
* refreshes it using the refresh token. If there is no session yet, or if the refresh token has itself expired or
|
|
689
|
+
* been revoked, the client logs back in with its credentials.
|
|
690
|
+
*
|
|
691
|
+
* Concurrent callers share a single in-flight acquisition, so a burst of API calls never triggers more than one
|
|
692
|
+
* refresh or login.
|
|
683
693
|
*
|
|
684
694
|
* @returns A promise that resolves to a valid CognitoUserSession.
|
|
685
|
-
* @throws {@link UnauthenticatedError} When
|
|
695
|
+
* @throws {@link UnauthenticatedError} When neither refreshing nor logging back in succeeds.
|
|
686
696
|
*/
|
|
687
697
|
async _getFreshSession() {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
}
|
|
691
|
-
if (this._cognitoUserSession.isValid() && import_dayjs.default.unix(this._cognitoUserSession.getIdToken().getExpiration()).isAfter()) {
|
|
698
|
+
var _a;
|
|
699
|
+
if (this._cognitoUser && ((_a = this._cognitoUserSession) == null ? void 0 : _a.isValid()) && import_dayjs.default.unix(this._cognitoUserSession.getIdToken().getExpiration()).isAfter()) {
|
|
692
700
|
this._logger.debug("Session is valid, no need to refresh");
|
|
693
|
-
return
|
|
701
|
+
return this._cognitoUserSession;
|
|
694
702
|
}
|
|
695
|
-
this.
|
|
696
|
-
|
|
697
|
-
this._cognitoUser.refreshSession(this._cognitoUserSession.getRefreshToken(), (error, session) => {
|
|
698
|
-
if (error) {
|
|
699
|
-
this._logger.error("Failed to refresh session:", error);
|
|
700
|
-
reject(new UnauthenticatedError("Unable to refresh the authentication session."));
|
|
701
|
-
} else {
|
|
702
|
-
this._logger.debug("Session refreshed successfully");
|
|
703
|
-
this._cognitoUserSession = session;
|
|
704
|
-
this.emitter.emit("sessionChanged", this.session);
|
|
705
|
-
resolve(session);
|
|
706
|
-
}
|
|
707
|
-
});
|
|
703
|
+
this._freshSessionPromise ??= this._acquireFreshSession().finally(() => {
|
|
704
|
+
this._freshSessionPromise = void 0;
|
|
708
705
|
});
|
|
706
|
+
return this._freshSessionPromise;
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Refreshes the current session, falling back to a full login when it cannot be refreshed.
|
|
710
|
+
*
|
|
711
|
+
* @returns A promise that resolves to a valid CognitoUserSession.
|
|
712
|
+
* @throws {@link UnauthenticatedError} When logging back in fails.
|
|
713
|
+
*/
|
|
714
|
+
async _acquireFreshSession() {
|
|
715
|
+
if (this._cognitoUser && this._cognitoUserSession) {
|
|
716
|
+
this._logger.debug("Session is not valid or expired, refreshing...");
|
|
717
|
+
try {
|
|
718
|
+
return await new Promise((resolve, reject) => {
|
|
719
|
+
this._cognitoUser.refreshSession(this._cognitoUserSession.getRefreshToken(), (error, session) => {
|
|
720
|
+
if (error) {
|
|
721
|
+
reject(error);
|
|
722
|
+
} else {
|
|
723
|
+
this._logger.debug("Session refreshed successfully");
|
|
724
|
+
this._cognitoUserSession = session;
|
|
725
|
+
resolve(session);
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
});
|
|
729
|
+
} catch (error) {
|
|
730
|
+
this._logger.warn("Failed to refresh session, logging back in:", error);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
try {
|
|
734
|
+
this._logger.info("Logging in...");
|
|
735
|
+
return await this._login();
|
|
736
|
+
} catch (error) {
|
|
737
|
+
this._logger.error("Failed to log in:", error);
|
|
738
|
+
throw new UnauthenticatedError("Unable to establish an authentication session.", error);
|
|
739
|
+
}
|
|
709
740
|
}
|
|
710
741
|
/**
|
|
711
742
|
* Establishes and returns an MQTT connection for real-time communication.
|
|
@@ -847,7 +878,7 @@ var MysaApiClient = class {
|
|
|
847
878
|
throw new Error("MQTT credentials are already expired.");
|
|
848
879
|
}
|
|
849
880
|
if (!this._mqttClientId) {
|
|
850
|
-
const username = ((_a = this.
|
|
881
|
+
const username = ((_a = this._cognitoUser) == null ? void 0 : _a.getUsername()) ?? "anon";
|
|
851
882
|
const usernameHash = (0, import_crypto.hash)("sha1", username);
|
|
852
883
|
this._mqttClientId = `mysa-js-sdk-${usernameHash}-${process.pid}-${getRandomClientId()}`;
|
|
853
884
|
}
|
|
@@ -1060,6 +1091,7 @@ var MysaApiClient = class {
|
|
|
1060
1091
|
MysaApiError,
|
|
1061
1092
|
OutMessageType,
|
|
1062
1093
|
UnauthenticatedError,
|
|
1094
|
+
UnknownDeviceError,
|
|
1063
1095
|
VoidLogger
|
|
1064
1096
|
});
|
|
1065
1097
|
//# sourceMappingURL=index.cjs.map
|