@homebridge-plugins/homebridge-eufy-security 4.7.1 → 5.0.0-beta.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/AUTHORS +4 -0
- package/CODE_OF_CONDUCT.md +30 -0
- package/CONTRIBUTING.md +77 -0
- package/README.md +152 -49
- package/SECURITY.md +30 -0
- package/config.schema.json +92 -3
- package/dist/account/ownership.js +290 -0
- package/dist/account/ownership.js.map +1 -0
- package/dist/account/persistence.js +274 -0
- package/dist/account/persistence.js.map +1 -0
- package/dist/account/temporary-authentication.js +348 -0
- package/dist/account/temporary-authentication.js.map +1 -0
- package/dist/configuration.js +167 -0
- package/dist/configuration.js.map +1 -0
- package/dist/device/member-evidence.js +66 -0
- package/dist/device/member-evidence.js.map +1 -0
- package/dist/device/member-trust.js +114 -0
- package/dist/device/member-trust.js.map +1 -0
- package/dist/device/snapshot.js +190 -0
- package/dist/device/snapshot.js.map +1 -0
- package/dist/diagnostics.js +2218 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/homekit/adapter.js +2 -0
- package/dist/homekit/adapter.js.map +1 -0
- package/dist/homekit/adapters/battery.js +208 -0
- package/dist/homekit/adapters/battery.js.map +1 -0
- package/dist/homekit/adapters/camera-controls.js +527 -0
- package/dist/homekit/adapters/camera-controls.js.map +1 -0
- package/dist/homekit/adapters/camera-streaming.js +1515 -0
- package/dist/homekit/adapters/camera-streaming.js.map +1 -0
- package/dist/homekit/adapters/contact.js +138 -0
- package/dist/homekit/adapters/contact.js.map +1 -0
- package/dist/homekit/adapters/doorbell.js +57 -0
- package/dist/homekit/adapters/doorbell.js.map +1 -0
- package/dist/homekit/adapters/information.js +55 -0
- package/dist/homekit/adapters/information.js.map +1 -0
- package/dist/homekit/adapters/lock.js +489 -0
- package/dist/homekit/adapters/lock.js.map +1 -0
- package/dist/homekit/adapters/motion.js +100 -0
- package/dist/homekit/adapters/motion.js.map +1 -0
- package/dist/homekit/adapters/registry.js +26 -0
- package/dist/homekit/adapters/registry.js.map +1 -0
- package/dist/homekit/adapters/security-system.js +425 -0
- package/dist/homekit/adapters/security-system.js.map +1 -0
- package/dist/homekit/adapters/siren.js +136 -0
- package/dist/homekit/adapters/siren.js.map +1 -0
- package/dist/homekit/adapters/smart-light.js +662 -0
- package/dist/homekit/adapters/smart-light.js.map +1 -0
- package/dist/homekit/coverage-matrix.js +119 -0
- package/dist/homekit/coverage-matrix.js.map +1 -0
- package/dist/homekit/device-control.js +175 -0
- package/dist/homekit/device-control.js.map +1 -0
- package/dist/homekit/reconciler.js +319 -0
- package/dist/homekit/reconciler.js.map +1 -0
- package/dist/homekit/representation.js +23 -0
- package/dist/homekit/representation.js.map +1 -0
- package/dist/index.js +4 -9
- package/dist/index.js.map +1 -1
- package/dist/media/contracts.js +2 -0
- package/dist/media/contracts.js.map +1 -0
- package/dist/media/last-successful-image.js +208 -0
- package/dist/media/last-successful-image.js.map +1 -0
- package/dist/media/live-stream.js +1003 -0
- package/dist/media/live-stream.js.map +1 -0
- package/dist/media/recording.js +560 -0
- package/dist/media/recording.js.map +1 -0
- package/dist/media/session-budget.js +39 -0
- package/dist/media/session-budget.js.map +1 -0
- package/dist/media/snapshot.js +406 -0
- package/dist/media/snapshot.js.map +1 -0
- package/dist/media/station-live-sessions.js +101 -0
- package/dist/media/station-live-sessions.js.map +1 -0
- package/dist/platform.js +97 -769
- package/dist/platform.js.map +1 -1
- package/dist/runtime/channel.js +375 -0
- package/dist/runtime/channel.js.map +1 -0
- package/dist/runtime/owner.js +682 -0
- package/dist/runtime/owner.js.map +1 -0
- package/dist/runtime/sdk-client.js +211 -0
- package/dist/runtime/sdk-client.js.map +1 -0
- package/dist/runtime/tracker.js +211 -0
- package/dist/runtime/tracker.js.map +1 -0
- package/dist/settings.js +1 -43
- package/dist/settings.js.map +1 -1
- package/dist/storage.js +59 -0
- package/dist/storage.js.map +1 -0
- package/dist/ui/dashboard.js +181 -0
- package/dist/ui/dashboard.js.map +1 -0
- package/dist/ui/device-image.js +22 -0
- package/dist/ui/device-image.js.map +1 -0
- package/dist/ui/runtime-channel-client.js +305 -0
- package/dist/ui/runtime-channel-client.js.map +1 -0
- package/dist/ui/server.js +259 -0
- package/dist/ui/server.js.map +1 -0
- package/homebridge-ui/public/app.css +1902 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T1250.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2070.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2080.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2081.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2103.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2117.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2118.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2119.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T211A.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2120.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2123.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2128.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2130.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2132.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2150.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2181.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2182.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2190.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2192.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2193.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2194.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2210.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2250.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2251.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2252.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2253.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2254.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2255.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2256.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2257.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2258.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2259.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2261.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2262.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2266.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2267.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2268.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2270.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2272.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2273.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2276.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2277.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2278.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2280.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2292.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2320.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2351.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2352.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2353.webp +0 -0
- package/homebridge-ui/public/assets/devices/life/life-T8L00.webp +0 -0
- package/homebridge-ui/public/assets/devices/life/life-T8L01.webp +0 -0
- package/homebridge-ui/public/assets/devices/life/life-T8L02.webp +0 -0
- package/homebridge-ui/public/assets/devices/life/life-T8L04.webp +0 -0
- package/homebridge-ui/public/assets/devices/life/life-T8L10.webp +0 -0
- package/homebridge-ui/public/assets/devices/life/life-T8L20.webp +0 -0
- package/homebridge-ui/public/assets/devices/life/life-T8L30.webp +0 -0
- package/homebridge-ui/public/assets/devices/life/life-T8L40.webp +0 -0
- package/homebridge-ui/public/assets/devices/mower/mower-T2801.webp +0 -0
- package/homebridge-ui/public/assets/devices/mower/mower-T280B.webp +0 -0
- package/homebridge-ui/public/assets/devices/mower/mower-T2880.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T7400.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T7401.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8001.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8002.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8010.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8021.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8023.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8025.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8030.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8110.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8111.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8112.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8113.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8114.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8122.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8123.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8124.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8130.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8131.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8134.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8140.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8141.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8150.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8151.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8152.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8153.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8160.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8161.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8162.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8170.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8171.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8172.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8173.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8200.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8200X.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8201.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8203.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8210.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8213.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8214.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8222.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8400.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8401.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8410.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8411.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8414.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8416.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8417.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8419.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8419N.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8420.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8420X.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8422.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8423.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8424.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8425.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8441.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8442.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8452.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T84A0.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T84A1.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8500.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8502.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8503.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8504.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8506.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8510.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8510P.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8520.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8520P.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8530.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8531.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T85D0.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T85L0.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T85P0.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T85V0.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8600.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T86P2.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8790.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T87B0.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T87B2.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8900.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8910.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8920.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8960.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8B0.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8E00.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8N00.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8W11C.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T90F0.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T90M0.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T90R0.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T90S0.webp +0 -0
- package/homebridge-ui/public/assets/icons/inventory.svg +1 -1
- package/homebridge-ui/public/assets/icons/troubleshoot.svg +1 -0
- package/homebridge-ui/public/assets/icons/warning.svg +1 -1
- package/homebridge-ui/public/assets/logo-dark.svg +10 -0
- package/homebridge-ui/public/assets/logo.svg +10 -0
- package/homebridge-ui/public/i18n/en.json +218 -0
- package/homebridge-ui/public/i18n/fr.json +218 -0
- package/homebridge-ui/public/index.html +377 -28
- package/homebridge-ui/public/js/app.js +1065 -0
- package/homebridge-ui/public/js/dashboard.js +385 -0
- package/homebridge-ui/public/js/legacy-settings.js +37 -0
- package/homebridge-ui/public/js/profile-wizard.js +68 -0
- package/homebridge-ui/server.js +2 -1393
- package/i18n/runtime/en.json +100 -0
- package/media/Snapshot-Unavailable.jpg +0 -0
- package/media/Snapshot-black.jpg +0 -0
- package/media/camera-disabled.jpg +0 -0
- package/media/camera-offline.jpg +0 -0
- package/package.json +29 -24
- package/.claude/CLAUDE.md +0 -175
- package/.claude/PRD.md +0 -241
- package/.claude/docs/debug-recording-lessons.md +0 -91
- package/.claude/docs/hksv-recording-fix.md +0 -160
- package/.claude/skills/architect/SKILL.md +0 -76
- package/.claude/skills/developer/SKILL.md +0 -59
- package/.claude/skills/new-device-support/SKILL.md +0 -212
- package/.claude/skills/new-device-support/check-device.mjs +0 -363
- package/.claude/skills/new-device-support/map-properties.mjs +0 -450
- package/.claude/skills/new-device-support/verify-device.mjs +0 -272
- package/.claude/skills/planner/SKILL.md +0 -100
- package/.claude/skills/qa/SKILL.md +0 -79
- package/.claude/skills/support/SKILL.md +0 -175
- package/.prettierignore +0 -7
- package/.prettierrc.json +0 -7
- package/FUNDING.yml +0 -1
- package/dist/accessories/AutoSyncStationAccessory.js +0 -157
- package/dist/accessories/AutoSyncStationAccessory.js.map +0 -1
- package/dist/accessories/BaseAccessory.js +0 -261
- package/dist/accessories/BaseAccessory.js.map +0 -1
- package/dist/accessories/CameraAccessory.js +0 -431
- package/dist/accessories/CameraAccessory.js.map +0 -1
- package/dist/accessories/Device.js +0 -65
- package/dist/accessories/Device.js.map +0 -1
- package/dist/accessories/EntrySensorAccessory.js +0 -48
- package/dist/accessories/EntrySensorAccessory.js.map +0 -1
- package/dist/accessories/LockAccessory.js +0 -142
- package/dist/accessories/LockAccessory.js.map +0 -1
- package/dist/accessories/MotionSensorAccessory.js +0 -48
- package/dist/accessories/MotionSensorAccessory.js.map +0 -1
- package/dist/accessories/SmartDropAccessory.js +0 -141
- package/dist/accessories/SmartDropAccessory.js.map +0 -1
- package/dist/accessories/StationAccessory.js +0 -373
- package/dist/accessories/StationAccessory.js.map +0 -1
- package/dist/config.js +0 -26
- package/dist/config.js.map +0 -1
- package/dist/controller/DebugRecordingManager.js +0 -402
- package/dist/controller/DebugRecordingManager.js.map +0 -1
- package/dist/controller/LocalLivestreamManager.js +0 -371
- package/dist/controller/LocalLivestreamManager.js.map +0 -1
- package/dist/controller/recordingDelegate.js +0 -252
- package/dist/controller/recordingDelegate.js.map +0 -1
- package/dist/controller/snapshotDelegate.js +0 -348
- package/dist/controller/snapshotDelegate.js.map +0 -1
- package/dist/controller/streamingDelegate.js +0 -389
- package/dist/controller/streamingDelegate.js.map +0 -1
- package/dist/interfaces.js +0 -2
- package/dist/interfaces.js.map +0 -1
- package/dist/media/Snapshot-Unavailable.png +0 -0
- package/dist/media/Snapshot-Unavailable.xcf +0 -0
- package/dist/media/Snapshot-black.png +0 -0
- package/dist/media/camera-disabled.png +0 -0
- package/dist/media/camera-offline.png +0 -0
- package/dist/media/media/Snapshot-Unavailable.png +0 -0
- package/dist/media/media/Snapshot-Unavailable.xcf +0 -0
- package/dist/media/media/Snapshot-black.png +0 -0
- package/dist/media/media/camera-disabled.png +0 -0
- package/dist/media/media/camera-offline.png +0 -0
- package/dist/utils/Talkback.js +0 -162
- package/dist/utils/Talkback.js.map +0 -1
- package/dist/utils/accessoriesStore.js +0 -207
- package/dist/utils/accessoriesStore.js.map +0 -1
- package/dist/utils/configTypes.js +0 -36
- package/dist/utils/configTypes.js.map +0 -1
- package/dist/utils/ffmpeg.js +0 -1111
- package/dist/utils/ffmpeg.js.map +0 -1
- package/dist/utils/interfaces.js +0 -8
- package/dist/utils/interfaces.js.map +0 -1
- package/dist/utils/utils.js +0 -224
- package/dist/utils/utils.js.map +0 -1
- package/dist/version.js +0 -2
- package/dist/version.js.map +0 -1
- package/homebridge-eufy-security.png +0 -0
- package/homebridge-ui/diagnosticsCrypto.js +0 -112
- package/homebridge-ui/public/app.js +0 -237
- package/homebridge-ui/public/assets/devices/4g_lte_cam_s330_large.png +0 -0
- package/homebridge-ui/public/assets/devices/4g_lte_starlight_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/BATTERY_DOORBELL_C30.png +0 -0
- package/homebridge-ui/public/assets/devices/BATTERY_DOORBELL_C31.png +0 -0
- package/homebridge-ui/public/assets/devices/batterydoorbell1080p_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/batterydoorbell2k_large.png +0 -0
- package/homebridge-ui/public/assets/devices/batterydoorbell2kdual_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/batterydoorbell_e340_large.png +0 -0
- package/homebridge-ui/public/assets/devices/eufy-security-client.png +0 -0
- package/homebridge-ui/public/assets/devices/eufyCamS4_large.png +0 -0
- package/homebridge-ui/public/assets/devices/eufycam2_large.png +0 -0
- package/homebridge-ui/public/assets/devices/eufycam2c_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/eufycam2cpro_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/eufycam2pro_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/eufycam3_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/eufycam3c_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/eufycam3pro_large.png +0 -0
- package/homebridge-ui/public/assets/devices/eufycam_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/eufycame330_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/flood_freeze_sensor_e20_t90F0_large.png +0 -0
- package/homebridge-ui/public/assets/devices/floodlight2_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/floodlight2pro_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/floodlight_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/floodlightcame340_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/garage_camera_t8452_large.png +0 -0
- package/homebridge-ui/public/assets/devices/homebase2_large.png +0 -0
- package/homebridge-ui/public/assets/devices/homebase3_large.png +0 -0
- package/homebridge-ui/public/assets/devices/homebase_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/homebasemini_large.png +0 -0
- package/homebridge-ui/public/assets/devices/indoorcamC210_large.png +0 -0
- package/homebridge-ui/public/assets/devices/indoorcamC220_large.png +0 -0
- package/homebridge-ui/public/assets/devices/indoorcamC220_t8419N_large.png +0 -0
- package/homebridge-ui/public/assets/devices/indoorcamE30_large.png +0 -0
- package/homebridge-ui/public/assets/devices/indoorcamc120_large.png +0 -0
- package/homebridge-ui/public/assets/devices/indoorcammini_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/indoorcamp24_large.png +0 -0
- package/homebridge-ui/public/assets/devices/indoorcams350_large.png +0 -0
- package/homebridge-ui/public/assets/devices/keypad_large.png +0 -0
- package/homebridge-ui/public/assets/devices/minibase_chime_T8023_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/motionsensor_large.png +0 -0
- package/homebridge-ui/public/assets/devices/nvr_s4_max_T8N00_large.png +0 -0
- package/homebridge-ui/public/assets/devices/pir_sensor_e20_large.png +0 -0
- package/homebridge-ui/public/assets/devices/poe_bullet_ptz_cam_s4_T8E00_large.png +0 -0
- package/homebridge-ui/public/assets/devices/sensor_large.png +0 -0
- package/homebridge-ui/public/assets/devices/siren_sensor_e20_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartdrop_t8790_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t8500_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t8500_wifibridge_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t8503_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t8504_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t8510P_t8520P_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t85D0_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t85L0_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t85P0_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t85V0_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_touch_and_wifi_t8502_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_touch_and_wifi_t8506_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_touch_and_wifi_t8520_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_touch_t8510_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_touch_t8510_wifibridge_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_video_t8530_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_video_t8531_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlockwifibridge_t8021_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/smartsafe_s10_t7400_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartsafe_s12_t7401_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smarttrack_card_t87B2_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smarttrack_link_t87B0_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smoke_alarm_e10_t90S0_large.png +0 -0
- package/homebridge-ui/public/assets/devices/solocamc210_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/solocamc35_large.png +0 -0
- package/homebridge-ui/public/assets/devices/solocame20_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/solocame30_large.png +0 -0
- package/homebridge-ui/public/assets/devices/solocame40_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/solocame42_large.png +0 -0
- package/homebridge-ui/public/assets/devices/solocaml20_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/solocams220_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/solocams340_large.png +0 -0
- package/homebridge-ui/public/assets/devices/solocams40_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/soloindoorcamc24_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/solooutdoorcamc22_large.png +0 -0
- package/homebridge-ui/public/assets/devices/solooutdoorcamc24_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/unknown.png +0 -0
- package/homebridge-ui/public/assets/devices/walllight_s100_large.png +0 -0
- package/homebridge-ui/public/assets/devices/walllight_s120_large.png +0 -0
- package/homebridge-ui/public/assets/devices/water_freeze_sensor_t8920_large.png +0 -0
- package/homebridge-ui/public/assets/devices/wireddoorbell1080p_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/wireddoorbell2k_large.png +0 -0
- package/homebridge-ui/public/assets/devices/wireddoorbelldual_large.jpg +0 -0
- package/homebridge-ui/public/assets/icons/attach.svg +0 -1
- package/homebridge-ui/public/assets/icons/battery_0.svg +0 -1
- package/homebridge-ui/public/assets/icons/battery_1.svg +0 -1
- package/homebridge-ui/public/assets/icons/battery_2.svg +0 -1
- package/homebridge-ui/public/assets/icons/battery_3.svg +0 -1
- package/homebridge-ui/public/assets/icons/battery_4.svg +0 -1
- package/homebridge-ui/public/assets/icons/battery_5.svg +0 -1
- package/homebridge-ui/public/assets/icons/battery_6.svg +0 -1
- package/homebridge-ui/public/assets/icons/copy.svg +0 -1
- package/homebridge-ui/public/assets/icons/delete.svg +0 -1
- package/homebridge-ui/public/assets/icons/download.svg +0 -1
- package/homebridge-ui/public/assets/icons/info.svg +0 -1
- package/homebridge-ui/public/assets/icons/refresh.svg +0 -1
- package/homebridge-ui/public/assets/icons/satellite_alt.svg +0 -1
- package/homebridge-ui/public/assets/icons/solar_power.svg +0 -1
- package/homebridge-ui/public/components/device-card.js +0 -174
- package/homebridge-ui/public/components/guard-modes.js +0 -88
- package/homebridge-ui/public/components/number-input.js +0 -121
- package/homebridge-ui/public/components/select.js +0 -73
- package/homebridge-ui/public/components/toggle.js +0 -68
- package/homebridge-ui/public/services/api.js +0 -250
- package/homebridge-ui/public/services/config.js +0 -144
- package/homebridge-ui/public/style.css +0 -818
- package/homebridge-ui/public/utils/countries.js +0 -73
- package/homebridge-ui/public/utils/device-images.js +0 -102
- package/homebridge-ui/public/utils/helpers.js +0 -115
- package/homebridge-ui/public/views/dashboard.js +0 -226
- package/homebridge-ui/public/views/device-detail.js +0 -626
- package/homebridge-ui/public/views/diagnostics.js +0 -402
- package/homebridge-ui/public/views/login.js +0 -650
- package/homebridge-ui/public/views/recordings.js +0 -319
- package/homebridge-ui/public/views/settings.js +0 -197
- package/homebridge-ui/public/views/unsupported-detail.js +0 -296
- package/media/Snapshot-Unavailable.png +0 -0
- package/media/Snapshot-Unavailable.xcf +0 -0
- package/media/Snapshot-black.png +0 -0
- package/media/camera-disabled.png +0 -0
- package/media/camera-offline.png +0 -0
- package/scripts/decrypt-diagnostics.mjs +0 -257
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { AccountOwnership } from '../account/ownership.js';
|
|
3
|
+
import { AccountSessionPersistence } from '../account/persistence.js';
|
|
4
|
+
import { armDiagnosticsAuthorization, PLUGIN_VERSION, reportRuntimeNotice, } from '../diagnostics.js';
|
|
5
|
+
import { indexDeviceEvidence } from '../device/member-evidence.js';
|
|
6
|
+
import { batteryLevel, cameraEnablement } from '../device/member-trust.js';
|
|
7
|
+
import { parseCompleteDeviceSnapshot } from '../device/snapshot.js';
|
|
8
|
+
import { RuntimeChannelServer, runtimeChannelEndpointForHost, } from './channel.js';
|
|
9
|
+
import { RuntimeTracker, runtimeStatusFor } from './tracker.js';
|
|
10
|
+
/**
|
|
11
|
+
* How long a runtime that stood down on request keeps trying to take the account session back.
|
|
12
|
+
*
|
|
13
|
+
* It outlasts the interactive authentication a stand-down is requested for, whose own deadline is five minutes.
|
|
14
|
+
*/
|
|
15
|
+
const REARM_WINDOW_MS = 15 * 60_000;
|
|
16
|
+
/** How long between attempts to take the account session back after standing down on request. */
|
|
17
|
+
const REARM_INTERVAL_MS = 15_000;
|
|
18
|
+
/** Owns the long-lived SDK session, canonical registry, and runtime state transitions. */
|
|
19
|
+
/**
|
|
20
|
+
* Whether a process with this title is a Homebridge child bridge, and so may end itself to be replaced.
|
|
21
|
+
*
|
|
22
|
+
* Homebridge titles a child bridge `homebridge: <plugin name>` and leaves the main process plain `homebridge`.
|
|
23
|
+
* Only a child bridge is respawned when it ends; ending the main process would stop every other plugin with it.
|
|
24
|
+
* A title this does not recognise answers false, so an unfamiliar host loses the offer rather than the bridge.
|
|
25
|
+
*/
|
|
26
|
+
export function isChildBridgeTitle(title) {
|
|
27
|
+
return title.startsWith('homebridge: ');
|
|
28
|
+
}
|
|
29
|
+
export class RuntimeOwner {
|
|
30
|
+
log;
|
|
31
|
+
configuredConfig;
|
|
32
|
+
clientFactory;
|
|
33
|
+
client;
|
|
34
|
+
startPromise;
|
|
35
|
+
stopPromise;
|
|
36
|
+
statusPublisher;
|
|
37
|
+
statusPublisherActive = false;
|
|
38
|
+
channel;
|
|
39
|
+
ownership;
|
|
40
|
+
accountScope;
|
|
41
|
+
runtimeLease;
|
|
42
|
+
pendingOwnership;
|
|
43
|
+
runtimeState = 'stopped';
|
|
44
|
+
registryView;
|
|
45
|
+
registryVersion = 0;
|
|
46
|
+
registryListeners = new Set();
|
|
47
|
+
eventListeners = new Set();
|
|
48
|
+
unconfirmedListeners = new Set();
|
|
49
|
+
stateListeners = new Set();
|
|
50
|
+
stopping = false;
|
|
51
|
+
cleanupTerminalState;
|
|
52
|
+
storageRoot;
|
|
53
|
+
persistence;
|
|
54
|
+
shutdownTimeoutMs;
|
|
55
|
+
rearmWindowMs;
|
|
56
|
+
rearmIntervalMs;
|
|
57
|
+
shuttingDown = false;
|
|
58
|
+
standingDown = false;
|
|
59
|
+
rearming = false;
|
|
60
|
+
rearmTimer;
|
|
61
|
+
rearmGeneration;
|
|
62
|
+
rearmBlocked = false;
|
|
63
|
+
activeGeneration;
|
|
64
|
+
constructor(log, configuredConfig, clientFactory, options = {}) {
|
|
65
|
+
this.log = log;
|
|
66
|
+
this.configuredConfig = configuredConfig;
|
|
67
|
+
this.clientFactory = clientFactory;
|
|
68
|
+
this.storageRoot = options.storageRoot;
|
|
69
|
+
this.shutdownTimeoutMs = options.shutdownTimeoutMs ?? 10_000;
|
|
70
|
+
this.rearmWindowMs = options.rearmWindowMs ?? REARM_WINDOW_MS;
|
|
71
|
+
this.rearmIntervalMs = options.rearmIntervalMs ?? REARM_INTERVAL_MS;
|
|
72
|
+
this.ownership = options.ownership;
|
|
73
|
+
this.statusPublisher = options.statusPublisher;
|
|
74
|
+
this.channel = options.channel;
|
|
75
|
+
if (this.storageRoot) {
|
|
76
|
+
this.persistence = options.persistence ?? new AccountSessionPersistence(join(this.storageRoot, 'accounts'));
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
this.persistence = options.persistence;
|
|
80
|
+
this.client = clientFactory(configuredConfig);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
start() {
|
|
84
|
+
if (this.stopPromise) {
|
|
85
|
+
return Promise.resolve();
|
|
86
|
+
}
|
|
87
|
+
this.startPromise ??= this.startClient();
|
|
88
|
+
return this.startPromise;
|
|
89
|
+
}
|
|
90
|
+
currentRegistry() {
|
|
91
|
+
return this.registryView;
|
|
92
|
+
}
|
|
93
|
+
currentState() {
|
|
94
|
+
return this.runtimeState;
|
|
95
|
+
}
|
|
96
|
+
currentAvailability(serial) {
|
|
97
|
+
return this.client?.deviceAvailability?.(serial);
|
|
98
|
+
}
|
|
99
|
+
subscribeRegistry(listener) {
|
|
100
|
+
this.registryListeners.add(listener);
|
|
101
|
+
return () => this.registryListeners.delete(listener);
|
|
102
|
+
}
|
|
103
|
+
subscribeEvents(listener) {
|
|
104
|
+
this.eventListeners.add(listener);
|
|
105
|
+
return () => this.eventListeners.delete(listener);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Follows a write the device acknowledged and never applied. Separate from the event subscription because
|
|
109
|
+
* it is an operation outcome and not device state: nothing about the device changed, which is the news.
|
|
110
|
+
*/
|
|
111
|
+
subscribeUnconfirmedWrites(listener) {
|
|
112
|
+
this.unconfirmedListeners.add(listener);
|
|
113
|
+
return () => this.unconfirmedListeners.delete(listener);
|
|
114
|
+
}
|
|
115
|
+
subscribeState(listener) {
|
|
116
|
+
this.stateListeners.add(listener);
|
|
117
|
+
return () => this.stateListeners.delete(listener);
|
|
118
|
+
}
|
|
119
|
+
stop() {
|
|
120
|
+
this.shuttingDown = true;
|
|
121
|
+
this.clearRearm();
|
|
122
|
+
return this.stopOwnership();
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Releases the account session so another process may own it, and takes it back when it can.
|
|
126
|
+
*
|
|
127
|
+
* Reports whether a stand-down was started, which is acceptance and not completion: the release runs the same
|
|
128
|
+
* bounded cleanup a shutdown runs, and the endpoint closes inside the release guard. Only the lease is
|
|
129
|
+
* evidence that the session is free. A runtime holding no lease, one already stopping, and one already
|
|
130
|
+
* standing down each refuse, so the caller behaves as it does without this channel.
|
|
131
|
+
*/
|
|
132
|
+
standDown() {
|
|
133
|
+
if (!this.storageRoot || !this.runtimeLease || this.stopping || this.standingDown || this.shuttingDown) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
this.standingDown = true;
|
|
137
|
+
this.rearmGeneration = this.activeGeneration;
|
|
138
|
+
void this.stopOwnership().finally(() => {
|
|
139
|
+
this.standingDown = false;
|
|
140
|
+
if (!this.shuttingDown) {
|
|
141
|
+
this.scheduleRearm(Date.now() + this.rearmWindowMs);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
stopOwnership() {
|
|
147
|
+
this.cleanupTerminalState = 'stopped';
|
|
148
|
+
if (!this.stopPromise) {
|
|
149
|
+
this.stopping = true;
|
|
150
|
+
if (this.runtimeState !== 'owner-conflict') {
|
|
151
|
+
this.transitionTo('stopping');
|
|
152
|
+
if (this.statusPublisherActive) {
|
|
153
|
+
this.statusPublisher?.update('stopping');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
this.stopPromise = this.cleanupWithinDeadline('stopped');
|
|
157
|
+
}
|
|
158
|
+
else if (this.runtimeState !== 'owner-conflict' &&
|
|
159
|
+
this.runtimeState !== 'failed' &&
|
|
160
|
+
this.runtimeState !== 'stopping') {
|
|
161
|
+
this.transitionTo('stopping');
|
|
162
|
+
if (this.statusPublisherActive) {
|
|
163
|
+
this.statusPublisher?.update('stopping');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return this.stopPromise.then(() => {
|
|
167
|
+
if (this.runtimeState !== 'owner-conflict' && this.runtimeState !== 'failed' && this.runtimeState !== 'stopped') {
|
|
168
|
+
if (this.statusPublisherActive) {
|
|
169
|
+
this.statusPublisher?.stop();
|
|
170
|
+
}
|
|
171
|
+
this.transitionTo('stopped');
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Schedules the next attempt to take the account session back, or gives up once the window has passed.
|
|
177
|
+
*
|
|
178
|
+
* Nothing can tell this runtime when the session is free: the endpoint's lifetime is the lease, so the channel
|
|
179
|
+
* is gone for exactly as long as the reason to re-arm lasts, and the process that asked may itself be gone.
|
|
180
|
+
*/
|
|
181
|
+
scheduleRearm(deadline) {
|
|
182
|
+
this.rearming = true;
|
|
183
|
+
if (Date.now() >= deadline) {
|
|
184
|
+
this.clearRearm();
|
|
185
|
+
reportRuntimeNotice(this.log, 'stand-down-not-rearmed');
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
this.rearmTimer = setTimeout(() => void this.attemptRearm(deadline), this.rearmIntervalMs);
|
|
189
|
+
this.rearmTimer.unref();
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Takes the account session back if it is free, and schedules another attempt only if the lease is still held.
|
|
193
|
+
*
|
|
194
|
+
* A held lease is the ordinary case for as long as the authentication this runtime stood down for is running,
|
|
195
|
+
* so it reports nothing and leaves this runtime stopped, which is what it is. Any other outcome is a state this
|
|
196
|
+
* runtime reached and reported, and retrying it would report it again on every interval of the window.
|
|
197
|
+
*/
|
|
198
|
+
async attemptRearm(deadline) {
|
|
199
|
+
this.rearmTimer = undefined;
|
|
200
|
+
if (this.shuttingDown || !this.rearming) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
this.rearmBlocked = false;
|
|
204
|
+
this.resetForRearm();
|
|
205
|
+
await this.start();
|
|
206
|
+
if (!this.rearming) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (!this.rearmBlocked) {
|
|
210
|
+
this.clearRearm();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
this.scheduleRearm(deadline);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Returns this owner to the state a start begins from, keeping what a stopped runtime is defined to retain.
|
|
217
|
+
*
|
|
218
|
+
* The latest complete registry view and its version survive, because HomeKit's topology is built from them
|
|
219
|
+
* and only a later complete inventory may replace them. Everything a cleanup consumed is cleared so that one
|
|
220
|
+
* cycle's cleanup cannot be a second cycle's, which is what keeps a lease from being released twice.
|
|
221
|
+
*/
|
|
222
|
+
resetForRearm() {
|
|
223
|
+
this.startPromise = undefined;
|
|
224
|
+
this.stopPromise = undefined;
|
|
225
|
+
this.stopping = false;
|
|
226
|
+
this.cleanupTerminalState = undefined;
|
|
227
|
+
this.statusPublisherActive = false;
|
|
228
|
+
this.pendingOwnership = undefined;
|
|
229
|
+
this.client = undefined;
|
|
230
|
+
}
|
|
231
|
+
clearRearm() {
|
|
232
|
+
clearTimeout(this.rearmTimer);
|
|
233
|
+
this.rearmTimer = undefined;
|
|
234
|
+
this.rearming = false;
|
|
235
|
+
this.rearmBlocked = false;
|
|
236
|
+
this.rearmGeneration = undefined;
|
|
237
|
+
}
|
|
238
|
+
async startClient() {
|
|
239
|
+
try {
|
|
240
|
+
const { active, config } = await this.activeAccount();
|
|
241
|
+
this.activeGeneration = active?.generation;
|
|
242
|
+
if (this.stopping) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (this.rearming && this.activeGeneration !== this.rearmGeneration) {
|
|
246
|
+
reportRuntimeNotice(this.log, 'stand-down-account-replaced');
|
|
247
|
+
this.clearRearm();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (this.storageRoot && !active) {
|
|
251
|
+
this.statusPublisher ??= this.createStatusPublisher();
|
|
252
|
+
this.statusPublisherActive = this.statusPublisher.update('authentication-required');
|
|
253
|
+
this.transitionTo('authentication-required');
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (this.storageRoot && config.username?.trim()) {
|
|
257
|
+
this.accountScope = config.username.trim().toLowerCase();
|
|
258
|
+
this.ownership ??= new AccountOwnership(join(this.storageRoot, 'ownership'));
|
|
259
|
+
this.statusPublisher ??= this.createStatusPublisher();
|
|
260
|
+
}
|
|
261
|
+
if (this.ownership && this.accountScope && active) {
|
|
262
|
+
if (!this.rearming) {
|
|
263
|
+
this.transitionTo('acquiring-ownership');
|
|
264
|
+
}
|
|
265
|
+
const pendingOwnership = this.ownership.acquire(this.accountScope, 'runtime');
|
|
266
|
+
this.pendingOwnership = pendingOwnership;
|
|
267
|
+
const ownership = await pendingOwnership;
|
|
268
|
+
if (this.stopping) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
this.pendingOwnership = undefined;
|
|
272
|
+
if (ownership.state === 'owner-conflict') {
|
|
273
|
+
if (this.rearming) {
|
|
274
|
+
this.rearmBlocked = true;
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
this.transitionTo('owner-conflict');
|
|
278
|
+
}
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
this.runtimeLease = ownership.lease;
|
|
282
|
+
this.clearRearm();
|
|
283
|
+
await this.openChannel();
|
|
284
|
+
const previousSnapshot = active.snapshot.load() ?? undefined;
|
|
285
|
+
if (!this.statusPublisher?.start('starting', {
|
|
286
|
+
generation: active.generation,
|
|
287
|
+
complete: false,
|
|
288
|
+
snapshot: previousSnapshot,
|
|
289
|
+
})) {
|
|
290
|
+
throw new Error('runtime status tracker could not be started');
|
|
291
|
+
}
|
|
292
|
+
this.statusPublisherActive = true;
|
|
293
|
+
this.transitionTo('starting');
|
|
294
|
+
if (!active.session.load()) {
|
|
295
|
+
this.transitionTo('authentication-required');
|
|
296
|
+
await this.beginCleanup('authentication-required', {
|
|
297
|
+
generation: active.generation,
|
|
298
|
+
complete: false,
|
|
299
|
+
snapshot: previousSnapshot,
|
|
300
|
+
});
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
this.client = this.clientFactory(config, active, this.log);
|
|
304
|
+
this.client.onEvent?.((event) => this.publishEvent(event));
|
|
305
|
+
this.client.onUnconfirmedWrite?.((write) => this.publishUnconfirmedWrite(write));
|
|
306
|
+
this.client.onInventory?.((result) => {
|
|
307
|
+
void this.applyRuntimeResult(active, result).catch(() => this.beginCleanup('failed'));
|
|
308
|
+
});
|
|
309
|
+
const result = await this.client.start();
|
|
310
|
+
if (result) {
|
|
311
|
+
await this.applyRuntimeResult(active, result);
|
|
312
|
+
}
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
this.client ??= this.clientFactory(config, active ?? undefined, this.log);
|
|
316
|
+
await this.client.start();
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
await this.beginCleanup('failed');
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
async activeAccount() {
|
|
323
|
+
const active = await this.persistence?.active();
|
|
324
|
+
const configuration = active?.configuration.load();
|
|
325
|
+
if (!active) {
|
|
326
|
+
return { active: null, config: this.configuredConfig };
|
|
327
|
+
}
|
|
328
|
+
if (!configuration) {
|
|
329
|
+
if (this.configuredConfig.username?.trim().toLowerCase() !== active.account) {
|
|
330
|
+
throw new Error('legacy active account generation does not match Homebridge configuration');
|
|
331
|
+
}
|
|
332
|
+
return { active, config: this.configuredConfig };
|
|
333
|
+
}
|
|
334
|
+
if (configuration.username?.trim().toLowerCase() !== active.account) {
|
|
335
|
+
throw new Error('active account generation has mismatched configuration');
|
|
336
|
+
}
|
|
337
|
+
return { active, config: configuration };
|
|
338
|
+
}
|
|
339
|
+
createStatusPublisher() {
|
|
340
|
+
return new RuntimeTracker(join(this.storageRoot, 'tracker.json'), 90_000, Date.now, () => {
|
|
341
|
+
reportRuntimeNotice(this.log, 'status-publication-failed');
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
async applyRuntimeResult(active, result) {
|
|
345
|
+
if (this.stopping ||
|
|
346
|
+
(this.runtimeState === 'authentication-required' && result.state !== 'authentication-required')) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
const previousSnapshot = active.snapshot.load() ?? undefined;
|
|
350
|
+
if (result.state === 'authentication-required') {
|
|
351
|
+
if (this.runtimeState === 'authentication-required') {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
this.transitionTo('authentication-required');
|
|
355
|
+
await this.beginCleanup('authentication-required', {
|
|
356
|
+
generation: active.generation,
|
|
357
|
+
complete: false,
|
|
358
|
+
snapshot: previousSnapshot,
|
|
359
|
+
});
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (result.state === 'degraded') {
|
|
363
|
+
let latestSnapshot = previousSnapshot;
|
|
364
|
+
const completeTopology = result.complete === true;
|
|
365
|
+
if (completeTopology) {
|
|
366
|
+
this.acceptCompleteRegistry(active, result.registry, result.snapshot);
|
|
367
|
+
latestSnapshot = result.snapshot;
|
|
368
|
+
}
|
|
369
|
+
this.statusPublisher?.update('degraded', {
|
|
370
|
+
generation: active.generation,
|
|
371
|
+
complete: completeTopology,
|
|
372
|
+
snapshot: latestSnapshot,
|
|
373
|
+
status: completeTopology ? 'transport-degraded' : 'incomplete-inventory',
|
|
374
|
+
});
|
|
375
|
+
this.transitionTo('degraded');
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
this.acceptCompleteRegistry(active, result.registry, result.snapshot);
|
|
379
|
+
if (!this.statusPublisher?.update('ready', {
|
|
380
|
+
generation: active.generation,
|
|
381
|
+
complete: true,
|
|
382
|
+
snapshot: result.snapshot,
|
|
383
|
+
})) {
|
|
384
|
+
throw new Error('complete runtime snapshot could not be published');
|
|
385
|
+
}
|
|
386
|
+
this.transitionTo('ready');
|
|
387
|
+
}
|
|
388
|
+
acceptCompleteRegistry(active, registry, snapshot) {
|
|
389
|
+
const registrySerials = [...registry.keys()].sort();
|
|
390
|
+
const snapshotSerials = snapshot.devices.map((device) => device.sn).sort();
|
|
391
|
+
if (JSON.stringify(registrySerials) !== JSON.stringify(snapshotSerials)) {
|
|
392
|
+
throw new Error('canonical registry does not match its complete device snapshot');
|
|
393
|
+
}
|
|
394
|
+
active.snapshot.save(snapshot);
|
|
395
|
+
this.publishRegistry(active.generation, registry, snapshot);
|
|
396
|
+
}
|
|
397
|
+
async releaseRuntimeLease(onReleased = () => undefined) {
|
|
398
|
+
const lease = this.runtimeLease;
|
|
399
|
+
this.runtimeLease = undefined;
|
|
400
|
+
if (!lease) {
|
|
401
|
+
onReleased();
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
try {
|
|
405
|
+
let finalized = false;
|
|
406
|
+
const release = await lease.release(() => {
|
|
407
|
+
finalized = true;
|
|
408
|
+
onReleased();
|
|
409
|
+
});
|
|
410
|
+
if (release.state !== 'stopped') {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
if (!finalized) {
|
|
414
|
+
reportRuntimeNotice(this.log, 'ownership-release-not-finalized');
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
reportRuntimeNotice(this.log, 'ownership-release-failed');
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
async stopRuntimeClient() {
|
|
425
|
+
const client = this.client;
|
|
426
|
+
this.client = undefined;
|
|
427
|
+
try {
|
|
428
|
+
await client?.stop();
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
reportRuntimeNotice(this.log, 'shutdown-failed');
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
beginCleanup(completedState, update) {
|
|
437
|
+
this.stopping = true;
|
|
438
|
+
if (!this.stopPromise) {
|
|
439
|
+
this.cleanupTerminalState = completedState;
|
|
440
|
+
this.stopPromise = this.cleanupWithinDeadline(completedState, update);
|
|
441
|
+
}
|
|
442
|
+
return this.stopPromise;
|
|
443
|
+
}
|
|
444
|
+
async cleanupWithinDeadline(completedState, update) {
|
|
445
|
+
if (this.runtimeState === 'owner-conflict') {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const deadline = Date.now() + this.shutdownTimeoutMs;
|
|
449
|
+
const clientStopped = await this.boundedCleanup(this.stopRuntimeClient(), deadline);
|
|
450
|
+
const ownershipSettled = await this.settlePendingOwnership(deadline);
|
|
451
|
+
const cleanupFailedBeforeRelease = clientStopped !== true || ownershipSettled !== true;
|
|
452
|
+
let publishedTerminalState = cleanupFailedBeforeRelease ? 'failed' : (this.cleanupTerminalState ?? completedState);
|
|
453
|
+
const leaseReleased = await this.boundedCleanup(this.releaseRuntimeLease(() => {
|
|
454
|
+
this.channel?.close();
|
|
455
|
+
this.publishTerminalState(publishedTerminalState, update);
|
|
456
|
+
}), deadline);
|
|
457
|
+
const timedOut = clientStopped === 'timeout' || ownershipSettled === 'timeout' || leaseReleased === 'timeout';
|
|
458
|
+
if (timedOut) {
|
|
459
|
+
reportRuntimeNotice(this.log, 'shutdown-timeout', { durationMs: this.shutdownTimeoutMs });
|
|
460
|
+
}
|
|
461
|
+
if (leaseReleased === 'timeout') {
|
|
462
|
+
publishedTerminalState = 'failed';
|
|
463
|
+
this.publishTerminalState('failed');
|
|
464
|
+
}
|
|
465
|
+
const terminalState = leaseReleased === true ? publishedTerminalState : 'failed';
|
|
466
|
+
this.transitionTo(terminalState);
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Binds the live channel once this process is the account's owner.
|
|
470
|
+
*
|
|
471
|
+
* The endpoint's lifetime is the lease's: opened here, and closed inside the release guard. An unbound
|
|
472
|
+
* endpoint is reported and the runtime starts without one.
|
|
473
|
+
*/
|
|
474
|
+
async openChannel() {
|
|
475
|
+
if (this.storageRoot) {
|
|
476
|
+
const storageRoot = this.storageRoot;
|
|
477
|
+
this.channel ??= new RuntimeChannelServer(runtimeChannelEndpointForHost(storageRoot), () => this.channelStatus(), {
|
|
478
|
+
devices: () => this.channelDevices(),
|
|
479
|
+
authorization: (notice) => armDiagnosticsAuthorization(this.log, storageRoot, notice.supportCaseId),
|
|
480
|
+
standDown: () => this.standDown(),
|
|
481
|
+
version: PLUGIN_VERSION,
|
|
482
|
+
restartable: isChildBridgeTitle(process.title),
|
|
483
|
+
restart: () => this.endForReplacement(),
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
if (this.channel && !(await this.channel.open())) {
|
|
487
|
+
reportRuntimeNotice(this.log, 'channel-serve-failed');
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Ends this process so Homebridge replaces it with the installed build, reporting whether that was begun.
|
|
492
|
+
*
|
|
493
|
+
* Refused unless this process is a child bridge, because the main process is not respawned and every other
|
|
494
|
+
* plugin would go down with it. The signal is deferred so the request that asked for it is answered first,
|
|
495
|
+
* and it is the same signal Homebridge itself uses to cycle a child bridge, rather than an abrupt exit.
|
|
496
|
+
*/
|
|
497
|
+
endForReplacement() {
|
|
498
|
+
if (!isChildBridgeTitle(process.title)) {
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
setTimeout(() => process.kill(process.pid, 'SIGTERM'), 250).unref();
|
|
502
|
+
return true;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* What the live channel answers: the state this runtime holds now, and the inventory it last published.
|
|
506
|
+
*
|
|
507
|
+
* `status` accompanies the state it was published with, and is otherwise derived from the live state
|
|
508
|
+
* through the one mapping the tracker uses, so the pair cannot disagree. `generation` and `complete`
|
|
509
|
+
* describe the published inventory and outlive a state change. `updatedAt` is when this answer was given.
|
|
510
|
+
*/
|
|
511
|
+
channelStatus() {
|
|
512
|
+
const published = this.statusPublisher?.latest?.();
|
|
513
|
+
const current = published?.state === this.runtimeState ? published : undefined;
|
|
514
|
+
return {
|
|
515
|
+
state: this.runtimeState,
|
|
516
|
+
status: current?.status ?? runtimeStatusFor(this.runtimeState),
|
|
517
|
+
...(published?.generation === undefined ? {} : { generation: published.generation }),
|
|
518
|
+
complete: published?.complete ?? false,
|
|
519
|
+
updatedAt: new Date().toISOString(),
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* What the live channel observes about each device its published inventory names.
|
|
524
|
+
*
|
|
525
|
+
* Taken from the registry this runtime already holds and the evidence the same published manifest carries,
|
|
526
|
+
* so no second capability model is built from SDK internals. Nothing is observed once the SDK client is
|
|
527
|
+
* gone, because a surface belonging to a stopped client states nothing about a device now. A field left
|
|
528
|
+
* `undefined` is dropped on the way to the wire: unobserved is not unreachable, and it is not switched off.
|
|
529
|
+
*/
|
|
530
|
+
channelDevices() {
|
|
531
|
+
const view = this.registryView;
|
|
532
|
+
if (!view || !this.client) {
|
|
533
|
+
return [];
|
|
534
|
+
}
|
|
535
|
+
return view.snapshot.devices.map((manifest) => ({
|
|
536
|
+
serial: manifest.sn,
|
|
537
|
+
availability: this.currentAvailability(manifest.sn)?.availability,
|
|
538
|
+
enabled: this.observedEnablement(view.registry.get(manifest.sn), manifest),
|
|
539
|
+
battery: this.observedBattery(view.registry.get(manifest.sn), manifest),
|
|
540
|
+
}));
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* A camera's enablement where its own surface states one.
|
|
544
|
+
*
|
|
545
|
+
* The capability accessor is a call into the SDK's binding and can fault, and a device that is not a camera
|
|
546
|
+
* has no accessor at all. Both are unobserved.
|
|
547
|
+
*/
|
|
548
|
+
observedEnablement(device, manifest) {
|
|
549
|
+
try {
|
|
550
|
+
const camera = device?.camera?.();
|
|
551
|
+
return camera ? cameraEnablement(camera, indexDeviceEvidence(manifest).members) : undefined;
|
|
552
|
+
}
|
|
553
|
+
catch {
|
|
554
|
+
return undefined;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* A device's battery level where its own surface states one.
|
|
559
|
+
*
|
|
560
|
+
* The capability accessor is a call into the SDK's binding and can fault, and a device that runs on mains
|
|
561
|
+
* power has no accessor at all. Both are unobserved.
|
|
562
|
+
*/
|
|
563
|
+
observedBattery(device, manifest) {
|
|
564
|
+
try {
|
|
565
|
+
const battery = device?.battery?.();
|
|
566
|
+
return battery ? batteryLevel(battery, indexDeviceEvidence(manifest).members) : undefined;
|
|
567
|
+
}
|
|
568
|
+
catch {
|
|
569
|
+
return undefined;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
publishTerminalState(terminalState, update) {
|
|
573
|
+
if (terminalState === 'stopped') {
|
|
574
|
+
if (this.statusPublisherActive) {
|
|
575
|
+
this.statusPublisher?.stop();
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
else if (this.statusPublisherActive) {
|
|
579
|
+
if (terminalState === 'authentication-required') {
|
|
580
|
+
this.statusPublisher?.update(terminalState, update);
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
this.statusPublisher?.update(terminalState);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
async boundedCleanup(operation, deadline) {
|
|
588
|
+
let timer;
|
|
589
|
+
const timeout = new Promise((resolve) => {
|
|
590
|
+
timer = setTimeout(() => resolve('timeout'), Math.max(0, deadline - Date.now()));
|
|
591
|
+
timer.unref();
|
|
592
|
+
});
|
|
593
|
+
const result = await Promise.race([operation, timeout]);
|
|
594
|
+
clearTimeout(timer);
|
|
595
|
+
return result;
|
|
596
|
+
}
|
|
597
|
+
async settlePendingOwnership(deadline) {
|
|
598
|
+
const pendingOwnership = this.pendingOwnership;
|
|
599
|
+
if (!pendingOwnership) {
|
|
600
|
+
return true;
|
|
601
|
+
}
|
|
602
|
+
const acquisition = pendingOwnership
|
|
603
|
+
.then((ownership) => {
|
|
604
|
+
if (this.pendingOwnership === pendingOwnership) {
|
|
605
|
+
this.pendingOwnership = undefined;
|
|
606
|
+
}
|
|
607
|
+
if (ownership.state === 'owner') {
|
|
608
|
+
this.runtimeLease = ownership.lease;
|
|
609
|
+
}
|
|
610
|
+
return true;
|
|
611
|
+
})
|
|
612
|
+
.catch(() => {
|
|
613
|
+
reportRuntimeNotice(this.log, 'ownership-acquisition-failed');
|
|
614
|
+
return false;
|
|
615
|
+
});
|
|
616
|
+
const settled = await this.boundedCleanup(acquisition, deadline);
|
|
617
|
+
if (settled === 'timeout') {
|
|
618
|
+
void acquisition.then(async (acquired) => {
|
|
619
|
+
if (!acquired) {
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
const released = await this.boundedCleanup(this.releaseRuntimeLease(), Date.now() + this.shutdownTimeoutMs);
|
|
623
|
+
if (released !== true) {
|
|
624
|
+
reportRuntimeNotice(this.log, 'ownership-release-incomplete');
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
return settled;
|
|
629
|
+
}
|
|
630
|
+
publishRegistry(generation, registry, snapshot) {
|
|
631
|
+
const view = Object.freeze({
|
|
632
|
+
version: ++this.registryVersion,
|
|
633
|
+
generation,
|
|
634
|
+
registry: new Map(registry),
|
|
635
|
+
snapshot: parseCompleteDeviceSnapshot(snapshot),
|
|
636
|
+
});
|
|
637
|
+
this.registryView = view;
|
|
638
|
+
for (const listener of this.registryListeners) {
|
|
639
|
+
try {
|
|
640
|
+
listener(view);
|
|
641
|
+
}
|
|
642
|
+
catch {
|
|
643
|
+
reportRuntimeNotice(this.log, 'registry-subscriber-failed');
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
publishUnconfirmedWrite(write) {
|
|
648
|
+
for (const listener of this.unconfirmedListeners) {
|
|
649
|
+
try {
|
|
650
|
+
listener(write);
|
|
651
|
+
}
|
|
652
|
+
catch {
|
|
653
|
+
reportRuntimeNotice(this.log, 'event-subscriber-failed');
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
publishEvent(event) {
|
|
658
|
+
for (const listener of this.eventListeners) {
|
|
659
|
+
try {
|
|
660
|
+
listener(event);
|
|
661
|
+
}
|
|
662
|
+
catch {
|
|
663
|
+
reportRuntimeNotice(this.log, 'event-subscriber-failed');
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
transitionTo(state) {
|
|
668
|
+
if (this.runtimeState === state) {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
this.runtimeState = state;
|
|
672
|
+
for (const listener of this.stateListeners) {
|
|
673
|
+
try {
|
|
674
|
+
listener(state);
|
|
675
|
+
}
|
|
676
|
+
catch {
|
|
677
|
+
reportRuntimeNotice(this.log, 'state-subscriber-failed');
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
//# sourceMappingURL=owner.js.map
|