@camstack/core 0.1.15 → 0.1.16
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/addon/addon-api-factory.d.ts +36 -0
- package/dist/addon/addon-api-factory.d.ts.map +1 -0
- package/dist/addon-routes/addon-route-registry.d.ts +38 -0
- package/dist/addon-routes/addon-route-registry.d.ts.map +1 -0
- package/dist/auth/api-key-manager.d.ts +27 -0
- package/dist/auth/api-key-manager.d.ts.map +1 -0
- package/dist/auth/auth-manager.d.ts +47 -0
- package/dist/auth/auth-manager.d.ts.map +1 -0
- package/dist/auth/parse-record.d.ts +19 -0
- package/dist/auth/parse-record.d.ts.map +1 -0
- package/dist/auth/scoped-token-manager.d.ts +18 -0
- package/dist/auth/scoped-token-manager.d.ts.map +1 -0
- package/dist/auth/user-manager.d.ts +34 -0
- package/dist/auth/user-manager.d.ts.map +1 -0
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.d.ts +54 -0
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.d.ts.map +1 -0
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +223 -217
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js.map +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +216 -7
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs.map +1 -1
- package/dist/builtins/addon-pages-aggregator/index.d.ts +2 -0
- package/dist/builtins/addon-pages-aggregator/index.d.ts.map +1 -0
- package/dist/builtins/addon-pages-aggregator/index.js +6 -221
- package/dist/builtins/addon-pages-aggregator/index.mjs +2 -9
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.d.ts +33 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.d.ts.map +1 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +199 -197
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js.map +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +192 -7
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs.map +1 -1
- package/dist/builtins/addon-widgets-aggregator/index.d.ts +2 -0
- package/dist/builtins/addon-widgets-aggregator/index.d.ts.map +1 -0
- package/dist/builtins/addon-widgets-aggregator/index.js +6 -201
- package/dist/builtins/addon-widgets-aggregator/index.mjs +2 -9
- package/dist/builtins/alerts/alerts.addon.d.ts +82 -0
- package/dist/builtins/alerts/alerts.addon.d.ts.map +1 -0
- package/dist/builtins/alerts/alerts.addon.js +590 -430
- package/dist/builtins/alerts/alerts.addon.js.map +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +595 -7
- package/dist/builtins/alerts/alerts.addon.mjs.map +1 -1
- package/dist/builtins/alerts/index.d.ts +2 -0
- package/dist/builtins/alerts/index.d.ts.map +1 -0
- package/dist/builtins/alerts/index.js +3 -443
- package/dist/builtins/alerts/index.mjs +2 -8
- package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.d.ts +8 -0
- package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.d.ts.map +1 -0
- package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.js +56 -0
- package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.js.map +1 -0
- package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.mjs +50 -0
- package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.mjs.map +1 -0
- package/dist/builtins/auth-orchestrator/index.d.ts +2 -0
- package/dist/builtins/auth-orchestrator/index.d.ts.map +1 -0
- package/dist/builtins/auth-orchestrator/index.js +7 -0
- package/dist/builtins/auth-orchestrator/index.mjs +2 -0
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +148 -0
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts.map +1 -0
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +7639 -0
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js.map +1 -0
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +7627 -0
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs.map +1 -0
- package/dist/builtins/backup-orchestrator/cron-helpers.d.ts +24 -0
- package/dist/builtins/backup-orchestrator/cron-helpers.d.ts.map +1 -0
- package/dist/builtins/backup-orchestrator/destination-policy.d.ts +73 -0
- package/dist/builtins/backup-orchestrator/destination-policy.d.ts.map +1 -0
- package/dist/builtins/backup-orchestrator/download-helpers.d.ts +13 -0
- package/dist/builtins/backup-orchestrator/download-helpers.d.ts.map +1 -0
- package/dist/builtins/backup-orchestrator/index.d.ts +3 -0
- package/dist/builtins/backup-orchestrator/index.d.ts.map +1 -0
- package/dist/builtins/backup-orchestrator/index.js +7 -0
- package/dist/builtins/backup-orchestrator/index.mjs +2 -0
- package/dist/builtins/backup-orchestrator/manifest-store.d.ts +78 -0
- package/dist/builtins/backup-orchestrator/manifest-store.d.ts.map +1 -0
- package/dist/builtins/console-logging/console-destination.d.ts +14 -0
- package/dist/builtins/console-logging/console-destination.d.ts.map +1 -0
- package/dist/builtins/console-logging/console-logging.addon.d.ts +26 -0
- package/dist/builtins/console-logging/console-logging.addon.d.ts.map +1 -0
- package/dist/builtins/console-logging/index.d.ts +4 -0
- package/dist/builtins/console-logging/index.d.ts.map +1 -0
- package/dist/builtins/console-logging/index.js +99 -235
- package/dist/builtins/console-logging/index.js.map +1 -1
- package/dist/builtins/console-logging/index.mjs +95 -9
- package/dist/builtins/console-logging/index.mjs.map +1 -1
- package/dist/builtins/device-manager/device-event-propagator.d.ts +27 -0
- package/dist/builtins/device-manager/device-event-propagator.d.ts.map +1 -0
- package/dist/builtins/device-manager/device-manager.addon.d.ts +259 -0
- package/dist/builtins/device-manager/device-manager.addon.d.ts.map +1 -0
- package/dist/builtins/device-manager/device-manager.addon.js +2125 -2127
- package/dist/builtins/device-manager/device-manager.addon.js.map +1 -1
- package/dist/builtins/device-manager/device-manager.addon.mjs +2145 -7
- package/dist/builtins/device-manager/device-manager.addon.mjs.map +1 -1
- package/dist/builtins/device-manager/index.d.ts +3 -0
- package/dist/builtins/device-manager/index.d.ts.map +1 -0
- package/dist/builtins/device-manager/index.js +6 -2156
- package/dist/builtins/device-manager/index.mjs +2 -10
- package/dist/builtins/hub-forwarder/hub-forwarder-destination.d.ts +45 -0
- package/dist/builtins/hub-forwarder/hub-forwarder-destination.d.ts.map +1 -0
- package/dist/builtins/hub-forwarder/hub-forwarder.addon.d.ts +16 -0
- package/dist/builtins/hub-forwarder/hub-forwarder.addon.d.ts.map +1 -0
- package/dist/builtins/hub-forwarder/index.d.ts +4 -0
- package/dist/builtins/hub-forwarder/index.d.ts.map +1 -0
- package/dist/builtins/hub-forwarder/index.js +150 -291
- package/dist/builtins/hub-forwarder/index.js.map +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +145 -9
- package/dist/builtins/hub-forwarder/index.mjs.map +1 -1
- package/dist/builtins/local-auth/auth-schema.d.ts +12 -0
- package/dist/builtins/local-auth/auth-schema.d.ts.map +1 -0
- package/dist/builtins/local-auth/index.d.ts +2 -0
- package/dist/builtins/local-auth/index.d.ts.map +1 -0
- package/dist/builtins/local-auth/index.js +3 -623
- package/dist/builtins/local-auth/index.mjs +2 -8
- package/dist/builtins/local-auth/local-auth.addon.d.ts +17 -0
- package/dist/builtins/local-auth/local-auth.addon.d.ts.map +1 -0
- package/dist/builtins/local-auth/local-auth.addon.js +6861 -589
- package/dist/builtins/local-auth/local-auth.addon.js.map +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +6883 -7
- package/dist/builtins/local-auth/local-auth.addon.mjs.map +1 -1
- package/dist/builtins/local-network/index.d.ts +3 -0
- package/dist/builtins/local-network/index.d.ts.map +1 -0
- package/dist/builtins/local-network/index.js +9 -0
- package/dist/builtins/local-network/index.mjs +2 -0
- package/dist/builtins/local-network/local-network.addon.d.ts +102 -0
- package/dist/builtins/local-network/local-network.addon.d.ts.map +1 -0
- package/dist/builtins/local-network/local-network.addon.js +404 -0
- package/dist/builtins/local-network/local-network.addon.js.map +1 -0
- package/dist/builtins/local-network/local-network.addon.mjs +392 -0
- package/dist/builtins/local-network/local-network.addon.mjs.map +1 -0
- package/dist/builtins/mesh-orchestrator/index.d.ts +2 -0
- package/dist/builtins/mesh-orchestrator/index.d.ts.map +1 -0
- package/dist/builtins/mesh-orchestrator/index.js +7 -0
- package/dist/builtins/mesh-orchestrator/index.mjs +2 -0
- package/dist/builtins/mesh-orchestrator/mesh-orchestrator.addon.d.ts +9 -0
- package/dist/builtins/mesh-orchestrator/mesh-orchestrator.addon.d.ts.map +1 -0
- package/dist/builtins/mesh-orchestrator/mesh-orchestrator.addon.js +83 -0
- package/dist/builtins/mesh-orchestrator/mesh-orchestrator.addon.js.map +1 -0
- package/dist/builtins/mesh-orchestrator/mesh-orchestrator.addon.mjs +77 -0
- package/dist/builtins/mesh-orchestrator/mesh-orchestrator.addon.mjs.map +1 -0
- package/dist/builtins/native-metrics/index.d.ts +3 -0
- package/dist/builtins/native-metrics/index.d.ts.map +1 -0
- package/dist/builtins/native-metrics/native-metrics-provider.d.ts +49 -0
- package/dist/builtins/native-metrics/native-metrics-provider.d.ts.map +1 -0
- package/dist/builtins/native-metrics/native-metrics.addon.d.ts +74 -0
- package/dist/builtins/native-metrics/native-metrics.addon.d.ts.map +1 -0
- package/dist/builtins/native-metrics/native-metrics.addon.js +887 -861
- package/dist/builtins/native-metrics/native-metrics.addon.js.map +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +914 -5
- package/dist/builtins/native-metrics/native-metrics.addon.mjs.map +1 -1
- package/dist/builtins/platform-probe/index.d.ts +12 -0
- package/dist/builtins/platform-probe/index.d.ts.map +1 -0
- package/dist/builtins/platform-probe/index.js +539 -0
- package/dist/builtins/platform-probe/index.js.map +1 -0
- package/dist/builtins/platform-probe/index.mjs +530 -0
- package/dist/builtins/platform-probe/index.mjs.map +1 -0
- package/dist/builtins/platform-probe/inference-config-resolver.d.ts +30 -0
- package/dist/builtins/platform-probe/inference-config-resolver.d.ts.map +1 -0
- package/dist/builtins/platform-probe/platform-scorer.d.ts +22 -0
- package/dist/builtins/platform-probe/platform-scorer.d.ts.map +1 -0
- package/dist/builtins/remote-access-orchestrator/index.d.ts +2 -0
- package/dist/builtins/remote-access-orchestrator/index.d.ts.map +1 -0
- package/dist/builtins/remote-access-orchestrator/index.js +7 -0
- package/dist/builtins/remote-access-orchestrator/index.mjs +2 -0
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.d.ts +9 -0
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.d.ts.map +1 -0
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +72 -0
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js.map +1 -0
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +66 -0
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs.map +1 -0
- package/dist/builtins/snapshot/index.d.ts +3 -0
- package/dist/builtins/snapshot/index.d.ts.map +1 -0
- package/dist/builtins/snapshot/index.js +481 -491
- package/dist/builtins/snapshot/index.js.map +1 -1
- package/dist/builtins/snapshot/index.mjs +475 -464
- package/dist/builtins/snapshot/index.mjs.map +1 -1
- package/dist/builtins/snapshot/snapshot.addon.d.ts +121 -0
- package/dist/builtins/snapshot/snapshot.addon.d.ts.map +1 -0
- package/dist/builtins/sqlite-storage/config-store.d.ts +9 -0
- package/dist/builtins/sqlite-storage/config-store.d.ts.map +1 -0
- package/dist/builtins/sqlite-storage/device-store.d.ts +24 -0
- package/dist/builtins/sqlite-storage/device-store.d.ts.map +1 -0
- package/dist/builtins/sqlite-storage/filesystem-storage-provider.d.ts +87 -0
- package/dist/builtins/sqlite-storage/filesystem-storage-provider.d.ts.map +1 -0
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.ts +32 -0
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.ts.map +1 -0
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +312 -56
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js.map +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +305 -7
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs.map +1 -1
- package/dist/builtins/sqlite-storage/index.d.ts +12 -0
- package/dist/builtins/sqlite-storage/index.d.ts.map +1 -0
- package/dist/builtins/sqlite-storage/index.js +229 -1001
- package/dist/builtins/sqlite-storage/index.js.map +1 -1
- package/dist/builtins/sqlite-storage/index.mjs +268 -26
- package/dist/builtins/sqlite-storage/index.mjs.map +1 -1
- package/dist/builtins/sqlite-storage/integration-registry.d.ts +28 -0
- package/dist/builtins/sqlite-storage/integration-registry.d.ts.map +1 -0
- package/dist/builtins/sqlite-storage/settings-store.d.ts +40 -0
- package/dist/builtins/sqlite-storage/settings-store.d.ts.map +1 -0
- package/dist/builtins/sqlite-storage/sql-schema.d.ts +33 -0
- package/dist/builtins/sqlite-storage/sql-schema.d.ts.map +1 -0
- package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +94 -0
- package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts.map +1 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +15 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts.map +1 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +586 -653
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js.map +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +582 -7
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs.map +1 -1
- package/dist/builtins/storage-orchestrator/index.d.ts +7 -0
- package/dist/builtins/storage-orchestrator/index.d.ts.map +1 -0
- package/dist/builtins/storage-orchestrator/index.js +9 -0
- package/dist/builtins/storage-orchestrator/index.mjs +2 -0
- package/dist/builtins/storage-orchestrator/location-store.d.ts +50 -0
- package/dist/builtins/storage-orchestrator/location-store.d.ts.map +1 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +60 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts.map +1 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +755 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js.map +1 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +746 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs.map +1 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +121 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts.map +1 -0
- package/dist/builtins/system-backup/system-backup.service.d.ts +138 -0
- package/dist/builtins/system-backup/system-backup.service.d.ts.map +1 -0
- package/dist/builtins/system-config/index.d.ts +2 -0
- package/dist/builtins/system-config/index.d.ts.map +1 -0
- package/dist/builtins/system-config/index.js +6 -188
- package/dist/builtins/system-config/index.mjs +2 -10
- package/dist/builtins/system-config/system-config.addon.d.ts +11 -0
- package/dist/builtins/system-config/system-config.addon.d.ts.map +1 -0
- package/dist/builtins/system-config/system-config.addon.js +227 -180
- package/dist/builtins/system-config/system-config.addon.js.map +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +226 -7
- package/dist/builtins/system-config/system-config.addon.mjs.map +1 -1
- package/dist/builtins/turn-orchestrator/index.d.ts +2 -0
- package/dist/builtins/turn-orchestrator/index.d.ts.map +1 -0
- package/dist/builtins/turn-orchestrator/index.js +7 -0
- package/dist/builtins/turn-orchestrator/index.mjs +2 -0
- package/dist/builtins/turn-orchestrator/turn-orchestrator.addon.d.ts +10 -0
- package/dist/builtins/turn-orchestrator/turn-orchestrator.addon.d.ts.map +1 -0
- package/dist/builtins/turn-orchestrator/turn-orchestrator.addon.js +78 -0
- package/dist/builtins/turn-orchestrator/turn-orchestrator.addon.js.map +1 -0
- package/dist/builtins/turn-orchestrator/turn-orchestrator.addon.mjs +72 -0
- package/dist/builtins/turn-orchestrator/turn-orchestrator.addon.mjs.map +1 -0
- package/dist/builtins/winston-logging/index.d.ts +4 -0
- package/dist/builtins/winston-logging/index.d.ts.map +1 -0
- package/dist/builtins/winston-logging/index.js +153 -300
- package/dist/builtins/winston-logging/index.js.map +1 -1
- package/dist/builtins/winston-logging/index.mjs +144 -9
- package/dist/builtins/winston-logging/index.mjs.map +1 -1
- package/dist/builtins/winston-logging/winston-destination.d.ts +22 -0
- package/dist/builtins/winston-logging/winston-destination.d.ts.map +1 -0
- package/dist/builtins/winston-logging/winston-logging.addon.d.ts +20 -0
- package/dist/builtins/winston-logging/winston-logging.addon.d.ts.map +1 -0
- package/dist/chunk-C13QxCFV.js +50 -0
- package/dist/chunk-hT5z_Zn9.mjs +35 -0
- package/dist/download/model-download-service.d.ts +42 -0
- package/dist/download/model-download-service.d.ts.map +1 -0
- package/dist/download/model-downloader.d.ts +32 -0
- package/dist/download/model-downloader.d.ts.map +1 -0
- package/dist/events/event-bus.d.ts +11 -0
- package/dist/events/event-bus.d.ts.map +1 -0
- package/dist/events/system-event-bus.d.ts +15 -0
- package/dist/events/system-event-bus.d.ts.map +1 -0
- package/dist/feature/feature-manager.d.ts +12 -0
- package/dist/feature/feature-manager.d.ts.map +1 -0
- package/dist/formatter-C-5An4Bl.mjs +164 -0
- package/dist/formatter-C-5An4Bl.mjs.map +1 -0
- package/dist/formatter-Dr_6NNZc.js +169 -0
- package/dist/formatter-Dr_6NNZc.js.map +1 -0
- package/dist/index.d.ts +76 -1696
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7780 -8035
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7707 -2148
- package/dist/index.mjs.map +1 -1
- package/dist/lifecycle/lifecycle-state-machine.d.ts +29 -0
- package/dist/lifecycle/lifecycle-state-machine.d.ts.map +1 -0
- package/dist/logging/formatter.d.ts +31 -0
- package/dist/logging/formatter.d.ts.map +1 -0
- package/dist/logging/log-manager.d.ts +52 -0
- package/dist/logging/log-manager.d.ts.map +1 -0
- package/dist/logging/log-ring-buffer.d.ts +48 -0
- package/dist/logging/log-ring-buffer.d.ts.map +1 -0
- package/dist/logging/scoped-logger.d.ts +18 -0
- package/dist/logging/scoped-logger.d.ts.map +1 -0
- package/dist/network/network-quality.d.ts +12 -0
- package/dist/network/network-quality.d.ts.map +1 -0
- package/dist/notification/notification-service.d.ts +38 -0
- package/dist/notification/notification-service.d.ts.map +1 -0
- package/dist/notification/toast-service.d.ts +23 -0
- package/dist/notification/toast-service.d.ts.map +1 -0
- package/dist/pipeline/engine-manager-resolver.d.ts +16 -0
- package/dist/pipeline/engine-manager-resolver.d.ts.map +1 -0
- package/dist/pipeline/pipeline-runner.d.ts +9 -0
- package/dist/pipeline/pipeline-runner.d.ts.map +1 -0
- package/dist/pipeline/pipeline-validator.d.ts +14 -0
- package/dist/pipeline/pipeline-validator.d.ts.map +1 -0
- package/dist/process/resource-monitor.d.ts +12 -0
- package/dist/process/resource-monitor.d.ts.map +1 -0
- package/dist/python/python-env-manager.d.ts +13 -0
- package/dist/python/python-env-manager.d.ts.map +1 -0
- package/dist/repl/interfaces.d.ts +32 -0
- package/dist/repl/interfaces.d.ts.map +1 -0
- package/dist/repl/repl-engine.d.ts +9 -0
- package/dist/repl/repl-engine.d.ts.map +1 -0
- package/dist/resource-monitor-CmuWlmap.js +76 -0
- package/dist/resource-monitor-CmuWlmap.js.map +1 -0
- package/dist/resource-monitor-DcQdKGYU.mjs +59 -0
- package/dist/resource-monitor-DcQdKGYU.mjs.map +1 -0
- package/dist/storage/fs-storage-backend.d.ts +41 -0
- package/dist/storage/fs-storage-backend.d.ts.map +1 -0
- package/dist/storage/storage-location-manager.d.ts +24 -0
- package/dist/storage/storage-location-manager.d.ts.map +1 -0
- package/dist/storage/storage-manager.d.ts +77 -0
- package/dist/storage/storage-manager.d.ts.map +1 -0
- package/dist/tls/cert-manager.d.ts +27 -0
- package/dist/tls/cert-manager.d.ts.map +1 -0
- package/dist/tls/index.d.ts +2 -0
- package/dist/tls/index.d.ts.map +1 -0
- package/package.json +119 -23
- package/dist/builtins/addon-pages-aggregator/index.js.map +0 -1
- package/dist/builtins/addon-pages-aggregator/index.mjs.map +0 -1
- package/dist/builtins/addon-widgets-aggregator/index.js.map +0 -1
- package/dist/builtins/addon-widgets-aggregator/index.mjs.map +0 -1
- package/dist/builtins/alerts/index.js.map +0 -1
- package/dist/builtins/alerts/index.mjs.map +0 -1
- package/dist/builtins/device-manager/index.js.map +0 -1
- package/dist/builtins/device-manager/index.mjs.map +0 -1
- package/dist/builtins/local-auth/index.js.map +0 -1
- package/dist/builtins/local-auth/index.mjs.map +0 -1
- package/dist/builtins/local-backup/index.js +0 -173
- package/dist/builtins/local-backup/index.js.map +0 -1
- package/dist/builtins/local-backup/index.mjs +0 -10
- package/dist/builtins/local-backup/index.mjs.map +0 -1
- package/dist/builtins/system-config/index.js.map +0 -1
- package/dist/builtins/system-config/index.mjs.map +0 -1
- package/dist/chunk-2CIYKDRN.mjs +0 -1
- package/dist/chunk-2CIYKDRN.mjs.map +0 -1
- package/dist/chunk-2F76X6NL.mjs +0 -136
- package/dist/chunk-2F76X6NL.mjs.map +0 -1
- package/dist/chunk-2QUFBZ7M.mjs +0 -1
- package/dist/chunk-2QUFBZ7M.mjs.map +0 -1
- package/dist/chunk-3BK2Y7GY.mjs +0 -593
- package/dist/chunk-3BK2Y7GY.mjs.map +0 -1
- package/dist/chunk-4OOHFJHT.mjs +0 -421
- package/dist/chunk-4OOHFJHT.mjs.map +0 -1
- package/dist/chunk-4XHB7IHT.mjs +0 -809
- package/dist/chunk-4XHB7IHT.mjs.map +0 -1
- package/dist/chunk-6M2HSSTQ.mjs +0 -98
- package/dist/chunk-6M2HSSTQ.mjs.map +0 -1
- package/dist/chunk-7FI7SQS7.mjs +0 -135
- package/dist/chunk-7FI7SQS7.mjs.map +0 -1
- package/dist/chunk-ED57RCQE.mjs +0 -171
- package/dist/chunk-ED57RCQE.mjs.map +0 -1
- package/dist/chunk-FZN56HGQ.mjs +0 -626
- package/dist/chunk-FZN56HGQ.mjs.map +0 -1
- package/dist/chunk-GL4OOB25.mjs +0 -51
- package/dist/chunk-GL4OOB25.mjs.map +0 -1
- package/dist/chunk-KDG2NTDB.mjs +0 -137
- package/dist/chunk-KDG2NTDB.mjs.map +0 -1
- package/dist/chunk-NRBQWBDM.mjs +0 -191
- package/dist/chunk-NRBQWBDM.mjs.map +0 -1
- package/dist/chunk-O4V246GG.mjs +0 -2137
- package/dist/chunk-O4V246GG.mjs.map +0 -1
- package/dist/chunk-QT57H266.mjs +0 -163
- package/dist/chunk-QT57H266.mjs.map +0 -1
- package/dist/chunk-QX4RH25I.mjs +0 -141
- package/dist/chunk-QX4RH25I.mjs.map +0 -1
- package/dist/chunk-TB562PZX.mjs +0 -86
- package/dist/chunk-TB562PZX.mjs.map +0 -1
- package/dist/chunk-TDYPZXK5.mjs +0 -1
- package/dist/chunk-TDYPZXK5.mjs.map +0 -1
- package/dist/chunk-UJI4LN5P.mjs +0 -36
- package/dist/chunk-UJI4LN5P.mjs.map +0 -1
- package/dist/chunk-W6RTHQGP.mjs +0 -1
- package/dist/chunk-W6RTHQGP.mjs.map +0 -1
- package/dist/chunk-ZELBCPDC.mjs +0 -369
- package/dist/chunk-ZELBCPDC.mjs.map +0 -1
- package/dist/index.d.mts +0 -1696
- package/dist/resource-monitor-UZUGPIAU.mjs +0 -9
- package/dist/resource-monitor-UZUGPIAU.mjs.map +0 -1
- package/dist/storage-location-manager-HFNB3PCS.mjs +0 -7
- package/dist/storage-location-manager-HFNB3PCS.mjs.map +0 -1
|
@@ -1,2155 +1,2153 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/builtins/device-manager/device-manager.addon.ts
|
|
21
|
-
var device_manager_addon_exports = {};
|
|
22
|
-
__export(device_manager_addon_exports, {
|
|
23
|
-
DeviceManagerAddon: () => DeviceManagerAddon,
|
|
24
|
-
default: () => device_manager_addon_default
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
25
4
|
});
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
5
|
+
require("../../chunk-C13QxCFV.js");
|
|
6
|
+
let node_crypto = require("node:crypto");
|
|
7
|
+
let _camstack_types = require("@camstack/types");
|
|
8
|
+
//#region src/builtins/device-manager/device-event-propagator.ts
|
|
9
|
+
/**
|
|
10
|
+
* Walks the parent chain for every device-sourced event and re-emits a
|
|
11
|
+
* copy on each ancestor scope with `via[]` populated.
|
|
12
|
+
*
|
|
13
|
+
* Design goals:
|
|
14
|
+
* - Transparent: drivers emit once on their own device scope; the
|
|
15
|
+
* framework handles fan-out. Zero provider boilerplate.
|
|
16
|
+
* - Anti-loop: events that already carry `via[]` are skipped (we only
|
|
17
|
+
* propagate ORIGINAL emissions).
|
|
18
|
+
* - Anti-cycle: the parent chain is bounded — if the device registry
|
|
19
|
+
* is corrupt and has a cycle, the walker caps at `MAX_CHAIN_DEPTH`
|
|
20
|
+
* and logs a warning.
|
|
21
|
+
* - Lazy: parent chain is resolved on-demand per event (no cached
|
|
22
|
+
* topology). The lookup is O(depth) which is ≤2 in practice.
|
|
23
|
+
*
|
|
24
|
+
* `via` contract (from SystemEvent.via JSDoc):
|
|
25
|
+
* - `via[0]` is the originating source (the device that produced the
|
|
26
|
+
* event). Subsequent entries walk up the parent chain.
|
|
27
|
+
* - On the re-emission, `source` is the ancestor at that level and
|
|
28
|
+
* `via[0..i]` is the prefix of the chain up to and including the
|
|
29
|
+
* first N ancestors below the current one.
|
|
30
|
+
*
|
|
31
|
+
* Example (grandchild → parent → grandparent):
|
|
32
|
+
* Original: { source: {id: 7}, data: {...}, via: undefined }
|
|
33
|
+
* Re-emit 1: { source: {id: 4}, data: {...}, via: [{id: 7}] }
|
|
34
|
+
* Re-emit 2: { source: {id: 1}, data: {...}, via: [{id: 7}, {id: 4}] }
|
|
35
|
+
*
|
|
36
|
+
* A consumer listening at `source.id === 1` receives re-emit 2 (with
|
|
37
|
+
* `via` showing the chain). A consumer listening at `source.id === 7`
|
|
38
|
+
* with `via === undefined` receives the original only.
|
|
39
|
+
*/
|
|
40
|
+
/** Bounded walk — paranoia against corrupt device registries with cycles. */
|
|
31
41
|
var MAX_CHAIN_DEPTH = 16;
|
|
32
42
|
var DeviceEventPropagator = class {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
43
|
+
unsubscribe = null;
|
|
44
|
+
constructor(opts) {
|
|
45
|
+
this.opts = opts;
|
|
46
|
+
}
|
|
47
|
+
start() {
|
|
48
|
+
if (this.unsubscribe) return;
|
|
49
|
+
const unsub = this.opts.eventBus.subscribe({}, (ev) => this.handle(ev));
|
|
50
|
+
this.unsubscribe = unsub;
|
|
51
|
+
}
|
|
52
|
+
stop() {
|
|
53
|
+
if (!this.unsubscribe) return;
|
|
54
|
+
this.unsubscribe();
|
|
55
|
+
this.unsubscribe = null;
|
|
56
|
+
}
|
|
57
|
+
/** Exposed for tests — lets them inject events without the full bus. */
|
|
58
|
+
handle(ev) {
|
|
59
|
+
if (ev.via !== void 0) return;
|
|
60
|
+
if (ev.source.type !== "device") return;
|
|
61
|
+
const rawId = ev.source.id;
|
|
62
|
+
const deviceId = typeof rawId === "number" ? rawId : Number(rawId);
|
|
63
|
+
if (!Number.isFinite(deviceId)) return;
|
|
64
|
+
const chain = this.resolveParentChain(deviceId);
|
|
65
|
+
if (chain.length === 0) return;
|
|
66
|
+
const via = [ev.source];
|
|
67
|
+
for (const ancestorId of chain) {
|
|
68
|
+
const reEmission = {
|
|
69
|
+
...ev,
|
|
70
|
+
source: {
|
|
71
|
+
type: "device",
|
|
72
|
+
id: ancestorId
|
|
73
|
+
},
|
|
74
|
+
via: [...via]
|
|
75
|
+
};
|
|
76
|
+
this.opts.eventBus.emit(reEmission);
|
|
77
|
+
via.push({
|
|
78
|
+
type: "device",
|
|
79
|
+
id: ancestorId
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
resolveParentChain(deviceId) {
|
|
84
|
+
const chain = [];
|
|
85
|
+
const seen = new Set([deviceId]);
|
|
86
|
+
let current = this.opts.getParentOf(deviceId);
|
|
87
|
+
while (current != null) {
|
|
88
|
+
if (seen.has(current)) {
|
|
89
|
+
this.opts.logger.warn("device-event-propagator: cycle detected in parent chain — aborting propagation", {
|
|
90
|
+
tags: { deviceId },
|
|
91
|
+
meta: {
|
|
92
|
+
cycleAt: current,
|
|
93
|
+
chainSoFar: [...chain]
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return chain;
|
|
97
|
+
}
|
|
98
|
+
seen.add(current);
|
|
99
|
+
chain.push(current);
|
|
100
|
+
if (chain.length >= MAX_CHAIN_DEPTH) {
|
|
101
|
+
this.opts.logger.warn("device-event-propagator: chain depth limit hit — truncating", {
|
|
102
|
+
tags: { deviceId },
|
|
103
|
+
meta: {
|
|
104
|
+
depth: chain.length,
|
|
105
|
+
max: MAX_CHAIN_DEPTH
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
current = this.opts.getParentOf(current);
|
|
111
|
+
}
|
|
112
|
+
return chain;
|
|
113
|
+
}
|
|
94
114
|
};
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/builtins/device-manager/device-manager.addon.ts
|
|
117
|
+
/**
|
|
118
|
+
* Device Manager addon — hub-side singleton that unifies device persistence,
|
|
119
|
+
* live registry queries, and all management operations into a single
|
|
120
|
+
* tRPC-routable capability.
|
|
121
|
+
*
|
|
122
|
+
* Persistence strategy: all device data is stored via `ctx.settings`, the same
|
|
123
|
+
* settings API every other addon uses. No raw SQLite access.
|
|
124
|
+
*
|
|
125
|
+
* Addon store layout:
|
|
126
|
+
* deviceIndex → Record<addonId, stableId[]> (which devices exist per addon)
|
|
127
|
+
* deviceMeta → Record<"addonId:stableId", DeviceMeta> (type, name, parentDeviceId, id)
|
|
128
|
+
*
|
|
129
|
+
* Device store (per-device config):
|
|
130
|
+
* readDeviceStore(numericDeviceId) → config blob
|
|
131
|
+
* writeDeviceStore(numericDeviceId, patch)
|
|
132
|
+
*
|
|
133
|
+
* Live registry: resolved from the kernel capability registry after Phase 2.
|
|
134
|
+
* This gives direct access to in-memory IDevice instances registered by provider addons.
|
|
135
|
+
* The DeviceManagerAddon is the single owner of the live device operations API.
|
|
136
|
+
*
|
|
137
|
+
* Replaces:
|
|
138
|
+
* - `device-persistence` capability (absorbed here)
|
|
139
|
+
* - live operations previously served by `device-management.router.ts`
|
|
140
|
+
*/
|
|
100
141
|
function shallowEqual(a, b) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
return true;
|
|
142
|
+
const ak = Object.keys(a);
|
|
143
|
+
const bk = Object.keys(b);
|
|
144
|
+
if (ak.length !== bk.length) return false;
|
|
145
|
+
for (const k of ak) if (a[k] !== b[k]) return false;
|
|
146
|
+
return true;
|
|
108
147
|
}
|
|
109
148
|
function deviceKey(addonId, stableId) {
|
|
110
|
-
|
|
149
|
+
return `${addonId}:${stableId}`;
|
|
111
150
|
}
|
|
112
151
|
function isCameraDevice(device) {
|
|
113
|
-
|
|
152
|
+
return "getStreamSources" in device && typeof device.getStreamSources === "function";
|
|
114
153
|
}
|
|
115
|
-
var DEVICE_FEATURE_VALUES = new Set(Object.values(
|
|
154
|
+
var DEVICE_FEATURE_VALUES = new Set(Object.values(_camstack_types.DeviceFeature));
|
|
155
|
+
/**
|
|
156
|
+
* Validate persisted feature strings against the `DeviceFeature` enum
|
|
157
|
+
* — workers serialise the live `device.features` array (so every entry
|
|
158
|
+
* is a valid enum value at write time) but the persisted blob is loose
|
|
159
|
+
* `string[]` on the wire. The narrow keeps unknown values out of the
|
|
160
|
+
* `getDevice` response without losing the enum-typed contract.
|
|
161
|
+
*/
|
|
116
162
|
function persistedFeatures(features) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
return out;
|
|
163
|
+
if (!features) return [];
|
|
164
|
+
const out = [];
|
|
165
|
+
for (const f of features) if (DEVICE_FEATURE_VALUES.has(f)) out.push(f);
|
|
166
|
+
return out;
|
|
123
167
|
}
|
|
124
168
|
function toDeviceInfo(addonId, device, metadata = null, metaRow = null) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// via `getAccessoryChildren()` — Reolink siren/floodlight/PIR,
|
|
147
|
-
// Hikvision supplemental light/siren). Top-level devices and
|
|
148
|
-
// first-class hub-adopted children leave it `undefined`. Surfacing
|
|
149
|
-
// it on `DeviceInfo` lets the UI distinguish accessory rows from
|
|
150
|
-
// adopted-camera rows without re-deriving the heuristic from the
|
|
151
|
-
// provider's `deviceClasses` map.
|
|
152
|
-
role: device.role ?? null,
|
|
153
|
-
online: device.online,
|
|
154
|
-
features: [...device.features],
|
|
155
|
-
isCamera: isCameraDevice(device),
|
|
156
|
-
config: configValues,
|
|
157
|
-
metadata
|
|
158
|
-
};
|
|
169
|
+
const configValues = {};
|
|
170
|
+
for (const entry of device.config.entries()) configValues[entry.key] = entry.value;
|
|
171
|
+
const name = metaRow?.name ?? device.name;
|
|
172
|
+
const location = metaRow?.location !== void 0 ? metaRow.location : device.location;
|
|
173
|
+
const disabled = metaRow?.disabled ?? device.disabled;
|
|
174
|
+
return {
|
|
175
|
+
id: device.id,
|
|
176
|
+
stableId: device.stableId,
|
|
177
|
+
addonId,
|
|
178
|
+
type: device.type,
|
|
179
|
+
name,
|
|
180
|
+
location,
|
|
181
|
+
disabled,
|
|
182
|
+
parentDeviceId: device.parentDeviceId,
|
|
183
|
+
role: device.role ?? null,
|
|
184
|
+
online: device.online,
|
|
185
|
+
features: [...device.features],
|
|
186
|
+
isCamera: isCameraDevice(device),
|
|
187
|
+
config: configValues,
|
|
188
|
+
metadata
|
|
189
|
+
};
|
|
159
190
|
}
|
|
160
191
|
function resolveDeviceById(registry, deviceId) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
192
|
+
const device = registry.getById(deviceId);
|
|
193
|
+
if (!device) return null;
|
|
194
|
+
const addonId = registry.getAddonId(deviceId);
|
|
195
|
+
if (!addonId) return null;
|
|
196
|
+
return {
|
|
197
|
+
addonId,
|
|
198
|
+
device
|
|
199
|
+
};
|
|
166
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Walk the sections/fields of a contribution and inject `writerCapName` +
|
|
203
|
+
* `writerAddonId` + `source` on each editable field. Readonly fields and
|
|
204
|
+
* structural fields (separator/info/button) pass through untouched. The
|
|
205
|
+
* aggregator is the single place that knows provenance — provider schemas
|
|
206
|
+
* stay clean, UI-bound metadata is attached once at the boundary.
|
|
207
|
+
*/
|
|
167
208
|
function tagContribution(contribution, capName, addonId, kind) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
209
|
+
const source = kind === "settings" ? "settings" : "live";
|
|
210
|
+
return {
|
|
211
|
+
...contribution.tabs ? { tabs: [...contribution.tabs] } : {},
|
|
212
|
+
sections: contribution.sections.map((section) => ({
|
|
213
|
+
...section,
|
|
214
|
+
fields: section.fields.map((field) => tagField(field, capName, addonId, source, kind))
|
|
215
|
+
}))
|
|
216
|
+
};
|
|
176
217
|
}
|
|
177
218
|
function isFieldRecord(value) {
|
|
178
|
-
|
|
219
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
179
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* Convert a strict `ConfigUISchemaWithValues` (readonly arrays, typed
|
|
223
|
+
* field union) into the cap wire shape `ContributionShape` (mutable
|
|
224
|
+
* arrays, opaque field records). Required because the cap method z.infer
|
|
225
|
+
* uses mutable arrays — readonly arrays are not assignable to mutable
|
|
226
|
+
* even when structurally identical, so a structural copy bridges the gap
|
|
227
|
+
* without disabling the type checker.
|
|
228
|
+
*/
|
|
180
229
|
function toWireShape(input) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
...s.order !== void 0 ? { order: s.order } : {},
|
|
196
|
-
fields: [...s.fields]
|
|
197
|
-
}))
|
|
198
|
-
};
|
|
199
|
-
if (input.tabs) out.tabs = [...input.tabs];
|
|
200
|
-
return out;
|
|
230
|
+
const out = { sections: input.sections.map((s) => ({
|
|
231
|
+
id: s.id,
|
|
232
|
+
title: s.title,
|
|
233
|
+
...s.description !== void 0 ? { description: s.description } : {},
|
|
234
|
+
...s.style !== void 0 ? { style: s.style } : {},
|
|
235
|
+
...s.defaultCollapsed !== void 0 ? { defaultCollapsed: s.defaultCollapsed } : {},
|
|
236
|
+
...s.columns !== void 0 ? { columns: s.columns } : {},
|
|
237
|
+
...s.tab !== void 0 ? { tab: s.tab } : {},
|
|
238
|
+
...s.location !== void 0 ? { location: s.location } : {},
|
|
239
|
+
...s.order !== void 0 ? { order: s.order } : {},
|
|
240
|
+
fields: [...s.fields]
|
|
241
|
+
})) };
|
|
242
|
+
if (input.tabs) out.tabs = [...input.tabs];
|
|
243
|
+
return out;
|
|
201
244
|
}
|
|
202
245
|
function tagField(field, capName, addonId, source, kind) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
246
|
+
if (!isFieldRecord(field)) return field;
|
|
247
|
+
const f = field;
|
|
248
|
+
const structuralTypes = new Set([
|
|
249
|
+
"separator",
|
|
250
|
+
"info",
|
|
251
|
+
"button"
|
|
252
|
+
]);
|
|
253
|
+
if (typeof f.type === "string" && structuralTypes.has(f.type)) return field;
|
|
254
|
+
const tagged = {
|
|
255
|
+
...f,
|
|
256
|
+
source
|
|
257
|
+
};
|
|
258
|
+
if (kind === "live" || f.readonlyField === true) tagged.readonlyField = true;
|
|
259
|
+
else {
|
|
260
|
+
tagged.writerCapName = capName;
|
|
261
|
+
tagged.writerAddonId = addonId;
|
|
262
|
+
}
|
|
263
|
+
if (f.type === "group") {
|
|
264
|
+
const children = Array.isArray(f.fields) ? f.fields : [];
|
|
265
|
+
if (children.length > 0) tagged.fields = children.map((child) => tagField(child, capName, addonId, source, kind));
|
|
266
|
+
} else if (f.type === "sub-tabs") {
|
|
267
|
+
const rawTabs = Array.isArray(f.tabs) ? f.tabs : [];
|
|
268
|
+
if (rawTabs.length > 0) tagged.tabs = rawTabs.map((tab) => {
|
|
269
|
+
if (!isFieldRecord(tab)) return tab;
|
|
270
|
+
const tabChildren = Array.isArray(tab.fields) ? tab.fields : [];
|
|
271
|
+
return {
|
|
272
|
+
...tab,
|
|
273
|
+
fields: tabChildren.map((child) => tagField(child, capName, addonId, source, kind))
|
|
274
|
+
};
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
return tagged;
|
|
233
278
|
}
|
|
234
279
|
function mergeAggregates(parts) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
280
|
+
const tabDecls = /* @__PURE__ */ new Map();
|
|
281
|
+
const sections = [];
|
|
282
|
+
for (const part of parts) {
|
|
283
|
+
if (part.tabs) {
|
|
284
|
+
for (const t of part.tabs) if (!tabDecls.has(t.id)) tabDecls.set(t.id, t);
|
|
285
|
+
}
|
|
286
|
+
for (const s of part.sections) sections.push(s);
|
|
287
|
+
}
|
|
288
|
+
for (const s of sections) {
|
|
289
|
+
const tabId = s.tab ?? "general";
|
|
290
|
+
if (tabDecls.has(tabId)) continue;
|
|
291
|
+
const known = _camstack_types.WELL_KNOWN_TAB_MAP[tabId];
|
|
292
|
+
if (known) tabDecls.set(tabId, {
|
|
293
|
+
id: known.id,
|
|
294
|
+
label: known.label,
|
|
295
|
+
icon: known.icon,
|
|
296
|
+
order: known.order
|
|
297
|
+
});
|
|
298
|
+
else tabDecls.set(tabId, {
|
|
299
|
+
id: tabId,
|
|
300
|
+
label: tabId,
|
|
301
|
+
icon: "wrench",
|
|
302
|
+
order: 100
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
sections.sort((a, b) => {
|
|
306
|
+
const tabA = a.tab ?? "general";
|
|
307
|
+
const tabB = b.tab ?? "general";
|
|
308
|
+
if (tabA !== tabB) {
|
|
309
|
+
const orderA = tabDecls.get(tabA)?.order ?? 100;
|
|
310
|
+
const orderB = tabDecls.get(tabB)?.order ?? 100;
|
|
311
|
+
if (orderA !== orderB) return orderA - orderB;
|
|
312
|
+
return tabA.localeCompare(tabB);
|
|
313
|
+
}
|
|
314
|
+
return (a.order ?? 0) - (b.order ?? 0);
|
|
315
|
+
});
|
|
316
|
+
const sortedTabs = [...tabDecls.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
|
|
317
|
+
const out = { sections };
|
|
318
|
+
if (sortedTabs.length > 0) out.tabs = sortedTabs;
|
|
319
|
+
return out;
|
|
272
320
|
}
|
|
273
|
-
var DeviceManagerAddon = class
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
if (!perCap) {
|
|
2101
|
-
perCap = /* @__PURE__ */ new Map();
|
|
2102
|
-
this.stateMirror.set(deviceId, perCap);
|
|
2103
|
-
}
|
|
2104
|
-
for (const [capName, raw] of Object.entries(blob)) {
|
|
2105
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
2106
|
-
perCap.set(capName, { ...raw });
|
|
2107
|
-
}
|
|
2108
|
-
}
|
|
2109
|
-
snapshotForDevice(deviceId) {
|
|
2110
|
-
const perCap = this.stateMirror.get(deviceId);
|
|
2111
|
-
if (!perCap) return {};
|
|
2112
|
-
const out = {};
|
|
2113
|
-
for (const [k, v] of perCap) out[k] = { ...v };
|
|
2114
|
-
return out;
|
|
2115
|
-
}
|
|
2116
|
-
emitStateChanged(deviceId, capName, slice) {
|
|
2117
|
-
this.ctx.eventBus.emit({
|
|
2118
|
-
id: (0, import_node_crypto.randomUUID)(),
|
|
2119
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
2120
|
-
source: { type: "device", id: deviceId },
|
|
2121
|
-
category: import_types.EventCategory.DeviceStateChanged,
|
|
2122
|
-
data: { deviceId, capName, slice }
|
|
2123
|
-
});
|
|
2124
|
-
}
|
|
2125
|
-
async onShutdown() {
|
|
2126
|
-
this.propagator?.stop();
|
|
2127
|
-
this.propagator = null;
|
|
2128
|
-
const settings = this.ctx.settings;
|
|
2129
|
-
const pending = [];
|
|
2130
|
-
for (const [deviceId, slot] of this.runtimeStateDebounce) {
|
|
2131
|
-
if (slot.timer) {
|
|
2132
|
-
clearTimeout(slot.timer);
|
|
2133
|
-
slot.timer = null;
|
|
2134
|
-
if (settings) {
|
|
2135
|
-
const blob = this.snapshotForDevice(deviceId);
|
|
2136
|
-
pending.push(settings.writeDeviceRuntimeState(deviceId, blob).catch((err) => {
|
|
2137
|
-
this.ctx.logger.warn("shutdown writeDeviceRuntimeState failed", {
|
|
2138
|
-
tags: { deviceId },
|
|
2139
|
-
meta: { error: err instanceof Error ? err.message : String(err) }
|
|
2140
|
-
});
|
|
2141
|
-
}));
|
|
2142
|
-
}
|
|
2143
|
-
}
|
|
2144
|
-
if (slot.inFlight) pending.push(slot.inFlight);
|
|
2145
|
-
}
|
|
2146
|
-
await Promise.all(pending);
|
|
2147
|
-
this.runtimeStateDebounce.clear();
|
|
2148
|
-
}
|
|
321
|
+
var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAddon {
|
|
322
|
+
constructor() {
|
|
323
|
+
super({});
|
|
324
|
+
}
|
|
325
|
+
/** Shorthand for the kernel-injected capability registry. */
|
|
326
|
+
get capabilityRegistry() {
|
|
327
|
+
return this.ctx.kernel.capabilityRegistry;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Parent-chain event propagator. Started in `onInitialize` once the
|
|
331
|
+
* hub's `deviceRegistry` is available; listens to every device-sourced
|
|
332
|
+
* event and re-emits a copy on each ancestor scope with `via[]`
|
|
333
|
+
* populated. Stopped in `onShutdown`.
|
|
334
|
+
*/
|
|
335
|
+
propagator = null;
|
|
336
|
+
/**
|
|
337
|
+
* Hub-side mirror of every device's cap-keyed runtime state.
|
|
338
|
+
* Populated whenever any caller writes via `deviceState.setCapSlice`
|
|
339
|
+
* (the canonical cross-layer write entrypoint) and on first load
|
|
340
|
+
* via `loadRuntimeState`. Cross-process consumers reach the mirror
|
|
341
|
+
* through the `deviceState` cap router; per-cap event subscribers
|
|
342
|
+
* (e.g. `battery.onStatusChanged`) get the same data via
|
|
343
|
+
* cap-specific events still emitted by the owning device.
|
|
344
|
+
*
|
|
345
|
+
* Key: deviceId. Value: per-cap slice map. Empty by default —
|
|
346
|
+
* slices show up as `setCapSlice` calls trickle in.
|
|
347
|
+
*/
|
|
348
|
+
stateMirror = /* @__PURE__ */ new Map();
|
|
349
|
+
/**
|
|
350
|
+
* Per-device disk-write debouncer for runtime-state. `setCapSlice`
|
|
351
|
+
* updates the in-memory mirror synchronously and emits the change
|
|
352
|
+
* event immediately, but the disk write is coalesced — frequent
|
|
353
|
+
* back-to-back writes (motion phase transitions, battery pushes,
|
|
354
|
+
* etc.) collapse to one `writeDeviceRuntimeState` per
|
|
355
|
+
* `RUNTIME_STATE_DEBOUNCE_MS` window. `flushRuntimeStateWrites`
|
|
356
|
+
* awaits any in-flight write + scheduled flush so shutdown is
|
|
357
|
+
* lossless.
|
|
358
|
+
*/
|
|
359
|
+
runtimeStateDebounce = /* @__PURE__ */ new Map();
|
|
360
|
+
static RUNTIME_STATE_DEBOUNCE_MS = 1e3;
|
|
361
|
+
/**
|
|
362
|
+
* Cross-process native-provider cache: deviceId (numeric) → capName → { addonId, nodeId }.
|
|
363
|
+
* Kept in sync with `device.bindings-changed` events emitted by forked
|
|
364
|
+
* workers. Union'd into `getBindings` so hub-side consumers see every
|
|
365
|
+
* native cap regardless of which process owns the IDevice. No persistence
|
|
366
|
+
* — entries re-register when the worker restarts. Purged on
|
|
367
|
+
* `$node.disconnected` to avoid stale routing after a worker crash.
|
|
368
|
+
*/
|
|
369
|
+
remoteNativeCaps = /* @__PURE__ */ new Map();
|
|
370
|
+
/** Wait for a device-provider by addonId, returning null on timeout. */
|
|
371
|
+
async waitDeviceProvider(addonId, timeoutMs = 5e3) {
|
|
372
|
+
const provider = await this.capabilityRegistry?.waitForProvider("device-provider", addonId, timeoutMs);
|
|
373
|
+
return provider ? provider : null;
|
|
374
|
+
}
|
|
375
|
+
/** Require a device-provider by addonId — throws if not found. */
|
|
376
|
+
async requireDeviceProvider(addonId) {
|
|
377
|
+
const dp = await this.waitDeviceProvider(addonId);
|
|
378
|
+
if (!dp) throw new Error(`Device provider "${addonId}" not found or not registered`);
|
|
379
|
+
return dp;
|
|
380
|
+
}
|
|
381
|
+
async readBindingsStore() {
|
|
382
|
+
return { deviceBindings: (await this.ctx.settings.readAddonStore()).deviceBindings ?? {} };
|
|
383
|
+
}
|
|
384
|
+
async writeBindingsStore(next) {
|
|
385
|
+
await this.ctx.settings.writeAddonStore({ deviceBindings: next.deviceBindings });
|
|
386
|
+
}
|
|
387
|
+
resolveWrapperNodeId(_wrapperAddonId) {
|
|
388
|
+
return "hub";
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Active discovery of native caps registered on a worker node.
|
|
392
|
+
* Called from the `$node.connected` handler to recover from lost
|
|
393
|
+
* `DeviceBindingsChanged` broadcasts after worker restart.
|
|
394
|
+
*
|
|
395
|
+
* Iterates the broker's service registry, picks
|
|
396
|
+
* `<addonId>.native-provider.<capName>` services owned by the
|
|
397
|
+
* connected node, calls each service's `$listDeviceIds` action to
|
|
398
|
+
* fetch the deviceIds the worker has registered that cap on, then
|
|
399
|
+
* writes entries into `remoteNativeCaps`.
|
|
400
|
+
*
|
|
401
|
+
* Best-effort: per-service failures are logged and swallowed so a
|
|
402
|
+
* single bad service doesn't abort the whole rebuild.
|
|
403
|
+
*/
|
|
404
|
+
async discoverWorkerNativeCaps(connectedNodeId, _connectedAddonId) {
|
|
405
|
+
const cluster = this.ctx.kernel.cluster;
|
|
406
|
+
if (!cluster) return;
|
|
407
|
+
const broker = cluster.broker;
|
|
408
|
+
const services = broker.registry?.getServiceList?.({
|
|
409
|
+
onlyAvailable: true,
|
|
410
|
+
withActions: false
|
|
411
|
+
}) ?? [];
|
|
412
|
+
const NATIVE_INFIX = ".native-provider.";
|
|
413
|
+
const matched = services.filter((s) => s.nodeID === connectedNodeId && s.name.includes(NATIVE_INFIX));
|
|
414
|
+
if (matched.length === 0) return;
|
|
415
|
+
for (const svc of matched) {
|
|
416
|
+
const idx = svc.name.indexOf(NATIVE_INFIX);
|
|
417
|
+
if (idx <= 0) continue;
|
|
418
|
+
const addonId = svc.name.slice(0, idx);
|
|
419
|
+
const capName = svc.name.slice(idx + 17);
|
|
420
|
+
if (!addonId || !capName) continue;
|
|
421
|
+
try {
|
|
422
|
+
const action = `${svc.name}.$listDeviceIds`;
|
|
423
|
+
const deviceIds = await broker.call?.(action, {}, { nodeID: connectedNodeId });
|
|
424
|
+
if (!deviceIds || deviceIds.length === 0) continue;
|
|
425
|
+
for (const deviceId of deviceIds) {
|
|
426
|
+
if (this.capabilityRegistry?.getNativeAddonId(capName, deviceId)) continue;
|
|
427
|
+
let perDevice = this.remoteNativeCaps.get(deviceId);
|
|
428
|
+
if (!perDevice) {
|
|
429
|
+
perDevice = /* @__PURE__ */ new Map();
|
|
430
|
+
this.remoteNativeCaps.set(deviceId, perDevice);
|
|
431
|
+
}
|
|
432
|
+
perDevice.set(capName, {
|
|
433
|
+
addonId,
|
|
434
|
+
nodeId: connectedNodeId
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
this.ctx.logger.debug("worker native-cap discovered", { meta: {
|
|
438
|
+
nodeId: connectedNodeId,
|
|
439
|
+
addonId,
|
|
440
|
+
capName,
|
|
441
|
+
deviceIds
|
|
442
|
+
} });
|
|
443
|
+
} catch (err) {
|
|
444
|
+
this.ctx.logger.debug("worker native-cap $listDeviceIds failed", { meta: {
|
|
445
|
+
service: svc.name,
|
|
446
|
+
nodeId: connectedNodeId,
|
|
447
|
+
error: (0, _camstack_types.errMsg)(err)
|
|
448
|
+
} });
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
this.ctx.logger.info("worker native-cap discovery completed", { meta: {
|
|
452
|
+
nodeId: connectedNodeId,
|
|
453
|
+
services: matched.length
|
|
454
|
+
} });
|
|
455
|
+
}
|
|
456
|
+
async getBindings(input) {
|
|
457
|
+
const storeKey = String(input.deviceId);
|
|
458
|
+
const perDevice = (await this.readBindingsStore()).deviceBindings[storeKey] ?? {};
|
|
459
|
+
const entries = [];
|
|
460
|
+
const seenCaps = /* @__PURE__ */ new Set();
|
|
461
|
+
for (const [capName, { wrapperAddonId }] of Object.entries(perDevice)) {
|
|
462
|
+
const hubLocalNative = this.capabilityRegistry?.getNativeAddonId(capName, input.deviceId) ?? null;
|
|
463
|
+
const remoteNative = this.remoteNativeCaps.get(input.deviceId)?.get(capName) ?? null;
|
|
464
|
+
const nativeAddonId = hubLocalNative ?? remoteNative?.addonId ?? "";
|
|
465
|
+
const nativeNodeId = hubLocalNative ? this.ctx.kernel.localNodeId ?? "hub" : remoteNative?.nodeId ?? this.ctx.kernel.localNodeId ?? "hub";
|
|
466
|
+
if (wrapperAddonId === null && !nativeAddonId) {
|
|
467
|
+
seenCaps.add(capName);
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
entries.push({
|
|
471
|
+
capName,
|
|
472
|
+
kind: wrapperAddonId ? "wrapped" : "native",
|
|
473
|
+
providerAddonId: wrapperAddonId ?? nativeAddonId,
|
|
474
|
+
providerNodeId: wrapperAddonId ? this.resolveWrapperNodeId(wrapperAddonId) : nativeNodeId,
|
|
475
|
+
nativeAddonId
|
|
476
|
+
});
|
|
477
|
+
seenCaps.add(capName);
|
|
478
|
+
}
|
|
479
|
+
const remote = this.remoteNativeCaps.get(input.deviceId);
|
|
480
|
+
if (this.capabilityRegistry) for (const capName of this.capabilityRegistry.getCapsWithDefaultWrapper()) {
|
|
481
|
+
if (seenCaps.has(capName)) continue;
|
|
482
|
+
const defaultWrapperAddonId = this.capabilityRegistry.getDefaultWrapperForCap(capName);
|
|
483
|
+
if (!defaultWrapperAddonId) continue;
|
|
484
|
+
const hubLocalNative = this.capabilityRegistry.getNativeAddonId(capName, input.deviceId) ?? null;
|
|
485
|
+
const remoteNative = remote?.get(capName) ?? null;
|
|
486
|
+
const nativeAddonId = hubLocalNative ?? remoteNative?.addonId ?? "";
|
|
487
|
+
entries.push({
|
|
488
|
+
capName,
|
|
489
|
+
kind: "wrapped",
|
|
490
|
+
providerAddonId: defaultWrapperAddonId,
|
|
491
|
+
providerNodeId: this.resolveWrapperNodeId(defaultWrapperAddonId),
|
|
492
|
+
nativeAddonId
|
|
493
|
+
});
|
|
494
|
+
seenCaps.add(capName);
|
|
495
|
+
}
|
|
496
|
+
if (this.capabilityRegistry) for (const capName of this.capabilityRegistry.getNativeCapsForDevice(input.deviceId)) {
|
|
497
|
+
if (seenCaps.has(capName)) continue;
|
|
498
|
+
const nativeAddonId = this.capabilityRegistry.getNativeAddonId(capName, input.deviceId) ?? "";
|
|
499
|
+
entries.push({
|
|
500
|
+
capName,
|
|
501
|
+
kind: "native",
|
|
502
|
+
providerAddonId: nativeAddonId,
|
|
503
|
+
providerNodeId: this.ctx.kernel.localNodeId ?? "hub",
|
|
504
|
+
nativeAddonId
|
|
505
|
+
});
|
|
506
|
+
seenCaps.add(capName);
|
|
507
|
+
}
|
|
508
|
+
if (remote) for (const [capName, info] of remote) {
|
|
509
|
+
if (seenCaps.has(capName)) continue;
|
|
510
|
+
entries.push({
|
|
511
|
+
capName,
|
|
512
|
+
kind: "native",
|
|
513
|
+
providerAddonId: info.addonId,
|
|
514
|
+
providerNodeId: info.nodeId,
|
|
515
|
+
nativeAddonId: info.addonId
|
|
516
|
+
});
|
|
517
|
+
seenCaps.add(capName);
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
deviceId: input.deviceId,
|
|
521
|
+
entries
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Whole-fleet binding dump. Iterates every device known to the
|
|
526
|
+
* deviceRegistry and reuses the per-device `getBindings` resolver
|
|
527
|
+
* for each — same routing rules, single round-trip. Used by
|
|
528
|
+
* `SystemManager.init()` for warm-boot.
|
|
529
|
+
*
|
|
530
|
+
* Bindings change rarely (wrapper toggle, device add/remove) so
|
|
531
|
+
* clients invalidate via the existing
|
|
532
|
+
* `capability.binding-changed` event rather than re-fetching this
|
|
533
|
+
* payload periodically.
|
|
534
|
+
*/
|
|
535
|
+
async getAllBindings() {
|
|
536
|
+
const hubRegistry = this.ctx.kernel?.deviceRegistry;
|
|
537
|
+
if (!hubRegistry) return [];
|
|
538
|
+
const out = [];
|
|
539
|
+
for (const device of hubRegistry.getAll()) out.push(await this.getBindings({ deviceId: device.id }));
|
|
540
|
+
return out;
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Resolve a numeric deviceId to a stableId via persisted meta.
|
|
544
|
+
* Used only by the device-identity section of the device-details
|
|
545
|
+
* aggregator (see `buildBaseDeviceSection`) to surface the stableId as
|
|
546
|
+
* a readonly display field. All runtime/registry lookups are keyed by
|
|
547
|
+
* numeric deviceId; this helper is display-only.
|
|
548
|
+
*/
|
|
549
|
+
async lookupPersistedStableId(deviceId) {
|
|
550
|
+
const meta = (await this.ctx.settings.readAddonStore()).deviceMeta ?? {};
|
|
551
|
+
for (const [key, m] of Object.entries(meta)) if (m.id === deviceId) {
|
|
552
|
+
const sep = key.indexOf(":");
|
|
553
|
+
if (sep < 0) continue;
|
|
554
|
+
return key.slice(sep + 1);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
async getDeviceAggregate(deviceId, kind) {
|
|
558
|
+
const registry = this.capabilityRegistry;
|
|
559
|
+
if (!registry) {
|
|
560
|
+
this.ctx.logger.debug("capability registry unavailable — aggregate empty", { meta: { kind } });
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
const method = kind === "settings" ? "getDeviceSettingsContribution" : "getDeviceLiveContribution";
|
|
564
|
+
const contributors = [];
|
|
565
|
+
for (const info of registry.listCapabilities()) {
|
|
566
|
+
if (!registry.getDefinition(info.name)?.exposesDeviceSettings) continue;
|
|
567
|
+
const seen = /* @__PURE__ */ new Set();
|
|
568
|
+
const sorted = [...info.providers].sort((a, b) => a.length - b.length);
|
|
569
|
+
for (const addonId of sorted) {
|
|
570
|
+
if (addonId.includes("::native-")) continue;
|
|
571
|
+
const baseId = addonId.includes("@") ? addonId.slice(0, addonId.indexOf("@")) : addonId;
|
|
572
|
+
if (seen.has(baseId)) continue;
|
|
573
|
+
seen.add(baseId);
|
|
574
|
+
contributors.push({
|
|
575
|
+
capName: info.name,
|
|
576
|
+
addonId
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
const results = await Promise.all(contributors.map(async ({ capName, addonId }) => {
|
|
581
|
+
const provider = registry.getProviderByAddon(capName, addonId);
|
|
582
|
+
if (!provider) throw new Error(`[device-manager] capability "${capName}" lists provider "${addonId}" but getProviderByAddon returned null — registry inconsistency`);
|
|
583
|
+
try {
|
|
584
|
+
const contribution = await provider[method]({ deviceId });
|
|
585
|
+
if (!contribution) return null;
|
|
586
|
+
return {
|
|
587
|
+
capName,
|
|
588
|
+
addonId,
|
|
589
|
+
contribution: tagContribution(toWireShape(contribution), capName, addonId, kind)
|
|
590
|
+
};
|
|
591
|
+
} catch (err) {
|
|
592
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
593
|
+
this.ctx.logger.warn("contribution method failed", {
|
|
594
|
+
tags: {
|
|
595
|
+
deviceId,
|
|
596
|
+
addonId
|
|
597
|
+
},
|
|
598
|
+
meta: {
|
|
599
|
+
capName,
|
|
600
|
+
method,
|
|
601
|
+
error: msg
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
}));
|
|
607
|
+
const base = kind === "settings" ? await this.buildBaseDeviceSection(deviceId) : null;
|
|
608
|
+
const parts = [...base ? [tagContribution(base, "device-manager", "device-manager", kind)] : [], ...results.filter((r) => r !== null).map((r) => r.contribution)];
|
|
609
|
+
if (parts.length === 0) return null;
|
|
610
|
+
return mergeAggregates(parts);
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Build the device-manager's own contribution to the aggregator — the
|
|
614
|
+
* device identity (id, stableId, addonId, type, online) + the
|
|
615
|
+
* driver-specific config exposed by the device class via
|
|
616
|
+
* `zodEntriesToConfigUI`.
|
|
617
|
+
*
|
|
618
|
+
* Two paths, deliberately symmetric with `getSettingsSchema`:
|
|
619
|
+
*
|
|
620
|
+
* - Hub-local: device's IDevice instance lives in this process'
|
|
621
|
+
* DeviceRegistry, we read config + schema directly by reference.
|
|
622
|
+
* - Cross-process: device lives in a forked worker (RtspCamera on
|
|
623
|
+
* provider-rtsp, ONVIF on provider-onvif, …). We ask the worker's
|
|
624
|
+
* `device-ops.getSettingsSchema` native provider for a wire-
|
|
625
|
+
* serializable ConfigUISchema and merge it in under the same
|
|
626
|
+
* "Driver Config" section, so the UI sees the same shape regardless
|
|
627
|
+
* of where the IDevice physically runs.
|
|
628
|
+
*
|
|
629
|
+
* Returns `null` only when the device genuinely doesn't exist anywhere
|
|
630
|
+
* (no hub-local, no persisted ownership, no device-ops native). The
|
|
631
|
+
* aggregator falls back to contributor sections only in that case.
|
|
632
|
+
*/
|
|
633
|
+
async buildBaseDeviceSection(deviceId) {
|
|
634
|
+
const hubRegistry = this.ctx.kernel?.deviceRegistry;
|
|
635
|
+
const hubLocal = hubRegistry ? resolveDeviceById(hubRegistry, deviceId) : null;
|
|
636
|
+
const stableId = hubLocal?.device.stableId ?? await this.lookupPersistedStableId(deviceId);
|
|
637
|
+
const nativeOwner = this.resolveNativeDeviceOwner(deviceId);
|
|
638
|
+
const addonId = hubLocal?.addonId ?? nativeOwner?.addonId ?? null;
|
|
639
|
+
if (!hubLocal && !nativeOwner) return null;
|
|
640
|
+
const sections = [{
|
|
641
|
+
id: "device-identity",
|
|
642
|
+
title: "Identity",
|
|
643
|
+
tab: "general",
|
|
644
|
+
order: 0,
|
|
645
|
+
fields: [
|
|
646
|
+
{
|
|
647
|
+
type: "text",
|
|
648
|
+
key: "_deviceId",
|
|
649
|
+
label: "Device ID",
|
|
650
|
+
readonlyField: true,
|
|
651
|
+
value: String(deviceId)
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
type: "text",
|
|
655
|
+
key: "_stableId",
|
|
656
|
+
label: "Stable ID",
|
|
657
|
+
readonlyField: true,
|
|
658
|
+
value: stableId ?? ""
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
type: "text",
|
|
662
|
+
key: "_addonId",
|
|
663
|
+
label: "Driver",
|
|
664
|
+
readonlyField: true,
|
|
665
|
+
value: addonId ?? "unknown"
|
|
666
|
+
},
|
|
667
|
+
...hubLocal ? [{
|
|
668
|
+
type: "text",
|
|
669
|
+
key: "_type",
|
|
670
|
+
label: "Type",
|
|
671
|
+
readonlyField: true,
|
|
672
|
+
value: hubLocal.device.type
|
|
673
|
+
}, {
|
|
674
|
+
type: "text",
|
|
675
|
+
key: "_online",
|
|
676
|
+
label: "Online",
|
|
677
|
+
readonlyField: true,
|
|
678
|
+
value: hubLocal.device.online ? "yes" : "no"
|
|
679
|
+
}] : []
|
|
680
|
+
]
|
|
681
|
+
}];
|
|
682
|
+
const driverSchema = await this.resolveDriverConfigSchema(deviceId, hubLocal);
|
|
683
|
+
if (driverSchema) for (const section of driverSchema.sections) sections.push({
|
|
684
|
+
id: section.id,
|
|
685
|
+
title: section.title,
|
|
686
|
+
tab: section.tab ?? "general",
|
|
687
|
+
order: section.order ?? 1,
|
|
688
|
+
fields: [...section.fields],
|
|
689
|
+
...section.description !== void 0 ? { description: section.description } : {},
|
|
690
|
+
...section.columns !== void 0 ? { columns: section.columns } : {}
|
|
691
|
+
});
|
|
692
|
+
return {
|
|
693
|
+
sections,
|
|
694
|
+
...driverSchema?.tabs ? { tabs: [...driverSchema.tabs] } : {}
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Lookup the native owner for `device-ops` on `deviceId` — the native-cap
|
|
699
|
+
* registry (hub-local and remote) is keyed by numeric id.
|
|
700
|
+
*/
|
|
701
|
+
resolveNativeDeviceOwner(deviceId) {
|
|
702
|
+
const local = this.capabilityRegistry?.getNativeAddonId("device-ops", deviceId) ?? null;
|
|
703
|
+
if (local) return {
|
|
704
|
+
addonId: local,
|
|
705
|
+
nodeId: this.ctx.kernel.localNodeId ?? "hub"
|
|
706
|
+
};
|
|
707
|
+
const remote = this.remoteNativeCaps.get(deviceId)?.get("device-ops") ?? null;
|
|
708
|
+
return remote ? {
|
|
709
|
+
addonId: remote.addonId,
|
|
710
|
+
nodeId: remote.nodeId
|
|
711
|
+
} : null;
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Aggregate `status` across every registered cap for a device.
|
|
715
|
+
*
|
|
716
|
+
* Walks the supplied cap list (or `CAP_NAMES_WITH_STATUS` when
|
|
717
|
+
* omitted), looks up a native provider per cap via the capability
|
|
718
|
+
* registry, calls `provider.getStatus({ deviceId })`, and validates
|
|
719
|
+
* the return against the cap's own `status.schema`. Validation
|
|
720
|
+
* failures log a warning and yield `null` for that cap so the
|
|
721
|
+
* overall aggregate stays usable — a single misbehaving provider
|
|
722
|
+
* must not blank out a device's entire status view.
|
|
723
|
+
*
|
|
724
|
+
* Returned shape is `Record<capName, unknown | null>`; the client-
|
|
725
|
+
* side hook tightens this to `CapStatusTypeMap` via the generated
|
|
726
|
+
* `cap-status-types.ts`.
|
|
727
|
+
*/
|
|
728
|
+
async getDeviceStatusAggregate(input) {
|
|
729
|
+
const capNames = input.caps ?? _camstack_types.CAP_NAMES_WITH_STATUS;
|
|
730
|
+
const registry = this.capabilityRegistry;
|
|
731
|
+
const out = {};
|
|
732
|
+
if (!registry) {
|
|
733
|
+
for (const name of capNames) out[name] = null;
|
|
734
|
+
return out;
|
|
735
|
+
}
|
|
736
|
+
await Promise.all(capNames.map(async (capName) => {
|
|
737
|
+
try {
|
|
738
|
+
const def = registry.getDefinition(capName);
|
|
739
|
+
if (!def?.status) {
|
|
740
|
+
out[capName] = null;
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
const provider = registry.getNativeProvider(capName, input.deviceId);
|
|
744
|
+
if (!provider || typeof provider.getStatus !== "function") {
|
|
745
|
+
out[capName] = null;
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
const raw = await provider.getStatus({ deviceId: input.deviceId });
|
|
749
|
+
if (raw == null) {
|
|
750
|
+
out[capName] = null;
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
const parsed = def.status.schema.safeParse(raw);
|
|
754
|
+
if (!parsed.success) {
|
|
755
|
+
this.ctx.logger.warn("getDeviceStatusAggregate: provider returned invalid status, dropping", {
|
|
756
|
+
tags: { deviceId: input.deviceId },
|
|
757
|
+
meta: {
|
|
758
|
+
capName,
|
|
759
|
+
issues: parsed.error.issues.slice(0, 3)
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
out[capName] = null;
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
out[capName] = parsed.data;
|
|
766
|
+
} catch (err) {
|
|
767
|
+
this.ctx.logger.warn("getDeviceStatusAggregate: provider threw, dropping", {
|
|
768
|
+
tags: { deviceId: input.deviceId },
|
|
769
|
+
meta: {
|
|
770
|
+
capName,
|
|
771
|
+
error: (0, _camstack_types.errMsg)(err)
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
out[capName] = null;
|
|
775
|
+
}
|
|
776
|
+
}));
|
|
777
|
+
return out;
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Return the driver-specific device-settings contribution. Hub-local
|
|
781
|
+
* devices call `getSettingsUISchema()` directly; forked-worker devices
|
|
782
|
+
* go through the `device-ops.getSettingsSchema` cap method on the
|
|
783
|
+
* numeric-id-keyed native registry.
|
|
784
|
+
*/
|
|
785
|
+
async resolveDriverConfigSchema(deviceId, hubLocal) {
|
|
786
|
+
if (hubLocal) {
|
|
787
|
+
const schema = hubLocal.device.getSettingsUISchema();
|
|
788
|
+
return schema.sections.length === 0 ? null : toWireShape(schema);
|
|
789
|
+
}
|
|
790
|
+
const ops = this.capabilityRegistry?.getNativeProvider("device-ops", deviceId);
|
|
791
|
+
if (!ops) return null;
|
|
792
|
+
try {
|
|
793
|
+
const schema = await ops.getSettingsSchema({ deviceId });
|
|
794
|
+
if (!schema) return null;
|
|
795
|
+
const wire = schema;
|
|
796
|
+
return wire.sections.length === 0 ? null : toWireShape(wire);
|
|
797
|
+
} catch (err) {
|
|
798
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
799
|
+
this.ctx.logger.warn("cross-process getSettingsSchema failed", {
|
|
800
|
+
tags: { deviceId },
|
|
801
|
+
meta: { error: msg }
|
|
802
|
+
});
|
|
803
|
+
return null;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
async updateDeviceField(input) {
|
|
807
|
+
if (input.writerCapName === "device-manager") {
|
|
808
|
+
const hubRegistry = this.ctx.kernel?.deviceRegistry;
|
|
809
|
+
const found = hubRegistry ? resolveDeviceById(hubRegistry, input.deviceId) : null;
|
|
810
|
+
if (found) {
|
|
811
|
+
await found.device.applySettingsPatch({ [input.key]: input.value });
|
|
812
|
+
return { success: true };
|
|
813
|
+
}
|
|
814
|
+
const ops = this.capabilityRegistry?.getNativeProvider("device-ops", input.deviceId);
|
|
815
|
+
if (!ops) throw new Error(`[device-manager] device "${input.deviceId}" not found (no hub-local entry, no device-ops native provider)`);
|
|
816
|
+
await ops.setConfig({
|
|
817
|
+
deviceId: input.deviceId,
|
|
818
|
+
values: { [input.key]: input.value }
|
|
819
|
+
});
|
|
820
|
+
return { success: true };
|
|
821
|
+
}
|
|
822
|
+
const registry = this.capabilityRegistry;
|
|
823
|
+
if (!registry) throw new Error("[device-manager] updateDeviceField requires capability registry — unavailable on this node");
|
|
824
|
+
if (!registry.getDefinition(input.writerCapName)?.exposesDeviceSettings) throw new Error(`[device-manager] cap "${input.writerCapName}" does not expose device settings`);
|
|
825
|
+
const provider = registry.getProviderByAddon(input.writerCapName, input.writerAddonId);
|
|
826
|
+
if (!provider) throw new Error(`[device-manager] provider "${input.writerAddonId}" not registered for cap "${input.writerCapName}"`);
|
|
827
|
+
await provider.applyDeviceSettingsPatch({
|
|
828
|
+
deviceId: input.deviceId,
|
|
829
|
+
patch: { [input.key]: input.value }
|
|
830
|
+
});
|
|
831
|
+
return { success: true };
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Batched counterpart of `updateDeviceField`. Groups changes by
|
|
835
|
+
* `(writerCapName, writerAddonId)` so each contributor receives a
|
|
836
|
+
* single `applyDeviceSettingsPatch` with all of its updates merged —
|
|
837
|
+
* avoids N round-trips for simultaneous edits in the same save.
|
|
838
|
+
*
|
|
839
|
+
* Per-provider failures are captured in the `failures[]` output so the
|
|
840
|
+
* admin UI can highlight which sections didn't persist; a failure on
|
|
841
|
+
* one provider does NOT abort the others.
|
|
842
|
+
*/
|
|
843
|
+
async updateDeviceFieldsBatch(input) {
|
|
844
|
+
const groups = /* @__PURE__ */ new Map();
|
|
845
|
+
for (const change of input.changes) {
|
|
846
|
+
const key = `${change.writerCapName}::${change.writerAddonId}`;
|
|
847
|
+
const existing = groups.get(key);
|
|
848
|
+
if (existing) existing.patch[change.key] = change.value;
|
|
849
|
+
else groups.set(key, {
|
|
850
|
+
writerCapName: change.writerCapName,
|
|
851
|
+
writerAddonId: change.writerAddonId,
|
|
852
|
+
patch: { [change.key]: change.value }
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
const failures = [];
|
|
856
|
+
for (const group of groups.values()) try {
|
|
857
|
+
await this.applyGroupPatch(input.deviceId, group);
|
|
858
|
+
} catch (err) {
|
|
859
|
+
failures.push({
|
|
860
|
+
writerCapName: group.writerCapName,
|
|
861
|
+
writerAddonId: group.writerAddonId,
|
|
862
|
+
error: err instanceof Error ? err.message : String(err)
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
return {
|
|
866
|
+
success: true,
|
|
867
|
+
failures
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
/** Apply a single grouped patch to the appropriate provider. Mirrors
|
|
871
|
+
* `updateDeviceField` routing (special-case device-manager, else
|
|
872
|
+
* registry lookup). Used by `updateDeviceFieldsBatch`. */
|
|
873
|
+
async applyGroupPatch(deviceId, group) {
|
|
874
|
+
if (group.writerCapName === "device-manager") {
|
|
875
|
+
const hubRegistry = this.ctx.kernel?.deviceRegistry;
|
|
876
|
+
const found = hubRegistry ? resolveDeviceById(hubRegistry, deviceId) : null;
|
|
877
|
+
if (found) {
|
|
878
|
+
await found.device.applySettingsPatch(group.patch);
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
const ops = this.capabilityRegistry?.getNativeProvider("device-ops", deviceId);
|
|
882
|
+
if (!ops) throw new Error(`[device-manager] device "${deviceId}" not found (no hub-local entry, no device-ops native provider)`);
|
|
883
|
+
await ops.setConfig({
|
|
884
|
+
deviceId,
|
|
885
|
+
values: group.patch
|
|
886
|
+
});
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
const registry = this.capabilityRegistry;
|
|
890
|
+
if (!registry) throw new Error("[device-manager] capability registry unavailable");
|
|
891
|
+
if (!registry.getDefinition(group.writerCapName)?.exposesDeviceSettings) throw new Error(`[device-manager] cap "${group.writerCapName}" does not expose device settings`);
|
|
892
|
+
const provider = registry.getProviderByAddon(group.writerCapName, group.writerAddonId);
|
|
893
|
+
if (!provider) throw new Error(`[device-manager] provider "${group.writerAddonId}" not registered for cap "${group.writerCapName}"`);
|
|
894
|
+
await provider.applyDeviceSettingsPatch({
|
|
895
|
+
deviceId,
|
|
896
|
+
patch: group.patch
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
async listWrappersForCap(input) {
|
|
900
|
+
return [...this.capabilityRegistry?.getWrappersForCap(input.capName) ?? []];
|
|
901
|
+
}
|
|
902
|
+
async listBindableCapsForDeviceType(input) {
|
|
903
|
+
const registry = this.capabilityRegistry;
|
|
904
|
+
if (!registry) return [];
|
|
905
|
+
return registry.listDeviceScopedCapsForType(input.deviceType).map((capName) => ({
|
|
906
|
+
capName,
|
|
907
|
+
wrappers: [...registry.getWrappersForCap(capName)]
|
|
908
|
+
}));
|
|
909
|
+
}
|
|
910
|
+
async setWrapperActive(input) {
|
|
911
|
+
const storeKey = String(input.deviceId);
|
|
912
|
+
const store = await this.readBindingsStore();
|
|
913
|
+
const perDevice = { ...store.deviceBindings[storeKey] ?? {} };
|
|
914
|
+
if (input.active) perDevice[input.capName] = { wrapperAddonId: input.wrapperAddonId };
|
|
915
|
+
else perDevice[input.capName] = { wrapperAddonId: null };
|
|
916
|
+
const nextDeviceBindings = Object.keys(perDevice).length > 0 ? {
|
|
917
|
+
...store.deviceBindings,
|
|
918
|
+
[storeKey]: perDevice
|
|
919
|
+
} : (() => {
|
|
920
|
+
const { [storeKey]: _drop, ...rest } = store.deviceBindings;
|
|
921
|
+
return rest;
|
|
922
|
+
})();
|
|
923
|
+
await this.writeBindingsStore({ deviceBindings: nextDeviceBindings });
|
|
924
|
+
this.ctx.eventBus.emit({
|
|
925
|
+
id: (0, node_crypto.randomUUID)(),
|
|
926
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
927
|
+
source: {
|
|
928
|
+
type: "addon",
|
|
929
|
+
id: this.ctx.id
|
|
930
|
+
},
|
|
931
|
+
category: _camstack_types.EventCategory.DeviceBindingsChanged,
|
|
932
|
+
data: {
|
|
933
|
+
deviceId: input.deviceId,
|
|
934
|
+
capName: input.capName,
|
|
935
|
+
reason: input.active ? "wrapper-activated" : "wrapper-deactivated",
|
|
936
|
+
addonId: input.wrapperAddonId,
|
|
937
|
+
nodeId: this.resolveWrapperNodeId(input.wrapperAddonId)
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
async onInitialize() {
|
|
942
|
+
const settings = this.ctx.settings;
|
|
943
|
+
if (!settings) {
|
|
944
|
+
this.ctx.logger.warn("ctx.settings not available — device persistence unavailable");
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
const registry = this.ctx.kernel.deviceRegistry ?? null;
|
|
948
|
+
if (!registry) this.ctx.logger.warn("device-registry not available — live operations will use persisted data only");
|
|
949
|
+
const localNodeId = this.ctx.kernel.localNodeId ?? "hub";
|
|
950
|
+
this.ctx.eventBus.subscribe({ category: _camstack_types.EventCategory.DeviceBindingsChanged }, (event) => {
|
|
951
|
+
const { deviceId, capName, reason, addonId, nodeId } = event.data;
|
|
952
|
+
if (nodeId === localNodeId) return;
|
|
953
|
+
if (reason === "native-registered") {
|
|
954
|
+
let perDevice = this.remoteNativeCaps.get(deviceId);
|
|
955
|
+
if (!perDevice) {
|
|
956
|
+
perDevice = /* @__PURE__ */ new Map();
|
|
957
|
+
this.remoteNativeCaps.set(deviceId, perDevice);
|
|
958
|
+
}
|
|
959
|
+
perDevice.set(capName, {
|
|
960
|
+
addonId,
|
|
961
|
+
nodeId
|
|
962
|
+
});
|
|
963
|
+
} else if (reason === "native-unregistered") {
|
|
964
|
+
const perDevice = this.remoteNativeCaps.get(deviceId);
|
|
965
|
+
if (!perDevice) return;
|
|
966
|
+
perDevice.delete(capName);
|
|
967
|
+
if (perDevice.size === 0) this.remoteNativeCaps.delete(deviceId);
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
const cluster = this.ctx.kernel.cluster;
|
|
971
|
+
if (cluster) cluster.broker.localBus.on("$node.disconnected", (payload) => {
|
|
972
|
+
const gone = payload.node.id;
|
|
973
|
+
const emptyDevices = [];
|
|
974
|
+
for (const [deviceId, perDevice] of this.remoteNativeCaps) {
|
|
975
|
+
const toDelete = [];
|
|
976
|
+
for (const [capName, entry] of perDevice) if (entry.nodeId === gone) toDelete.push(capName);
|
|
977
|
+
for (const capName of toDelete) perDevice.delete(capName);
|
|
978
|
+
if (perDevice.size === 0) emptyDevices.push(deviceId);
|
|
979
|
+
}
|
|
980
|
+
for (const deviceId of emptyDevices) this.remoteNativeCaps.delete(deviceId);
|
|
981
|
+
});
|
|
982
|
+
const requireDeviceOps = (deviceId) => {
|
|
983
|
+
const ops = this.capabilityRegistry?.getNativeProvider("device-ops", deviceId);
|
|
984
|
+
if (!ops) throw new Error(`[device-manager] device-ops native provider not found for '${deviceId}'`);
|
|
985
|
+
return ops;
|
|
986
|
+
};
|
|
987
|
+
const readStore = async () => {
|
|
988
|
+
return await settings.readAddonStore();
|
|
989
|
+
};
|
|
990
|
+
const readIndex = async () => {
|
|
991
|
+
return (await readStore()).deviceIndex ?? {};
|
|
992
|
+
};
|
|
993
|
+
const readMeta = async () => {
|
|
994
|
+
return (await readStore()).deviceMeta ?? {};
|
|
995
|
+
};
|
|
996
|
+
/** Hardware-identity metadata map. Lives in a sibling key on the
|
|
997
|
+
* device-manager addon store so its writers (`setMetadata`) never
|
|
998
|
+
* collide with the lifecycle writers on `deviceMeta`
|
|
999
|
+
* (`registerDevice` / `setName` / `setLocation` / `setDisabled`).
|
|
1000
|
+
* Single-writer per row eliminates the "writer X clobbers writer
|
|
1001
|
+
* Y's field" bug class — `setMetadata` is the only producer. */
|
|
1002
|
+
const readMetadataMap = async () => {
|
|
1003
|
+
return (await readStore()).deviceMetadata ?? {};
|
|
1004
|
+
};
|
|
1005
|
+
let metaWriteChain = Promise.resolve();
|
|
1006
|
+
const withMetaWriteLock = async (fn) => {
|
|
1007
|
+
const previous = metaWriteChain;
|
|
1008
|
+
let release = () => {};
|
|
1009
|
+
metaWriteChain = new Promise((resolve) => {
|
|
1010
|
+
release = resolve;
|
|
1011
|
+
});
|
|
1012
|
+
try {
|
|
1013
|
+
await previous.catch(() => {});
|
|
1014
|
+
return await fn();
|
|
1015
|
+
} finally {
|
|
1016
|
+
release();
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
/**
|
|
1020
|
+
* Resolve a numeric deviceId to the owning `(addonId, stableId)` pair.
|
|
1021
|
+
* Scans persisted meta — live IDevice lookup (hub registry) is handled
|
|
1022
|
+
* separately per call site so callers can decide whether to route to
|
|
1023
|
+
* an in-process driver or to the cross-process `device-ops` bridge.
|
|
1024
|
+
* Returns null when no device with that id is known to the hub.
|
|
1025
|
+
*/
|
|
1026
|
+
const resolvePersistedById = async (deviceId) => {
|
|
1027
|
+
const meta = await readMeta();
|
|
1028
|
+
for (const [key, m] of Object.entries(meta)) if (m.id === deviceId) {
|
|
1029
|
+
const sep = key.indexOf(":");
|
|
1030
|
+
if (sep < 0) continue;
|
|
1031
|
+
return {
|
|
1032
|
+
addonId: key.slice(0, sep),
|
|
1033
|
+
stableId: key.slice(sep + 1),
|
|
1034
|
+
meta: m
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
return null;
|
|
1038
|
+
};
|
|
1039
|
+
const idToAddonId = /* @__PURE__ */ new Map();
|
|
1040
|
+
{
|
|
1041
|
+
const meta = await readMeta();
|
|
1042
|
+
for (const [key, m] of Object.entries(meta)) {
|
|
1043
|
+
const sep = key.indexOf(":");
|
|
1044
|
+
if (sep < 0) continue;
|
|
1045
|
+
idToAddonId.set(m.id, key.slice(0, sep));
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
if (cluster) cluster.broker.localBus.on("$node.connected", (payload) => {
|
|
1049
|
+
const connectedNodeId = payload.node.id;
|
|
1050
|
+
const lastSlash = connectedNodeId.lastIndexOf("/");
|
|
1051
|
+
if (lastSlash < 0) return;
|
|
1052
|
+
const connectedAddonId = connectedNodeId.slice(lastSlash + 1);
|
|
1053
|
+
if (connectedAddonId.length === 0) return;
|
|
1054
|
+
for (const [deviceId, ownerAddonId] of idToAddonId) {
|
|
1055
|
+
if (ownerAddonId !== connectedAddonId) continue;
|
|
1056
|
+
if (this.capabilityRegistry?.getNativeAddonId("device-ops", deviceId)) continue;
|
|
1057
|
+
let perDevice = this.remoteNativeCaps.get(deviceId);
|
|
1058
|
+
if (!perDevice) {
|
|
1059
|
+
perDevice = /* @__PURE__ */ new Map();
|
|
1060
|
+
this.remoteNativeCaps.set(deviceId, perDevice);
|
|
1061
|
+
}
|
|
1062
|
+
if (!perDevice.has("device-ops")) perDevice.set("device-ops", {
|
|
1063
|
+
addonId: connectedAddonId,
|
|
1064
|
+
nodeId: connectedNodeId
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
setTimeout(() => {
|
|
1068
|
+
this.discoverWorkerNativeCaps(connectedNodeId, connectedAddonId).catch((err) => {
|
|
1069
|
+
this.ctx.logger.warn("worker native-cap discovery failed", { meta: {
|
|
1070
|
+
nodeId: connectedNodeId,
|
|
1071
|
+
addonId: connectedAddonId,
|
|
1072
|
+
error: (0, _camstack_types.errMsg)(err)
|
|
1073
|
+
} });
|
|
1074
|
+
});
|
|
1075
|
+
}, 500);
|
|
1076
|
+
});
|
|
1077
|
+
const allocateNextDeviceId = async () => {
|
|
1078
|
+
const current = (await readStore()).nextDeviceId ?? 1;
|
|
1079
|
+
await settings.writeAddonStore({ nextDeviceId: current + 1 });
|
|
1080
|
+
return current;
|
|
1081
|
+
};
|
|
1082
|
+
const provider = {
|
|
1083
|
+
/** Sync ownership lookup backing persistence fallbacks (e.g. remove()
|
|
1084
|
+
* when the owning worker is offline). Ownership is keyed by numeric
|
|
1085
|
+
* deviceId → owning addonId as recorded in the persisted meta. NOT
|
|
1086
|
+
* a native-cap lookup: an addon can own a device without registering
|
|
1087
|
+
* every possible cap natively (e.g. RtspCamera without snapshotUrl
|
|
1088
|
+
* doesn't register the snapshot cap). Use
|
|
1089
|
+
* `resolveNativeCapOwnerSync` for cap-resolution paths.
|
|
1090
|
+
*/
|
|
1091
|
+
resolveDeviceOwnerSync: (deviceId) => {
|
|
1092
|
+
return idToAddonId.get(deviceId) ?? null;
|
|
1093
|
+
},
|
|
1094
|
+
/** Sync lookup for the addon that registered a native provider for
|
|
1095
|
+
* `(capName, deviceId)`. Backs `CapabilityRegistry`'s native fallback
|
|
1096
|
+
* so the hub only synthesizes a cross-process proxy when the cap is
|
|
1097
|
+
* actually published — never on speculative device ownership.
|
|
1098
|
+
*
|
|
1099
|
+
* Consults hub-local registrations first (in-process natives),
|
|
1100
|
+
* then the `remoteNativeCaps` map populated from
|
|
1101
|
+
* `DeviceBindingsChanged` events emitted by forked-worker
|
|
1102
|
+
* `registerNativeCap` calls. Both are generic: any addon that hosts
|
|
1103
|
+
* devices and registers caps via the standard context API shows up
|
|
1104
|
+
* here without per-addon branching.
|
|
1105
|
+
*/
|
|
1106
|
+
resolveNativeCapOwnerSync: (capName, deviceId) => {
|
|
1107
|
+
const localAddonId = this.capabilityRegistry?.getNativeAddonId(capName, deviceId) ?? null;
|
|
1108
|
+
if (localAddonId) return {
|
|
1109
|
+
addonId: localAddonId,
|
|
1110
|
+
nodeId: this.ctx.kernel.localNodeId ?? "hub"
|
|
1111
|
+
};
|
|
1112
|
+
const remote = this.remoteNativeCaps.get(deviceId)?.get(capName) ?? null;
|
|
1113
|
+
if (remote) return {
|
|
1114
|
+
addonId: remote.addonId,
|
|
1115
|
+
nodeId: remote.nodeId
|
|
1116
|
+
};
|
|
1117
|
+
return null;
|
|
1118
|
+
},
|
|
1119
|
+
/** Idempotent numeric-id reservation. Callers invoke this before
|
|
1120
|
+
* constructing the owning `IDevice` so `DeviceContext.id` is bound
|
|
1121
|
+
* at construction time. A repeat call for the same `(addonId,
|
|
1122
|
+
* stableId)` returns the already-persisted id — same physical
|
|
1123
|
+
* device reconnecting after a driver restart keeps its original
|
|
1124
|
+
* number. Fresh pairs burn one slot from the monotonic
|
|
1125
|
+
* `nextDeviceId` counter and seed a meta placeholder so the
|
|
1126
|
+
* `deviceMeta` → `id` invariant holds even before
|
|
1127
|
+
* `registerDevice` completes. */
|
|
1128
|
+
allocateDeviceId: async (input) => {
|
|
1129
|
+
const { addonId, stableId } = input;
|
|
1130
|
+
const key = deviceKey(addonId, stableId);
|
|
1131
|
+
return await withMetaWriteLock(async () => {
|
|
1132
|
+
const meta = await readMeta();
|
|
1133
|
+
const existing = meta[key];
|
|
1134
|
+
if (existing) return { id: existing.id };
|
|
1135
|
+
const id = await allocateNextDeviceId();
|
|
1136
|
+
await settings.writeAddonStore({ deviceMeta: {
|
|
1137
|
+
...meta,
|
|
1138
|
+
[key]: {
|
|
1139
|
+
type: "generic",
|
|
1140
|
+
name: stableId,
|
|
1141
|
+
location: null,
|
|
1142
|
+
disabled: false,
|
|
1143
|
+
parentDeviceId: null,
|
|
1144
|
+
id
|
|
1145
|
+
}
|
|
1146
|
+
} });
|
|
1147
|
+
return { id };
|
|
1148
|
+
});
|
|
1149
|
+
},
|
|
1150
|
+
registerDevice: async (input) => {
|
|
1151
|
+
const { addonId, stableId, id, type, name, parentDeviceId, features, config } = input;
|
|
1152
|
+
const key = deviceKey(addonId, stableId);
|
|
1153
|
+
const featuresArr = Array.isArray(features) ? [...features] : [];
|
|
1154
|
+
const { isFirstRegistration } = await withMetaWriteLock(async () => {
|
|
1155
|
+
const index = await readIndex();
|
|
1156
|
+
const existing = index[addonId] ?? [];
|
|
1157
|
+
const wasInIndex = existing.includes(stableId);
|
|
1158
|
+
if (!wasInIndex) await settings.writeAddonStore({ deviceIndex: {
|
|
1159
|
+
...index,
|
|
1160
|
+
[addonId]: [...existing, stableId]
|
|
1161
|
+
} });
|
|
1162
|
+
const meta = await readMeta();
|
|
1163
|
+
const existingMeta = meta[key];
|
|
1164
|
+
const isFirst = !existingMeta || !wasInIndex;
|
|
1165
|
+
await settings.writeAddonStore({ deviceMeta: {
|
|
1166
|
+
...meta,
|
|
1167
|
+
[key]: {
|
|
1168
|
+
type,
|
|
1169
|
+
name: existingMeta && existingMeta.name !== stableId ? existingMeta.name : name,
|
|
1170
|
+
location: existingMeta?.location ?? null,
|
|
1171
|
+
disabled: existingMeta?.disabled ?? false,
|
|
1172
|
+
parentDeviceId,
|
|
1173
|
+
id,
|
|
1174
|
+
features: featuresArr
|
|
1175
|
+
}
|
|
1176
|
+
} });
|
|
1177
|
+
return { isFirstRegistration: isFirst };
|
|
1178
|
+
});
|
|
1179
|
+
if (Object.keys(config).length > 0) await settings.writeDeviceStore(id, config);
|
|
1180
|
+
idToAddonId.set(id, addonId);
|
|
1181
|
+
if (isFirstRegistration) this.ctx.eventBus.emit({
|
|
1182
|
+
id: (0, node_crypto.randomUUID)(),
|
|
1183
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1184
|
+
source: {
|
|
1185
|
+
type: "device",
|
|
1186
|
+
id
|
|
1187
|
+
},
|
|
1188
|
+
category: _camstack_types.EventCategory.DeviceRegistered,
|
|
1189
|
+
data: {
|
|
1190
|
+
deviceId: id,
|
|
1191
|
+
name: name.length > 0 ? name : stableId,
|
|
1192
|
+
providerId: addonId,
|
|
1193
|
+
parentDeviceId: parentDeviceId ?? null
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1196
|
+
else this.ctx.eventBus.emit({
|
|
1197
|
+
id: (0, node_crypto.randomUUID)(),
|
|
1198
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1199
|
+
source: {
|
|
1200
|
+
type: "device",
|
|
1201
|
+
id
|
|
1202
|
+
},
|
|
1203
|
+
category: _camstack_types.EventCategory.DeviceMetaChanged,
|
|
1204
|
+
data: {
|
|
1205
|
+
deviceId: id,
|
|
1206
|
+
name: name.length > 0 ? name : stableId,
|
|
1207
|
+
providerId: addonId,
|
|
1208
|
+
parentDeviceId: parentDeviceId ?? null,
|
|
1209
|
+
features: featuresArr
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
},
|
|
1213
|
+
removeDevice: async (input) => {
|
|
1214
|
+
const { deviceId } = input;
|
|
1215
|
+
const persisted = await resolvePersistedById(deviceId);
|
|
1216
|
+
if (!persisted) return;
|
|
1217
|
+
const { addonId, stableId, meta: persistedMeta } = persisted;
|
|
1218
|
+
const key = deviceKey(addonId, stableId);
|
|
1219
|
+
const deviceName = persistedMeta.name;
|
|
1220
|
+
await withMetaWriteLock(async () => {
|
|
1221
|
+
const index = await readIndex();
|
|
1222
|
+
const remaining = (index[addonId] ?? []).filter((sid) => sid !== stableId);
|
|
1223
|
+
const updatedIndex = remaining.length > 0 ? {
|
|
1224
|
+
...index,
|
|
1225
|
+
[addonId]: remaining
|
|
1226
|
+
} : (() => {
|
|
1227
|
+
const { [addonId]: _removed, ...rest } = index;
|
|
1228
|
+
return rest;
|
|
1229
|
+
})();
|
|
1230
|
+
await settings.writeAddonStore({ deviceIndex: updatedIndex });
|
|
1231
|
+
const { [key]: _removedMeta, ...restMeta } = await readMeta();
|
|
1232
|
+
await settings.writeAddonStore({ deviceMeta: restMeta });
|
|
1233
|
+
const map = await readMetadataMap();
|
|
1234
|
+
if (key in map) {
|
|
1235
|
+
const { [key]: _removedMetadata, ...restMap } = map;
|
|
1236
|
+
await settings.writeAddonStore({ deviceMetadata: restMap });
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
await settings.clearDeviceStore(deviceId);
|
|
1240
|
+
const bindingsStore = await this.readBindingsStore();
|
|
1241
|
+
const bindingKey = String(deviceId);
|
|
1242
|
+
if (bindingsStore.deviceBindings[bindingKey]) {
|
|
1243
|
+
const { [bindingKey]: _removedBindings, ...restBindings } = bindingsStore.deviceBindings;
|
|
1244
|
+
await this.writeBindingsStore({ deviceBindings: restBindings });
|
|
1245
|
+
}
|
|
1246
|
+
this.remoteNativeCaps.delete(deviceId);
|
|
1247
|
+
this.capabilityRegistry?.unregisterAllNativeForDevice(deviceId);
|
|
1248
|
+
idToAddonId.delete(deviceId);
|
|
1249
|
+
this.ctx.logger.info("removed device", { tags: {
|
|
1250
|
+
deviceId,
|
|
1251
|
+
deviceName: deviceName.length > 0 ? deviceName : stableId
|
|
1252
|
+
} });
|
|
1253
|
+
this.ctx.eventBus.emit({
|
|
1254
|
+
id: (0, node_crypto.randomUUID)(),
|
|
1255
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1256
|
+
source: {
|
|
1257
|
+
type: "device",
|
|
1258
|
+
id: deviceId
|
|
1259
|
+
},
|
|
1260
|
+
category: _camstack_types.EventCategory.DeviceUnregistered,
|
|
1261
|
+
data: {
|
|
1262
|
+
deviceId,
|
|
1263
|
+
providerId: addonId,
|
|
1264
|
+
parentDeviceId: persistedMeta.parentDeviceId ?? null
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1267
|
+
},
|
|
1268
|
+
persistConfig: async (input) => {
|
|
1269
|
+
const { deviceId, data } = input;
|
|
1270
|
+
if (!await resolvePersistedById(deviceId)) throw new Error(`[device-manager] persistConfig: unknown device id=${deviceId}`);
|
|
1271
|
+
await settings.writeDeviceStore(deviceId, data);
|
|
1272
|
+
},
|
|
1273
|
+
loadConfig: async (input) => {
|
|
1274
|
+
const { deviceId } = input;
|
|
1275
|
+
if (!await resolvePersistedById(deviceId)) return {};
|
|
1276
|
+
return settings.readDeviceStore(deviceId);
|
|
1277
|
+
},
|
|
1278
|
+
/**
|
|
1279
|
+
* Load the operator-organisational meta surface for one device
|
|
1280
|
+
* (`name` / `location` / `disabled` / `type` / `parentDeviceId`
|
|
1281
|
+
* / `addonId` + `id` / `stableId`). Used by the kernel proxy's
|
|
1282
|
+
* device-context factory to populate `ctx.deviceMeta` before
|
|
1283
|
+
* the device class constructor runs. Returns `null` when no
|
|
1284
|
+
* persisted row exists for the id.
|
|
1285
|
+
*
|
|
1286
|
+
* Reads default `location` to `null` and `disabled` to `false`
|
|
1287
|
+
* for legacy rows that predate the field — production code
|
|
1288
|
+
* relies on the IDevice type contract that both are present.
|
|
1289
|
+
*/
|
|
1290
|
+
loadMeta: async (input) => {
|
|
1291
|
+
const { deviceId } = input;
|
|
1292
|
+
const persisted = await resolvePersistedById(deviceId);
|
|
1293
|
+
if (!persisted) return null;
|
|
1294
|
+
const { addonId, stableId, meta: m } = persisted;
|
|
1295
|
+
const key = deviceKey(addonId, stableId);
|
|
1296
|
+
const metadata = (await readMetadataMap())[key] ?? null;
|
|
1297
|
+
return {
|
|
1298
|
+
id: m.id,
|
|
1299
|
+
stableId,
|
|
1300
|
+
addonId,
|
|
1301
|
+
type: m.type,
|
|
1302
|
+
name: m.name,
|
|
1303
|
+
location: m.location ?? null,
|
|
1304
|
+
disabled: m.disabled ?? false,
|
|
1305
|
+
parentDeviceId: m.parentDeviceId,
|
|
1306
|
+
metadata
|
|
1307
|
+
};
|
|
1308
|
+
},
|
|
1309
|
+
/**
|
|
1310
|
+
* Update the operator-edited display name. Writes the meta
|
|
1311
|
+
* row, emits a `DeviceMetaChanged` event so live consumers
|
|
1312
|
+
* (UI device list, alert center) see the rename without
|
|
1313
|
+
* polling. The live `IDevice.name` mirror is updated by the
|
|
1314
|
+
* kernel proxy on its side (`device-cap-proxy.ts`).
|
|
1315
|
+
*/
|
|
1316
|
+
setName: async (input) => {
|
|
1317
|
+
const { deviceId, name } = input;
|
|
1318
|
+
await withMetaWriteLock(async () => {
|
|
1319
|
+
const persisted = await resolvePersistedById(deviceId);
|
|
1320
|
+
if (!persisted) throw new Error(`[device-manager] setName: unknown device id=${deviceId}`);
|
|
1321
|
+
const { addonId, stableId, meta: m } = persisted;
|
|
1322
|
+
const key = deviceKey(addonId, stableId);
|
|
1323
|
+
const allMeta = await readMeta();
|
|
1324
|
+
await settings.writeAddonStore({ deviceMeta: {
|
|
1325
|
+
...allMeta,
|
|
1326
|
+
[key]: {
|
|
1327
|
+
...m,
|
|
1328
|
+
name
|
|
1329
|
+
}
|
|
1330
|
+
} });
|
|
1331
|
+
});
|
|
1332
|
+
this.ctx.eventBus.emit({
|
|
1333
|
+
id: (0, node_crypto.randomUUID)(),
|
|
1334
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1335
|
+
source: {
|
|
1336
|
+
type: "device",
|
|
1337
|
+
id: deviceId
|
|
1338
|
+
},
|
|
1339
|
+
category: _camstack_types.EventCategory.DeviceMetaChanged,
|
|
1340
|
+
data: {
|
|
1341
|
+
deviceId,
|
|
1342
|
+
field: "name",
|
|
1343
|
+
value: name
|
|
1344
|
+
}
|
|
1345
|
+
});
|
|
1346
|
+
},
|
|
1347
|
+
/**
|
|
1348
|
+
* Update the operator-organisational location label. `null`
|
|
1349
|
+
* clears it. Mirrors the same persist-then-emit shape as
|
|
1350
|
+
* `setName`; consumers subscribe to `DeviceMetaChanged` and
|
|
1351
|
+
* filter on `field: 'location'`.
|
|
1352
|
+
*/
|
|
1353
|
+
setLocation: async (input) => {
|
|
1354
|
+
const { deviceId, location } = input;
|
|
1355
|
+
await withMetaWriteLock(async () => {
|
|
1356
|
+
const persisted = await resolvePersistedById(deviceId);
|
|
1357
|
+
if (!persisted) throw new Error(`[device-manager] setLocation: unknown device id=${deviceId}`);
|
|
1358
|
+
const { addonId, stableId, meta: m } = persisted;
|
|
1359
|
+
const key = deviceKey(addonId, stableId);
|
|
1360
|
+
const allMeta = await readMeta();
|
|
1361
|
+
await settings.writeAddonStore({ deviceMeta: {
|
|
1362
|
+
...allMeta,
|
|
1363
|
+
[key]: {
|
|
1364
|
+
...m,
|
|
1365
|
+
location
|
|
1366
|
+
}
|
|
1367
|
+
} });
|
|
1368
|
+
});
|
|
1369
|
+
this.ctx.eventBus.emit({
|
|
1370
|
+
id: (0, node_crypto.randomUUID)(),
|
|
1371
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1372
|
+
source: {
|
|
1373
|
+
type: "device",
|
|
1374
|
+
id: deviceId
|
|
1375
|
+
},
|
|
1376
|
+
category: _camstack_types.EventCategory.DeviceMetaChanged,
|
|
1377
|
+
data: {
|
|
1378
|
+
deviceId,
|
|
1379
|
+
field: "location",
|
|
1380
|
+
value: location
|
|
1381
|
+
}
|
|
1382
|
+
});
|
|
1383
|
+
},
|
|
1384
|
+
/**
|
|
1385
|
+
* Patch the device's hardware-identity metadata blob. Shallow
|
|
1386
|
+
* merge — `null` removes a key, anything else overwrites.
|
|
1387
|
+
* Drivers populate factual fields on first probe; operators
|
|
1388
|
+
* augment via the Device Info tab. Idempotent: a no-op patch
|
|
1389
|
+
* (every key already present with the same value) doesn't emit
|
|
1390
|
+
* the meta-changed event.
|
|
1391
|
+
*/
|
|
1392
|
+
setMetadata: async (input) => {
|
|
1393
|
+
const { deviceId, patch } = input;
|
|
1394
|
+
const result = await withMetaWriteLock(async () => {
|
|
1395
|
+
const persisted = await resolvePersistedById(deviceId);
|
|
1396
|
+
if (!persisted) throw new Error(`[device-manager] setMetadata: unknown device id=${deviceId}`);
|
|
1397
|
+
const { addonId, stableId } = persisted;
|
|
1398
|
+
const key = deviceKey(addonId, stableId);
|
|
1399
|
+
const map = await readMetadataMap();
|
|
1400
|
+
const next = { ...map[key] ?? {} };
|
|
1401
|
+
let changed = false;
|
|
1402
|
+
for (const [k, v] of Object.entries(patch)) if (v === null) {
|
|
1403
|
+
if (k in next) {
|
|
1404
|
+
delete next[k];
|
|
1405
|
+
changed = true;
|
|
1406
|
+
}
|
|
1407
|
+
} else if (next[k] !== v) {
|
|
1408
|
+
next[k] = v;
|
|
1409
|
+
changed = true;
|
|
1410
|
+
}
|
|
1411
|
+
if (!changed) return { changed: false };
|
|
1412
|
+
const hasFields = Object.keys(next).length > 0;
|
|
1413
|
+
const updatedMap = { ...map };
|
|
1414
|
+
if (hasFields) updatedMap[key] = next;
|
|
1415
|
+
else delete updatedMap[key];
|
|
1416
|
+
await settings.writeAddonStore({ deviceMetadata: updatedMap });
|
|
1417
|
+
return {
|
|
1418
|
+
changed: true,
|
|
1419
|
+
finalMeta: hasFields ? next : null
|
|
1420
|
+
};
|
|
1421
|
+
});
|
|
1422
|
+
if (!result.changed) return;
|
|
1423
|
+
this.ctx.eventBus.emit({
|
|
1424
|
+
id: (0, node_crypto.randomUUID)(),
|
|
1425
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1426
|
+
source: {
|
|
1427
|
+
type: "device",
|
|
1428
|
+
id: deviceId
|
|
1429
|
+
},
|
|
1430
|
+
category: _camstack_types.EventCategory.DeviceMetaChanged,
|
|
1431
|
+
data: {
|
|
1432
|
+
deviceId,
|
|
1433
|
+
field: "metadata",
|
|
1434
|
+
value: result.finalMeta
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
},
|
|
1438
|
+
/**
|
|
1439
|
+
* Soft-disable the device. Persisted on the meta row;
|
|
1440
|
+
* lifecycle gating is the driver's responsibility (BaseDevice
|
|
1441
|
+
* exposes `this.disabled` for the driver to consult at the top
|
|
1442
|
+
* of its lifecycle methods).
|
|
1443
|
+
*/
|
|
1444
|
+
setDisabled: async (input) => {
|
|
1445
|
+
const { deviceId, disabled } = input;
|
|
1446
|
+
await withMetaWriteLock(async () => {
|
|
1447
|
+
const persisted = await resolvePersistedById(deviceId);
|
|
1448
|
+
if (!persisted) throw new Error(`[device-manager] setDisabled: unknown device id=${deviceId}`);
|
|
1449
|
+
const { addonId, stableId, meta: m } = persisted;
|
|
1450
|
+
const key = deviceKey(addonId, stableId);
|
|
1451
|
+
const allMeta = await readMeta();
|
|
1452
|
+
await settings.writeAddonStore({ deviceMeta: {
|
|
1453
|
+
...allMeta,
|
|
1454
|
+
[key]: {
|
|
1455
|
+
...m,
|
|
1456
|
+
disabled
|
|
1457
|
+
}
|
|
1458
|
+
} });
|
|
1459
|
+
});
|
|
1460
|
+
this.ctx.eventBus.emit({
|
|
1461
|
+
id: (0, node_crypto.randomUUID)(),
|
|
1462
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1463
|
+
source: {
|
|
1464
|
+
type: "device",
|
|
1465
|
+
id: deviceId
|
|
1466
|
+
},
|
|
1467
|
+
category: _camstack_types.EventCategory.DeviceMetaChanged,
|
|
1468
|
+
data: {
|
|
1469
|
+
deviceId,
|
|
1470
|
+
field: "disabled",
|
|
1471
|
+
value: disabled
|
|
1472
|
+
}
|
|
1473
|
+
});
|
|
1474
|
+
},
|
|
1475
|
+
loadRuntimeState: async (input) => {
|
|
1476
|
+
const { deviceId } = input;
|
|
1477
|
+
if (!await resolvePersistedById(deviceId)) return {};
|
|
1478
|
+
const data = await settings.readDeviceRuntimeState(deviceId);
|
|
1479
|
+
this.seedMirror(deviceId, data);
|
|
1480
|
+
return data;
|
|
1481
|
+
},
|
|
1482
|
+
/**
|
|
1483
|
+
* Union of (1) operator-curated location registry and (2) labels
|
|
1484
|
+
* currently in use on persisted devices. Case-insensitive
|
|
1485
|
+
* dedupe (preserves the first-seen casing). Sorted
|
|
1486
|
+
* case-insensitively for stable UI. Drives the Device Info
|
|
1487
|
+
* location autocomplete.
|
|
1488
|
+
*/
|
|
1489
|
+
listLocations: async () => {
|
|
1490
|
+
const store = await settings.readAddonStore();
|
|
1491
|
+
const meta = store.deviceMeta ?? {};
|
|
1492
|
+
const registry = store.locations ?? [];
|
|
1493
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1494
|
+
const consider = (raw) => {
|
|
1495
|
+
if (typeof raw !== "string") return;
|
|
1496
|
+
const trimmed = raw.trim();
|
|
1497
|
+
if (trimmed.length === 0) return;
|
|
1498
|
+
const key = trimmed.toLowerCase();
|
|
1499
|
+
if (!seen.has(key)) seen.set(key, trimmed);
|
|
1500
|
+
};
|
|
1501
|
+
for (const label of registry) consider(label);
|
|
1502
|
+
for (const m of Object.values(meta)) consider(m.location);
|
|
1503
|
+
return [...seen.values()].sort((a, b) => a.localeCompare(b, void 0, { sensitivity: "base" }));
|
|
1504
|
+
},
|
|
1505
|
+
/**
|
|
1506
|
+
* Add a label to the curated location registry. Idempotent:
|
|
1507
|
+
* existing entries (case-insensitive match) are silently kept.
|
|
1508
|
+
* Empty / whitespace-only inputs throw — operators must supply a
|
|
1509
|
+
* meaningful label.
|
|
1510
|
+
*/
|
|
1511
|
+
addLocation: async (input) => {
|
|
1512
|
+
const trimmed = input.name.trim();
|
|
1513
|
+
if (trimmed.length === 0) throw new Error("[device-manager] addLocation: name must be non-empty");
|
|
1514
|
+
const current = (await settings.readAddonStore()).locations ?? [];
|
|
1515
|
+
if (current.some((l) => l.toLowerCase() === trimmed.toLowerCase())) return;
|
|
1516
|
+
await settings.writeAddonStore({ locations: [...current, trimmed] });
|
|
1517
|
+
},
|
|
1518
|
+
/**
|
|
1519
|
+
* Remove a label from the curated registry. Match is
|
|
1520
|
+
* case-insensitive. Devices that still reference this label keep
|
|
1521
|
+
* their `meta.location` value (the registry is a suggestion
|
|
1522
|
+
* list, not a foreign key) — pass `cascade: true` to also clear
|
|
1523
|
+
* `setLocation` on every device that referenced this exact
|
|
1524
|
+
* label. Cascade only matches case-insensitively + trimmed, same
|
|
1525
|
+
* as the registry equality check.
|
|
1526
|
+
*/
|
|
1527
|
+
removeLocation: async (input) => {
|
|
1528
|
+
const trimmed = input.name.trim();
|
|
1529
|
+
if (trimmed.length === 0) return;
|
|
1530
|
+
const store = await settings.readAddonStore();
|
|
1531
|
+
const current = store.locations ?? [];
|
|
1532
|
+
const remaining = current.filter((l) => l.toLowerCase() !== trimmed.toLowerCase());
|
|
1533
|
+
if (remaining.length !== current.length) await settings.writeAddonStore({ locations: remaining });
|
|
1534
|
+
if (input.cascade !== true) return;
|
|
1535
|
+
const meta = store.deviceMeta ?? {};
|
|
1536
|
+
const updates = { ...meta };
|
|
1537
|
+
const cleared = [];
|
|
1538
|
+
for (const [key, m] of Object.entries(meta)) {
|
|
1539
|
+
if (typeof m.location !== "string") continue;
|
|
1540
|
+
if (m.location.trim().toLowerCase() !== trimmed.toLowerCase()) continue;
|
|
1541
|
+
updates[key] = {
|
|
1542
|
+
...m,
|
|
1543
|
+
location: null
|
|
1544
|
+
};
|
|
1545
|
+
cleared.push(m.id);
|
|
1546
|
+
}
|
|
1547
|
+
if (cleared.length === 0) return;
|
|
1548
|
+
await settings.writeAddonStore({ deviceMeta: updates });
|
|
1549
|
+
for (const deviceId of cleared) this.ctx.eventBus.emit({
|
|
1550
|
+
id: (0, node_crypto.randomUUID)(),
|
|
1551
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1552
|
+
source: {
|
|
1553
|
+
type: "device",
|
|
1554
|
+
id: deviceId
|
|
1555
|
+
},
|
|
1556
|
+
category: _camstack_types.EventCategory.DeviceMetaChanged,
|
|
1557
|
+
data: {
|
|
1558
|
+
deviceId,
|
|
1559
|
+
field: "location",
|
|
1560
|
+
value: null
|
|
1561
|
+
}
|
|
1562
|
+
});
|
|
1563
|
+
},
|
|
1564
|
+
listPersistedByAddon: async (input) => {
|
|
1565
|
+
const { addonId } = input;
|
|
1566
|
+
const [index, meta] = await Promise.all([readIndex(), readMeta()]);
|
|
1567
|
+
return (index[addonId] ?? []).map((stableId) => {
|
|
1568
|
+
const m = meta[deviceKey(addonId, stableId)];
|
|
1569
|
+
return {
|
|
1570
|
+
id: m.id,
|
|
1571
|
+
stableId,
|
|
1572
|
+
type: m.type,
|
|
1573
|
+
name: m.name,
|
|
1574
|
+
location: m.location ?? null,
|
|
1575
|
+
disabled: m.disabled ?? false,
|
|
1576
|
+
parentDeviceId: m.parentDeviceId
|
|
1577
|
+
};
|
|
1578
|
+
});
|
|
1579
|
+
},
|
|
1580
|
+
listAll: async (input) => {
|
|
1581
|
+
const { addonId } = input;
|
|
1582
|
+
const results = [];
|
|
1583
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1584
|
+
const meta = await readMeta();
|
|
1585
|
+
const metadataMap = await readMetadataMap();
|
|
1586
|
+
if (registry) {
|
|
1587
|
+
const liveEntries = addonId ? registry.getAllForAddon(addonId).map((device) => ({
|
|
1588
|
+
addonId,
|
|
1589
|
+
device
|
|
1590
|
+
})) : registry.getAllWithAddonId();
|
|
1591
|
+
for (const { addonId: aid, device } of liveEntries) {
|
|
1592
|
+
const key = deviceKey(aid, device.stableId);
|
|
1593
|
+
const metadata = metadataMap[key] ?? null;
|
|
1594
|
+
const metaRow = meta[key] ?? null;
|
|
1595
|
+
results.push(toDeviceInfo(aid, device, metadata, metaRow));
|
|
1596
|
+
seen.add(key);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
const index = await readIndex();
|
|
1600
|
+
const targetAddons = addonId ? [addonId] : Object.keys(index);
|
|
1601
|
+
for (const aid of targetAddons) for (const stableId of index[aid] ?? []) {
|
|
1602
|
+
const key = deviceKey(aid, stableId);
|
|
1603
|
+
if (seen.has(key)) continue;
|
|
1604
|
+
const m = meta[key];
|
|
1605
|
+
const persistedType = m.type;
|
|
1606
|
+
const persistedConfig = await settings.readDeviceStore(m.id);
|
|
1607
|
+
const metadata = metadataMap[key] ?? null;
|
|
1608
|
+
results.push({
|
|
1609
|
+
id: m.id,
|
|
1610
|
+
stableId,
|
|
1611
|
+
addonId: aid,
|
|
1612
|
+
type: persistedType,
|
|
1613
|
+
name: m?.name ?? stableId,
|
|
1614
|
+
location: m?.location ?? null,
|
|
1615
|
+
disabled: m?.disabled ?? false,
|
|
1616
|
+
parentDeviceId: m?.parentDeviceId ?? null,
|
|
1617
|
+
role: null,
|
|
1618
|
+
online: registry !== null,
|
|
1619
|
+
features: persistedFeatures(m?.features),
|
|
1620
|
+
isCamera: persistedType === _camstack_types.DeviceType.Camera,
|
|
1621
|
+
config: persistedConfig ?? {},
|
|
1622
|
+
metadata
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
return results;
|
|
1626
|
+
},
|
|
1627
|
+
getDevice: async (input) => {
|
|
1628
|
+
const { deviceId } = input;
|
|
1629
|
+
if (registry) {
|
|
1630
|
+
const found = resolveDeviceById(registry, deviceId);
|
|
1631
|
+
if (found) {
|
|
1632
|
+
const key = deviceKey(found.addonId, found.device.stableId);
|
|
1633
|
+
const [map, metaMap] = await Promise.all([readMetadataMap(), readMeta()]);
|
|
1634
|
+
const metadata = map[key] ?? null;
|
|
1635
|
+
const metaRow = metaMap[key] ?? null;
|
|
1636
|
+
return toDeviceInfo(found.addonId, found.device, metadata, metaRow);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
const persisted = await resolvePersistedById(deviceId);
|
|
1640
|
+
if (!persisted) return null;
|
|
1641
|
+
const { addonId: aid, stableId, meta: m } = persisted;
|
|
1642
|
+
const persistedConfig = await settings.readDeviceStore(m.id);
|
|
1643
|
+
const key = deviceKey(aid, stableId);
|
|
1644
|
+
const metadata = (await readMetadataMap())[key] ?? null;
|
|
1645
|
+
return {
|
|
1646
|
+
id: deviceId,
|
|
1647
|
+
stableId,
|
|
1648
|
+
addonId: aid,
|
|
1649
|
+
type: m.type,
|
|
1650
|
+
name: m.name,
|
|
1651
|
+
location: m.location ?? null,
|
|
1652
|
+
disabled: m.disabled ?? false,
|
|
1653
|
+
parentDeviceId: m.parentDeviceId,
|
|
1654
|
+
role: null,
|
|
1655
|
+
online: true,
|
|
1656
|
+
features: persistedFeatures(m.features),
|
|
1657
|
+
isCamera: false,
|
|
1658
|
+
config: persistedConfig ?? {},
|
|
1659
|
+
metadata
|
|
1660
|
+
};
|
|
1661
|
+
},
|
|
1662
|
+
getChildren: async (input) => {
|
|
1663
|
+
const { parentDeviceId } = input;
|
|
1664
|
+
let ownerAddonId = null;
|
|
1665
|
+
if (registry) {
|
|
1666
|
+
if (registry.getById(parentDeviceId)) ownerAddonId = registry.getAddonId(parentDeviceId);
|
|
1667
|
+
}
|
|
1668
|
+
if (!ownerAddonId) {
|
|
1669
|
+
const persisted = await resolvePersistedById(parentDeviceId);
|
|
1670
|
+
if (!persisted) return [];
|
|
1671
|
+
ownerAddonId = persisted.addonId;
|
|
1672
|
+
}
|
|
1673
|
+
const results = [];
|
|
1674
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1675
|
+
const [index, meta, metadataMap] = await Promise.all([
|
|
1676
|
+
readIndex(),
|
|
1677
|
+
readMeta(),
|
|
1678
|
+
readMetadataMap()
|
|
1679
|
+
]);
|
|
1680
|
+
if (registry) {
|
|
1681
|
+
const liveChildren = registry.getChildren(parentDeviceId);
|
|
1682
|
+
for (const device of liveChildren) {
|
|
1683
|
+
const key = deviceKey(ownerAddonId, device.stableId);
|
|
1684
|
+
const metadata = metadataMap[key] ?? null;
|
|
1685
|
+
const metaRow = meta[key] ?? null;
|
|
1686
|
+
results.push(toDeviceInfo(ownerAddonId, device, metadata, metaRow));
|
|
1687
|
+
seen.add(key);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
const persistedChildren = (index[ownerAddonId] ?? []).filter((sid) => meta[deviceKey(ownerAddonId, sid)]?.parentDeviceId === parentDeviceId);
|
|
1691
|
+
for (const childStableId of persistedChildren) {
|
|
1692
|
+
const key = deviceKey(ownerAddonId, childStableId);
|
|
1693
|
+
if (seen.has(key)) continue;
|
|
1694
|
+
const m = meta[key];
|
|
1695
|
+
const persistedConfig = await settings.readDeviceStore(m.id);
|
|
1696
|
+
const metadata = metadataMap[key] ?? null;
|
|
1697
|
+
results.push({
|
|
1698
|
+
id: m.id,
|
|
1699
|
+
stableId: childStableId,
|
|
1700
|
+
addonId: ownerAddonId,
|
|
1701
|
+
type: m.type,
|
|
1702
|
+
name: m.name,
|
|
1703
|
+
location: m.location ?? null,
|
|
1704
|
+
disabled: m.disabled ?? false,
|
|
1705
|
+
parentDeviceId,
|
|
1706
|
+
role: null,
|
|
1707
|
+
online: registry !== null,
|
|
1708
|
+
features: persistedFeatures(m.features),
|
|
1709
|
+
isCamera: false,
|
|
1710
|
+
config: persistedConfig ?? {},
|
|
1711
|
+
metadata
|
|
1712
|
+
});
|
|
1713
|
+
}
|
|
1714
|
+
return results;
|
|
1715
|
+
},
|
|
1716
|
+
getStreamSources: async (input) => {
|
|
1717
|
+
const { deviceId } = input;
|
|
1718
|
+
if (registry) {
|
|
1719
|
+
const found = resolveDeviceById(registry, deviceId);
|
|
1720
|
+
if (found) {
|
|
1721
|
+
if (!isCameraDevice(found.device)) return [];
|
|
1722
|
+
return (await found.device.getStreamSources()).map((s) => ({
|
|
1723
|
+
id: s.id,
|
|
1724
|
+
label: s.label,
|
|
1725
|
+
protocol: s.protocol,
|
|
1726
|
+
url: s.url,
|
|
1727
|
+
resolution: s.resolution,
|
|
1728
|
+
fps: s.fps,
|
|
1729
|
+
bitrate: s.bitrate,
|
|
1730
|
+
codec: s.codec,
|
|
1731
|
+
profileHint: s.profileHint
|
|
1732
|
+
}));
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
if (!await resolvePersistedById(deviceId)) throw new Error(`[device-manager] Device with id ${deviceId} not found`);
|
|
1736
|
+
return (await requireDeviceOps(deviceId).getStreamSources({ deviceId })).map((s) => ({ ...s }));
|
|
1737
|
+
},
|
|
1738
|
+
getConfigSchema: async (input) => {
|
|
1739
|
+
const { deviceId } = input;
|
|
1740
|
+
if (registry) {
|
|
1741
|
+
const found = resolveDeviceById(registry, deviceId);
|
|
1742
|
+
if (found) return found.device.config.entries().map((entry) => ({
|
|
1743
|
+
key: entry.key,
|
|
1744
|
+
value: entry.value,
|
|
1745
|
+
...entry.description !== void 0 ? { description: entry.description } : {}
|
|
1746
|
+
}));
|
|
1747
|
+
}
|
|
1748
|
+
if (!await resolvePersistedById(deviceId)) throw new Error(`[device-manager] Device with id ${deviceId} not found`);
|
|
1749
|
+
return (await requireDeviceOps(deviceId).getConfigEntries({ deviceId })).map((e) => ({ ...e }));
|
|
1750
|
+
},
|
|
1751
|
+
getSettingsSchema: async (input) => {
|
|
1752
|
+
const { deviceId } = input;
|
|
1753
|
+
if (registry) {
|
|
1754
|
+
const found = resolveDeviceById(registry, deviceId);
|
|
1755
|
+
if (found) return found.device.getSettingsUISchema();
|
|
1756
|
+
}
|
|
1757
|
+
if (!await resolvePersistedById(deviceId)) return null;
|
|
1758
|
+
return await requireDeviceOps(deviceId).getSettingsSchema({ deviceId }) ?? null;
|
|
1759
|
+
},
|
|
1760
|
+
updateConfig: async (input) => {
|
|
1761
|
+
const { deviceId } = input;
|
|
1762
|
+
if (registry) {
|
|
1763
|
+
const found = resolveDeviceById(registry, deviceId);
|
|
1764
|
+
if (found) {
|
|
1765
|
+
await found.device.config.setAll(input.values);
|
|
1766
|
+
return { success: true };
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
if (!await resolvePersistedById(deviceId)) throw new Error(`[device-manager] Device with id ${deviceId} not found`);
|
|
1770
|
+
await requireDeviceOps(deviceId).setConfig({
|
|
1771
|
+
deviceId,
|
|
1772
|
+
values: input.values
|
|
1773
|
+
});
|
|
1774
|
+
return { success: true };
|
|
1775
|
+
},
|
|
1776
|
+
enable: async (input) => {
|
|
1777
|
+
await provider.setDisabled({
|
|
1778
|
+
deviceId: input.deviceId,
|
|
1779
|
+
disabled: false
|
|
1780
|
+
});
|
|
1781
|
+
return { success: true };
|
|
1782
|
+
},
|
|
1783
|
+
disable: async (input) => {
|
|
1784
|
+
await provider.setDisabled({
|
|
1785
|
+
deviceId: input.deviceId,
|
|
1786
|
+
disabled: true
|
|
1787
|
+
});
|
|
1788
|
+
return { success: true };
|
|
1789
|
+
},
|
|
1790
|
+
remove: async (input) => {
|
|
1791
|
+
const { deviceId } = input;
|
|
1792
|
+
if (registry) {
|
|
1793
|
+
const live = resolveDeviceById(registry, deviceId);
|
|
1794
|
+
if (live) {
|
|
1795
|
+
const deviceName = live.device.name;
|
|
1796
|
+
await live.device.removeDevice();
|
|
1797
|
+
registry.remove(deviceId);
|
|
1798
|
+
await provider.removeDevice({ deviceId });
|
|
1799
|
+
this.ctx.logger.info("removed hub-local device", { tags: {
|
|
1800
|
+
deviceId,
|
|
1801
|
+
deviceName
|
|
1802
|
+
} });
|
|
1803
|
+
return { success: true };
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
const persisted = await resolvePersistedById(deviceId);
|
|
1807
|
+
if (!persisted) throw new Error(`[device-manager] Device with id ${deviceId} not found`);
|
|
1808
|
+
const { meta: persistedMeta } = persisted;
|
|
1809
|
+
try {
|
|
1810
|
+
await requireDeviceOps(deviceId).removeDevice({ deviceId });
|
|
1811
|
+
} catch (err) {
|
|
1812
|
+
this.ctx.logger.warn("remove via device-ops failed — clearing persistence anyway", {
|
|
1813
|
+
tags: {
|
|
1814
|
+
deviceId,
|
|
1815
|
+
deviceName: persistedMeta.name
|
|
1816
|
+
},
|
|
1817
|
+
meta: { error: (0, _camstack_types.errMsg)(err) }
|
|
1818
|
+
});
|
|
1819
|
+
}
|
|
1820
|
+
await provider.removeDevice({ deviceId });
|
|
1821
|
+
return { success: true };
|
|
1822
|
+
},
|
|
1823
|
+
getStreamProfileMap: async (input) => {
|
|
1824
|
+
if (!registry) return {};
|
|
1825
|
+
const found = resolveDeviceById(registry, input.deviceId);
|
|
1826
|
+
if (!found) return {};
|
|
1827
|
+
const storedMap = found.device.config.entries().find((e) => e.key === "_profileMap")?.value;
|
|
1828
|
+
if (storedMap !== void 0 && typeof storedMap === "object" && storedMap !== null) return storedMap;
|
|
1829
|
+
if (!isCameraDevice(found.device)) return {};
|
|
1830
|
+
const sources = await found.device.getStreamSources();
|
|
1831
|
+
const profileMap = {};
|
|
1832
|
+
for (const s of sources) if (s.profileHint && s.id) profileMap[s.profileHint] = s.id;
|
|
1833
|
+
return profileMap;
|
|
1834
|
+
},
|
|
1835
|
+
setStreamProfileMap: async (input) => {
|
|
1836
|
+
const { deviceId } = input;
|
|
1837
|
+
if (registry) {
|
|
1838
|
+
const found = resolveDeviceById(registry, deviceId);
|
|
1839
|
+
if (found) {
|
|
1840
|
+
await found.device.config.setAll({ _profileMap: input.profileMap });
|
|
1841
|
+
return { success: true };
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
if (!await resolvePersistedById(deviceId)) throw new Error(`[device-manager] Device with id ${deviceId} not found`);
|
|
1845
|
+
await requireDeviceOps(deviceId).setConfig({
|
|
1846
|
+
deviceId,
|
|
1847
|
+
values: { _profileMap: input.profileMap }
|
|
1848
|
+
});
|
|
1849
|
+
return { success: true };
|
|
1850
|
+
},
|
|
1851
|
+
probeStreams: async (input) => {
|
|
1852
|
+
const streamProbe = this.ctx.kernel.streamProbe;
|
|
1853
|
+
if (!streamProbe) return [];
|
|
1854
|
+
const sources = await provider.getStreamSources({ deviceId: input.deviceId });
|
|
1855
|
+
const results = [];
|
|
1856
|
+
for (const s of sources) {
|
|
1857
|
+
if (!s.url) continue;
|
|
1858
|
+
try {
|
|
1859
|
+
const metadata = await streamProbe.probe(s.url, { force: true });
|
|
1860
|
+
results.push({
|
|
1861
|
+
streamId: s.id,
|
|
1862
|
+
width: metadata.width,
|
|
1863
|
+
height: metadata.height,
|
|
1864
|
+
codec: metadata.codec,
|
|
1865
|
+
fps: metadata.fps,
|
|
1866
|
+
bitrateKbps: metadata.bitrateKbps
|
|
1867
|
+
});
|
|
1868
|
+
} catch (err) {
|
|
1869
|
+
this.ctx.logger.debug("streamProbe.probe failed — returning placeholder", { meta: {
|
|
1870
|
+
deviceId: input.deviceId,
|
|
1871
|
+
streamId: s.id,
|
|
1872
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1873
|
+
} });
|
|
1874
|
+
results.push({ streamId: s.id });
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
return results;
|
|
1878
|
+
},
|
|
1879
|
+
discoverDevices: async (input) => {
|
|
1880
|
+
const dp = await this.requireDeviceProvider(input.addonId);
|
|
1881
|
+
if (!await dp.supportsDiscovery({})) throw new Error(`Addon "${input.addonId}" does not support device discovery`);
|
|
1882
|
+
return (await dp.discoverDevices({})).map((d) => ({
|
|
1883
|
+
stableId: d.stableId,
|
|
1884
|
+
type: d.type,
|
|
1885
|
+
suggestedName: d.suggestedName,
|
|
1886
|
+
prefilledConfig: d.prefilledConfig
|
|
1887
|
+
}));
|
|
1888
|
+
},
|
|
1889
|
+
adoptDevice: async (input) => {
|
|
1890
|
+
const dp = await this.requireDeviceProvider(input.addonId);
|
|
1891
|
+
if (!await dp.supportsDiscovery({})) throw new Error(`Addon "${input.addonId}" does not support device adoption`);
|
|
1892
|
+
return dp.adoptDiscoveredDevice({ candidate: input.candidate });
|
|
1893
|
+
},
|
|
1894
|
+
getCreationSchema: async (input) => {
|
|
1895
|
+
const dp = await this.requireDeviceProvider(input.addonId);
|
|
1896
|
+
if (!await dp.supportsManualCreation({})) return null;
|
|
1897
|
+
return await dp.getChildCreationSchema({ type: input.type }) ?? null;
|
|
1898
|
+
},
|
|
1899
|
+
createDevice: async (input) => {
|
|
1900
|
+
const dp = await this.requireDeviceProvider(input.addonId);
|
|
1901
|
+
if (!await dp.supportsManualCreation({})) throw new Error(`Addon "${input.addonId}" does not support manual device creation`);
|
|
1902
|
+
return dp.createDevice({
|
|
1903
|
+
type: input.type,
|
|
1904
|
+
config: input.config
|
|
1905
|
+
});
|
|
1906
|
+
},
|
|
1907
|
+
testCreationField: async (input) => {
|
|
1908
|
+
return (await this.requireDeviceProvider(input.addonId)).testCreationField({
|
|
1909
|
+
type: input.type,
|
|
1910
|
+
key: input.key,
|
|
1911
|
+
value: input.value,
|
|
1912
|
+
...input.formValues !== void 0 ? { formValues: input.formValues } : {}
|
|
1913
|
+
});
|
|
1914
|
+
},
|
|
1915
|
+
testField: async (input) => {
|
|
1916
|
+
const { deviceId } = input;
|
|
1917
|
+
let owningAddonId = null;
|
|
1918
|
+
if (registry) owningAddonId = registry.getAddonId(deviceId);
|
|
1919
|
+
if (!owningAddonId) owningAddonId = (await resolvePersistedById(deviceId))?.addonId ?? null;
|
|
1920
|
+
if (!owningAddonId) throw new Error(`Device with id ${deviceId} not found`);
|
|
1921
|
+
const dp = await this.waitDeviceProvider(owningAddonId);
|
|
1922
|
+
if (!dp) return {
|
|
1923
|
+
status: "ok",
|
|
1924
|
+
labels: [],
|
|
1925
|
+
error: void 0
|
|
1926
|
+
};
|
|
1927
|
+
if (typeof dp.testCreationField !== "function") return {
|
|
1928
|
+
status: "ok",
|
|
1929
|
+
labels: [],
|
|
1930
|
+
error: void 0
|
|
1931
|
+
};
|
|
1932
|
+
return dp.testCreationField({
|
|
1933
|
+
type: _camstack_types.DeviceType.Camera,
|
|
1934
|
+
key: input.key,
|
|
1935
|
+
value: input.value
|
|
1936
|
+
});
|
|
1937
|
+
},
|
|
1938
|
+
getBindings: async (input) => {
|
|
1939
|
+
const result = await this.getBindings({ deviceId: input.deviceId });
|
|
1940
|
+
return {
|
|
1941
|
+
deviceId: input.deviceId,
|
|
1942
|
+
entries: result.entries
|
|
1943
|
+
};
|
|
1944
|
+
},
|
|
1945
|
+
getAllBindings: async () => {
|
|
1946
|
+
return this.getAllBindings();
|
|
1947
|
+
},
|
|
1948
|
+
setWrapperActive: async (input) => {
|
|
1949
|
+
return this.setWrapperActive({
|
|
1950
|
+
deviceId: input.deviceId,
|
|
1951
|
+
capName: input.capName,
|
|
1952
|
+
wrapperAddonId: input.wrapperAddonId,
|
|
1953
|
+
active: input.active
|
|
1954
|
+
});
|
|
1955
|
+
},
|
|
1956
|
+
listWrappersForCap: async (input) => this.listWrappersForCap(input),
|
|
1957
|
+
listBindableCapsForDeviceType: async (input) => this.listBindableCapsForDeviceType(input),
|
|
1958
|
+
getDeviceSettingsAggregate: async (input) => {
|
|
1959
|
+
return this.getDeviceAggregate(input.deviceId, "settings");
|
|
1960
|
+
},
|
|
1961
|
+
getDeviceLiveInfoAggregate: async (input) => {
|
|
1962
|
+
return this.getDeviceAggregate(input.deviceId, "live");
|
|
1963
|
+
},
|
|
1964
|
+
getDeviceAggregate: async (input) => {
|
|
1965
|
+
const [settings, live] = await Promise.all([this.getDeviceAggregate(input.deviceId, "settings"), this.getDeviceAggregate(input.deviceId, "live")]);
|
|
1966
|
+
return {
|
|
1967
|
+
settings,
|
|
1968
|
+
live
|
|
1969
|
+
};
|
|
1970
|
+
},
|
|
1971
|
+
updateDeviceField: async (input) => {
|
|
1972
|
+
return this.updateDeviceField({
|
|
1973
|
+
deviceId: input.deviceId,
|
|
1974
|
+
writerCapName: input.writerCapName,
|
|
1975
|
+
writerAddonId: input.writerAddonId,
|
|
1976
|
+
key: input.key,
|
|
1977
|
+
value: input.value
|
|
1978
|
+
});
|
|
1979
|
+
},
|
|
1980
|
+
updateDeviceFieldsBatch: async (input) => {
|
|
1981
|
+
return this.updateDeviceFieldsBatch({
|
|
1982
|
+
deviceId: input.deviceId,
|
|
1983
|
+
changes: input.changes
|
|
1984
|
+
});
|
|
1985
|
+
},
|
|
1986
|
+
getDeviceStatusAggregate: async (input) => this.getDeviceStatusAggregate(input)
|
|
1987
|
+
};
|
|
1988
|
+
this.ctx.logger.info("registered device-manager capability", { meta: { liveRegistry: registry !== null } });
|
|
1989
|
+
if (registry) {
|
|
1990
|
+
this.propagator = new DeviceEventPropagator({
|
|
1991
|
+
eventBus: this.ctx.eventBus,
|
|
1992
|
+
getParentOf: (id) => registry.getById(id)?.parentDeviceId ?? null,
|
|
1993
|
+
logger: {
|
|
1994
|
+
warn: (msg, meta) => this.ctx.logger.warn(msg, meta ?? {}),
|
|
1995
|
+
debug: (msg, meta) => this.ctx.logger.debug(msg, meta ?? {})
|
|
1996
|
+
}
|
|
1997
|
+
});
|
|
1998
|
+
this.propagator.start();
|
|
1999
|
+
this.ctx.logger.info("device-event-propagator started");
|
|
2000
|
+
}
|
|
2001
|
+
return [{
|
|
2002
|
+
capability: _camstack_types.deviceManagerCapability,
|
|
2003
|
+
provider
|
|
2004
|
+
}, {
|
|
2005
|
+
capability: _camstack_types.deviceStateCapability,
|
|
2006
|
+
provider: {
|
|
2007
|
+
getSnapshot: async (input) => {
|
|
2008
|
+
return this.snapshotForDevice(input.deviceId);
|
|
2009
|
+
},
|
|
2010
|
+
getCapSlice: async (input) => {
|
|
2011
|
+
const slice = this.stateMirror.get(input.deviceId)?.get(input.capName);
|
|
2012
|
+
return slice ? { ...slice } : null;
|
|
2013
|
+
},
|
|
2014
|
+
getAllSnapshots: async () => {
|
|
2015
|
+
const out = {};
|
|
2016
|
+
for (const [deviceId, perCap] of this.stateMirror) {
|
|
2017
|
+
const dev = {};
|
|
2018
|
+
for (const [capName, slice] of perCap) dev[capName] = { ...slice };
|
|
2019
|
+
out[String(deviceId)] = dev;
|
|
2020
|
+
}
|
|
2021
|
+
return out;
|
|
2022
|
+
},
|
|
2023
|
+
setCapSlice: async (input) => {
|
|
2024
|
+
const { deviceId, capName, slice } = input;
|
|
2025
|
+
if (!await resolvePersistedById(deviceId)) throw new Error(`[device-manager] setCapSlice: unknown device id=${deviceId}`);
|
|
2026
|
+
this.applySingleCapUpdate(deviceId, capName, slice);
|
|
2027
|
+
this.scheduleRuntimeStateDiskWrite(deviceId, settings);
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
}];
|
|
2031
|
+
}
|
|
2032
|
+
/**
|
|
2033
|
+
* Single-cap mirror update — diff against the current mirror,
|
|
2034
|
+
* persist the new slice in-memory, emit `DeviceStateChanged` for
|
|
2035
|
+
* this cap. No-op on identical writes (both same shape and same
|
|
2036
|
+
* values). Called by `setCapSlice` provider.
|
|
2037
|
+
*/
|
|
2038
|
+
applySingleCapUpdate(deviceId, capName, slice) {
|
|
2039
|
+
let perCap = this.stateMirror.get(deviceId);
|
|
2040
|
+
if (!perCap) {
|
|
2041
|
+
perCap = /* @__PURE__ */ new Map();
|
|
2042
|
+
this.stateMirror.set(deviceId, perCap);
|
|
2043
|
+
}
|
|
2044
|
+
const prior = perCap.get(capName);
|
|
2045
|
+
if (prior && shallowEqual(prior, slice)) return;
|
|
2046
|
+
perCap.set(capName, { ...slice });
|
|
2047
|
+
this.emitStateChanged(deviceId, capName, slice);
|
|
2048
|
+
}
|
|
2049
|
+
/**
|
|
2050
|
+
* Debounced disk writer. Coalesces frequent writes (motion phase
|
|
2051
|
+
* transitions, battery pushes) into one `writeDeviceRuntimeState`
|
|
2052
|
+
* per `RUNTIME_STATE_DEBOUNCE_MS` window. Reads the per-device
|
|
2053
|
+
* blob from the live mirror at flush time so the disk picture is
|
|
2054
|
+
* always the latest state — no risk of writing a stale snapshot.
|
|
2055
|
+
*/
|
|
2056
|
+
scheduleRuntimeStateDiskWrite(deviceId, settings) {
|
|
2057
|
+
let slot = this.runtimeStateDebounce.get(deviceId);
|
|
2058
|
+
if (!slot) {
|
|
2059
|
+
slot = {
|
|
2060
|
+
timer: null,
|
|
2061
|
+
inFlight: null
|
|
2062
|
+
};
|
|
2063
|
+
this.runtimeStateDebounce.set(deviceId, slot);
|
|
2064
|
+
}
|
|
2065
|
+
if (slot.timer) return;
|
|
2066
|
+
slot.timer = setTimeout(() => {
|
|
2067
|
+
slot.timer = null;
|
|
2068
|
+
const blob = this.snapshotForDevice(deviceId);
|
|
2069
|
+
const write = (async () => {
|
|
2070
|
+
try {
|
|
2071
|
+
await settings.writeDeviceRuntimeState(deviceId, blob);
|
|
2072
|
+
} catch (err) {
|
|
2073
|
+
this.ctx.logger.warn("writeDeviceRuntimeState failed", {
|
|
2074
|
+
tags: { deviceId },
|
|
2075
|
+
meta: { error: err instanceof Error ? err.message : String(err) }
|
|
2076
|
+
});
|
|
2077
|
+
} finally {
|
|
2078
|
+
slot.inFlight = null;
|
|
2079
|
+
}
|
|
2080
|
+
})();
|
|
2081
|
+
slot.inFlight = write;
|
|
2082
|
+
}, DeviceManagerAddon.RUNTIME_STATE_DEBOUNCE_MS);
|
|
2083
|
+
}
|
|
2084
|
+
/**
|
|
2085
|
+
* One-shot mirror seed used by `loadRuntimeState` at boot so the
|
|
2086
|
+
* hub knows about every persisted slice without waiting for the
|
|
2087
|
+
* first `setCapSlice` call. No events emitted — this is
|
|
2088
|
+
* initial-state population, not a transition.
|
|
2089
|
+
*/
|
|
2090
|
+
seedMirror(deviceId, blob) {
|
|
2091
|
+
let perCap = this.stateMirror.get(deviceId);
|
|
2092
|
+
if (!perCap) {
|
|
2093
|
+
perCap = /* @__PURE__ */ new Map();
|
|
2094
|
+
this.stateMirror.set(deviceId, perCap);
|
|
2095
|
+
}
|
|
2096
|
+
for (const [capName, raw] of Object.entries(blob)) {
|
|
2097
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
2098
|
+
perCap.set(capName, { ...raw });
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
snapshotForDevice(deviceId) {
|
|
2102
|
+
const perCap = this.stateMirror.get(deviceId);
|
|
2103
|
+
if (!perCap) return {};
|
|
2104
|
+
const out = {};
|
|
2105
|
+
for (const [k, v] of perCap) out[k] = { ...v };
|
|
2106
|
+
return out;
|
|
2107
|
+
}
|
|
2108
|
+
emitStateChanged(deviceId, capName, slice) {
|
|
2109
|
+
this.ctx.eventBus.emit({
|
|
2110
|
+
id: (0, node_crypto.randomUUID)(),
|
|
2111
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
2112
|
+
source: {
|
|
2113
|
+
type: "device",
|
|
2114
|
+
id: deviceId
|
|
2115
|
+
},
|
|
2116
|
+
category: _camstack_types.EventCategory.DeviceStateChanged,
|
|
2117
|
+
data: {
|
|
2118
|
+
deviceId,
|
|
2119
|
+
capName,
|
|
2120
|
+
slice
|
|
2121
|
+
}
|
|
2122
|
+
});
|
|
2123
|
+
}
|
|
2124
|
+
async onShutdown() {
|
|
2125
|
+
this.propagator?.stop();
|
|
2126
|
+
this.propagator = null;
|
|
2127
|
+
const settings = this.ctx.settings;
|
|
2128
|
+
const pending = [];
|
|
2129
|
+
for (const [deviceId, slot] of this.runtimeStateDebounce) {
|
|
2130
|
+
if (slot.timer) {
|
|
2131
|
+
clearTimeout(slot.timer);
|
|
2132
|
+
slot.timer = null;
|
|
2133
|
+
if (settings) {
|
|
2134
|
+
const blob = this.snapshotForDevice(deviceId);
|
|
2135
|
+
pending.push(settings.writeDeviceRuntimeState(deviceId, blob).catch((err) => {
|
|
2136
|
+
this.ctx.logger.warn("shutdown writeDeviceRuntimeState failed", {
|
|
2137
|
+
tags: { deviceId },
|
|
2138
|
+
meta: { error: err instanceof Error ? err.message : String(err) }
|
|
2139
|
+
});
|
|
2140
|
+
}));
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
if (slot.inFlight) pending.push(slot.inFlight);
|
|
2144
|
+
}
|
|
2145
|
+
await Promise.all(pending);
|
|
2146
|
+
this.runtimeStateDebounce.clear();
|
|
2147
|
+
}
|
|
2149
2148
|
};
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
});
|
|
2149
|
+
//#endregion
|
|
2150
|
+
exports.DeviceManagerAddon = DeviceManagerAddon;
|
|
2151
|
+
exports.default = DeviceManagerAddon;
|
|
2152
|
+
|
|
2155
2153
|
//# sourceMappingURL=device-manager.addon.js.map
|