@camstack/addon-provider-homeassistant 1.1.28 → 1.1.29

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
@@ -7356,6 +7356,24 @@ var RecordingRetentionSchema = object({
7356
7356
  maxSizeGb: number().min(0).optional()
7357
7357
  });
7358
7358
  /**
7359
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7360
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7361
+ * previews at. Five graduated steps; absent on a config = `standard` (the
7362
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
7363
+ *
7364
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7365
+ * Each window's index sidecar carries its own tile dims, so a camera whose
7366
+ * preset changed over time renders every historical window at the dims it was
7367
+ * written with.
7368
+ */
7369
+ var ScrubThumbnailPresetSchema = _enum([
7370
+ "minimal",
7371
+ "low",
7372
+ "standard",
7373
+ "high",
7374
+ "max"
7375
+ ]);
7376
+ /**
7359
7377
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7360
7378
  *
7361
7379
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -7392,7 +7410,14 @@ var RecordingConfigSchema = object({
7392
7410
  * derived into bands once via `migrateConfigToBands`.
7393
7411
  */
7394
7412
  bands: array(RecordingBandSchema).optional(),
7395
- retention: RecordingRetentionSchema.optional()
7413
+ retention: RecordingRetentionSchema.optional(),
7414
+ /**
7415
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7416
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7417
+ * windows only — existing sheets are immutable, and each window's index
7418
+ * carries its own tile dims so mixed-preset history renders correctly.
7419
+ */
7420
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7396
7421
  });
7397
7422
  /**
7398
7423
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -32801,7 +32826,7 @@ var BUS_EVENT_TYPES = [
32801
32826
  "deconz_event",
32802
32827
  "xiaomi_aqara.click"
32803
32828
  ];
32804
- var HaIntegrationManager = class {
32829
+ var HaIntegrationManager = class HaIntegrationManager {
32805
32830
  opts;
32806
32831
  wsClient = null;
32807
32832
  connection = null;
@@ -32815,6 +32840,15 @@ var HaIntegrationManager = class {
32815
32840
  lastCheckedAt = null;
32816
32841
  lastError = null;
32817
32842
  haInfo = {};
32843
+ /** Deadline timer for the INITIAL connect. `start()` no longer blocks on
32844
+ * the socket (see the comment there), so a broker whose host silently
32845
+ * drops packets would otherwise sit at `connecting` for the full OS TCP
32846
+ * timeout (tens of seconds) with no UI signal. This timer flips it to
32847
+ * `unreachable` promptly; a late successful connect still overrides it. */
32848
+ connectDeadlineTimer = null;
32849
+ /** Ceiling on the `connecting` state before the UI is shown `unreachable`.
32850
+ * Slightly above the throwaway `testSettings` probe timeout (8s). */
32851
+ static CONNECT_DEADLINE_MS = 1e4;
32818
32852
  constructor(opts) {
32819
32853
  this.opts = opts;
32820
32854
  }
@@ -32828,6 +32862,8 @@ var HaIntegrationManager = class {
32828
32862
  await this.refreshHaConfigInfo(connection);
32829
32863
  await this.ensureBaselineSubscription();
32830
32864
  await this.ensureBusEventSubscriptions();
32865
+ this.clearConnectDeadline();
32866
+ this.lastError = null;
32831
32867
  this.setStatus("connected");
32832
32868
  try {
32833
32869
  this.opts.onConnected?.(this.opts.id);
@@ -32863,14 +32899,32 @@ var HaIntegrationManager = class {
32863
32899
  }
32864
32900
  }
32865
32901
  });
