@camstack/addon-provider-unifi 0.2.60 → 0.2.62

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.js CHANGED
@@ -12756,7 +12756,26 @@ var DiscoveryCandidateSchema = object({
12756
12756
  * identity ahead of adoption. Rendering metadata (unit, precision)
12757
12757
  * flows live through the cap STATUS SLICE after adoption.
12758
12758
  */
12759
- sourceInfo: SourceInfoSchema.optional()
12759
+ sourceInfo: SourceInfoSchema.optional(),
12760
+ /**
12761
+ * Set when this candidate is a device the provider ALREADY owns.
12762
+ *
12763
+ * A scan cannot generally produce the identity a device was onboarded under
12764
+ * (Reolink keys on `mac-<mac>`, learned at adopt time), so a stableId
12765
+ * comparison never matches and an owned device looks addable. Re-adopting one
12766
+ * overwrites its config with scan-derived values — that is how a Home Hub's
12767
+ * Baichuan port was overwritten with its ONVIF port, taking the hub and its
12768
+ * three child cameras offline for four hours.
12769
+ *
12770
+ * A provider that can recognise its own devices says so here. Absent means
12771
+ * "not recognised", which is not the same as "known to be new" — a provider
12772
+ * that cannot tell simply never sets it.
12773
+ */
12774
+ alreadyOnboarded: boolean().optional(),
12775
+ /** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
12776
+ onboardedDeviceId: number().optional(),
12777
+ /** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
12778
+ onboardedName: string().optional()
12760
12779
  });
12761
12780
  /**
12762
12781
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -33011,6 +33030,22 @@ var BaseDeviceProvider = class extends BaseAddon {
33011
33030
  * failed — logged at ERROR with `tags.deviceId` and surfaced via
33012
33031
  * `getStatus().error`.
33013
33032
  */
33033
+ /**
33034
+ * Repair a row's PERSISTED config blob immediately before it is restored.
33035
+ * Default: no-op — most providers have nothing to heal.
33036
+ *
33037
+ * This exists because a restored device self-hydrates from the DB: `create()`
33038
+ * passes `{}` and `BaseDevice` parses the stored blob against the device
33039
+ * schema. A blob that lost a REQUIRED field therefore fails restore forever,
33040
+ * and no later pass revisits it — a hub-adopted Reolink camera whose blob had
33041
+ * been emptied failed all four bounded attempts against fields
33042
+ * (`host`, `password`) it inherits from its parent and never dials itself.
33043
+ *
33044
+ * Implementations get every saved row, so a child can read its parent's blob.
33045
+ * A heal that throws is treated like any other restore failure: retried under
33046
+ * the bound, then reported — never swallowed.
33047
+ */
33048
+ async healSavedConfig(_saved, _allSaved) {}
33014
33049
  async onRestoreDevices(savedDevices) {
33015
33050
  const restored = /* @__PURE__ */ new Set();
33016
33051
  const failures = [];
@@ -33019,6 +33054,7 @@ var BaseDeviceProvider = class extends BaseAddon {
33019
33054
  const Class = this.deviceClasses[saved.type];
33020
33055
  if (!Class) throw new Error(`no device class registered for type "${saved.type}"`);
33021
33056
  if (saved.parentDeviceId !== null && !restored.has(saved.parentDeviceId)) throw new Error(`parent device ${saved.parentDeviceId} not restored`);
33057
+ await this.healSavedConfig(saved, savedDevices);
33022
33058
  await this.ctx.kernel.devices.create(saved.stableId, Class, {}, saved.parentDeviceId);
33023
33059
  restored.add(saved.id);
33024
33060
  };
package/dist/addon.mjs CHANGED
@@ -12755,7 +12755,26 @@ var DiscoveryCandidateSchema = object({
12755
12755
  * identity ahead of adoption. Rendering metadata (unit, precision)
12756
12756
  * flows live through the cap STATUS SLICE after adoption.
12757
12757
  */
12758
- sourceInfo: SourceInfoSchema.optional()
12758
+ sourceInfo: SourceInfoSchema.optional(),
12759
+ /**
12760
+ * Set when this candidate is a device the provider ALREADY owns.
12761
+ *
12762
+ * A scan cannot generally produce the identity a device was onboarded under
12763
+ * (Reolink keys on `mac-<mac>`, learned at adopt time), so a stableId
12764
+ * comparison never matches and an owned device looks addable. Re-adopting one
12765
+ * overwrites its config with scan-derived values — that is how a Home Hub's
12766
+ * Baichuan port was overwritten with its ONVIF port, taking the hub and its
12767
+ * three child cameras offline for four hours.
12768
+ *
12769
+ * A provider that can recognise its own devices says so here. Absent means
12770
+ * "not recognised", which is not the same as "known to be new" — a provider
12771
+ * that cannot tell simply never sets it.
12772
+ */
12773
+ alreadyOnboarded: boolean().optional(),
12774
+ /** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
12775
+ onboardedDeviceId: number().optional(),
12776
+ /** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
12777
+ onboardedName: string().optional()
12759
12778
  });
12760
12779
  /**
12761
12780
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -33010,6 +33029,22 @@ var BaseDeviceProvider = class extends BaseAddon {
33010
33029
  * failed — logged at ERROR with `tags.deviceId` and surfaced via
33011
33030
  * `getStatus().error`.
33012
33031
  */
33032
+ /**
33033
+ * Repair a row's PERSISTED config blob immediately before it is restored.
33034
+ * Default: no-op — most providers have nothing to heal.
33035
+ *
33036
+ * This exists because a restored device self-hydrates from the DB: `create()`
33037
+ * passes `{}` and `BaseDevice` parses the stored blob against the device
33038
+ * schema. A blob that lost a REQUIRED field therefore fails restore forever,
33039
+ * and no later pass revisits it — a hub-adopted Reolink camera whose blob had
33040
+ * been emptied failed all four bounded attempts against fields
33041
+ * (`host`, `password`) it inherits from its parent and never dials itself.
33042
+ *
33043
+ * Implementations get every saved row, so a child can read its parent's blob.
33044
+ * A heal that throws is treated like any other restore failure: retried under
33045
+ * the bound, then reported — never swallowed.
33046
+ */
33047
+ async healSavedConfig(_saved, _allSaved) {}
33013
33048
  async onRestoreDevices(savedDevices) {
33014
33049
  const restored = /* @__PURE__ */ new Set();
33015
33050
  const failures = [];
@@ -33018,6 +33053,7 @@ var BaseDeviceProvider = class extends BaseAddon {
33018
33053
  const Class = this.deviceClasses[saved.type];
33019
33054
  if (!Class) throw new Error(`no device class registered for type "${saved.type}"`);
33020
33055
  if (saved.parentDeviceId !== null && !restored.has(saved.parentDeviceId)) throw new Error(`parent device ${saved.parentDeviceId} not restored`);
33056
+ await this.healSavedConfig(saved, savedDevices);
33021
33057
  await this.ctx.kernel.devices.create(saved.stableId, Class, {}, saved.parentDeviceId);
33022
33058
  restored.add(saved.id);
33023
33059
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-unifi",
3
- "version": "0.2.60",
3
+ "version": "0.2.62",
4
4
  "description": "UniFi Network controller device-provider addon for CamStack — local-controller infra switches/APs (as containers) + network-client presence. NO cameras/Protect.",
5
5
  "keywords": [
6
6
  "camstack",