@camstack/system 1.2.437 → 1.2.439
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-runner.js +64 -12
- package/dist/addon-runner.mjs +65 -13
- package/dist/{agent-https-BZIwS4xF.js → agent-https-DlpTMDqA.js} +252 -22
- package/dist/{agent-https-BPfHrI4c.mjs → agent-https-U0d4uvn2.mjs} +211 -23
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +7 -1
- package/dist/builtins/alerts/alerts.addon.mjs +7 -1
- package/dist/builtins/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
- package/dist/builtins/calendars/calendars.addon.js +1 -1
- package/dist/builtins/calendars/calendars.addon.mjs +1 -1
- package/dist/builtins/camera-grid/index.js +9 -1
- package/dist/builtins/camera-grid/index.mjs +9 -1
- package/dist/builtins/composer/composer.addon.js +2 -2
- package/dist/builtins/composer/composer.addon.mjs +2 -2
- package/dist/builtins/console-logging/index.js +12 -5
- package/dist/builtins/console-logging/index.mjs +12 -5
- package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
- package/dist/builtins/device-manager/device-manager.addon.d.ts +5 -0
- package/dist/builtins/device-manager/device-manager.addon.js +717 -28
- package/dist/builtins/device-manager/device-manager.addon.mjs +717 -28
- package/dist/builtins/device-manager/device-meta-types.d.ts +4 -0
- package/dist/builtins/device-manager/device-provider-context.d.ts +9 -0
- package/dist/builtins/device-manager/device-removal-hooks.d.ts +103 -0
- package/dist/builtins/device-manager/device-row-store.d.ts +1 -0
- package/dist/builtins/device-manager/device-tombstone-store.d.ts +68 -0
- package/dist/builtins/device-manager/removal-cleanup.d.ts +50 -0
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/keychain/index.js +37 -4
- package/dist/builtins/keychain/index.mjs +37 -4
- package/dist/builtins/keychain/keychain-service.d.ts +14 -0
- package/dist/builtins/keychain/keychain-store.d.ts +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +12 -12
- package/dist/builtins/local-auth/local-auth.addon.mjs +12 -12
- package/dist/builtins/local-network/local-network.addon.js +16 -2
- package/dist/builtins/local-network/local-network.addon.mjs +16 -2
- package/dist/builtins/loki-logging/index.js +28 -5
- package/dist/builtins/loki-logging/index.mjs +28 -5
- package/dist/builtins/native-metrics/native-metrics.addon.js +11 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +11 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +11 -1
- package/dist/builtins/snapshot/index.mjs +11 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +4 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +4 -1
- package/dist/builtins/sqlite-storage/index.js +1 -1
- package/dist/builtins/sqlite-storage/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +3 -3
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +3 -3
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +22 -4
- package/dist/builtins/system-config/system-config.addon.mjs +22 -4
- package/dist/builtins/winston-logging/index.js +14 -5
- package/dist/builtins/winston-logging/index.mjs +14 -5
- package/dist/{composition-sources-BIT8IUKm.js → composition-sources-B4jC2FC7.js} +1 -1
- package/dist/{composition-sources-tpYR0f1x.mjs → composition-sources-BGOSCGWa.mjs} +1 -1
- package/dist/{constants-Bc6trhoW.mjs → constants-B92BjObl.mjs} +1 -1
- package/dist/{constants-CwRY32Nx.js → constants-BRsjQwi-.js} +1 -1
- package/dist/{device-ca-BhdBByGD.mjs → device-ca-46BUsWD1.mjs} +2 -2
- package/dist/{device-ca-DImD8IzR.js → device-ca-DHG9OW2m.js} +2 -2
- package/dist/{dist-CEAyXW3Q.js → dist-BOWdLjoK.js} +341 -13
- package/dist/{dist-m5JkT8UN.mjs → dist-yI6kF6r6.mjs} +306 -14
- package/dist/file-host-worker.js +1 -1
- package/dist/file-host-worker.mjs +1 -1
- package/dist/{hub-mdns-ciao-CBLvq2oj.js → hub-mdns-ciao-D7S5AJRU.js} +377 -69
- package/dist/{hub-mdns-ciao-DMlQTdPU.mjs → hub-mdns-ciao-DGwVc2uh.mjs} +344 -72
- package/dist/i18n/catalog-registry.d.ts +35 -0
- package/dist/i18n/index.d.ts +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +698 -22
- package/dist/index.mjs +681 -24
- package/dist/kernel/gc-census.d.ts +18 -1
- package/dist/kernel/settings-view-types.d.ts +2 -0
- package/dist/kernel/transport/local-transport.d.ts +3 -0
- package/dist/kernel/transport/peer-answered-error.d.ts +5 -1
- package/dist/{lib-NNxHSOIE.js → lib-8oZrELLm.js} +1 -1
- package/dist/{lib-BeGzVQcd.mjs → lib-DK38ompL.mjs} +1 -1
- package/dist/{restore-archive-DNxAStrJ.mjs → restore-archive-CpdUO-uS.mjs} +1 -1
- package/dist/{restore-archive-C75srVbG.js → restore-archive-DiGiI6Jp.js} +1 -1
- package/dist/{retired-settings-keys-DNfV28R_.js → retired-settings-keys-CyQ7uvta.js} +1 -1
- package/dist/{retired-settings-keys-C5qP9QIf.mjs → retired-settings-keys-UsVtLOT0.mjs} +1 -1
- package/dist/tls/agent-https.d.ts +2 -2
- package/dist/tls/boot-tls.d.ts +6 -0
- package/dist/tls/cert-evaluation.d.ts +1 -1
- package/dist/tls/cert-issue.d.ts +5 -1
- package/dist/tls/cert-manager.d.ts +18 -0
- package/dist/tls/cert-name-constraints.d.ts +53 -0
- package/dist/tls/index.d.ts +5 -4
- package/dist/tls/tls-actions.d.ts +27 -7
- package/dist/tls/tls-status.d.ts +22 -0
- package/dist/tls/uploaded-cert.d.ts +36 -4
- package/dist/{wal-maintenance-Ch79gy9S.js → wal-maintenance-DEL_jeG0.js} +0 -0
- package/dist/{wal-maintenance-DvnVO5IK.mjs → wal-maintenance-xsvdT5Go.mjs} +0 -0
- package/dist/{x509.es-r7Ht1thA.js → x509.es-C4cOo48M.js} +36 -0
- package/dist/{x509.es-CDvj67p1.mjs → x509.es-DK__ETIe.mjs} +1 -1
- package/package.json +3 -1
|
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
7
|
-
const require_retired_settings_keys = require("../../retired-settings-keys-
|
|
6
|
+
const require_dist = require("../../dist-BOWdLjoK.js");
|
|
7
|
+
const require_retired_settings_keys = require("../../retired-settings-keys-CyQ7uvta.js");
|
|
8
8
|
let zod = require("zod");
|
|
9
9
|
let node_crypto = require("node:crypto");
|
|
10
10
|
let _camstack_types_node = require("@camstack/types/node");
|
|
@@ -969,7 +969,9 @@ function toWireShape(input) {
|
|
|
969
969
|
const out = { sections: input.sections.map((s) => ({
|
|
970
970
|
id: s.id,
|
|
971
971
|
title: s.title,
|
|
972
|
+
...s.titleKey !== void 0 ? { titleKey: s.titleKey } : {},
|
|
972
973
|
...s.description !== void 0 ? { description: s.description } : {},
|
|
974
|
+
...s.descriptionKey !== void 0 ? { descriptionKey: s.descriptionKey } : {},
|
|
973
975
|
...s.style !== void 0 ? { style: s.style } : {},
|
|
974
976
|
...s.defaultCollapsed !== void 0 ? { defaultCollapsed: s.defaultCollapsed } : {},
|
|
975
977
|
...s.columns !== void 0 ? { columns: s.columns } : {},
|
|
@@ -1780,6 +1782,7 @@ function buildDayNightConfigSchema(options, status) {
|
|
|
1780
1782
|
type: "select",
|
|
1781
1783
|
key: "dayNight_mode",
|
|
1782
1784
|
label: "Mode",
|
|
1785
|
+
labelKey: "server:dayNightConfig.dayNight_mode.label",
|
|
1783
1786
|
options: options.modes.map((m) => ({
|
|
1784
1787
|
value: m,
|
|
1785
1788
|
label: MODE_LABELS[m]
|
|
@@ -1790,6 +1793,7 @@ function buildDayNightConfigSchema(options, status) {
|
|
|
1790
1793
|
type: "slider",
|
|
1791
1794
|
key: "dayNight_sensitivity",
|
|
1792
1795
|
label: "IR-cut sensitivity",
|
|
1796
|
+
labelKey: "server:dayNightConfig.dayNight_sensitivity.label",
|
|
1793
1797
|
min: options.sensitivity.min,
|
|
1794
1798
|
max: options.sensitivity.max,
|
|
1795
1799
|
step: options.sensitivity.step,
|
|
@@ -1800,6 +1804,7 @@ function buildDayNightConfigSchema(options, status) {
|
|
|
1800
1804
|
type: "number",
|
|
1801
1805
|
key: "dayNight_switchDelaySec",
|
|
1802
1806
|
label: "Switch delay",
|
|
1807
|
+
labelKey: "server:dayNightConfig.dayNight_switchDelaySec.label",
|
|
1803
1808
|
unit: "s",
|
|
1804
1809
|
min: options.switchDelaySec.min,
|
|
1805
1810
|
max: options.switchDelaySec.max,
|
|
@@ -1811,7 +1816,9 @@ function buildDayNightConfigSchema(options, status) {
|
|
|
1811
1816
|
id: "day-night",
|
|
1812
1817
|
tab: "image",
|
|
1813
1818
|
title: "Day / Night",
|
|
1819
|
+
titleKey: "server:section.s_day-night.title",
|
|
1814
1820
|
description: "IR-cut switching mode and the photocell knobs that gate it.",
|
|
1821
|
+
descriptionKey: "server:section.s_day-night.description",
|
|
1815
1822
|
columns: 2,
|
|
1816
1823
|
fields
|
|
1817
1824
|
}] };
|
|
@@ -1904,6 +1911,7 @@ function buildImageSettingsConfigSchema(options, status) {
|
|
|
1904
1911
|
type: "slider",
|
|
1905
1912
|
key: "imageSettings_brightness",
|
|
1906
1913
|
label: "Brightness",
|
|
1914
|
+
labelKey: "server:imageSettingsConfig.imageSettings_brightness.label",
|
|
1907
1915
|
min: options.brightness.min,
|
|
1908
1916
|
max: options.brightness.max,
|
|
1909
1917
|
step: options.brightness.step,
|
|
@@ -1914,6 +1922,7 @@ function buildImageSettingsConfigSchema(options, status) {
|
|
|
1914
1922
|
type: "slider",
|
|
1915
1923
|
key: "imageSettings_contrast",
|
|
1916
1924
|
label: "Contrast",
|
|
1925
|
+
labelKey: "server:imageSettingsConfig.imageSettings_contrast.label",
|
|
1917
1926
|
min: options.contrast.min,
|
|
1918
1927
|
max: options.contrast.max,
|
|
1919
1928
|
step: options.contrast.step,
|
|
@@ -1924,6 +1933,7 @@ function buildImageSettingsConfigSchema(options, status) {
|
|
|
1924
1933
|
type: "slider",
|
|
1925
1934
|
key: "imageSettings_saturation",
|
|
1926
1935
|
label: "Saturation",
|
|
1936
|
+
labelKey: "server:imageSettingsConfig.imageSettings_saturation.label",
|
|
1927
1937
|
min: options.saturation.min,
|
|
1928
1938
|
max: options.saturation.max,
|
|
1929
1939
|
step: options.saturation.step,
|
|
@@ -1934,6 +1944,7 @@ function buildImageSettingsConfigSchema(options, status) {
|
|
|
1934
1944
|
type: "slider",
|
|
1935
1945
|
key: "imageSettings_sharpness",
|
|
1936
1946
|
label: "Sharpness",
|
|
1947
|
+
labelKey: "server:imageSettingsConfig.imageSettings_sharpness.label",
|
|
1937
1948
|
min: options.sharpness.min,
|
|
1938
1949
|
max: options.sharpness.max,
|
|
1939
1950
|
step: options.sharpness.step,
|
|
@@ -1944,6 +1955,7 @@ function buildImageSettingsConfigSchema(options, status) {
|
|
|
1944
1955
|
type: "boolean",
|
|
1945
1956
|
key: "imageSettings_mirror",
|
|
1946
1957
|
label: "Mirror",
|
|
1958
|
+
labelKey: "server:imageSettingsConfig.imageSettings_mirror.label",
|
|
1947
1959
|
style: "switch",
|
|
1948
1960
|
default: status?.mirror ?? false
|
|
1949
1961
|
});
|
|
@@ -1951,6 +1963,7 @@ function buildImageSettingsConfigSchema(options, status) {
|
|
|
1951
1963
|
type: "boolean",
|
|
1952
1964
|
key: "imageSettings_flip",
|
|
1953
1965
|
label: "Flip",
|
|
1966
|
+
labelKey: "server:imageSettingsConfig.imageSettings_flip.label",
|
|
1954
1967
|
style: "switch",
|
|
1955
1968
|
default: status?.flip ?? false
|
|
1956
1969
|
});
|
|
@@ -1958,6 +1971,7 @@ function buildImageSettingsConfigSchema(options, status) {
|
|
|
1958
1971
|
type: "select",
|
|
1959
1972
|
key: "imageSettings_rotate",
|
|
1960
1973
|
label: "Rotation",
|
|
1974
|
+
labelKey: "server:imageSettingsConfig.imageSettings_rotate.label",
|
|
1961
1975
|
options: options.rotateOptions.map((r) => ({
|
|
1962
1976
|
value: r,
|
|
1963
1977
|
label: ROTATE_LABELS[r]
|
|
@@ -1968,6 +1982,7 @@ function buildImageSettingsConfigSchema(options, status) {
|
|
|
1968
1982
|
type: "select",
|
|
1969
1983
|
key: "imageSettings_whiteBalance",
|
|
1970
1984
|
label: "White balance",
|
|
1985
|
+
labelKey: "server:imageSettingsConfig.imageSettings_whiteBalance.label",
|
|
1971
1986
|
options: options.whiteBalanceModes.map((m) => ({
|
|
1972
1987
|
value: m,
|
|
1973
1988
|
label: WHITE_BALANCE_LABELS[m]
|
|
@@ -1978,6 +1993,7 @@ function buildImageSettingsConfigSchema(options, status) {
|
|
|
1978
1993
|
type: "slider",
|
|
1979
1994
|
key: "imageSettings_warmth",
|
|
1980
1995
|
label: "Warmth",
|
|
1996
|
+
labelKey: "server:imageSettingsConfig.imageSettings_warmth.label",
|
|
1981
1997
|
min: options.warmth.min,
|
|
1982
1998
|
max: options.warmth.max,
|
|
1983
1999
|
step: options.warmth.step,
|
|
@@ -1992,6 +2008,7 @@ function buildImageSettingsConfigSchema(options, status) {
|
|
|
1992
2008
|
type: "select",
|
|
1993
2009
|
key: "imageSettings_exposureMode",
|
|
1994
2010
|
label: "Exposure mode",
|
|
2011
|
+
labelKey: "server:imageSettingsConfig.imageSettings_exposureMode.label",
|
|
1995
2012
|
options: options.exposureModes.map((m) => ({
|
|
1996
2013
|
value: m,
|
|
1997
2014
|
label: EXPOSURE_LABELS[m]
|
|
@@ -2002,6 +2019,7 @@ function buildImageSettingsConfigSchema(options, status) {
|
|
|
2002
2019
|
type: "select",
|
|
2003
2020
|
key: "imageSettings_backlightMode",
|
|
2004
2021
|
label: "Backlight compensation",
|
|
2022
|
+
labelKey: "server:imageSettingsConfig.imageSettings_backlightMode.label",
|
|
2005
2023
|
options: options.backlightModes.map((m) => ({
|
|
2006
2024
|
value: m,
|
|
2007
2025
|
label: BACKLIGHT_LABELS[m]
|
|
@@ -2013,7 +2031,9 @@ function buildImageSettingsConfigSchema(options, status) {
|
|
|
2013
2031
|
id: "image-settings",
|
|
2014
2032
|
tab: "image",
|
|
2015
2033
|
title: "Image adjustment",
|
|
2034
|
+
titleKey: "server:section.s_image-settings.title",
|
|
2016
2035
|
description: "Picture sliders, orientation and exposure. Option lists are read live from the camera — fields the firmware doesn't expose are hidden.",
|
|
2036
|
+
descriptionKey: "server:section.s_image-settings.description",
|
|
2017
2037
|
columns: 2,
|
|
2018
2038
|
fields
|
|
2019
2039
|
}] };
|
|
@@ -2147,26 +2167,31 @@ function volumesField(status) {
|
|
|
2147
2167
|
{
|
|
2148
2168
|
key: "id",
|
|
2149
2169
|
label: "Id",
|
|
2170
|
+
labelKey: "server:volumesField.id.label",
|
|
2150
2171
|
kind: "monospace",
|
|
2151
2172
|
width: "70px"
|
|
2152
2173
|
},
|
|
2153
2174
|
{
|
|
2154
2175
|
key: "label",
|
|
2155
2176
|
label: "Name",
|
|
2177
|
+
labelKey: "server:volumesField.label.label",
|
|
2156
2178
|
kind: "monospace"
|
|
2157
2179
|
},
|
|
2158
2180
|
{
|
|
2159
2181
|
key: "status",
|
|
2160
2182
|
label: "Status",
|
|
2183
|
+
labelKey: "server:volumesField.status.label",
|
|
2161
2184
|
kind: "status"
|
|
2162
2185
|
},
|
|
2163
2186
|
{
|
|
2164
2187
|
key: "capacity",
|
|
2165
|
-
label: "Capacity"
|
|
2188
|
+
label: "Capacity",
|
|
2189
|
+
labelKey: "server:volumesField.capacity.label"
|
|
2166
2190
|
},
|
|
2167
2191
|
{
|
|
2168
2192
|
key: "free",
|
|
2169
|
-
label: "Free"
|
|
2193
|
+
label: "Free",
|
|
2194
|
+
labelKey: "server:volumesField.free.label"
|
|
2170
2195
|
}
|
|
2171
2196
|
],
|
|
2172
2197
|
value: rows,
|
|
@@ -2190,27 +2215,32 @@ function tracksField(status) {
|
|
|
2190
2215
|
{
|
|
2191
2216
|
key: "id",
|
|
2192
2217
|
label: "Track",
|
|
2218
|
+
labelKey: "server:tracksField.id.label",
|
|
2193
2219
|
kind: "monospace",
|
|
2194
2220
|
width: "80px"
|
|
2195
2221
|
},
|
|
2196
2222
|
{
|
|
2197
2223
|
key: "primary",
|
|
2198
2224
|
label: "Primary",
|
|
2225
|
+
labelKey: "server:tracksField.primary.label",
|
|
2199
2226
|
width: "80px"
|
|
2200
2227
|
},
|
|
2201
2228
|
{
|
|
2202
2229
|
key: "enabled",
|
|
2203
2230
|
label: "Enabled",
|
|
2231
|
+
labelKey: "server:tracksField.enabled.label",
|
|
2204
2232
|
kind: "status",
|
|
2205
2233
|
width: "90px"
|
|
2206
2234
|
},
|
|
2207
2235
|
{
|
|
2208
2236
|
key: "codec",
|
|
2209
|
-
label: "Codec"
|
|
2237
|
+
label: "Codec",
|
|
2238
|
+
labelKey: "server:tracksField.codec.label"
|
|
2210
2239
|
},
|
|
2211
2240
|
{
|
|
2212
2241
|
key: "resolution",
|
|
2213
|
-
label: "Resolution"
|
|
2242
|
+
label: "Resolution",
|
|
2243
|
+
labelKey: "server:tracksField.resolution.label"
|
|
2214
2244
|
}
|
|
2215
2245
|
],
|
|
2216
2246
|
value: rows,
|
|
@@ -2412,7 +2442,9 @@ function buildRecordingOnboardConfigSchema(options, status) {
|
|
|
2412
2442
|
id: "recording-onboard-storage",
|
|
2413
2443
|
tab: "recording",
|
|
2414
2444
|
title: "Camera storage",
|
|
2445
|
+
titleKey: "server:storageSection.s_recording-onboard-storage.title",
|
|
2415
2446
|
description: "The card in the camera, as the camera itself describes it.",
|
|
2447
|
+
descriptionKey: "server:storageSection.s_recording-onboard-storage.description",
|
|
2416
2448
|
columns: 1,
|
|
2417
2449
|
fields: [
|
|
2418
2450
|
storageBanner(status),
|
|
@@ -2423,7 +2455,9 @@ function buildRecordingOnboardConfigSchema(options, status) {
|
|
|
2423
2455
|
id: "recording-onboard-recording",
|
|
2424
2456
|
tab: "recording",
|
|
2425
2457
|
title: "Camera recording",
|
|
2458
|
+
titleKey: "server:recordingSection.s_recording-onboard-recording.title",
|
|
2426
2459
|
description: "What the camera records to its own card. This writes the camera, not CamStack's recorder.",
|
|
2460
|
+
descriptionKey: "server:recordingSection.s_recording-onboard-recording.description",
|
|
2427
2461
|
columns: 2,
|
|
2428
2462
|
fields: [
|
|
2429
2463
|
booleanField(RECORDING_ONBOARD_KEYS.enabled, "Record to card", "The camera-side master switch.", options.enabled, status?.enabled ?? null),
|
|
@@ -3032,7 +3066,7 @@ async function getStreamSources(pctx, input) {
|
|
|
3032
3066
|
}));
|
|
3033
3067
|
}
|
|
3034
3068
|
}
|
|
3035
|
-
if (!await pctx.metaStore.resolvePersistedById(deviceId)) throw
|
|
3069
|
+
if (!await pctx.metaStore.resolvePersistedById(deviceId)) throw require_dist.userError("device.not-found", { id: deviceId }, `[device-manager] Device with id ${deviceId} not found`);
|
|
3036
3070
|
return (await pctx.requireDeviceOps(deviceId).getStreamSources({ deviceId })).map((s) => ({ ...s }));
|
|
3037
3071
|
}
|
|
3038
3072
|
async function getConfigSchema(pctx, input) {
|
|
@@ -3045,7 +3079,7 @@ async function getConfigSchema(pctx, input) {
|
|
|
3045
3079
|
...entry.description !== void 0 ? { description: entry.description } : {}
|
|
3046
3080
|
}));
|
|
3047
3081
|
}
|
|
3048
|
-
if (!await pctx.metaStore.resolvePersistedById(deviceId)) throw
|
|
3082
|
+
if (!await pctx.metaStore.resolvePersistedById(deviceId)) throw require_dist.userError("device.not-found", { id: deviceId }, `[device-manager] Device with id ${deviceId} not found`);
|
|
3049
3083
|
return (await pctx.requireDeviceOps(deviceId).getConfigEntries({ deviceId })).map((e) => ({ ...e }));
|
|
3050
3084
|
}
|
|
3051
3085
|
async function getSettingsSchema(pctx, input) {
|
|
@@ -3066,7 +3100,7 @@ async function updateConfig(pctx, input) {
|
|
|
3066
3100
|
return { success: true };
|
|
3067
3101
|
}
|
|
3068
3102
|
}
|
|
3069
|
-
if (!await pctx.metaStore.resolvePersistedById(deviceId)) throw
|
|
3103
|
+
if (!await pctx.metaStore.resolvePersistedById(deviceId)) throw require_dist.userError("device.not-found", { id: deviceId }, `[device-manager] Device with id ${deviceId} not found`);
|
|
3070
3104
|
await pctx.requireDeviceOps(deviceId).setConfig({
|
|
3071
3105
|
deviceId,
|
|
3072
3106
|
values: input.values
|
|
@@ -3166,7 +3200,7 @@ async function setStreamProfileMap(pctx, input) {
|
|
|
3166
3200
|
return { success: true };
|
|
3167
3201
|
}
|
|
3168
3202
|
}
|
|
3169
|
-
if (!await pctx.metaStore.resolvePersistedById(deviceId)) throw
|
|
3203
|
+
if (!await pctx.metaStore.resolvePersistedById(deviceId)) throw require_dist.userError("device.not-found", { id: deviceId }, `[device-manager] Device with id ${deviceId} not found`);
|
|
3170
3204
|
await pctx.requireDeviceOps(deviceId).setConfig({
|
|
3171
3205
|
deviceId,
|
|
3172
3206
|
values: { _profileMap: input.profileMap }
|
|
@@ -3203,7 +3237,7 @@ async function probeStreams(pctx, input) {
|
|
|
3203
3237
|
}
|
|
3204
3238
|
async function discoverDevices(pctx, input) {
|
|
3205
3239
|
const dp = await pctx.host.requireDeviceProvider(input.addonId);
|
|
3206
|
-
if (!await dp.supportsDiscovery({})) throw
|
|
3240
|
+
if (!await dp.supportsDiscovery({})) throw require_dist.userError("system.addon-no-discovery", { addonId: String(input.addonId) }, `Addon "${input.addonId}" does not support device discovery`);
|
|
3207
3241
|
return (await dp.discoverDevices({})).map((d) => ({
|
|
3208
3242
|
stableId: d.stableId,
|
|
3209
3243
|
type: d.type,
|
|
@@ -3216,7 +3250,7 @@ async function discoverDevices(pctx, input) {
|
|
|
3216
3250
|
}
|
|
3217
3251
|
async function adoptDevice(pctx, input) {
|
|
3218
3252
|
const dp = await pctx.host.requireDeviceProvider(input.addonId);
|
|
3219
|
-
if (!await dp.supportsDiscovery({})) throw
|
|
3253
|
+
if (!await dp.supportsDiscovery({})) throw require_dist.userError("system.addon-no-adoption", { addonId: String(input.addonId) }, `Addon "${input.addonId}" does not support device adoption`);
|
|
3220
3254
|
const summary = await pctx.removalFence.runUnfenced(() => dp.adoptDiscoveredDevice({ candidate: input.candidate }));
|
|
3221
3255
|
if (input.integrationId !== void 0) try {
|
|
3222
3256
|
await pctx.stampIntegrationId(summary.id, input.integrationId);
|
|
@@ -3238,7 +3272,7 @@ async function getCreationSchema(pctx, input) {
|
|
|
3238
3272
|
}
|
|
3239
3273
|
async function createDevice(pctx, input) {
|
|
3240
3274
|
const dp = await pctx.host.requireDeviceProvider(input.addonId);
|
|
3241
|
-
if (!await dp.supportsManualCreation({})) throw
|
|
3275
|
+
if (!await dp.supportsManualCreation({})) throw require_dist.userError("system.addon-no-manual-create", { addonId: String(input.addonId) }, `Addon "${input.addonId}" does not support manual device creation`);
|
|
3242
3276
|
const summary = await pctx.removalFence.runUnfenced(() => dp.createDevice({
|
|
3243
3277
|
type: input.type,
|
|
3244
3278
|
config: input.config
|
|
@@ -3331,7 +3365,7 @@ async function testField(pctx, input) {
|
|
|
3331
3365
|
let owningAddonId = null;
|
|
3332
3366
|
if (pctx.registry) owningAddonId = pctx.registry.getAddonId(deviceId);
|
|
3333
3367
|
if (!owningAddonId) owningAddonId = (await pctx.metaStore.resolvePersistedById(deviceId))?.addonId ?? null;
|
|
3334
|
-
if (!owningAddonId) throw
|
|
3368
|
+
if (!owningAddonId) throw require_dist.userError("device.not-found", { id: deviceId }, `Device with id ${deviceId} not found`);
|
|
3335
3369
|
const dp = await pctx.host.waitDeviceProvider(owningAddonId);
|
|
3336
3370
|
if (!dp) return {
|
|
3337
3371
|
status: "ok",
|
|
@@ -3840,12 +3874,14 @@ async function buildBaseDeviceSection(deps, deviceId) {
|
|
|
3840
3874
|
} else if (driverResult.status === "unavailable") sections.push({
|
|
3841
3875
|
id: "driver-settings-unavailable",
|
|
3842
3876
|
title: "Driver Settings",
|
|
3877
|
+
titleKey: "server:baseDeviceSection.s_driver-settings-unavailable.title",
|
|
3843
3878
|
tab: "general",
|
|
3844
3879
|
order: 1,
|
|
3845
3880
|
fields: [{
|
|
3846
3881
|
type: "info",
|
|
3847
3882
|
key: "driver-settings-unavailable-notice",
|
|
3848
3883
|
label: "Temporarily unavailable",
|
|
3884
|
+
labelKey: "server:baseDeviceSection.s_driver-settings-unavailable.driver-settings-unavailable-notice.label",
|
|
3849
3885
|
content: "Driver settings are temporarily unavailable — the addon may be restarting. Refresh the page in a moment.",
|
|
3850
3886
|
variant: "warning"
|
|
3851
3887
|
}]
|
|
@@ -5546,6 +5582,250 @@ async function runLocationBackfillAtBoot(pctx, bootRows) {
|
|
|
5546
5582
|
});
|
|
5547
5583
|
}
|
|
5548
5584
|
//#endregion
|
|
5585
|
+
//#region src/builtins/device-manager/device-removal-hooks.ts
|
|
5586
|
+
/**
|
|
5587
|
+
* device-manager's side of `device-removal-hook` (D865): tell every implementer
|
|
5588
|
+
* a device was deleted, and answer the admin question "which stored data belongs
|
|
5589
|
+
* to devices that no longer exist?".
|
|
5590
|
+
*
|
|
5591
|
+
* Two entry points, one rule: **an implementer is told to delete only on a
|
|
5592
|
+
* recorded delete (a tombstone), or on an explicit operator confirm.** Nothing
|
|
5593
|
+
* here infers a deletion from a device being absent from a single read.
|
|
5594
|
+
*/
|
|
5595
|
+
/** The capability name implementers register under. */
|
|
5596
|
+
var DEVICE_REMOVAL_HOOK_CAP = "device-removal-hook";
|
|
5597
|
+
async function hookProviders(api) {
|
|
5598
|
+
return (await api.addons.listCapabilityProviders.query({ capName: DEVICE_REMOVAL_HOOK_CAP })).map((p) => p.addonId);
|
|
5599
|
+
}
|
|
5600
|
+
/**
|
|
5601
|
+
* The removal step that calls every implementer. Each implementer is its own
|
|
5602
|
+
* nested step, so a failing one is retried by the bounded reconcile without
|
|
5603
|
+
* re-calling the others. Failing to ENUMERATE the implementers throws, and the
|
|
5604
|
+
* outer cleanup retries the whole step.
|
|
5605
|
+
*/
|
|
5606
|
+
function removalHookStep(deps, input) {
|
|
5607
|
+
return {
|
|
5608
|
+
store: "removal-hooks",
|
|
5609
|
+
run: async () => {
|
|
5610
|
+
const api = deps.api;
|
|
5611
|
+
if (api === void 0) {
|
|
5612
|
+
deps.logger.warn("removal hooks not called — no api on this context", { tags: { deviceId: input.deviceId } });
|
|
5613
|
+
return 0;
|
|
5614
|
+
}
|
|
5615
|
+
const addonIds = await hookProviders(api);
|
|
5616
|
+
if (addonIds.length === 0) return 0;
|
|
5617
|
+
const steps = addonIds.map((addonId) => ({
|
|
5618
|
+
store: `hook:${addonId}`,
|
|
5619
|
+
run: async () => {
|
|
5620
|
+
const result = await api.deviceRemovalHook.onDeviceRemoved.mutate({
|
|
5621
|
+
...input,
|
|
5622
|
+
addonId
|
|
5623
|
+
});
|
|
5624
|
+
return Object.values(result.counts).reduce((sum, n) => sum + n, 0);
|
|
5625
|
+
}
|
|
5626
|
+
}));
|
|
5627
|
+
const report = await deps.cleanup.run(input.deviceId, steps);
|
|
5628
|
+
return Object.values(report.counts).reduce((sum, n) => sum + n, 0);
|
|
5629
|
+
}
|
|
5630
|
+
};
|
|
5631
|
+
}
|
|
5632
|
+
/**
|
|
5633
|
+
* The ids device-manager knows, from TWO consistent reads (D49). Refuses when a
|
|
5634
|
+
* read throws, answers empty (an empty fleet and a failed read look the same),
|
|
5635
|
+
* or returns fewer ids than the ledger has rows (a malformed row is skipped by
|
|
5636
|
+
* the decoder, and a device skipped is a device whose data would be deleted).
|
|
5637
|
+
* The known set is the UNION of both reads: an id is absent only when BOTH
|
|
5638
|
+
* reads lack it.
|
|
5639
|
+
*/
|
|
5640
|
+
async function checkFleetTwice(reads) {
|
|
5641
|
+
const known = /* @__PURE__ */ new Set();
|
|
5642
|
+
for (const attempt of [1, 2]) {
|
|
5643
|
+
let read;
|
|
5644
|
+
try {
|
|
5645
|
+
read = await reads.readFleet();
|
|
5646
|
+
} catch (err) {
|
|
5647
|
+
return {
|
|
5648
|
+
ok: false,
|
|
5649
|
+
reason: `device list read ${attempt} failed: ${require_dist.errMsg(err)}`
|
|
5650
|
+
};
|
|
5651
|
+
}
|
|
5652
|
+
if (read.ids.length === 0) return {
|
|
5653
|
+
ok: false,
|
|
5654
|
+
reason: `device list read ${attempt} answered empty`
|
|
5655
|
+
};
|
|
5656
|
+
if (read.ids.length !== read.rowCount) return {
|
|
5657
|
+
ok: false,
|
|
5658
|
+
reason: `device list read ${attempt} returned ${read.ids.length} of ${read.rowCount} rows`
|
|
5659
|
+
};
|
|
5660
|
+
for (const id of read.ids) known.add(id);
|
|
5661
|
+
}
|
|
5662
|
+
return {
|
|
5663
|
+
ok: true,
|
|
5664
|
+
known
|
|
5665
|
+
};
|
|
5666
|
+
}
|
|
5667
|
+
/** Stores holding data for devices that are not in `known`, grouped per device. */
|
|
5668
|
+
function orphanDevices(held, known) {
|
|
5669
|
+
const byDevice = /* @__PURE__ */ new Map();
|
|
5670
|
+
const holders = /* @__PURE__ */ new Set();
|
|
5671
|
+
let overlap = 0;
|
|
5672
|
+
for (const [, entry] of held) if (!holders.has(entry.deviceId)) {
|
|
5673
|
+
holders.add(entry.deviceId);
|
|
5674
|
+
if (known.has(entry.deviceId)) overlap += 1;
|
|
5675
|
+
}
|
|
5676
|
+
for (const [addonId, entry] of held) {
|
|
5677
|
+
if (known.has(entry.deviceId) || entry.count === 0) continue;
|
|
5678
|
+
const stores = byDevice.get(entry.deviceId) ?? {};
|
|
5679
|
+
stores[`${addonId}:${entry.store}`] = (stores[`${addonId}:${entry.store}`] ?? 0) + entry.count;
|
|
5680
|
+
byDevice.set(entry.deviceId, stores);
|
|
5681
|
+
}
|
|
5682
|
+
const idSpaceMismatch = holders.size >= 5 && overlap === 0;
|
|
5683
|
+
return {
|
|
5684
|
+
devices: [...byDevice.entries()].toSorted(([a], [b]) => a - b).map(([deviceId, stores]) => ({
|
|
5685
|
+
deviceId,
|
|
5686
|
+
stores
|
|
5687
|
+
})),
|
|
5688
|
+
idSpaceMismatch
|
|
5689
|
+
};
|
|
5690
|
+
}
|
|
5691
|
+
/** The admin "Clean data of deleted devices" action: dry run unless `confirm`. */
|
|
5692
|
+
async function cleanupDeletedDevicesData(deps, input) {
|
|
5693
|
+
const skipped = (reason, unreachable = []) => {
|
|
5694
|
+
deps.logger.warn("cleanup of deleted devices data skipped — nothing listed or deleted", { meta: { reason } });
|
|
5695
|
+
return {
|
|
5696
|
+
status: "skipped",
|
|
5697
|
+
reason,
|
|
5698
|
+
devices: [],
|
|
5699
|
+
unreachable: [...unreachable]
|
|
5700
|
+
};
|
|
5701
|
+
};
|
|
5702
|
+
const api = deps.api;
|
|
5703
|
+
if (api === void 0) return skipped("no api on this context");
|
|
5704
|
+
const fleet = await checkFleetTwice(deps.reads);
|
|
5705
|
+
if (!fleet.ok) return skipped(fleet.reason);
|
|
5706
|
+
let addonIds;
|
|
5707
|
+
try {
|
|
5708
|
+
addonIds = await hookProviders(api);
|
|
5709
|
+
} catch (err) {
|
|
5710
|
+
return skipped(`implementers could not be listed: ${require_dist.errMsg(err)}`);
|
|
5711
|
+
}
|
|
5712
|
+
const unreachable = /* @__PURE__ */ new Set();
|
|
5713
|
+
const held = [];
|
|
5714
|
+
for (const addonId of addonIds) try {
|
|
5715
|
+
for (const entry of await api.deviceRemovalHook.listDeviceData.query({ addonId })) held.push([addonId, entry]);
|
|
5716
|
+
} catch (err) {
|
|
5717
|
+
unreachable.add(addonId);
|
|
5718
|
+
deps.logger.warn("cleanup of deleted devices data: an implementer did not answer", { meta: {
|
|
5719
|
+
addonId,
|
|
5720
|
+
error: require_dist.errMsg(err)
|
|
5721
|
+
} });
|
|
5722
|
+
}
|
|
5723
|
+
if (deps.settings !== void 0) try {
|
|
5724
|
+
for (const scope of await deps.settings.list()) held.push(["settings", {
|
|
5725
|
+
store: scope.addonId,
|
|
5726
|
+
deviceId: scope.deviceId,
|
|
5727
|
+
count: 1
|
|
5728
|
+
}]);
|
|
5729
|
+
} catch (err) {
|
|
5730
|
+
return skipped(`device settings could not be listed: ${require_dist.errMsg(err)}`, [...unreachable]);
|
|
5731
|
+
}
|
|
5732
|
+
const { devices, idSpaceMismatch } = orphanDevices(held, fleet.known);
|
|
5733
|
+
if (idSpaceMismatch) return skipped("none of the device ids holding data is a known device — the id space looks wrong, refusing", [...unreachable]);
|
|
5734
|
+
if (!input.confirm) {
|
|
5735
|
+
deps.logger.info("cleanup of deleted devices data: dry run", { meta: {
|
|
5736
|
+
devices: devices.length,
|
|
5737
|
+
unreachable: [...unreachable]
|
|
5738
|
+
} });
|
|
5739
|
+
return {
|
|
5740
|
+
status: "dry-run",
|
|
5741
|
+
reason: null,
|
|
5742
|
+
devices: [...devices],
|
|
5743
|
+
unreachable: [...unreachable]
|
|
5744
|
+
};
|
|
5745
|
+
}
|
|
5746
|
+
const now = (deps.now ?? (() => Date.now()))();
|
|
5747
|
+
const cleaned = [];
|
|
5748
|
+
for (const device of devices) {
|
|
5749
|
+
const deleted = {};
|
|
5750
|
+
for (const addonId of addonIds) try {
|
|
5751
|
+
const result = await api.deviceRemovalHook.onDeviceRemoved.mutate({
|
|
5752
|
+
addonId,
|
|
5753
|
+
deviceId: device.deviceId,
|
|
5754
|
+
deletedAt: now,
|
|
5755
|
+
providerAddonId: "unknown"
|
|
5756
|
+
});
|
|
5757
|
+
for (const [store, count] of Object.entries(result.counts)) deleted[`${addonId}:${store}`] = count;
|
|
5758
|
+
} catch (err) {
|
|
5759
|
+
deps.logger.warn("cleanup of deleted devices data: an implementer failed", {
|
|
5760
|
+
tags: { deviceId: device.deviceId },
|
|
5761
|
+
meta: {
|
|
5762
|
+
addonId,
|
|
5763
|
+
error: require_dist.errMsg(err)
|
|
5764
|
+
}
|
|
5765
|
+
});
|
|
5766
|
+
}
|
|
5767
|
+
if (deps.settings !== void 0 && Object.keys(device.stores).some((k) => k.startsWith("settings:"))) try {
|
|
5768
|
+
deleted["settings"] = await deps.settings.clear(device.deviceId);
|
|
5769
|
+
} catch (err) {
|
|
5770
|
+
deps.logger.warn("cleanup of deleted devices data: settings not cleared", {
|
|
5771
|
+
tags: { deviceId: device.deviceId },
|
|
5772
|
+
meta: { error: require_dist.errMsg(err) }
|
|
5773
|
+
});
|
|
5774
|
+
}
|
|
5775
|
+
deps.logger.info("cleaned data of a device deleted before tombstones", {
|
|
5776
|
+
tags: { deviceId: device.deviceId },
|
|
5777
|
+
meta: { counts: deleted }
|
|
5778
|
+
});
|
|
5779
|
+
cleaned.push({
|
|
5780
|
+
deviceId: device.deviceId,
|
|
5781
|
+
stores: deleted
|
|
5782
|
+
});
|
|
5783
|
+
}
|
|
5784
|
+
return {
|
|
5785
|
+
status: "cleaned",
|
|
5786
|
+
reason: null,
|
|
5787
|
+
devices: cleaned,
|
|
5788
|
+
unreachable: [...unreachable]
|
|
5789
|
+
};
|
|
5790
|
+
}
|
|
5791
|
+
/** The settings-store collection every addon's per-device settings share. */
|
|
5792
|
+
var ADDON_DEVICE_SETTINGS_COLLECTION = "addon-device-settings";
|
|
5793
|
+
/** The engine's hard ceiling on one read; a result this long may be a truncated one. */
|
|
5794
|
+
var SCOPE_READ_LIMIT = 2e4;
|
|
5795
|
+
/** Scope access over the settings store; undefined when the store is unavailable. */
|
|
5796
|
+
function settingsScopes(store) {
|
|
5797
|
+
if (store === void 0) return void 0;
|
|
5798
|
+
return {
|
|
5799
|
+
list: async () => {
|
|
5800
|
+
const rows = await store.query.query({
|
|
5801
|
+
collection: ADDON_DEVICE_SETTINGS_COLLECTION,
|
|
5802
|
+
filter: { limit: SCOPE_READ_LIMIT }
|
|
5803
|
+
});
|
|
5804
|
+
if (rows.length >= SCOPE_READ_LIMIT) throw new Error(`the settings read returned ${rows.length} rows — possibly truncated`);
|
|
5805
|
+
const seen = /* @__PURE__ */ new Map();
|
|
5806
|
+
for (const row of rows) {
|
|
5807
|
+
const addonId = row.data["addonId"];
|
|
5808
|
+
const raw = row.data["deviceId"];
|
|
5809
|
+
if (typeof addonId !== "string" || typeof raw !== "string") continue;
|
|
5810
|
+
const deviceId = Number(raw);
|
|
5811
|
+
if (!Number.isInteger(deviceId) || deviceId <= 0) continue;
|
|
5812
|
+
seen.set(`${addonId}:${deviceId}`, {
|
|
5813
|
+
deviceId,
|
|
5814
|
+
addonId
|
|
5815
|
+
});
|
|
5816
|
+
}
|
|
5817
|
+
return [...seen.values()];
|
|
5818
|
+
},
|
|
5819
|
+
clear: async (deviceId) => {
|
|
5820
|
+
const { deleted } = await store.deleteWhere.mutate({
|
|
5821
|
+
collection: ADDON_DEVICE_SETTINGS_COLLECTION,
|
|
5822
|
+
filter: { where: { deviceId: String(deviceId) } }
|
|
5823
|
+
});
|
|
5824
|
+
return deleted;
|
|
5825
|
+
}
|
|
5826
|
+
};
|
|
5827
|
+
}
|
|
5828
|
+
//#endregion
|
|
5549
5829
|
//#region src/builtins/device-manager/device-meta-actions.ts
|
|
5550
5830
|
/**
|
|
5551
5831
|
* Device meta-mutation + persistence actions for the device-manager addon —
|
|
@@ -5944,6 +6224,91 @@ async function registerDevice(pctx, input) {
|
|
|
5944
6224
|
}
|
|
5945
6225
|
});
|
|
5946
6226
|
}
|
|
6227
|
+
/**
|
|
6228
|
+
* The stores device-manager (or the keychain, for secrets) keeps for a device.
|
|
6229
|
+
* Another addon's own per-device data is cleaned by that addon, off
|
|
6230
|
+
* `DeviceUnregistered` — the kernel never reaches into its tables (D864).
|
|
6231
|
+
*/
|
|
6232
|
+
function removalSteps(pctx, deviceId, providerAddonId, deletedAt) {
|
|
6233
|
+
const secrets = pctx.host.ctx.secrets;
|
|
6234
|
+
return [
|
|
6235
|
+
{
|
|
6236
|
+
store: "tombstone",
|
|
6237
|
+
run: async () => {
|
|
6238
|
+
await pctx.tombstones.record({
|
|
6239
|
+
deviceId,
|
|
6240
|
+
deletedAt,
|
|
6241
|
+
providerAddonId
|
|
6242
|
+
});
|
|
6243
|
+
return 1;
|
|
6244
|
+
}
|
|
6245
|
+
},
|
|
6246
|
+
{
|
|
6247
|
+
store: "keychain-secrets",
|
|
6248
|
+
run: async () => {
|
|
6249
|
+
const purge = secrets?.deleteDeviceScope;
|
|
6250
|
+
if (purge === void 0) {
|
|
6251
|
+
pctx.host.ctx.logger.warn("device secrets not purged — no keychain door", { tags: { deviceId } });
|
|
6252
|
+
return 0;
|
|
6253
|
+
}
|
|
6254
|
+
return (await purge(deviceId)).length;
|
|
6255
|
+
}
|
|
6256
|
+
},
|
|
6257
|
+
{
|
|
6258
|
+
store: "device-config",
|
|
6259
|
+
run: async () => {
|
|
6260
|
+
const stored = await pctx.settings.readDeviceStore(deviceId);
|
|
6261
|
+
await pctx.settings.clearDeviceStore(deviceId);
|
|
6262
|
+
return Object.keys(stored).length;
|
|
6263
|
+
}
|
|
6264
|
+
},
|
|
6265
|
+
{
|
|
6266
|
+
store: "addon-device-settings",
|
|
6267
|
+
run: async () => {
|
|
6268
|
+
const api = pctx.host.ctx.api?.settingsStore;
|
|
6269
|
+
if (api === void 0) throw new Error("settings-store api not available");
|
|
6270
|
+
const { deleted } = await api.deleteWhere.mutate({
|
|
6271
|
+
collection: "addon-device-settings",
|
|
6272
|
+
filter: { where: { deviceId: String(deviceId) } }
|
|
6273
|
+
});
|
|
6274
|
+
return deleted;
|
|
6275
|
+
}
|
|
6276
|
+
},
|
|
6277
|
+
{
|
|
6278
|
+
store: "runtime-state",
|
|
6279
|
+
run: async () => {
|
|
6280
|
+
await pctx.host.forgetMirrorDevice(deviceId);
|
|
6281
|
+
const stored = await pctx.settings.readDeviceRuntimeState(deviceId);
|
|
6282
|
+
await pctx.settings.clearDeviceRuntimeState(deviceId);
|
|
6283
|
+
return Object.keys(stored).length;
|
|
6284
|
+
}
|
|
6285
|
+
},
|
|
6286
|
+
{
|
|
6287
|
+
store: "field-claims",
|
|
6288
|
+
run: async () => {
|
|
6289
|
+
await pctx.host.fieldClaims.pruneDevice(deviceId, pctx.settings);
|
|
6290
|
+
return 0;
|
|
6291
|
+
}
|
|
6292
|
+
},
|
|
6293
|
+
{
|
|
6294
|
+
store: "bindings",
|
|
6295
|
+
run: () => pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
|
|
6296
|
+
const key = String(deviceId);
|
|
6297
|
+
const bindingsStore = await readBindingsStore(pctx.bindingsDeps);
|
|
6298
|
+
const slice = bindingsStore.deviceBindings[key];
|
|
6299
|
+
if (!slice) return 0;
|
|
6300
|
+
const { [key]: _removed, ...rest } = bindingsStore.deviceBindings;
|
|
6301
|
+
await writeBindingsStore(pctx.bindingsDeps, { deviceBindings: rest });
|
|
6302
|
+
return Object.keys(slice).length;
|
|
6303
|
+
})
|
|
6304
|
+
},
|
|
6305
|
+
removalHookStep(pctx.removalHooks, {
|
|
6306
|
+
deviceId,
|
|
6307
|
+
deletedAt,
|
|
6308
|
+
providerAddonId
|
|
6309
|
+
})
|
|
6310
|
+
];
|
|
6311
|
+
}
|
|
5947
6312
|
async function removeDevice(pctx, input) {
|
|
5948
6313
|
const { deviceId } = input;
|
|
5949
6314
|
const persisted = await pctx.metaStore.resolvePersistedById(deviceId);
|
|
@@ -5959,17 +6324,7 @@ async function removeDevice(pctx, input) {
|
|
|
5959
6324
|
});
|
|
5960
6325
|
});
|
|
5961
6326
|
pctx.secretConfigPaths.delete(deviceId);
|
|
5962
|
-
await pctx.
|
|
5963
|
-
await pctx.host.forgetMirrorDevice(deviceId);
|
|
5964
|
-
await pctx.settings.clearDeviceRuntimeState(deviceId);
|
|
5965
|
-
await pctx.host.fieldClaims.pruneDevice(deviceId, pctx.settings);
|
|
5966
|
-
const bindingKey = String(deviceId);
|
|
5967
|
-
await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
|
|
5968
|
-
const bindingsStore = await readBindingsStore(pctx.bindingsDeps);
|
|
5969
|
-
if (!bindingsStore.deviceBindings[bindingKey]) return;
|
|
5970
|
-
const { [bindingKey]: _removedBindings, ...restBindings } = bindingsStore.deviceBindings;
|
|
5971
|
-
await writeBindingsStore(pctx.bindingsDeps, { deviceBindings: restBindings });
|
|
5972
|
-
});
|
|
6327
|
+
await pctx.removalCleanup.run(deviceId, removalSteps(pctx, deviceId, addonId, Date.now()));
|
|
5973
6328
|
pctx.host.remoteNativeCaps.delete(deviceId);
|
|
5974
6329
|
pctx.host.capabilityRegistry?.unregisterAllNativeForDevice(deviceId);
|
|
5975
6330
|
pctx.metaStore.idToAddonId.delete(deviceId);
|
|
@@ -6666,7 +7021,7 @@ async function listLocations(pctx) {
|
|
|
6666
7021
|
*/
|
|
6667
7022
|
async function addLocation(pctx, input) {
|
|
6668
7023
|
const trimmed = input.name.trim();
|
|
6669
|
-
if (trimmed.length === 0) throw
|
|
7024
|
+
if (trimmed.length === 0) throw require_dist.userError("system.location-name-required", void 0, "[device-manager] addLocation: name must be non-empty");
|
|
6670
7025
|
await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
|
|
6671
7026
|
const current = (await pctx.metaStore.readStore()).locations ?? [];
|
|
6672
7027
|
if (current.some((l) => l.toLowerCase() === trimmed.toLowerCase())) return;
|
|
@@ -6712,7 +7067,7 @@ async function listLocationIcons(pctx) {
|
|
|
6712
7067
|
*/
|
|
6713
7068
|
async function setLocationIcon(pctx, input) {
|
|
6714
7069
|
const trimmed = input.location.trim();
|
|
6715
|
-
if (trimmed.length === 0) throw
|
|
7070
|
+
if (trimmed.length === 0) throw require_dist.userError("system.location-required", void 0, "[device-manager] setLocationIcon: location must be non-empty");
|
|
6716
7071
|
const key = require_dist.locationIconKey(trimmed);
|
|
6717
7072
|
await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
|
|
6718
7073
|
const icons = (await pctx.metaStore.readStore()).locationIcons ?? {};
|
|
@@ -6812,7 +7167,7 @@ async function listSiteZones(pctx) {
|
|
|
6812
7167
|
*/
|
|
6813
7168
|
async function setSiteZone(pctx, input) {
|
|
6814
7169
|
const label = input.name.trim();
|
|
6815
|
-
if (label.length === 0) throw
|
|
7170
|
+
if (label.length === 0) throw require_dist.userError("system.site-zone-name-required", void 0, "[device-manager] setSiteZone: name must be non-empty");
|
|
6816
7171
|
const key = require_dist.siteZoneKey(label);
|
|
6817
7172
|
await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
|
|
6818
7173
|
const zones = (await pctx.metaStore.readStore()).siteZones ?? {};
|
|
@@ -8299,6 +8654,181 @@ var DeviceRowStore = class {
|
|
|
8299
8654
|
}
|
|
8300
8655
|
};
|
|
8301
8656
|
//#endregion
|
|
8657
|
+
//#region src/builtins/device-manager/device-tombstone-store.ts
|
|
8658
|
+
/**
|
|
8659
|
+
* The tombstones of deleted devices (D865).
|
|
8660
|
+
*
|
|
8661
|
+
* A device DELETE is a fact other addons must be able to learn reliably, long
|
|
8662
|
+
* after the `DeviceUnregistered` event (telemetry, droppable — D8/D11) is gone.
|
|
8663
|
+
* device-manager is the only authority on whether a device exists, so the record
|
|
8664
|
+
* of a deletion lives here, in its own collection, beside the device ledger.
|
|
8665
|
+
*
|
|
8666
|
+
* ## What a tombstone is NOT
|
|
8667
|
+
*
|
|
8668
|
+
* It is not "the device is unregistered / offline / its provider is restarting".
|
|
8669
|
+
* Those are states of a device that still exists; only {@link record}'s one
|
|
8670
|
+
* caller — `removeDevice`, the single door every delete converges on — writes a
|
|
8671
|
+
* tombstone. Anything that reconciles from tombstones therefore can never
|
|
8672
|
+
* destroy data of a device that merely went quiet.
|
|
8673
|
+
*
|
|
8674
|
+
* ## Expiry
|
|
8675
|
+
*
|
|
8676
|
+
* {@link DEVICE_TOMBSTONE_RETENTION_MS} (90 days). A tombstone older than that
|
|
8677
|
+
* is deleted the next time one is written or read. Ninety days is generous on
|
|
8678
|
+
* purpose: an addon that stayed disabled for a quarter still learns of the
|
|
8679
|
+
* deletions on its return. Data of a device deleted longer ago than that is
|
|
8680
|
+
* reachable only by the admin "clean data of deleted devices" action.
|
|
8681
|
+
*/
|
|
8682
|
+
/**
|
|
8683
|
+
* @durable class=ledger owner=device-manager
|
|
8684
|
+
* write="`removeDevice` records one row per deleted device (key = device id) after the
|
|
8685
|
+
* device row is gone"
|
|
8686
|
+
* retention="90 days (`DEVICE_TOMBSTONE_RETENTION_MS`), pruned whenever a tombstone is
|
|
8687
|
+
* written or the list is read. Bounded by the deletions an operator makes in a quarter."
|
|
8688
|
+
*/
|
|
8689
|
+
var DEVICE_TOMBSTONES_COLLECTION = "device-manager:tombstones";
|
|
8690
|
+
var DEVICE_TOMBSTONE_COLUMNS = [
|
|
8691
|
+
{
|
|
8692
|
+
name: "id",
|
|
8693
|
+
type: "TEXT",
|
|
8694
|
+
primaryKey: true,
|
|
8695
|
+
notNull: true
|
|
8696
|
+
},
|
|
8697
|
+
{
|
|
8698
|
+
name: "deviceId",
|
|
8699
|
+
type: "INTEGER",
|
|
8700
|
+
notNull: true
|
|
8701
|
+
},
|
|
8702
|
+
{
|
|
8703
|
+
name: "deletedAt",
|
|
8704
|
+
type: "INTEGER",
|
|
8705
|
+
notNull: true
|
|
8706
|
+
},
|
|
8707
|
+
{
|
|
8708
|
+
name: "providerAddonId",
|
|
8709
|
+
type: "TEXT",
|
|
8710
|
+
notNull: true
|
|
8711
|
+
}
|
|
8712
|
+
];
|
|
8713
|
+
var DEVICE_TOMBSTONE_INDEXES = [{
|
|
8714
|
+
name: "idx_dm_tombstones_deleted_at",
|
|
8715
|
+
columns: ["deletedAt"]
|
|
8716
|
+
}];
|
|
8717
|
+
/** More than the engine's default row cap, so a read is never silently truncated. */
|
|
8718
|
+
var TOMBSTONE_READ_LIMIT = 2e4;
|
|
8719
|
+
function decodeTombstone(raw) {
|
|
8720
|
+
const deviceId = raw["deviceId"];
|
|
8721
|
+
const deletedAt = raw["deletedAt"];
|
|
8722
|
+
const providerAddonId = raw["providerAddonId"];
|
|
8723
|
+
if (typeof deviceId !== "number" || !Number.isInteger(deviceId) || deviceId <= 0) return null;
|
|
8724
|
+
if (typeof deletedAt !== "number" || typeof providerAddonId !== "string") return null;
|
|
8725
|
+
return {
|
|
8726
|
+
deviceId,
|
|
8727
|
+
deletedAt,
|
|
8728
|
+
providerAddonId
|
|
8729
|
+
};
|
|
8730
|
+
}
|
|
8731
|
+
var DeviceTombstoneStore = class {
|
|
8732
|
+
backend;
|
|
8733
|
+
logger;
|
|
8734
|
+
now;
|
|
8735
|
+
retentionMs;
|
|
8736
|
+
declared = null;
|
|
8737
|
+
constructor(opts) {
|
|
8738
|
+
this.backend = opts.backend;
|
|
8739
|
+
this.logger = opts.logger;
|
|
8740
|
+
this.now = opts.now ?? (() => Date.now());
|
|
8741
|
+
this.retentionMs = opts.retentionMs ?? 7776e6;
|
|
8742
|
+
}
|
|
8743
|
+
/**
|
|
8744
|
+
* Declare the collection now. Idempotent; a rejection is not latched.
|
|
8745
|
+
*
|
|
8746
|
+
* Called at device-manager init (D871) so the table exists on a hub that has
|
|
8747
|
+
* not deleted a device yet. It used to be declared only by the first
|
|
8748
|
+
* `record` / `listSince`, i.e. by the first delete or by an implementer's
|
|
8749
|
+
* reconcile — so a hub whose implementers had not been redeployed, and where
|
|
8750
|
+
* nothing was deleted, never had the table, and the census read that as
|
|
8751
|
+
* "D865 is not running". A DECLARATION only: nothing is read, written or
|
|
8752
|
+
* purged here.
|
|
8753
|
+
*/
|
|
8754
|
+
declare() {
|
|
8755
|
+
if (this.declared !== null) return this.declared;
|
|
8756
|
+
const run = this.backend.declareCollection({
|
|
8757
|
+
collection: DEVICE_TOMBSTONES_COLLECTION,
|
|
8758
|
+
columns: DEVICE_TOMBSTONE_COLUMNS,
|
|
8759
|
+
indexes: DEVICE_TOMBSTONE_INDEXES
|
|
8760
|
+
}).catch((err) => {
|
|
8761
|
+
this.declared = null;
|
|
8762
|
+
throw err;
|
|
8763
|
+
});
|
|
8764
|
+
this.declared = run;
|
|
8765
|
+
return run;
|
|
8766
|
+
}
|
|
8767
|
+
/** Record a deletion. Re-recording the same id keeps the EARLIER `deletedAt`. */
|
|
8768
|
+
async record(tombstone) {
|
|
8769
|
+
await this.declare();
|
|
8770
|
+
const key = String(tombstone.deviceId);
|
|
8771
|
+
const existing = await this.backend.get({
|
|
8772
|
+
collection: DEVICE_TOMBSTONES_COLLECTION,
|
|
8773
|
+
key
|
|
8774
|
+
});
|
|
8775
|
+
if ((isPlainObject(existing) ? decodeTombstone(existing) : null) === null) await this.backend.set({
|
|
8776
|
+
collection: DEVICE_TOMBSTONES_COLLECTION,
|
|
8777
|
+
key,
|
|
8778
|
+
value: {
|
|
8779
|
+
deviceId: tombstone.deviceId,
|
|
8780
|
+
deletedAt: tombstone.deletedAt,
|
|
8781
|
+
providerAddonId: tombstone.providerAddonId
|
|
8782
|
+
}
|
|
8783
|
+
});
|
|
8784
|
+
await this.pruneExpired();
|
|
8785
|
+
}
|
|
8786
|
+
/** Tombstones with `deletedAt >= sinceMs`, oldest first. Expired ones are pruned first. */
|
|
8787
|
+
async listSince(sinceMs) {
|
|
8788
|
+
await this.declare();
|
|
8789
|
+
await this.pruneExpired();
|
|
8790
|
+
const floor = Math.max(sinceMs, this.now() - this.retentionMs);
|
|
8791
|
+
const rows = await this.backend.query({
|
|
8792
|
+
collection: DEVICE_TOMBSTONES_COLLECTION,
|
|
8793
|
+
filter: {
|
|
8794
|
+
whereBetween: { deletedAt: [floor, Number.MAX_SAFE_INTEGER] },
|
|
8795
|
+
orderBy: {
|
|
8796
|
+
field: "deletedAt",
|
|
8797
|
+
direction: "asc"
|
|
8798
|
+
},
|
|
8799
|
+
limit: TOMBSTONE_READ_LIMIT
|
|
8800
|
+
}
|
|
8801
|
+
});
|
|
8802
|
+
const out = [];
|
|
8803
|
+
for (const row of rows) {
|
|
8804
|
+
const decoded = decodeTombstone(row.data);
|
|
8805
|
+
if (decoded !== null) out.push(decoded);
|
|
8806
|
+
}
|
|
8807
|
+
return out;
|
|
8808
|
+
}
|
|
8809
|
+
/** Delete tombstones older than the retention window. Best-effort: a failure is logged. */
|
|
8810
|
+
async pruneExpired() {
|
|
8811
|
+
try {
|
|
8812
|
+
const cutoff = this.now() - this.retentionMs;
|
|
8813
|
+
const stale = await this.backend.query({
|
|
8814
|
+
collection: DEVICE_TOMBSTONES_COLLECTION,
|
|
8815
|
+
filter: {
|
|
8816
|
+
whereBetween: { deletedAt: [0, cutoff - 1] },
|
|
8817
|
+
limit: TOMBSTONE_READ_LIMIT
|
|
8818
|
+
}
|
|
8819
|
+
});
|
|
8820
|
+
for (const row of stale) await this.backend.delete({
|
|
8821
|
+
collection: DEVICE_TOMBSTONES_COLLECTION,
|
|
8822
|
+
key: row.id
|
|
8823
|
+
});
|
|
8824
|
+
return stale.length;
|
|
8825
|
+
} catch (err) {
|
|
8826
|
+
this.logger.warn("device tombstone prune failed — retried on the next write", { meta: { error: require_dist.errMsg(err) } });
|
|
8827
|
+
return 0;
|
|
8828
|
+
}
|
|
8829
|
+
}
|
|
8830
|
+
};
|
|
8831
|
+
//#endregion
|
|
8302
8832
|
//#region src/builtins/device-manager/device-state-claims.ts
|
|
8303
8833
|
function createDeviceStateClaimMethods(deps) {
|
|
8304
8834
|
const { mirror, index, settings, logger } = deps;
|
|
@@ -9765,6 +10295,119 @@ function createMigrationGuard(now = Date.now) {
|
|
|
9765
10295
|
}
|
|
9766
10296
|
};
|
|
9767
10297
|
}
|
|
10298
|
+
//#endregion
|
|
10299
|
+
//#region src/builtins/device-manager/removal-cleanup.ts
|
|
10300
|
+
/**
|
|
10301
|
+
* What a device removal leaves behind is cleaned AT REMOVAL, by steps that
|
|
10302
|
+
* cannot block one another (D864).
|
|
10303
|
+
*
|
|
10304
|
+
* `removeDevice` used to run its clean-up as one straight line: the first step
|
|
10305
|
+
* that threw skipped every later one, and the keychain was not in the line at
|
|
10306
|
+
* all — a device-scoped secret (`secret://provider-dreame/device/4483/…`)
|
|
10307
|
+
* outlived its device for good. Each store is a {@link RemovalStep} now: run
|
|
10308
|
+
* independently, counted, logged ONCE per device with `tags: { deviceId }`,
|
|
10309
|
+
* and a step that failed is retried by a BOUNDED in-memory reconcile
|
|
10310
|
+
* (a few attempts, growing delays, then it gives up loudly).
|
|
10311
|
+
*
|
|
10312
|
+
* There is deliberately no boot-time sweep (the operator forbids purges on
|
|
10313
|
+
* boot): a retry that dies with the process is picked up by the keychain
|
|
10314
|
+
* admin's orphan listing (System -> Secrets), which names a device-scoped
|
|
10315
|
+
* secret whose device no longer points at it.
|
|
10316
|
+
*/
|
|
10317
|
+
/** Retry delays after a failed step; the list length bounds the attempts. */
|
|
10318
|
+
var REMOVAL_RETRY_DELAYS_MS = [
|
|
10319
|
+
3e4,
|
|
10320
|
+
12e4,
|
|
10321
|
+
6e5
|
|
10322
|
+
];
|
|
10323
|
+
async function attempt(steps) {
|
|
10324
|
+
const counts = {};
|
|
10325
|
+
const failed = [];
|
|
10326
|
+
const errors = {};
|
|
10327
|
+
for (const step of steps) try {
|
|
10328
|
+
counts[step.store] = await step.run();
|
|
10329
|
+
} catch (err) {
|
|
10330
|
+
failed.push(step);
|
|
10331
|
+
errors[step.store] = require_dist.errMsg(err);
|
|
10332
|
+
}
|
|
10333
|
+
return {
|
|
10334
|
+
counts,
|
|
10335
|
+
failed,
|
|
10336
|
+
errors
|
|
10337
|
+
};
|
|
10338
|
+
}
|
|
10339
|
+
function createRemovalCleanup(opts) {
|
|
10340
|
+
const label = opts.label ?? "device removal cleanup";
|
|
10341
|
+
const delays = opts.retryDelaysMs ?? REMOVAL_RETRY_DELAYS_MS;
|
|
10342
|
+
const setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
10343
|
+
const clearTimer = opts.clearTimer ?? ((handle) => clearTimeout(handle));
|
|
10344
|
+
const pending = /* @__PURE__ */ new Set();
|
|
10345
|
+
let disposed = false;
|
|
10346
|
+
const schedule = (deviceId, steps, retryIndex, first) => {
|
|
10347
|
+
if (disposed) return;
|
|
10348
|
+
const delay = delays[retryIndex];
|
|
10349
|
+
if (delay === void 0) {
|
|
10350
|
+
opts.logger.error(`${label} gave up — the data stays until cleaned by hand`, {
|
|
10351
|
+
tags: { deviceId },
|
|
10352
|
+
meta: {
|
|
10353
|
+
stores: steps.map((s) => s.store),
|
|
10354
|
+
attempts: retryIndex + (first ? 1 : 0)
|
|
10355
|
+
}
|
|
10356
|
+
});
|
|
10357
|
+
return;
|
|
10358
|
+
}
|
|
10359
|
+
const handle = setTimer(() => {
|
|
10360
|
+
pending.delete(handle);
|
|
10361
|
+
retry(deviceId, steps, retryIndex);
|
|
10362
|
+
}, delay);
|
|
10363
|
+
handle.unref?.();
|
|
10364
|
+
pending.add(handle);
|
|
10365
|
+
};
|
|
10366
|
+
const retry = async (deviceId, steps, retryIndex) => {
|
|
10367
|
+
const result = await attempt(steps);
|
|
10368
|
+
opts.logger.info(`${label} retried`, {
|
|
10369
|
+
tags: { deviceId },
|
|
10370
|
+
meta: {
|
|
10371
|
+
counts: result.counts,
|
|
10372
|
+
failed: result.failed.map((s) => s.store),
|
|
10373
|
+
retryIndex
|
|
10374
|
+
}
|
|
10375
|
+
});
|
|
10376
|
+
if (result.failed.length > 0) schedule(deviceId, result.failed, retryIndex + 1, false);
|
|
10377
|
+
};
|
|
10378
|
+
return {
|
|
10379
|
+
async run(deviceId, steps) {
|
|
10380
|
+
const result = await attempt(steps);
|
|
10381
|
+
const failedStores = result.failed.map((s) => s.store);
|
|
10382
|
+
const meta = {
|
|
10383
|
+
counts: result.counts,
|
|
10384
|
+
failed: failedStores,
|
|
10385
|
+
errors: result.errors
|
|
10386
|
+
};
|
|
10387
|
+
if (failedStores.length === 0) opts.logger.info(label, {
|
|
10388
|
+
tags: { deviceId },
|
|
10389
|
+
meta
|
|
10390
|
+
});
|
|
10391
|
+
else {
|
|
10392
|
+
opts.logger.warn(`${label} incomplete — failed steps are retried`, {
|
|
10393
|
+
tags: { deviceId },
|
|
10394
|
+
meta
|
|
10395
|
+
});
|
|
10396
|
+
schedule(deviceId, result.failed, 0, true);
|
|
10397
|
+
}
|
|
10398
|
+
return {
|
|
10399
|
+
deviceId,
|
|
10400
|
+
counts: result.counts,
|
|
10401
|
+
failed: failedStores
|
|
10402
|
+
};
|
|
10403
|
+
},
|
|
10404
|
+
dispose() {
|
|
10405
|
+
disposed = true;
|
|
10406
|
+
for (const handle of pending) clearTimer(handle);
|
|
10407
|
+
pending.clear();
|
|
10408
|
+
}
|
|
10409
|
+
};
|
|
10410
|
+
}
|
|
9768
10411
|
function pairKey(addonId, stableId) {
|
|
9769
10412
|
return `${addonId}${stableId}`;
|
|
9770
10413
|
}
|
|
@@ -9887,6 +10530,9 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
|
|
|
9887
10530
|
* `allocateDeviceId` never consults.
|
|
9888
10531
|
*/
|
|
9889
10532
|
removalFence = createRemovalFence();
|
|
10533
|
+
/** Store-by-store clean-up of a removed device, with a bounded retry (D864). */
|
|
10534
|
+
removalCleanupImpl = null;
|
|
10535
|
+
removalHooksCleanupImpl = null;
|
|
9890
10536
|
/** Declared secret config paths per device, as last registered (D783). */
|
|
9891
10537
|
secretConfigPaths = createSecretConfigPathRegistry();
|
|
9892
10538
|
/** The keychain side of driver device forms (D789), wired in onInitialize. */
|
|
@@ -10098,6 +10744,15 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
|
|
|
10098
10744
|
const deviceRows = new DeviceRowStore(deviceRowBackendOf(settingsStoreApi), this.ctx.logger.child("rows"));
|
|
10099
10745
|
await deviceRows.declare();
|
|
10100
10746
|
this.deviceRows = deviceRows;
|
|
10747
|
+
const tombstones = new DeviceTombstoneStore({
|
|
10748
|
+
backend: deviceRowBackendOf(settingsStoreApi),
|
|
10749
|
+
logger: this.ctx.logger.child("tombstones")
|
|
10750
|
+
});
|
|
10751
|
+
try {
|
|
10752
|
+
await tombstones.declare();
|
|
10753
|
+
} catch (err) {
|
|
10754
|
+
this.ctx.logger.warn("device tombstones collection not declared at init — retried on first use", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
10755
|
+
}
|
|
10101
10756
|
this.staleWrapperActivations.checkOnce(() => readBindingsStore(this.bindingsDeps));
|
|
10102
10757
|
try {
|
|
10103
10758
|
await require_retired_settings_keys.purgeRetiredSettingsRows(deviceRows.retiredRowStore(), this.ctx.logger.child("RetiredRows"));
|
|
@@ -10133,6 +10788,13 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
|
|
|
10133
10788
|
bindingsDeps: this.bindingsDeps,
|
|
10134
10789
|
migrationGuard: createMigrationGuard(),
|
|
10135
10790
|
removalFence: this.removalFence,
|
|
10791
|
+
removalCleanup: this.removalCleanup,
|
|
10792
|
+
tombstones,
|
|
10793
|
+
removalHooks: {
|
|
10794
|
+
api: this.ctx.api,
|
|
10795
|
+
logger: this.ctx.logger,
|
|
10796
|
+
cleanup: this.removalHooksCleanup
|
|
10797
|
+
},
|
|
10136
10798
|
secretConfigPaths: this.secretConfigPaths,
|
|
10137
10799
|
requireDeviceOps,
|
|
10138
10800
|
stampIntegrationId: stampIntegrationId$1,
|
|
@@ -10166,6 +10828,18 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
|
|
|
10166
10828
|
migrateDevice: (input) => migrateDevice(pctx, input),
|
|
10167
10829
|
registerDevice: (input) => registerDevice(pctx, input),
|
|
10168
10830
|
removeDevice: (input) => removeDevice(pctx, input),
|
|
10831
|
+
listDeletedSince: ({ sinceMs }) => tombstones.listSince(sinceMs),
|
|
10832
|
+
cleanupDeletedDevicesData: (input) => cleanupDeletedDevicesData({
|
|
10833
|
+
api: this.ctx.api,
|
|
10834
|
+
logger: this.ctx.logger,
|
|
10835
|
+
settings: settingsScopes(this.ctx.api?.settingsStore),
|
|
10836
|
+
reads: { readFleet: async () => {
|
|
10837
|
+
return {
|
|
10838
|
+
ids: (await deviceRows.listAll()).map((r) => r.meta.id),
|
|
10839
|
+
rowCount: await deviceRows.count()
|
|
10840
|
+
};
|
|
10841
|
+
} }
|
|
10842
|
+
}, input),
|
|
10169
10843
|
persistConfig: (input) => persistConfig(pctx, input),
|
|
10170
10844
|
loadConfig: (input) => loadConfig(pctx, input),
|
|
10171
10845
|
loadMeta: (input) => loadMeta(pctx, input),
|
|
@@ -10389,7 +11063,22 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
|
|
|
10389
11063
|
}
|
|
10390
11064
|
];
|
|
10391
11065
|
}
|
|
11066
|
+
get removalCleanup() {
|
|
11067
|
+
if (this.removalCleanupImpl === null) this.removalCleanupImpl = createRemovalCleanup({ logger: this.ctx.logger });
|
|
11068
|
+
return this.removalCleanupImpl;
|
|
11069
|
+
}
|
|
11070
|
+
get removalHooksCleanup() {
|
|
11071
|
+
if (this.removalHooksCleanupImpl === null) this.removalHooksCleanupImpl = createRemovalCleanup({
|
|
11072
|
+
logger: this.ctx.logger,
|
|
11073
|
+
label: "device removal hooks"
|
|
11074
|
+
});
|
|
11075
|
+
return this.removalHooksCleanupImpl;
|
|
11076
|
+
}
|
|
10392
11077
|
async onShutdown() {
|
|
11078
|
+
this.removalCleanupImpl?.dispose();
|
|
11079
|
+
this.removalCleanupImpl = null;
|
|
11080
|
+
this.removalHooksCleanupImpl?.dispose();
|
|
11081
|
+
this.removalHooksCleanupImpl = null;
|
|
10393
11082
|
this.propagator?.stop();
|
|
10394
11083
|
this.propagator = null;
|
|
10395
11084
|
this.uninstallStatusOverlay?.();
|