32866
- try {
32867
- await this.wsClient.start();
32868
- } catch (err) {
32902
+ this.startConnectDeadline();
32903
+ this.wsClient.start().catch((err) => {
32869
32904
  this.classifyAuthError(err);
32870
- throw err;
32905
+ });
32906
+ }
32907
+ /** Arm the initial-connect deadline. Idempotent — clears any prior timer. */
32908
+ startConnectDeadline() {
32909
+ this.clearConnectDeadline();
32910
+ const timer = setTimeout(() => {
32911
+ this.connectDeadlineTimer = null;
32912
+ if (this.status === "connecting") {
32913
+ if (this.lastError === null) this.lastError = "Connection timed out";
32914
+ this.setStatus("unreachable");
32915
+ }
32916
+ }, HaIntegrationManager.CONNECT_DEADLINE_MS);
32917
+ if (typeof timer.unref === "function") timer.unref();
32918
+ this.connectDeadlineTimer = timer;
32919
+ }
32920
+ clearConnectDeadline() {
32921
+ if (this.connectDeadlineTimer) {
32922
+ clearTimeout(this.connectDeadlineTimer);
32923
+ this.connectDeadlineTimer = null;
32871
32924
  }
32872
32925
  }
32873
32926
  async stop() {
32927
+ this.clearConnectDeadline();
32874
32928
  if (this.upstreamSubscriptionUnsub) {
32875
32929
  try {
32876
32930
  await this.upstreamSubscriptionUnsub();
@@ -33085,15 +33139,19 @@ var HaIntegrationManager = class {
33085
33139
  }));
33086
33140
  }
33087
33141
  classifyAuthError(err) {
33142
+ this.clearConnectDeadline();
33088
33143
  if (err === 2) {
33144
+ this.lastError = "Authentication failed — check the access token";
33089
33145
  this.setStatus("auth-failed");
33090
- this.lastError = "invalid_auth";
33091
- } else if (err === 1 || err === 4) {
33146
+ } else if (err === 1) {
33147
+ this.lastError = "Cannot connect host unreachable or Home Assistant not responding";
33148
+ this.setStatus("unreachable");
33149
+ } else if (err === 4) {
33150
+ this.lastError = "Home Assistant host / URL is required";
33092
33151
  this.setStatus("unreachable");
33093
- this.lastError = errMsg(err);
33094
33152
  } else {
33095
- this.setStatus("error");
33096
33153
  this.lastError = errMsg(err);
33154
+ this.setStatus("error");
33097
33155
  }
33098
33156
  }
33099
33157
  requireConnection() {
package/dist/addon.mjs CHANGED
@@ -7355,6 +7355,24 @@ var RecordingRetentionSchema = object({
7355
7355
  maxSizeGb: number().min(0).optional()
7356
7356
  });
7357
7357
  /**
7358
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7359
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7360
+ * previews at. Five graduated steps; absent on a config = `standard` (the
7361
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
7362
+ *
7363
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7364
+ * Each window's index sidecar carries its own tile dims, so a camera whose
7365
+ * preset changed over time renders every historical window at the dims it was
7366
+ * written with.
7367
+ */
7368
+ var ScrubThumbnailPresetSchema = _enum([
7369
+ "minimal",
7370
+ "low",
7371
+ "standard",
7372
+ "high",
7373
+ "max"
7374
+ ]);
7375
+ /**
7358
7376
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7359
7377
  *
7360
7378
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -7391,7 +7409,14 @@ var RecordingConfigSchema = object({
7391
7409
  * derived into bands once via `migrateConfigToBands`.
7392
7410
  */
7393
7411
  bands: array(RecordingBandSchema).optional(),
7394
- retention: RecordingRetentionSchema.optional()
7412
+ retention: RecordingRetentionSchema.optional(),
7413
+ /**
7414
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7415
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7416
+ * windows only — existing sheets are immutable, and each window's index
7417
+ * carries its own tile dims so mixed-preset history renders correctly.
7418
+ */
7419
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7395
7420
  });
