myio-js-library 0.1.52 → 0.1.53
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/dist/index.cjs +135 -0
- package/dist/index.d.cts +64 -1
- package/dist/index.js +132 -0
- package/dist/myio-js-library.umd.js +132 -0
- package/dist/myio-js-library.umd.min.js +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -576,12 +576,14 @@ __export(index_exports, {
|
|
|
576
576
|
addNamespace: () => addNamespace,
|
|
577
577
|
averageByDay: () => averageByDay,
|
|
578
578
|
buildListItemsThingsboardByUniqueDatasource: () => buildListItemsThingsboardByUniqueDatasource,
|
|
579
|
+
buildMyioIngestionAuth: () => buildMyioIngestionAuth,
|
|
579
580
|
buildWaterReportCSV: () => buildWaterReportCSV,
|
|
580
581
|
buildWaterStoresCSV: () => buildWaterStoresCSV,
|
|
581
582
|
calcDeltaPercent: () => calcDeltaPercent,
|
|
582
583
|
classify: () => classify,
|
|
583
584
|
classifyWaterLabel: () => classifyWaterLabel,
|
|
584
585
|
classifyWaterLabels: () => classifyWaterLabels,
|
|
586
|
+
clearAllAuthCaches: () => clearAllAuthCaches,
|
|
585
587
|
createDateRangePicker: () => createDateRangePicker2,
|
|
586
588
|
createInputDateRangePickerInsideDIV: () => createInputDateRangePickerInsideDIV,
|
|
587
589
|
decodePayload: () => decodePayload,
|
|
@@ -603,6 +605,7 @@ __export(index_exports, {
|
|
|
603
605
|
formatTankHeadFromCm: () => formatTankHeadFromCm,
|
|
604
606
|
formatWaterByGroup: () => formatWaterByGroup,
|
|
605
607
|
formatWaterVolumeM3: () => formatWaterVolumeM3,
|
|
608
|
+
getAuthCacheStats: () => getAuthCacheStats,
|
|
606
609
|
getAvailableContexts: () => getAvailableContexts,
|
|
607
610
|
getDateRangeArray: () => getDateRangeArray,
|
|
608
611
|
getSaoPauloISOString: () => getSaoPauloISOString,
|
|
@@ -1371,6 +1374,135 @@ function buildListItemsThingsboardByUniqueDatasource(datasources, data) {
|
|
|
1371
1374
|
return items;
|
|
1372
1375
|
}
|
|
1373
1376
|
|
|
1377
|
+
// src/thingsboard/auth/buildMyioIngestionAuth.ts
|
|
1378
|
+
var globalCache = /* @__PURE__ */ new Map();
|
|
1379
|
+
function generateCacheKey(config) {
|
|
1380
|
+
return `${config.dataApiHost}:${config.clientId}:${config.clientSecret}`;
|
|
1381
|
+
}
|
|
1382
|
+
function buildMyioIngestionAuth(config) {
|
|
1383
|
+
const {
|
|
1384
|
+
dataApiHost,
|
|
1385
|
+
clientId,
|
|
1386
|
+
clientSecret,
|
|
1387
|
+
renewSkewSeconds = 60,
|
|
1388
|
+
retryBaseMs = 500,
|
|
1389
|
+
retryMaxAttempts = 3
|
|
1390
|
+
} = config;
|
|
1391
|
+
if (!dataApiHost || !clientId || !clientSecret) {
|
|
1392
|
+
throw new Error("dataApiHost, clientId, and clientSecret are required");
|
|
1393
|
+
}
|
|
1394
|
+
const authUrl = new URL(`${dataApiHost}/api/v1/auth`);
|
|
1395
|
+
const cacheKey = generateCacheKey(config);
|
|
1396
|
+
if (!globalCache.has(cacheKey)) {
|
|
1397
|
+
globalCache.set(cacheKey, {
|
|
1398
|
+
token: null,
|
|
1399
|
+
expiresAt: 0,
|
|
1400
|
+
inFlight: null
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
const cache = globalCache.get(cacheKey);
|
|
1404
|
+
function now() {
|
|
1405
|
+
return Date.now();
|
|
1406
|
+
}
|
|
1407
|
+
function aboutToExpire() {
|
|
1408
|
+
if (!cache.token) return true;
|
|
1409
|
+
const skewMs = renewSkewSeconds * 1e3;
|
|
1410
|
+
return now() >= cache.expiresAt - skewMs;
|
|
1411
|
+
}
|
|
1412
|
+
async function sleep(ms) {
|
|
1413
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1414
|
+
}
|
|
1415
|
+
async function requestNewToken() {
|
|
1416
|
+
const body = {
|
|
1417
|
+
client_id: clientId,
|
|
1418
|
+
client_secret: clientSecret
|
|
1419
|
+
};
|
|
1420
|
+
let attempt = 0;
|
|
1421
|
+
while (true) {
|
|
1422
|
+
try {
|
|
1423
|
+
const response = await fetch(authUrl, {
|
|
1424
|
+
method: "POST",
|
|
1425
|
+
headers: {
|
|
1426
|
+
"Content-Type": "application/json"
|
|
1427
|
+
},
|
|
1428
|
+
body: JSON.stringify(body)
|
|
1429
|
+
});
|
|
1430
|
+
if (!response.ok) {
|
|
1431
|
+
const text = await response.text().catch(() => "");
|
|
1432
|
+
throw new Error(
|
|
1433
|
+
`Auth failed: HTTP ${response.status} ${response.statusText} ${text}`
|
|
1434
|
+
);
|
|
1435
|
+
}
|
|
1436
|
+
const json = await response.json();
|
|
1437
|
+
if (!json || !json.access_token || !json.expires_in) {
|
|
1438
|
+
throw new Error("Auth response missing required fields (access_token, expires_in)");
|
|
1439
|
+
}
|
|
1440
|
+
cache.token = json.access_token;
|
|
1441
|
+
cache.expiresAt = now() + Number(json.expires_in) * 1e3;
|
|
1442
|
+
console.log(
|
|
1443
|
+
`[MyIOAuth] New token obtained for ${clientId}. Expires in ~${Math.round(Number(json.expires_in) / 60)} min`
|
|
1444
|
+
);
|
|
1445
|
+
return cache.token;
|
|
1446
|
+
} catch (error) {
|
|
1447
|
+
attempt++;
|
|
1448
|
+
console.warn(
|
|
1449
|
+
`[MyIOAuth] Error obtaining token (attempt ${attempt}/${retryMaxAttempts}):`,
|
|
1450
|
+
error instanceof Error ? error.message : error
|
|
1451
|
+
);
|
|
1452
|
+
if (attempt >= retryMaxAttempts) {
|
|
1453
|
+
throw error;
|
|
1454
|
+
}
|
|
1455
|
+
const backoff = retryBaseMs * Math.pow(2, attempt - 1);
|
|
1456
|
+
await sleep(backoff);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
async function getToken() {
|
|
1461
|
+
if (cache.inFlight) {
|
|
1462
|
+
return cache.inFlight;
|
|
1463
|
+
}
|
|
1464
|
+
if (aboutToExpire()) {
|
|
1465
|
+
cache.inFlight = requestNewToken().finally(() => {
|
|
1466
|
+
cache.inFlight = null;
|
|
1467
|
+
});
|
|
1468
|
+
return cache.inFlight;
|
|
1469
|
+
}
|
|
1470
|
+
return cache.token;
|
|
1471
|
+
}
|
|
1472
|
+
function getExpiryInfo() {
|
|
1473
|
+
return {
|
|
1474
|
+
expiresAt: cache.expiresAt,
|
|
1475
|
+
expiresInSeconds: Math.max(0, Math.floor((cache.expiresAt - now()) / 1e3))
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
function clearCache() {
|
|
1479
|
+
cache.token = null;
|
|
1480
|
+
cache.expiresAt = 0;
|
|
1481
|
+
cache.inFlight = null;
|
|
1482
|
+
}
|
|
1483
|
+
function isTokenValid() {
|
|
1484
|
+
if (!globalCache.has(cacheKey)) {
|
|
1485
|
+
return false;
|
|
1486
|
+
}
|
|
1487
|
+
return !aboutToExpire();
|
|
1488
|
+
}
|
|
1489
|
+
return {
|
|
1490
|
+
getToken,
|
|
1491
|
+
getExpiryInfo,
|
|
1492
|
+
clearCache,
|
|
1493
|
+
isTokenValid
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
function clearAllAuthCaches() {
|
|
1497
|
+
globalCache.clear();
|
|
1498
|
+
}
|
|
1499
|
+
function getAuthCacheStats() {
|
|
1500
|
+
return {
|
|
1501
|
+
totalCaches: globalCache.size,
|
|
1502
|
+
cacheKeys: Array.from(globalCache.keys())
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1374
1506
|
// src/utils/deviceType.js
|
|
1375
1507
|
function normalize(str) {
|
|
1376
1508
|
if (typeof str !== "string") return "";
|
|
@@ -10051,12 +10183,14 @@ async function openDemandModal(params) {
|
|
|
10051
10183
|
addNamespace,
|
|
10052
10184
|
averageByDay,
|
|
10053
10185
|
buildListItemsThingsboardByUniqueDatasource,
|
|
10186
|
+
buildMyioIngestionAuth,
|
|
10054
10187
|
buildWaterReportCSV,
|
|
10055
10188
|
buildWaterStoresCSV,
|
|
10056
10189
|
calcDeltaPercent,
|
|
10057
10190
|
classify,
|
|
10058
10191
|
classifyWaterLabel,
|
|
10059
10192
|
classifyWaterLabels,
|
|
10193
|
+
clearAllAuthCaches,
|
|
10060
10194
|
createDateRangePicker,
|
|
10061
10195
|
createInputDateRangePickerInsideDIV,
|
|
10062
10196
|
decodePayload,
|
|
@@ -10078,6 +10212,7 @@ async function openDemandModal(params) {
|
|
|
10078
10212
|
formatTankHeadFromCm,
|
|
10079
10213
|
formatWaterByGroup,
|
|
10080
10214
|
formatWaterVolumeM3,
|
|
10215
|
+
getAuthCacheStats,
|
|
10081
10216
|
getAvailableContexts,
|
|
10082
10217
|
getDateRangeArray,
|
|
10083
10218
|
getSaoPauloISOString,
|
package/dist/index.d.cts
CHANGED
|
@@ -345,6 +345,69 @@ interface ThingsBoardItem {
|
|
|
345
345
|
*/
|
|
346
346
|
declare function buildListItemsThingsboardByUniqueDatasource(datasources: any[], data: any[]): ThingsBoardItem[];
|
|
347
347
|
|
|
348
|
+
/**
|
|
349
|
+
* MyIO Ingestion Authentication Component
|
|
350
|
+
*
|
|
351
|
+
* Factory function that creates authentication instances with shared token caching
|
|
352
|
+
* based on credentials. Multiple instances with the same credentials will share
|
|
353
|
+
* the same token cache for efficiency.
|
|
354
|
+
*/
|
|
355
|
+
/**
|
|
356
|
+
* Authentication configuration parameters
|
|
357
|
+
*/
|
|
358
|
+
interface MyIOAuthConfig {
|
|
359
|
+
dataApiHost: string;
|
|
360
|
+
clientId: string;
|
|
361
|
+
clientSecret: string;
|
|
362
|
+
renewSkewSeconds?: number;
|
|
363
|
+
retryBaseMs?: number;
|
|
364
|
+
retryMaxAttempts?: number;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Authentication instance interface
|
|
368
|
+
*/
|
|
369
|
+
interface MyIOAuthInstance {
|
|
370
|
+
getToken(): Promise<string>;
|
|
371
|
+
getExpiryInfo(): {
|
|
372
|
+
expiresAt: number;
|
|
373
|
+
expiresInSeconds: number;
|
|
374
|
+
};
|
|
375
|
+
clearCache(): void;
|
|
376
|
+
isTokenValid(): boolean;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Create a MyIO Ingestion Authentication instance
|
|
380
|
+
*
|
|
381
|
+
* Multiple instances with the same credentials will share the same token cache,
|
|
382
|
+
* which is efficient and prevents unnecessary API calls.
|
|
383
|
+
*
|
|
384
|
+
* @param config Authentication configuration
|
|
385
|
+
* @returns Authentication instance
|
|
386
|
+
*
|
|
387
|
+
* @example
|
|
388
|
+
* ```typescript
|
|
389
|
+
* const auth = buildMyioIngestionAuth({
|
|
390
|
+
* dataApiHost: 'https://api.data.apps.myio-bas.com',
|
|
391
|
+
* clientId: 'your-client-id',
|
|
392
|
+
* clientSecret: 'your-client-secret'
|
|
393
|
+
* });
|
|
394
|
+
*
|
|
395
|
+
* const token = await auth.getToken();
|
|
396
|
+
* ```
|
|
397
|
+
*/
|
|
398
|
+
declare function buildMyioIngestionAuth(config: MyIOAuthConfig): MyIOAuthInstance;
|
|
399
|
+
/**
|
|
400
|
+
* Clear all cached tokens (useful for testing or logout scenarios)
|
|
401
|
+
*/
|
|
402
|
+
declare function clearAllAuthCaches(): void;
|
|
403
|
+
/**
|
|
404
|
+
* Get cache statistics (useful for debugging)
|
|
405
|
+
*/
|
|
406
|
+
declare function getAuthCacheStats(): {
|
|
407
|
+
totalCaches: number;
|
|
408
|
+
cacheKeys: string[];
|
|
409
|
+
};
|
|
410
|
+
|
|
348
411
|
/**
|
|
349
412
|
* Detects the device type based on the given name and context.
|
|
350
413
|
* Uses the specified detection context to identify device types.
|
|
@@ -1124,4 +1187,4 @@ interface DemandModalInstance {
|
|
|
1124
1187
|
*/
|
|
1125
1188
|
declare function openDemandModal(params: DemandModalParams): Promise<DemandModalInstance>;
|
|
1126
1189
|
|
|
1127
|
-
export { type CreateDateRangePickerOptions, type CreateInputDateRangePickerInsideDIVParams, type DateRangeControl, type DateRangeInputController, type DateRangeResult, type DemandModalInstance, type DemandModalParams, type DemandModalPdfConfig, type DemandModalStyles, MyIOChartModal, MyIODraggableCard, MyIOSelectionStore, MyIOSelectionStoreClass, type OpenDashboardPopupSettingsParams, type PersistResult, type SettingsError, type SettingsEvent, type StoreRow, type TbScope, type TelemetryFetcher, type TimedValue, type WaterRow, addDetectionContext, addNamespace, averageByDay, buildListItemsThingsboardByUniqueDatasource, buildWaterReportCSV, buildWaterStoresCSV, calcDeltaPercent, classify, classifyWaterLabel, classifyWaterLabels, createDateRangePicker, createInputDateRangePickerInsideDIV, decodePayload, decodePayloadBase64Xor, detectDeviceType, determineInterval, exportToCSV, exportToCSVAll, findValue, fmtPerc$1 as fmtPerc, fmtPerc as fmtPercLegacy, formatAllInSameUnit, formatAllInSameWaterUnit, formatDateForInput, formatDateToYMD, formatDateWithTimezoneOffset, formatEnergy, formatNumberReadable, formatTankHeadFromCm, formatWaterByGroup, formatWaterVolumeM3, getAvailableContexts, getDateRangeArray, getSaoPauloISOString, getSaoPauloISOStringFixed, getValueByDatakey, getValueByDatakeyLegacy, getWaterCategories, groupByDay, isWaterCategory, normalizeRecipients, numbers, openDashboardPopup, openDashboardPopupAllReport, openDashboardPopupEnergy, openDashboardPopupReport, openDashboardPopupSettings, openDemandModal, parseInputDateToDate, renderCardCompenteHeadOffice, renderCardComponent$1 as renderCardComponent, renderCardComponent as renderCardComponentEnhanced, renderCardComponentLegacy, renderCardComponentV2, strings, timeWindowFromInputYMD, toCSV, toFixedSafe };
|
|
1190
|
+
export { type CreateDateRangePickerOptions, type CreateInputDateRangePickerInsideDIVParams, type DateRangeControl, type DateRangeInputController, type DateRangeResult, type DemandModalInstance, type DemandModalParams, type DemandModalPdfConfig, type DemandModalStyles, type MyIOAuthConfig, type MyIOAuthInstance, MyIOChartModal, MyIODraggableCard, MyIOSelectionStore, MyIOSelectionStoreClass, type OpenDashboardPopupSettingsParams, type PersistResult, type SettingsError, type SettingsEvent, type StoreRow, type TbScope, type TelemetryFetcher, type TimedValue, type WaterRow, addDetectionContext, addNamespace, averageByDay, buildListItemsThingsboardByUniqueDatasource, buildMyioIngestionAuth, buildWaterReportCSV, buildWaterStoresCSV, calcDeltaPercent, classify, classifyWaterLabel, classifyWaterLabels, clearAllAuthCaches, createDateRangePicker, createInputDateRangePickerInsideDIV, decodePayload, decodePayloadBase64Xor, detectDeviceType, determineInterval, exportToCSV, exportToCSVAll, findValue, fmtPerc$1 as fmtPerc, fmtPerc as fmtPercLegacy, formatAllInSameUnit, formatAllInSameWaterUnit, formatDateForInput, formatDateToYMD, formatDateWithTimezoneOffset, formatEnergy, formatNumberReadable, formatTankHeadFromCm, formatWaterByGroup, formatWaterVolumeM3, getAuthCacheStats, getAvailableContexts, getDateRangeArray, getSaoPauloISOString, getSaoPauloISOStringFixed, getValueByDatakey, getValueByDatakeyLegacy, getWaterCategories, groupByDay, isWaterCategory, normalizeRecipients, numbers, openDashboardPopup, openDashboardPopupAllReport, openDashboardPopupEnergy, openDashboardPopupReport, openDashboardPopupSettings, openDemandModal, parseInputDateToDate, renderCardCompenteHeadOffice, renderCardComponent$1 as renderCardComponent, renderCardComponent as renderCardComponentEnhanced, renderCardComponentLegacy, renderCardComponentV2, strings, timeWindowFromInputYMD, toCSV, toFixedSafe };
|
package/dist/index.js
CHANGED
|
@@ -1303,6 +1303,135 @@ function buildListItemsThingsboardByUniqueDatasource(datasources, data) {
|
|
|
1303
1303
|
return items;
|
|
1304
1304
|
}
|
|
1305
1305
|
|
|
1306
|
+
// src/thingsboard/auth/buildMyioIngestionAuth.ts
|
|
1307
|
+
var globalCache = /* @__PURE__ */ new Map();
|
|
1308
|
+
function generateCacheKey(config) {
|
|
1309
|
+
return `${config.dataApiHost}:${config.clientId}:${config.clientSecret}`;
|
|
1310
|
+
}
|
|
1311
|
+
function buildMyioIngestionAuth(config) {
|
|
1312
|
+
const {
|
|
1313
|
+
dataApiHost,
|
|
1314
|
+
clientId,
|
|
1315
|
+
clientSecret,
|
|
1316
|
+
renewSkewSeconds = 60,
|
|
1317
|
+
retryBaseMs = 500,
|
|
1318
|
+
retryMaxAttempts = 3
|
|
1319
|
+
} = config;
|
|
1320
|
+
if (!dataApiHost || !clientId || !clientSecret) {
|
|
1321
|
+
throw new Error("dataApiHost, clientId, and clientSecret are required");
|
|
1322
|
+
}
|
|
1323
|
+
const authUrl = new URL(`${dataApiHost}/api/v1/auth`);
|
|
1324
|
+
const cacheKey = generateCacheKey(config);
|
|
1325
|
+
if (!globalCache.has(cacheKey)) {
|
|
1326
|
+
globalCache.set(cacheKey, {
|
|
1327
|
+
token: null,
|
|
1328
|
+
expiresAt: 0,
|
|
1329
|
+
inFlight: null
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
const cache = globalCache.get(cacheKey);
|
|
1333
|
+
function now() {
|
|
1334
|
+
return Date.now();
|
|
1335
|
+
}
|
|
1336
|
+
function aboutToExpire() {
|
|
1337
|
+
if (!cache.token) return true;
|
|
1338
|
+
const skewMs = renewSkewSeconds * 1e3;
|
|
1339
|
+
return now() >= cache.expiresAt - skewMs;
|
|
1340
|
+
}
|
|
1341
|
+
async function sleep(ms) {
|
|
1342
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1343
|
+
}
|
|
1344
|
+
async function requestNewToken() {
|
|
1345
|
+
const body = {
|
|
1346
|
+
client_id: clientId,
|
|
1347
|
+
client_secret: clientSecret
|
|
1348
|
+
};
|
|
1349
|
+
let attempt = 0;
|
|
1350
|
+
while (true) {
|
|
1351
|
+
try {
|
|
1352
|
+
const response = await fetch(authUrl, {
|
|
1353
|
+
method: "POST",
|
|
1354
|
+
headers: {
|
|
1355
|
+
"Content-Type": "application/json"
|
|
1356
|
+
},
|
|
1357
|
+
body: JSON.stringify(body)
|
|
1358
|
+
});
|
|
1359
|
+
if (!response.ok) {
|
|
1360
|
+
const text = await response.text().catch(() => "");
|
|
1361
|
+
throw new Error(
|
|
1362
|
+
`Auth failed: HTTP ${response.status} ${response.statusText} ${text}`
|
|
1363
|
+
);
|
|
1364
|
+
}
|
|
1365
|
+
const json = await response.json();
|
|
1366
|
+
if (!json || !json.access_token || !json.expires_in) {
|
|
1367
|
+
throw new Error("Auth response missing required fields (access_token, expires_in)");
|
|
1368
|
+
}
|
|
1369
|
+
cache.token = json.access_token;
|
|
1370
|
+
cache.expiresAt = now() + Number(json.expires_in) * 1e3;
|
|
1371
|
+
console.log(
|
|
1372
|
+
`[MyIOAuth] New token obtained for ${clientId}. Expires in ~${Math.round(Number(json.expires_in) / 60)} min`
|
|
1373
|
+
);
|
|
1374
|
+
return cache.token;
|
|
1375
|
+
} catch (error) {
|
|
1376
|
+
attempt++;
|
|
1377
|
+
console.warn(
|
|
1378
|
+
`[MyIOAuth] Error obtaining token (attempt ${attempt}/${retryMaxAttempts}):`,
|
|
1379
|
+
error instanceof Error ? error.message : error
|
|
1380
|
+
);
|
|
1381
|
+
if (attempt >= retryMaxAttempts) {
|
|
1382
|
+
throw error;
|
|
1383
|
+
}
|
|
1384
|
+
const backoff = retryBaseMs * Math.pow(2, attempt - 1);
|
|
1385
|
+
await sleep(backoff);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
async function getToken() {
|
|
1390
|
+
if (cache.inFlight) {
|
|
1391
|
+
return cache.inFlight;
|
|
1392
|
+
}
|
|
1393
|
+
if (aboutToExpire()) {
|
|
1394
|
+
cache.inFlight = requestNewToken().finally(() => {
|
|
1395
|
+
cache.inFlight = null;
|
|
1396
|
+
});
|
|
1397
|
+
return cache.inFlight;
|
|
1398
|
+
}
|
|
1399
|
+
return cache.token;
|
|
1400
|
+
}
|
|
1401
|
+
function getExpiryInfo() {
|
|
1402
|
+
return {
|
|
1403
|
+
expiresAt: cache.expiresAt,
|
|
1404
|
+
expiresInSeconds: Math.max(0, Math.floor((cache.expiresAt - now()) / 1e3))
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
function clearCache() {
|
|
1408
|
+
cache.token = null;
|
|
1409
|
+
cache.expiresAt = 0;
|
|
1410
|
+
cache.inFlight = null;
|
|
1411
|
+
}
|
|
1412
|
+
function isTokenValid() {
|
|
1413
|
+
if (!globalCache.has(cacheKey)) {
|
|
1414
|
+
return false;
|
|
1415
|
+
}
|
|
1416
|
+
return !aboutToExpire();
|
|
1417
|
+
}
|
|
1418
|
+
return {
|
|
1419
|
+
getToken,
|
|
1420
|
+
getExpiryInfo,
|
|
1421
|
+
clearCache,
|
|
1422
|
+
isTokenValid
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
function clearAllAuthCaches() {
|
|
1426
|
+
globalCache.clear();
|
|
1427
|
+
}
|
|
1428
|
+
function getAuthCacheStats() {
|
|
1429
|
+
return {
|
|
1430
|
+
totalCaches: globalCache.size,
|
|
1431
|
+
cacheKeys: Array.from(globalCache.keys())
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1306
1435
|
// src/utils/deviceType.js
|
|
1307
1436
|
function normalize(str) {
|
|
1308
1437
|
if (typeof str !== "string") return "";
|
|
@@ -9982,12 +10111,14 @@ export {
|
|
|
9982
10111
|
addNamespace,
|
|
9983
10112
|
averageByDay,
|
|
9984
10113
|
buildListItemsThingsboardByUniqueDatasource,
|
|
10114
|
+
buildMyioIngestionAuth,
|
|
9985
10115
|
buildWaterReportCSV,
|
|
9986
10116
|
buildWaterStoresCSV,
|
|
9987
10117
|
calcDeltaPercent,
|
|
9988
10118
|
classify,
|
|
9989
10119
|
classifyWaterLabel,
|
|
9990
10120
|
classifyWaterLabels,
|
|
10121
|
+
clearAllAuthCaches,
|
|
9991
10122
|
createDateRangePicker2 as createDateRangePicker,
|
|
9992
10123
|
createInputDateRangePickerInsideDIV,
|
|
9993
10124
|
decodePayload,
|
|
@@ -10009,6 +10140,7 @@ export {
|
|
|
10009
10140
|
formatTankHeadFromCm,
|
|
10010
10141
|
formatWaterByGroup,
|
|
10011
10142
|
formatWaterVolumeM3,
|
|
10143
|
+
getAuthCacheStats,
|
|
10012
10144
|
getAvailableContexts,
|
|
10013
10145
|
getDateRangeArray,
|
|
10014
10146
|
getSaoPauloISOString,
|
|
@@ -1309,6 +1309,135 @@
|
|
|
1309
1309
|
return items;
|
|
1310
1310
|
}
|
|
1311
1311
|
|
|
1312
|
+
// src/thingsboard/auth/buildMyioIngestionAuth.ts
|
|
1313
|
+
var globalCache = /* @__PURE__ */ new Map();
|
|
1314
|
+
function generateCacheKey(config) {
|
|
1315
|
+
return `${config.dataApiHost}:${config.clientId}:${config.clientSecret}`;
|
|
1316
|
+
}
|
|
1317
|
+
function buildMyioIngestionAuth(config) {
|
|
1318
|
+
const {
|
|
1319
|
+
dataApiHost,
|
|
1320
|
+
clientId,
|
|
1321
|
+
clientSecret,
|
|
1322
|
+
renewSkewSeconds = 60,
|
|
1323
|
+
retryBaseMs = 500,
|
|
1324
|
+
retryMaxAttempts = 3
|
|
1325
|
+
} = config;
|
|
1326
|
+
if (!dataApiHost || !clientId || !clientSecret) {
|
|
1327
|
+
throw new Error("dataApiHost, clientId, and clientSecret are required");
|
|
1328
|
+
}
|
|
1329
|
+
const authUrl = new URL(`${dataApiHost}/api/v1/auth`);
|
|
1330
|
+
const cacheKey = generateCacheKey(config);
|
|
1331
|
+
if (!globalCache.has(cacheKey)) {
|
|
1332
|
+
globalCache.set(cacheKey, {
|
|
1333
|
+
token: null,
|
|
1334
|
+
expiresAt: 0,
|
|
1335
|
+
inFlight: null
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
const cache = globalCache.get(cacheKey);
|
|
1339
|
+
function now() {
|
|
1340
|
+
return Date.now();
|
|
1341
|
+
}
|
|
1342
|
+
function aboutToExpire() {
|
|
1343
|
+
if (!cache.token) return true;
|
|
1344
|
+
const skewMs = renewSkewSeconds * 1e3;
|
|
1345
|
+
return now() >= cache.expiresAt - skewMs;
|
|
1346
|
+
}
|
|
1347
|
+
async function sleep(ms) {
|
|
1348
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1349
|
+
}
|
|
1350
|
+
async function requestNewToken() {
|
|
1351
|
+
const body = {
|
|
1352
|
+
client_id: clientId,
|
|
1353
|
+
client_secret: clientSecret
|
|
1354
|
+
};
|
|
1355
|
+
let attempt = 0;
|
|
1356
|
+
while (true) {
|
|
1357
|
+
try {
|
|
1358
|
+
const response = await fetch(authUrl, {
|
|
1359
|
+
method: "POST",
|
|
1360
|
+
headers: {
|
|
1361
|
+
"Content-Type": "application/json"
|
|
1362
|
+
},
|
|
1363
|
+
body: JSON.stringify(body)
|
|
1364
|
+
});
|
|
1365
|
+
if (!response.ok) {
|
|
1366
|
+
const text = await response.text().catch(() => "");
|
|
1367
|
+
throw new Error(
|
|
1368
|
+
`Auth failed: HTTP ${response.status} ${response.statusText} ${text}`
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
const json = await response.json();
|
|
1372
|
+
if (!json || !json.access_token || !json.expires_in) {
|
|
1373
|
+
throw new Error("Auth response missing required fields (access_token, expires_in)");
|
|
1374
|
+
}
|
|
1375
|
+
cache.token = json.access_token;
|
|
1376
|
+
cache.expiresAt = now() + Number(json.expires_in) * 1e3;
|
|
1377
|
+
console.log(
|
|
1378
|
+
`[MyIOAuth] New token obtained for ${clientId}. Expires in ~${Math.round(Number(json.expires_in) / 60)} min`
|
|
1379
|
+
);
|
|
1380
|
+
return cache.token;
|
|
1381
|
+
} catch (error) {
|
|
1382
|
+
attempt++;
|
|
1383
|
+
console.warn(
|
|
1384
|
+
`[MyIOAuth] Error obtaining token (attempt ${attempt}/${retryMaxAttempts}):`,
|
|
1385
|
+
error instanceof Error ? error.message : error
|
|
1386
|
+
);
|
|
1387
|
+
if (attempt >= retryMaxAttempts) {
|
|
1388
|
+
throw error;
|
|
1389
|
+
}
|
|
1390
|
+
const backoff = retryBaseMs * Math.pow(2, attempt - 1);
|
|
1391
|
+
await sleep(backoff);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
async function getToken() {
|
|
1396
|
+
if (cache.inFlight) {
|
|
1397
|
+
return cache.inFlight;
|
|
1398
|
+
}
|
|
1399
|
+
if (aboutToExpire()) {
|
|
1400
|
+
cache.inFlight = requestNewToken().finally(() => {
|
|
1401
|
+
cache.inFlight = null;
|
|
1402
|
+
});
|
|
1403
|
+
return cache.inFlight;
|
|
1404
|
+
}
|
|
1405
|
+
return cache.token;
|
|
1406
|
+
}
|
|
1407
|
+
function getExpiryInfo() {
|
|
1408
|
+
return {
|
|
1409
|
+
expiresAt: cache.expiresAt,
|
|
1410
|
+
expiresInSeconds: Math.max(0, Math.floor((cache.expiresAt - now()) / 1e3))
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
function clearCache() {
|
|
1414
|
+
cache.token = null;
|
|
1415
|
+
cache.expiresAt = 0;
|
|
1416
|
+
cache.inFlight = null;
|
|
1417
|
+
}
|
|
1418
|
+
function isTokenValid() {
|
|
1419
|
+
if (!globalCache.has(cacheKey)) {
|
|
1420
|
+
return false;
|
|
1421
|
+
}
|
|
1422
|
+
return !aboutToExpire();
|
|
1423
|
+
}
|
|
1424
|
+
return {
|
|
1425
|
+
getToken,
|
|
1426
|
+
getExpiryInfo,
|
|
1427
|
+
clearCache,
|
|
1428
|
+
isTokenValid
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
function clearAllAuthCaches() {
|
|
1432
|
+
globalCache.clear();
|
|
1433
|
+
}
|
|
1434
|
+
function getAuthCacheStats() {
|
|
1435
|
+
return {
|
|
1436
|
+
totalCaches: globalCache.size,
|
|
1437
|
+
cacheKeys: Array.from(globalCache.keys())
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1312
1441
|
// src/utils/deviceType.js
|
|
1313
1442
|
function normalize(str) {
|
|
1314
1443
|
if (typeof str !== "string") return "";
|
|
@@ -9971,12 +10100,14 @@
|
|
|
9971
10100
|
exports.addNamespace = addNamespace;
|
|
9972
10101
|
exports.averageByDay = averageByDay;
|
|
9973
10102
|
exports.buildListItemsThingsboardByUniqueDatasource = buildListItemsThingsboardByUniqueDatasource;
|
|
10103
|
+
exports.buildMyioIngestionAuth = buildMyioIngestionAuth;
|
|
9974
10104
|
exports.buildWaterReportCSV = buildWaterReportCSV;
|
|
9975
10105
|
exports.buildWaterStoresCSV = buildWaterStoresCSV;
|
|
9976
10106
|
exports.calcDeltaPercent = calcDeltaPercent;
|
|
9977
10107
|
exports.classify = classify;
|
|
9978
10108
|
exports.classifyWaterLabel = classifyWaterLabel;
|
|
9979
10109
|
exports.classifyWaterLabels = classifyWaterLabels;
|
|
10110
|
+
exports.clearAllAuthCaches = clearAllAuthCaches;
|
|
9980
10111
|
exports.createDateRangePicker = createDateRangePicker2;
|
|
9981
10112
|
exports.createInputDateRangePickerInsideDIV = createInputDateRangePickerInsideDIV;
|
|
9982
10113
|
exports.decodePayload = decodePayload;
|
|
@@ -9998,6 +10129,7 @@
|
|
|
9998
10129
|
exports.formatTankHeadFromCm = formatTankHeadFromCm;
|
|
9999
10130
|
exports.formatWaterByGroup = formatWaterByGroup;
|
|
10000
10131
|
exports.formatWaterVolumeM3 = formatWaterVolumeM3;
|
|
10132
|
+
exports.getAuthCacheStats = getAuthCacheStats;
|
|
10001
10133
|
exports.getAvailableContexts = getAvailableContexts;
|
|
10002
10134
|
exports.getDateRangeArray = getDateRangeArray;
|
|
10003
10135
|
exports.getSaoPauloISOString = getSaoPauloISOString;
|