@mp-consulting/homebridge-daikin-cloud 1.5.1 → 1.6.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/README.md +40 -27
- package/config.schema.json +66 -12
- package/dist/src/accessories/air-conditioning-accessory.js +1 -1
- package/dist/src/accessories/air-conditioning-accessory.js.map +1 -1
- package/dist/src/accessories/altherma-accessory.js +1 -1
- package/dist/src/accessories/altherma-accessory.js.map +1 -1
- package/dist/src/accessories/base-accessory.d.ts +5 -4
- package/dist/src/accessories/base-accessory.d.ts.map +1 -1
- package/dist/src/accessories/base-accessory.js +9 -10
- package/dist/src/accessories/base-accessory.js.map +1 -1
- package/dist/src/api/daikin-api.d.ts +22 -6
- package/dist/src/api/daikin-api.d.ts.map +1 -1
- package/dist/src/api/daikin-api.js +49 -23
- package/dist/src/api/daikin-api.js.map +1 -1
- package/dist/src/api/daikin-controller.d.ts +4 -48
- package/dist/src/api/daikin-controller.d.ts.map +1 -1
- package/dist/src/api/daikin-controller.js +14 -84
- package/dist/src/api/daikin-controller.js.map +1 -1
- package/dist/src/api/daikin-mobile-oauth.d.ts +11 -40
- package/dist/src/api/daikin-mobile-oauth.d.ts.map +1 -1
- package/dist/src/api/daikin-mobile-oauth.js +36 -160
- package/dist/src/api/daikin-mobile-oauth.js.map +1 -1
- package/dist/src/api/daikin-oauth.d.ts +10 -37
- package/dist/src/api/daikin-oauth.d.ts.map +1 -1
- package/dist/src/api/daikin-oauth.js +36 -166
- package/dist/src/api/daikin-oauth.js.map +1 -1
- package/dist/src/api/daikin-schemas.d.ts +20 -22
- package/dist/src/api/daikin-schemas.d.ts.map +1 -1
- package/dist/src/api/daikin-schemas.js +17 -49
- package/dist/src/api/daikin-schemas.js.map +1 -1
- package/dist/src/api/daikin-types.d.ts +11 -23
- package/dist/src/api/daikin-types.d.ts.map +1 -1
- package/dist/src/api/daikin-types.js.map +1 -1
- package/dist/src/api/daikin-websocket.d.ts +4 -5
- package/dist/src/api/daikin-websocket.d.ts.map +1 -1
- package/dist/src/api/daikin-websocket.js +62 -34
- package/dist/src/api/daikin-websocket.js.map +1 -1
- package/dist/src/api/http-defaults.d.ts +3 -2
- package/dist/src/api/http-defaults.d.ts.map +1 -1
- package/dist/src/api/http-defaults.js.map +1 -1
- package/dist/src/api/http-transport.d.ts +14 -2
- package/dist/src/api/http-transport.d.ts.map +1 -1
- package/dist/src/api/http-transport.js +32 -9
- package/dist/src/api/http-transport.js.map +1 -1
- package/dist/src/api/token-client.d.ts +82 -0
- package/dist/src/api/token-client.d.ts.map +1 -0
- package/dist/src/api/token-client.js +188 -0
- package/dist/src/api/token-client.js.map +1 -0
- package/dist/src/api/token-storage.d.ts +6 -0
- package/dist/src/api/token-storage.d.ts.map +1 -1
- package/dist/src/api/token-storage.js +27 -1
- package/dist/src/api/token-storage.js.map +1 -1
- package/dist/src/config/config-manager.d.ts +69 -125
- package/dist/src/config/config-manager.d.ts.map +1 -1
- package/dist/src/config/config-manager.js +288 -225
- package/dist/src/config/config-manager.js.map +1 -1
- package/dist/src/constants/api.constants.d.ts +6 -0
- package/dist/src/constants/api.constants.d.ts.map +1 -1
- package/dist/src/constants/api.constants.js +7 -1
- package/dist/src/constants/api.constants.js.map +1 -1
- package/dist/src/constants/auth.constants.d.ts +11 -0
- package/dist/src/constants/auth.constants.d.ts.map +1 -1
- package/dist/src/constants/auth.constants.js +12 -1
- package/dist/src/constants/auth.constants.js.map +1 -1
- package/dist/src/constants/device.constants.d.ts +1 -7
- package/dist/src/constants/device.constants.d.ts.map +1 -1
- package/dist/src/constants/device.constants.js +2 -11
- package/dist/src/constants/device.constants.js.map +1 -1
- package/dist/src/constants/time.constants.d.ts +0 -2
- package/dist/src/constants/time.constants.d.ts.map +1 -1
- package/dist/src/constants/time.constants.js +1 -3
- package/dist/src/constants/time.constants.js.map +1 -1
- package/dist/src/device/accessory-factory.d.ts +0 -5
- package/dist/src/device/accessory-factory.d.ts.map +1 -1
- package/dist/src/device/accessory-factory.js +0 -7
- package/dist/src/device/accessory-factory.js.map +1 -1
- package/dist/src/device/capability-detector.d.ts +9 -1
- package/dist/src/device/capability-detector.d.ts.map +1 -1
- package/dist/src/device/capability-detector.js +32 -28
- package/dist/src/device/capability-detector.js.map +1 -1
- package/dist/src/device/capability-docs.d.ts.map +1 -1
- package/dist/src/device/capability-docs.js +3 -0
- package/dist/src/device/capability-docs.js.map +1 -1
- package/dist/src/device/data-paths.d.ts +18 -0
- package/dist/src/device/data-paths.d.ts.map +1 -0
- package/dist/src/device/data-paths.js +20 -0
- package/dist/src/device/data-paths.js.map +1 -0
- package/dist/src/device/profiles/device-profile.d.ts +0 -5
- package/dist/src/device/profiles/device-profile.d.ts.map +1 -1
- package/dist/src/device/profiles/device-profile.js +0 -21
- package/dist/src/device/profiles/device-profile.js.map +1 -1
- package/dist/src/features/base-feature.d.ts +53 -9
- package/dist/src/features/base-feature.d.ts.map +1 -1
- package/dist/src/features/base-feature.js +93 -40
- package/dist/src/features/base-feature.js.map +1 -1
- package/dist/src/features/feature-manager.d.ts +27 -15
- package/dist/src/features/feature-manager.d.ts.map +1 -1
- package/dist/src/features/feature-manager.js +27 -24
- package/dist/src/features/feature-manager.js.map +1 -1
- package/dist/src/features/index.d.ts +1 -0
- package/dist/src/features/index.d.ts.map +1 -1
- package/dist/src/features/index.js +1 -0
- package/dist/src/features/index.js.map +1 -1
- package/dist/src/features/modes/auto-fan-mode.feature.d.ts +16 -10
- package/dist/src/features/modes/auto-fan-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/auto-fan-mode.feature.js +16 -40
- package/dist/src/features/modes/auto-fan-mode.feature.js.map +1 -1
- package/dist/src/features/modes/dry-operation-mode.feature.d.ts +12 -9
- package/dist/src/features/modes/dry-operation-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/dry-operation-mode.feature.js +11 -30
- package/dist/src/features/modes/dry-operation-mode.feature.js.map +1 -1
- package/dist/src/features/modes/econo-mode.feature.d.ts +12 -9
- package/dist/src/features/modes/econo-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/econo-mode.feature.js +11 -31
- package/dist/src/features/modes/econo-mode.feature.js.map +1 -1
- package/dist/src/features/modes/fan-only-operation-mode.feature.d.ts +12 -9
- package/dist/src/features/modes/fan-only-operation-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/fan-only-operation-mode.feature.js +11 -30
- package/dist/src/features/modes/fan-only-operation-mode.feature.js.map +1 -1
- package/dist/src/features/modes/firmware-update.feature.d.ts +7 -6
- package/dist/src/features/modes/firmware-update.feature.d.ts.map +1 -1
- package/dist/src/features/modes/firmware-update.feature.js +15 -22
- package/dist/src/features/modes/firmware-update.feature.js.map +1 -1
- package/dist/src/features/modes/holiday-mode.feature.d.ts +2 -1
- package/dist/src/features/modes/holiday-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/holiday-mode.feature.js +5 -15
- package/dist/src/features/modes/holiday-mode.feature.js.map +1 -1
- package/dist/src/features/modes/indoor-silent-mode.feature.d.ts +15 -12
- package/dist/src/features/modes/indoor-silent-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/indoor-silent-mode.feature.js +15 -39
- package/dist/src/features/modes/indoor-silent-mode.feature.js.map +1 -1
- package/dist/src/features/modes/oscillation-mode.feature.d.ts +5 -6
- package/dist/src/features/modes/oscillation-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/oscillation-mode.feature.js +14 -36
- package/dist/src/features/modes/oscillation-mode.feature.js.map +1 -1
- package/dist/src/features/modes/outdoor-silent-mode.feature.d.ts +12 -9
- package/dist/src/features/modes/outdoor-silent-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/outdoor-silent-mode.feature.js +11 -31
- package/dist/src/features/modes/outdoor-silent-mode.feature.js.map +1 -1
- package/dist/src/features/modes/powerful-mode.feature.d.ts +14 -10
- package/dist/src/features/modes/powerful-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/powerful-mode.feature.js +13 -32
- package/dist/src/features/modes/powerful-mode.feature.js.map +1 -1
- package/dist/src/features/modes/streamer-mode.feature.d.ts +12 -9
- package/dist/src/features/modes/streamer-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/streamer-mode.feature.js +11 -31
- package/dist/src/features/modes/streamer-mode.feature.js.map +1 -1
- package/dist/src/features/on-off-data-point-feature.d.ts +43 -0
- package/dist/src/features/on-off-data-point-feature.d.ts.map +1 -0
- package/dist/src/features/on-off-data-point-feature.js +49 -0
- package/dist/src/features/on-off-data-point-feature.js.map +1 -0
- package/dist/src/platform.d.ts +56 -9
- package/dist/src/platform.d.ts.map +1 -1
- package/dist/src/platform.js +248 -128
- package/dist/src/platform.js.map +1 -1
- package/dist/src/services/climate-control/fan-speed.d.ts +45 -0
- package/dist/src/services/climate-control/fan-speed.d.ts.map +1 -0
- package/dist/src/services/climate-control/fan-speed.js +104 -0
- package/dist/src/services/climate-control/fan-speed.js.map +1 -0
- package/dist/src/services/climate-control/separate-fan.service.d.ts +45 -0
- package/dist/src/services/climate-control/separate-fan.service.d.ts.map +1 -0
- package/dist/src/services/climate-control/separate-fan.service.js +82 -0
- package/dist/src/services/climate-control/separate-fan.service.js.map +1 -0
- package/dist/src/services/climate-control/setpoint-resolver.d.ts +68 -0
- package/dist/src/services/climate-control/setpoint-resolver.d.ts.map +1 -0
- package/dist/src/services/climate-control/setpoint-resolver.js +170 -0
- package/dist/src/services/climate-control/setpoint-resolver.js.map +1 -0
- package/dist/src/services/climate-control/swing-controller.d.ts +28 -0
- package/dist/src/services/climate-control/swing-controller.d.ts.map +1 -0
- package/dist/src/services/climate-control/swing-controller.js +65 -0
- package/dist/src/services/climate-control/swing-controller.js.map +1 -0
- package/dist/src/services/climate-control.service.d.ts +39 -48
- package/dist/src/services/climate-control.service.d.ts.map +1 -1
- package/dist/src/services/climate-control.service.js +223 -554
- package/dist/src/services/climate-control.service.js.map +1 -1
- package/dist/src/services/hot-water-tank.service.d.ts +17 -8
- package/dist/src/services/hot-water-tank.service.d.ts.map +1 -1
- package/dist/src/services/hot-water-tank.service.js +102 -157
- package/dist/src/services/hot-water-tank.service.js.map +1 -1
- package/dist/src/types/daikin-enums.d.ts +0 -5
- package/dist/src/types/daikin-enums.d.ts.map +1 -1
- package/dist/src/types/daikin-enums.js +1 -7
- package/dist/src/types/daikin-enums.js.map +1 -1
- package/dist/src/types/device-capabilities.d.ts +1 -0
- package/dist/src/types/device-capabilities.d.ts.map +1 -1
- package/dist/src/utils/errors.d.ts +9 -0
- package/dist/src/utils/errors.d.ts.map +1 -0
- package/dist/src/utils/errors.js +25 -0
- package/dist/src/utils/errors.js.map +1 -0
- package/dist/src/utils/hap-write.d.ts +22 -0
- package/dist/src/utils/hap-write.d.ts.map +1 -0
- package/dist/src/utils/hap-write.js +33 -0
- package/dist/src/utils/hap-write.js.map +1 -0
- package/dist/src/utils/sleep.d.ts +5 -0
- package/dist/src/utils/sleep.d.ts.map +1 -0
- package/dist/src/utils/sleep.js +10 -0
- package/dist/src/utils/sleep.js.map +1 -0
- package/homebridge-ui/README.md +25 -2
- package/homebridge-ui/public/index.html +19 -17
- package/homebridge-ui/public/lib/kit.css +1 -1
- package/homebridge-ui/public/script.js +95 -32
- package/homebridge-ui/public/styles.css +109 -0
- package/homebridge-ui/server.js +321 -125
- package/package.json +14 -15
- package/dist/src/utils/log-context.d.ts +0 -105
- package/dist/src/utils/log-context.d.ts.map +0 -1
- package/dist/src/utils/log-context.js +0 -206
- package/dist/src/utils/log-context.js.map +0 -1
- package/dist/src/utils/update-mapper.d.ts +0 -59
- package/dist/src/utils/update-mapper.d.ts.map +0 -1
- package/dist/src/utils/update-mapper.js +0 -192
- package/dist/src/utils/update-mapper.js.map +0 -1
|
@@ -23,7 +23,7 @@ const Utils = {
|
|
|
23
23
|
|
|
24
24
|
escapeHtml(text) {
|
|
25
25
|
const div = document.createElement('div');
|
|
26
|
-
div.textContent = text;
|
|
26
|
+
div.textContent = text === null || text === undefined ? '' : String(text);
|
|
27
27
|
return div.innerHTML;
|
|
28
28
|
},
|
|
29
29
|
|
|
@@ -379,7 +379,7 @@ const Wizard = {
|
|
|
379
379
|
try {
|
|
380
380
|
const validation = await homebridge.request('/config/validate', config);
|
|
381
381
|
if (!validation.valid) {
|
|
382
|
-
El.validationErrors.innerHTML = validation.errors.map(e => `<div>${e}</div>`).join('');
|
|
382
|
+
El.validationErrors.innerHTML = validation.errors.map(e => `<div>${Utils.escapeHtml(e)}</div>`).join('');
|
|
383
383
|
DOM.show(El.validationErrors);
|
|
384
384
|
UI.hideLoading();
|
|
385
385
|
State.isValidating = false;
|
|
@@ -401,7 +401,7 @@ const Wizard = {
|
|
|
401
401
|
Polling.start();
|
|
402
402
|
this.goToStep(2);
|
|
403
403
|
} catch (error) {
|
|
404
|
-
El.validationErrors.innerHTML = `<div>${error.message}</div>`;
|
|
404
|
+
El.validationErrors.innerHTML = `<div>${Utils.escapeHtml(error.message)}</div>`;
|
|
405
405
|
DOM.show(El.validationErrors);
|
|
406
406
|
}
|
|
407
407
|
|
|
@@ -563,6 +563,37 @@ const Config = {
|
|
|
563
563
|
},
|
|
564
564
|
};
|
|
565
565
|
|
|
566
|
+
// ============================================================================
|
|
567
|
+
// Device List Request (shared)
|
|
568
|
+
// ============================================================================
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Settings and Devices both need /devices/list on page load. Share one
|
|
572
|
+
* in-flight request so the page spends at most one API call; an explicit
|
|
573
|
+
* refresh bypasses both this and the server-side cache.
|
|
574
|
+
*/
|
|
575
|
+
const DeviceList = {
|
|
576
|
+
_inFlight: null,
|
|
577
|
+
_mode: null,
|
|
578
|
+
|
|
579
|
+
fetch({ refresh = false } = {}) {
|
|
580
|
+
const mode = AuthMode.current;
|
|
581
|
+
if (!refresh && this._inFlight && this._mode === mode) {
|
|
582
|
+
return this._inFlight;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const request = homebridge.request('/devices/list', { mode, refresh })
|
|
586
|
+
.finally(() => {
|
|
587
|
+
if (this._inFlight === request) {
|
|
588
|
+
this._inFlight = null;
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
this._inFlight = request;
|
|
592
|
+
this._mode = mode;
|
|
593
|
+
return request;
|
|
594
|
+
},
|
|
595
|
+
};
|
|
596
|
+
|
|
566
597
|
// ============================================================================
|
|
567
598
|
// Settings Module
|
|
568
599
|
// ============================================================================
|
|
@@ -571,6 +602,7 @@ const Settings = {
|
|
|
571
602
|
devices: [],
|
|
572
603
|
excludedIds: [],
|
|
573
604
|
saveTimeout: null,
|
|
605
|
+
toggleListenerAttached: false,
|
|
574
606
|
|
|
575
607
|
// Feature switches that honour the legacy "Show Extra Features" flag as their default.
|
|
576
608
|
FEATURE_KEYS: [
|
|
@@ -642,10 +674,12 @@ const Settings = {
|
|
|
642
674
|
populateForm(config) {
|
|
643
675
|
const showAllLegacy = config.showExtraFeatures === true;
|
|
644
676
|
|
|
677
|
+
// Same rule as the plugin (resolveFeatures in config-manager): only a boolean
|
|
678
|
+
// overrides; absent or null follows showExtraFeatures.
|
|
645
679
|
this.FEATURE_KEYS.forEach(key => {
|
|
646
680
|
const el = $id(key);
|
|
647
681
|
if (el) {
|
|
648
|
-
el.checked = key
|
|
682
|
+
el.checked = typeof config[key] === 'boolean' ? config[key] : showAllLegacy;
|
|
649
683
|
}
|
|
650
684
|
});
|
|
651
685
|
|
|
@@ -693,11 +727,11 @@ const Settings = {
|
|
|
693
727
|
DOM.hide(empty);
|
|
694
728
|
|
|
695
729
|
try {
|
|
696
|
-
const result = await
|
|
730
|
+
const result = await DeviceList.fetch();
|
|
697
731
|
DOM.hide(loading);
|
|
698
732
|
|
|
699
733
|
if (!result.success) {
|
|
700
|
-
empty.innerHTML = `<p class="mb-0">${result.message || 'No devices found. Authenticate first to see your devices.'}</p>`;
|
|
734
|
+
empty.innerHTML = `<p class="mb-0">${Utils.escapeHtml(result.message || 'No devices found. Authenticate first to see your devices.')}</p>`;
|
|
701
735
|
DOM.show(empty);
|
|
702
736
|
return;
|
|
703
737
|
}
|
|
@@ -711,18 +745,21 @@ const Settings = {
|
|
|
711
745
|
this.devices = result.devices;
|
|
712
746
|
list.innerHTML = this.devices.map((d, i) => this.renderDeviceToggle(d, i)).join('');
|
|
713
747
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
748
|
+
if (!this.toggleListenerAttached) {
|
|
749
|
+
this.toggleListenerAttached = true;
|
|
750
|
+
list.addEventListener('change', (e) => {
|
|
751
|
+
if (e.target.classList.contains('device-visibility-toggle')) {
|
|
752
|
+
const idx = parseInt(e.target.dataset.index, 10);
|
|
753
|
+
const device = this.devices[idx];
|
|
754
|
+
if (device) {
|
|
755
|
+
this.toggleDevice(device.id, e.target.checked, idx);
|
|
756
|
+
}
|
|
720
757
|
}
|
|
721
|
-
}
|
|
722
|
-
}
|
|
758
|
+
});
|
|
759
|
+
}
|
|
723
760
|
} catch (error) {
|
|
724
761
|
DOM.hide(loading);
|
|
725
|
-
empty.innerHTML = `<p class="mb-0">Failed to load: ${error.message}</p>`;
|
|
762
|
+
empty.innerHTML = `<p class="mb-0">Failed to load: ${Utils.escapeHtml(error.message)}</p>`;
|
|
726
763
|
DOM.show(empty);
|
|
727
764
|
}
|
|
728
765
|
},
|
|
@@ -730,12 +767,12 @@ const Settings = {
|
|
|
730
767
|
renderDeviceToggle(device, index) {
|
|
731
768
|
const visible = !this.excludedIds.includes(device.id);
|
|
732
769
|
return `
|
|
733
|
-
<div class="list-group-item d-flex justify-content-between align-items-center">
|
|
734
|
-
<div>
|
|
770
|
+
<div class="list-group-item d-flex justify-content-between align-items-center gap-3">
|
|
771
|
+
<div class="min-w-0">
|
|
735
772
|
<div class="fw-medium">${Utils.escapeHtml(device.name)}</div>
|
|
736
|
-
<small class="text-muted">${Utils.escapeHtml(device.id)}</small>
|
|
773
|
+
<small class="device-id text-muted">${Utils.escapeHtml(device.id)}</small>
|
|
737
774
|
</div>
|
|
738
|
-
<div class="d-flex align-items-center gap-2">
|
|
775
|
+
<div class="d-flex align-items-center gap-2 flex-shrink-0">
|
|
739
776
|
<span class="device-toggle-label small ${visible ? 'visible' : 'hidden-label'}" data-label-index="${index}">${visible ? 'Visible' : 'Hidden'}</span>
|
|
740
777
|
<div class="form-check form-switch mb-0">
|
|
741
778
|
<input type="checkbox" class="form-check-input device-visibility-toggle" role="switch" data-index="${index}" ${visible ? 'checked' : ''}>
|
|
@@ -816,14 +853,14 @@ const Settings = {
|
|
|
816
853
|
// ============================================================================
|
|
817
854
|
|
|
818
855
|
const Devices = {
|
|
819
|
-
async load() {
|
|
856
|
+
async load({ refresh = false } = {}) {
|
|
820
857
|
DOM.show(El.devicesLoading);
|
|
821
858
|
El.devicesList.innerHTML = '';
|
|
822
859
|
DOM.hide(El.devicesEmpty);
|
|
823
860
|
DOM.hide(El.devicesError);
|
|
824
861
|
|
|
825
862
|
try {
|
|
826
|
-
const result = await
|
|
863
|
+
const result = await DeviceList.fetch({ refresh });
|
|
827
864
|
DOM.hide(El.devicesLoading);
|
|
828
865
|
|
|
829
866
|
if (!result.success) {
|
|
@@ -862,7 +899,7 @@ const Devices = {
|
|
|
862
899
|
const powerOn = device.powerState === 'on';
|
|
863
900
|
const mode = device.operationMode ? Utils.capitalize(device.operationMode) : '-';
|
|
864
901
|
const features = device.features?.length
|
|
865
|
-
? device.features.map(f => `<span class="badge bg-secondary bg-opacity-50
|
|
902
|
+
? device.features.map(f => `<span class="badge bg-secondary bg-opacity-50">${Utils.escapeHtml(f)}</span>`).join('')
|
|
866
903
|
: '';
|
|
867
904
|
|
|
868
905
|
const meta = [
|
|
@@ -874,18 +911,22 @@ const Devices = {
|
|
|
874
911
|
|
|
875
912
|
return `
|
|
876
913
|
<div class="list-group-item">
|
|
877
|
-
<div class="
|
|
878
|
-
<span class="device-power ${powerOn ? 'power-on' : 'power-off'}
|
|
879
|
-
<span class="fw-semibold">${Utils.escapeHtml(device.name)}</span>
|
|
880
|
-
${features ? `<div class="
|
|
881
|
-
<span class="device-status ${online ? 'online' : 'offline'}
|
|
914
|
+
<div class="device-header">
|
|
915
|
+
<span class="device-power ${powerOn ? 'power-on' : 'power-off'}">${powerOn ? 'ON' : 'OFF'}</span>
|
|
916
|
+
<span class="device-name fw-semibold">${Utils.escapeHtml(device.name)}</span>
|
|
917
|
+
${features ? `<div class="device-features">${features}</div>` : ''}
|
|
918
|
+
<span class="device-status ${online ? 'online' : 'offline'}">${online ? 'Online' : 'Offline'}</span>
|
|
882
919
|
</div>
|
|
883
920
|
<div class="device-meta mt-1">${meta}</div>
|
|
884
921
|
</div>`;
|
|
885
922
|
},
|
|
886
923
|
|
|
887
|
-
refresh() {
|
|
888
|
-
|
|
924
|
+
async refresh() {
|
|
925
|
+
// Bypass the shared request and the server cache, then let the Settings
|
|
926
|
+
// device toggles reuse the same fresh request.
|
|
927
|
+
const pending = this.load({ refresh: true });
|
|
928
|
+
Settings.loadDeviceToggles();
|
|
929
|
+
await pending;
|
|
889
930
|
},
|
|
890
931
|
};
|
|
891
932
|
|
|
@@ -894,11 +935,16 @@ const Devices = {
|
|
|
894
935
|
// ============================================================================
|
|
895
936
|
|
|
896
937
|
const RateLimit = {
|
|
938
|
+
MODE_LABELS: { developer_portal: 'Developer Portal', mobile_app: 'Mobile App' },
|
|
939
|
+
|
|
897
940
|
async check() {
|
|
898
941
|
const display = El.rateLimitDisplay;
|
|
942
|
+
const updated = El.rateLimitUpdated;
|
|
899
943
|
display.textContent = 'Checking...';
|
|
944
|
+
DOM.hide(updated);
|
|
900
945
|
|
|
901
946
|
try {
|
|
947
|
+
// Reads the status the running plugin last recorded; no API call is made.
|
|
902
948
|
const result = await homebridge.request('/api/rate-limit', { mode: AuthMode.current });
|
|
903
949
|
if (result.success && result.rateLimit) {
|
|
904
950
|
const { limitDay, remainingDay, limitMinute, remainingMinute } = result.rateLimit;
|
|
@@ -906,7 +952,8 @@ const RateLimit = {
|
|
|
906
952
|
if (remainingMinute !== undefined) {
|
|
907
953
|
text += text ? `, ${remainingMinute}/${limitMinute}/min` : `${remainingMinute}/${limitMinute}/min`;
|
|
908
954
|
}
|
|
909
|
-
display.textContent = text || 'No rate limit headers';
|
|
955
|
+
display.textContent = text || 'No rate limit headers in the last response';
|
|
956
|
+
this.showUpdated(updated, result);
|
|
910
957
|
} else {
|
|
911
958
|
display.textContent = result.message || 'No info';
|
|
912
959
|
}
|
|
@@ -914,6 +961,21 @@ const RateLimit = {
|
|
|
914
961
|
display.textContent = 'Error: ' + error.message;
|
|
915
962
|
}
|
|
916
963
|
},
|
|
964
|
+
|
|
965
|
+
showUpdated(el, result) {
|
|
966
|
+
if (!el || !result.updatedAt) {
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
const parts = [`as of ${new Date(result.updatedAt).toLocaleString()}`];
|
|
970
|
+
if (typeof result.ageMs === 'number') {
|
|
971
|
+
parts.push(`(${Utils.formatTime(result.ageMs)} ago)`);
|
|
972
|
+
}
|
|
973
|
+
if (result.mode && result.mode !== AuthMode.current) {
|
|
974
|
+
parts.push(`— recorded in ${this.MODE_LABELS[result.mode] || result.mode} mode`);
|
|
975
|
+
}
|
|
976
|
+
el.textContent = parts.join(' ');
|
|
977
|
+
DOM.show(el);
|
|
978
|
+
},
|
|
917
979
|
};
|
|
918
980
|
|
|
919
981
|
// ============================================================================
|
|
@@ -976,8 +1038,8 @@ const MobileAuth = {
|
|
|
976
1038
|
if (result.success) {
|
|
977
1039
|
await this.saveCredentials(email, password);
|
|
978
1040
|
success.innerHTML = `
|
|
979
|
-
<strong>Success!</strong> Found ${result.deviceCount || 0} device(s).<br>
|
|
980
|
-
Rate limit: ${result.rateLimit?.remainingDay
|
|
1041
|
+
<strong>Success!</strong> Found ${Utils.escapeHtml(result.deviceCount || 0)} device(s).<br>
|
|
1042
|
+
Rate limit: ${Utils.escapeHtml(result.rateLimit?.remainingDay ?? '?')}/${Utils.escapeHtml(result.rateLimit?.limitDay ?? '3000')}/day<br>
|
|
981
1043
|
<small>Restart Homebridge to apply.</small>`;
|
|
982
1044
|
DOM.show(success);
|
|
983
1045
|
setTimeout(() => {
|
|
@@ -1154,6 +1216,7 @@ function cacheElements() {
|
|
|
1154
1216
|
devicesError: $id('devices-error'),
|
|
1155
1217
|
settingsStatus: $id('settings-status'),
|
|
1156
1218
|
rateLimitDisplay: $id('rate-limit-display'),
|
|
1219
|
+
rateLimitUpdated: $id('rate-limit-updated'),
|
|
1157
1220
|
loading: $id('loading'),
|
|
1158
1221
|
globalError: $id('global-error'),
|
|
1159
1222
|
};
|
|
@@ -105,18 +105,88 @@
|
|
|
105
105
|
color: var(--bs-danger-text-emphasis);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
/* Device header row: power, name, features, status */
|
|
109
|
+
.device-header {
|
|
110
|
+
display: flex;
|
|
111
|
+
flex-wrap: wrap;
|
|
112
|
+
align-items: center;
|
|
113
|
+
gap: 0.25rem 0.5rem;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.device-name {
|
|
117
|
+
min-width: 0;
|
|
118
|
+
overflow-wrap: anywhere;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.device-features {
|
|
122
|
+
display: flex;
|
|
123
|
+
flex-wrap: wrap;
|
|
124
|
+
gap: 0.25rem;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.device-header .device-status {
|
|
128
|
+
margin-left: auto;
|
|
129
|
+
flex-shrink: 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
108
132
|
/* Device meta row */
|
|
109
133
|
.device-meta {
|
|
134
|
+
display: flex;
|
|
135
|
+
flex-wrap: wrap;
|
|
110
136
|
font-size: 0.75rem;
|
|
111
137
|
color: var(--bs-tertiary-color);
|
|
112
138
|
opacity: 0.55;
|
|
113
139
|
}
|
|
114
140
|
|
|
141
|
+
.device-meta-item {
|
|
142
|
+
white-space: nowrap;
|
|
143
|
+
}
|
|
144
|
+
|
|
115
145
|
.device-meta-sep {
|
|
116
146
|
margin: 0 0.4rem;
|
|
117
147
|
opacity: 0.4;
|
|
118
148
|
}
|
|
119
149
|
|
|
150
|
+
.device-id {
|
|
151
|
+
font-family: var(--bs-font-monospace);
|
|
152
|
+
font-size: 0.75rem;
|
|
153
|
+
overflow-wrap: anywhere;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.min-w-0 {
|
|
157
|
+
min-width: 0;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Header status (token expiry + badge) */
|
|
161
|
+
.mp-header {
|
|
162
|
+
flex-wrap: wrap;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.header-status {
|
|
166
|
+
white-space: nowrap;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* Tabs: keep on one row, scroll horizontally if they still don't fit */
|
|
170
|
+
.app-tabs {
|
|
171
|
+
flex-wrap: nowrap;
|
|
172
|
+
overflow-x: auto;
|
|
173
|
+
scrollbar-width: none;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.app-tabs::-webkit-scrollbar {
|
|
177
|
+
display: none;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.app-tabs .nav-link {
|
|
181
|
+
white-space: nowrap;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/* Lists inside a card body: align with the card's own padding */
|
|
185
|
+
.card-body > .list-group-flush > .list-group-item {
|
|
186
|
+
padding-left: 0;
|
|
187
|
+
padding-right: 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
120
190
|
/* Device Toggle Labels */
|
|
121
191
|
.device-toggle-label.visible {
|
|
122
192
|
color: var(--bs-success);
|
|
@@ -238,3 +308,42 @@
|
|
|
238
308
|
#token-expires.text-danger {
|
|
239
309
|
color: var(--bs-danger) !important;
|
|
240
310
|
}
|
|
311
|
+
|
|
312
|
+
/* Mobile */
|
|
313
|
+
@media (max-width: 575.98px) {
|
|
314
|
+
.container-fluid {
|
|
315
|
+
padding: 0.75rem !important;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.mp-header-title {
|
|
319
|
+
font-size: 1.25rem;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/* Status drops to its own line under the title */
|
|
323
|
+
.header-status {
|
|
324
|
+
width: 100%;
|
|
325
|
+
margin-left: 0 !important;
|
|
326
|
+
justify-content: space-between;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.app-tabs .nav-link {
|
|
330
|
+
padding: 0.375rem 0.625rem;
|
|
331
|
+
font-size: 0.875rem;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/* Name takes the row, features move under it */
|
|
335
|
+
.device-name {
|
|
336
|
+
flex: 1 1 0;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.device-features {
|
|
340
|
+
order: 1;
|
|
341
|
+
width: 100%;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
@media (max-width: 359.98px) {
|
|
346
|
+
.app-tabs .nav-link {
|
|
347
|
+
padding: 0.375rem 0.4rem;
|
|
348
|
+
}
|
|
349
|
+
}
|