7396
7421
  /**
7397
7422
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -32800,7 +32825,7 @@ var BUS_EVENT_TYPES = [
32800
32825
  "deconz_event",
32801
32826
  "xiaomi_aqara.click"
32802
32827
  ];
32803
- var HaIntegrationManager = class {
32828
+ var HaIntegrationManager = class HaIntegrationManager {
32804
32829
  opts;
32805
32830
  wsClient = null;
32806
32831
  connection = null;
@@ -32814,6 +32839,15 @@ var HaIntegrationManager = class {
32814
32839
  lastCheckedAt = null;
32815
32840
  lastError = null;
32816
32841
  haInfo = {};
32842
+ /** Deadline timer for the INITIAL connect. `start()` no longer blocks on
32843
+ * the socket (see the comment there), so a broker whose host silently
32844
+ * drops packets would otherwise sit at `connecting` for the full OS TCP
32845
+ * timeout (tens of seconds) with no UI signal. This timer flips it to
32846
+ * `unreachable` promptly; a late successful connect still overrides it. */
32847
+ connectDeadlineTimer = null;
32848
+ /** Ceiling on the `connecting` state before the UI is shown `unreachable`.
32849
+ * Slightly above the throwaway `testSettings` probe timeout (8s). */
32850
+ static CONNECT_DEADLINE_MS = 1e4;
32817
32851
  constructor(opts) {
32818
32852
  this.opts = opts;
32819
32853
  }
@@ -32827,6 +32861,8 @@ var HaIntegrationManager = class {
32827
32861
  await this.refreshHaConfigInfo(connection);
32828
32862
  await this.ensureBaselineSubscription();
32829
32863
  await this.ensureBusEventSubscriptions();
32864
+ this.clearConnectDeadline();
32865
+ this.lastError = null;
32830
32866
  this.setStatus("connected");
32831
32867
  try {
32832
32868
  this.opts.onConnected?.(this.opts.id);
@@ -32862,14 +32898,32 @@ var HaIntegrationManager = class {
32862
32898
  }
32863
32899
  }
32864
32900
  });
32865
- try {
32866
- await this.wsClient.start();
32867
- } catch (err) {
32901
+ this.startConnectDeadline();
32902
+ this.wsClient.start().catch((err) => {
32868
32903
  this.classifyAuthError(err);
32869
- throw err;
32904
+ });
32905
+ }
32906
+ /** Arm the initial-connect deadline. Idempotent — clears any prior timer. */
32907
+ startConnectDeadline() {
32908
+ this.clearConnectDeadline();
32909
+ const timer = setTimeout(() => {
32910
+ this.connectDeadlineTimer = null;
32911
+ if (this.status === "connecting") {
32912
+ if (this.lastError === null) this.lastError = "Connection timed out";
32913
+ this.setStatus("unreachable");
32914
+ }
32915
+ }, HaIntegrationManager.CONNECT_DEADLINE_MS);
32916
+ if (typeof timer.unref === "function") timer.unref();
32917
+ this.connectDeadlineTimer = timer;
32918
+ }
32919
+ clearConnectDeadline() {
32920
+ if (this.connectDeadlineTimer) {
32921
+ clearTimeout(this.connectDeadlineTimer);
32922
+ this.connectDeadlineTimer = null;
32870
32923
  }
32871
32924
  }
32872
32925
  async stop() {
32926
+ this.clearConnectDeadline();
32873
32927
  if (this.upstreamSubscriptionUnsub) {
32874
32928
  try {
32875
32929
  await this.upstreamSubscriptionUnsub();
@@ -33084,15 +33138,19 @@ var HaIntegrationManager = class {
33084
33138
  }));
33085
33139
  }
33086
33140
  classifyAuthError(err) {
33141
+ this.clearConnectDeadline();
33087
33142
  if (err === 2) {
33143
+ this.lastError = "Authentication failed — check the access token";
33088
33144
  this.setStatus("auth-failed");
33089
- this.lastError = "invalid_auth";
33090
- } else if (err === 1 || err === 4) {
33145
+ } else if (err === 1) {
33146
+ this.lastError = "Cannot connect host unreachable or Home Assistant not responding";
33147
+ this.setStatus("unreachable");
33148
+ } else if (err === 4) {
33149
+ this.lastError = "Home Assistant host / URL is required";
33091
33150
  this.setStatus("unreachable");
33092
- this.lastError = errMsg(err);
33093
33151
  } else {
33094
- this.setStatus("error");
33095
33152
  this.lastError = errMsg(err);
33153
+ this.setStatus("error");
33096
33154
  }
33097
33155
  }
33098
33156
  requireConnection() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-homeassistant",
3
- "version": "1.1.28",
3
+ "version": "1.1.29",
4
4
  "description": "Home Assistant device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",