iobroker.parcelapp 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -129,12 +129,17 @@ sendTo("parcelapp.0", "addDelivery", {
129
129
  Placeholder for the next version (at the beginning of the line):
130
130
  ### **WORK IN PROGRESS**
131
131
  -->
132
+ ### 0.9.0 (2026-06-23)
133
+
134
+ - Fixed: tracked packages could disappear from the object tree after a temporary update error or an unexpected API response — a package is now kept until parcel.app actually stops returning it.
135
+ - Changed: multi-day delivery windows now show the date on each side (e.g. `12-06 14:30 - 12-08 18:30`) instead of looking same-day; out-of-range or reversed dates no longer produce a misleading window.
136
+
132
137
  ### 0.8.0 (2026-06-19)
133
138
 
134
139
  - The delivery window is now also shown for carriers that report it only as a date/time range, not just when the API provides a Unix timestamp.
135
140
  - When adding a delivery via script, you can now set an optional tracking language and request a push confirmation.
136
141
 
137
- ### 0.7.2 (2026-06-12)
142
+ ### 0.7.2 (2026-06-12) — stable
138
143
 
139
144
  - Much quieter state updates: a package's last-updated timestamp now only changes when its tracking data actually changed, and device entries are no longer rewritten on every poll
140
145
  - Adding a delivery with a malformed request now returns a clear error message instead of failing cryptically
@@ -147,12 +152,6 @@ sendTo("parcelapp.0", "addDelivery", {
147
152
 
148
153
  - Added optional Sentry error reporting: crashes are sent to the developer so issues get fixed faster. Active only with ioBroker diagnostics enabled; anonymous.
149
154
 
150
- ### 0.6.0 (2026-05-31)
151
-
152
- - The summary delivery window now covers the full time range when several packages are expected the same day — previously an overlapping window could be cut short.
153
- - Packages reported with an unrecognized status are no longer mistaken for delivered and removed; they stay visible as "Unknown".
154
- - A delivery added via the admin button now appears immediately instead of only after the next polling cycle.
155
-
156
155
  [Older changelogs can be found there](CHANGELOG_OLD.md)
157
156
 
158
157
  ## Support
@@ -21,7 +21,8 @@ __export(coerce_exports, {
21
21
  coerceClampedInt: () => coerceClampedInt,
22
22
  coerceFiniteNumber: () => coerceFiniteNumber,
23
23
  errText: () => errText,
24
- isTrueish: () => isTrueish
24
+ isTrueish: () => isTrueish,
25
+ oneLine: () => oneLine
25
26
  });
26
27
  module.exports = __toCommonJS(coerce_exports);
27
28
  const DECIMAL_NUMBER_RE = /^-?\d+(\.\d+)?$/;
@@ -77,11 +78,15 @@ function coerceClampedInt(raw, min, max, defaultValue) {
77
78
  }
78
79
  return Math.max(min, Math.min(max, Math.floor(n)));
79
80
  }
81
+ function oneLine(value) {
82
+ return value.replace(/[\r\n\t]+/g, " ");
83
+ }
80
84
  // Annotate the CommonJS export names for ESM import in node:
81
85
  0 && (module.exports = {
82
86
  coerceClampedInt,
83
87
  coerceFiniteNumber,
84
88
  errText,
85
- isTrueish
89
+ isTrueish,
90
+ oneLine
86
91
  });
87
92
  //# sourceMappingURL=coerce.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/coerce.ts"],
4
- "sourcesContent": ["/**\n * Boundary coercion helpers for external API data.\n *\n * The parcel.app API is documented but field types still drift in practice\n * (rare success-flag returned as `\"true\"` string, occasional null where a\n * number is expected). These helpers guard against NaN/Infinity/non-string\n * values reaching ioBroker states.\n */\n\n// Strict decimal regex \u2014 only optional minus sign + digits + optional fractional part.\n// Rejects HEX (`0x...`), exponential (`1e3`), Infinity, NaN, leading/trailing whitespace.\n// Hassemu (E8 in v1.9.0) hardened the same coerce-helper this way; homewizard\n// adopted it in v0.7.2 (D8). Consistent with both adapters.\nconst DECIMAL_NUMBER_RE = /^-?\\d+(\\.\\d+)?$/;\n\n/**\n * Coerce to a finite number or null.\n * Accepts numbers directly; parses strict decimal strings; rejects NaN, Infinity,\n * HEX (`0x...`) and exponential notation (`1e3`).\n *\n * @param value Unknown external value\n */\nexport function coerceFiniteNumber(value: unknown): number | null {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : null;\n }\n if (typeof value === \"string\" && DECIMAL_NUMBER_RE.test(value)) {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\n/**\n * Coerce a parcel.app `success` flag. The API returns a real boolean in normal\n * operation, but the guard accepts common string/number encodings (`1`, `\"true\"`,\n * `\"1\"`) so a one-off drift doesn't break the entire poll cycle.\n *\n * @param v Value to interpret as a success flag\n */\nexport function isTrueish(v: unknown): boolean {\n if (typeof v === \"boolean\") {\n return v;\n }\n if (typeof v === \"number\") {\n return v === 1;\n }\n if (typeof v === \"string\") {\n const s = v.toLowerCase();\n return s === \"true\" || s === \"1\";\n }\n return false;\n}\n\n/**\n * Extract a log-friendly message from a thrown / rejected value. Centralizes the\n * `err instanceof Error ? err.message : String(err)` pattern that otherwise\n * gets repeated at every catch-site. Plain objects are JSON-stringified so a\n * `[object Object]` log is avoided when callers throw bag-of-fields.\n *\n * @param err Caught value of unknown shape (Error, string, undefined, ...).\n */\nexport function errText(err: unknown): string {\n if (err instanceof Error) {\n return err.message;\n }\n if (err === null) {\n return \"null\";\n }\n if (err === undefined) {\n return \"undefined\";\n }\n if (typeof err === \"string\") {\n return err;\n }\n if (typeof err === \"number\" || typeof err === \"boolean\" || typeof err === \"bigint\") {\n return String(err);\n }\n // Plain objects + symbols would otherwise stringify to \"[object Object]\" / fail.\n // Prefer JSON for the common case so the log is at least diagnosable.\n try {\n return JSON.stringify(err);\n } catch {\n return Object.prototype.toString.call(err);\n }\n}\n\n/**\n * v0.4.2 (X5): coerce an admin-config integer setting (number-or-string)\n * to a finite, clamped integer. Returns `defaultValue` for non-finite\n * input \u2014 guards against `setInterval(fn, NaN)` tight-loops when the\n * config field happens to come back as a string from the admin UI.\n *\n * @param raw Raw value from `this.config.<field>`.\n * @param min Inclusive lower bound.\n * @param max Inclusive upper bound.\n * @param defaultValue Fallback when raw is missing or unparseable.\n */\nexport function coerceClampedInt(raw: unknown, min: number, max: number, defaultValue: number): number {\n const n = typeof raw === \"number\" ? raw : typeof raw === \"string\" ? parseFloat(raw) : NaN;\n if (!Number.isFinite(n)) {\n return defaultValue;\n }\n return Math.max(min, Math.min(max, Math.floor(n)));\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,MAAM,oBAAoB;AASnB,SAAS,mBAAmB,OAA+B;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,EAC1C;AACA,MAAI,OAAO,UAAU,YAAY,kBAAkB,KAAK,KAAK,GAAG;AAC9D,UAAM,IAAI,OAAO,KAAK;AACtB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AASO,SAAS,UAAU,GAAqB;AAC7C,MAAI,OAAO,MAAM,WAAW;AAC1B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,MAAM,UAAU;AACzB,WAAO,MAAM;AAAA,EACf;AACA,MAAI,OAAO,MAAM,UAAU;AACzB,UAAM,IAAI,EAAE,YAAY;AACxB,WAAO,MAAM,UAAU,MAAM;AAAA,EAC/B;AACA,SAAO;AACT;AAUO,SAAS,QAAQ,KAAsB;AAC5C,MAAI,eAAe,OAAO;AACxB,WAAO,IAAI;AAAA,EACb;AACA,MAAI,QAAQ,MAAM;AAChB,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,QAAW;AACrB,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,UAAU;AAC3B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,aAAa,OAAO,QAAQ,UAAU;AAClF,WAAO,OAAO,GAAG;AAAA,EACnB;AAGA,MAAI;AACF,WAAO,KAAK,UAAU,GAAG;AAAA,EAC3B,QAAQ;AACN,WAAO,OAAO,UAAU,SAAS,KAAK,GAAG;AAAA,EAC3C;AACF;AAaO,SAAS,iBAAiB,KAAc,KAAa,KAAa,cAA8B;AACrG,QAAM,IAAI,OAAO,QAAQ,WAAW,MAAM,OAAO,QAAQ,WAAW,WAAW,GAAG,IAAI;AACtF,MAAI,CAAC,OAAO,SAAS,CAAC,GAAG;AACvB,WAAO;AAAA,EACT;AACA,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC;AACnD;",
4
+ "sourcesContent": ["/**\n * Boundary coercion helpers for external API data.\n *\n * The parcel.app API is documented but field types still drift in practice\n * (rare success-flag returned as `\"true\"` string, occasional null where a\n * number is expected). These helpers guard against NaN/Infinity/non-string\n * values reaching ioBroker states.\n */\n\n// Strict decimal regex \u2014 only optional minus sign + digits + optional fractional part.\n// Rejects HEX (`0x...`), exponential (`1e3`), Infinity, NaN, leading/trailing whitespace.\n// Hassemu (E8 in v1.9.0) hardened the same coerce-helper this way; homewizard\n// adopted it in v0.7.2 (D8). Consistent with both adapters.\nconst DECIMAL_NUMBER_RE = /^-?\\d+(\\.\\d+)?$/;\n\n/**\n * Coerce to a finite number or null.\n * Accepts numbers directly; parses strict decimal strings; rejects NaN, Infinity,\n * HEX (`0x...`) and exponential notation (`1e3`).\n *\n * @param value Unknown external value\n */\nexport function coerceFiniteNumber(value: unknown): number | null {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : null;\n }\n if (typeof value === \"string\" && DECIMAL_NUMBER_RE.test(value)) {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\n/**\n * Coerce a parcel.app `success` flag. The API returns a real boolean in normal\n * operation, but the guard accepts common string/number encodings (`1`, `\"true\"`,\n * `\"1\"`) so a one-off drift doesn't break the entire poll cycle.\n *\n * @param v Value to interpret as a success flag\n */\nexport function isTrueish(v: unknown): boolean {\n if (typeof v === \"boolean\") {\n return v;\n }\n if (typeof v === \"number\") {\n return v === 1;\n }\n if (typeof v === \"string\") {\n const s = v.toLowerCase();\n return s === \"true\" || s === \"1\";\n }\n return false;\n}\n\n/**\n * Extract a log-friendly message from a thrown / rejected value. Centralizes the\n * `err instanceof Error ? err.message : String(err)` pattern that otherwise\n * gets repeated at every catch-site. Plain objects are JSON-stringified so a\n * `[object Object]` log is avoided when callers throw bag-of-fields.\n *\n * @param err Caught value of unknown shape (Error, string, undefined, ...).\n */\nexport function errText(err: unknown): string {\n if (err instanceof Error) {\n return err.message;\n }\n if (err === null) {\n return \"null\";\n }\n if (err === undefined) {\n return \"undefined\";\n }\n if (typeof err === \"string\") {\n return err;\n }\n if (typeof err === \"number\" || typeof err === \"boolean\" || typeof err === \"bigint\") {\n return String(err);\n }\n // Plain objects + symbols would otherwise stringify to \"[object Object]\" / fail.\n // Prefer JSON for the common case so the log is at least diagnosable.\n try {\n return JSON.stringify(err);\n } catch {\n return Object.prototype.toString.call(err);\n }\n}\n\n/**\n * v0.4.2 (X5): coerce an admin-config integer setting (number-or-string)\n * to a finite, clamped integer. Returns `defaultValue` for non-finite\n * input \u2014 guards against `setInterval(fn, NaN)` tight-loops when the\n * config field happens to come back as a string from the admin UI.\n *\n * @param raw Raw value from `this.config.<field>`.\n * @param min Inclusive lower bound.\n * @param max Inclusive upper bound.\n * @param defaultValue Fallback when raw is missing or unparseable.\n */\nexport function coerceClampedInt(raw: unknown, min: number, max: number, defaultValue: number): number {\n const n = typeof raw === \"number\" ? raw : typeof raw === \"string\" ? parseFloat(raw) : NaN;\n if (!Number.isFinite(n)) {\n return defaultValue;\n }\n return Math.max(min, Math.min(max, Math.floor(n)));\n}\n\n/**\n * Collapse CR / LF / TAB runs in an untrusted string to a single space before\n * it is interpolated into a log line \u2014 prevents log-injection (a forged second\n * log line) from external values (tracking number, carrier code, raw API body,\n * reverse-DNS owner key, \u2026). Fleet convention (hassemu/hueemu v1.36.0 S4).\n *\n * @param value Untrusted string to flatten for single-line logging.\n */\nexport function oneLine(value: string): string {\n return value.replace(/[\\r\\n\\t]+/g, \" \");\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,MAAM,oBAAoB;AASnB,SAAS,mBAAmB,OAA+B;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,EAC1C;AACA,MAAI,OAAO,UAAU,YAAY,kBAAkB,KAAK,KAAK,GAAG;AAC9D,UAAM,IAAI,OAAO,KAAK;AACtB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AASO,SAAS,UAAU,GAAqB;AAC7C,MAAI,OAAO,MAAM,WAAW;AAC1B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,MAAM,UAAU;AACzB,WAAO,MAAM;AAAA,EACf;AACA,MAAI,OAAO,MAAM,UAAU;AACzB,UAAM,IAAI,EAAE,YAAY;AACxB,WAAO,MAAM,UAAU,MAAM;AAAA,EAC/B;AACA,SAAO;AACT;AAUO,SAAS,QAAQ,KAAsB;AAC5C,MAAI,eAAe,OAAO;AACxB,WAAO,IAAI;AAAA,EACb;AACA,MAAI,QAAQ,MAAM;AAChB,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,QAAW;AACrB,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,UAAU;AAC3B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,aAAa,OAAO,QAAQ,UAAU;AAClF,WAAO,OAAO,GAAG;AAAA,EACnB;AAGA,MAAI;AACF,WAAO,KAAK,UAAU,GAAG;AAAA,EAC3B,QAAQ;AACN,WAAO,OAAO,UAAU,SAAS,KAAK,GAAG;AAAA,EAC3C;AACF;AAaO,SAAS,iBAAiB,KAAc,KAAa,KAAa,cAA8B;AACrG,QAAM,IAAI,OAAO,QAAQ,WAAW,MAAM,OAAO,QAAQ,WAAW,WAAW,GAAG,IAAI;AACtF,MAAI,CAAC,OAAO,SAAS,CAAC,GAAG;AACvB,WAAO;AAAA,EACT;AACA,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC;AACnD;AAUO,SAAS,QAAQ,OAAuB;AAC7C,SAAO,MAAM,QAAQ,cAAc,GAAG;AACxC;",
6
6
  "names": []
7
7
  }
@@ -52,7 +52,7 @@ class ParcelClient {
52
52
  * v0.7.2: in-flight fetch for the carrier list. The per-delivery updates run
53
53
  * in parallel (Promise.all) and each resolves carrier names — without this
54
54
  * mutex the first poll with N packages fired N identical concurrent fetches
55
- * of the static 447-entry file (and a persistently failing endpoint was
55
+ * of the static carrier-list file (and a persistently failing endpoint was
56
56
  * retried N times per poll). Same pattern as beszel's auth mutex (B1).
57
57
  */
58
58
  carrierFetchInFlight = null;
@@ -93,7 +93,7 @@ class ParcelClient {
93
93
  * @param filterMode Filter active or recent deliveries
94
94
  */
95
95
  async getDeliveries(filterMode = "active") {
96
- var _a, _b;
96
+ var _a, _b, _c;
97
97
  const response = await this.request("GET", `/deliveries/?filter_mode=${filterMode}`, true);
98
98
  if (!response || typeof response !== "object") {
99
99
  (_a = this.log) == null ? void 0 : _a.debug(`API drift: malformed response (got ${typeof response})`);
@@ -104,7 +104,14 @@ class ParcelClient {
104
104
  (_b = this.log) == null ? void 0 : _b.debug(`API drift: success=false, msg='${rawMsg}'`);
105
105
  throw apiError(`API error: ${rawMsg || "UNKNOWN"}`, "API_ERROR");
106
106
  }
107
- return Array.isArray(response.deliveries) ? response.deliveries : [];
107
+ if (response.deliveries == null) {
108
+ return [];
109
+ }
110
+ if (!Array.isArray(response.deliveries)) {
111
+ (_c = this.log) == null ? void 0 : _c.debug(`API drift: deliveries not an array (got ${typeof response.deliveries})`);
112
+ throw apiError("API error: deliveries not an array", "API_ERROR");
113
+ }
114
+ return response.deliveries;
108
115
  }
109
116
  /**
110
117
  * Add a new delivery to parcel.app.
@@ -132,7 +139,13 @@ class ParcelClient {
132
139
  try {
133
140
  const raw = await this.request("GET", "/supported_carriers.json", false);
134
141
  if (raw && typeof raw === "object" && !Array.isArray(raw)) {
135
- this.carrierCache = raw;
142
+ const clean = {};
143
+ for (const [code, name] of Object.entries(raw)) {
144
+ if (typeof name === "string") {
145
+ clean[code] = name;
146
+ }
147
+ }
148
+ this.carrierCache = clean;
136
149
  (_a = this.log) == null ? void 0 : _a.debug(`carriers: fetched ${Object.keys(this.carrierCache).length} entries`);
137
150
  return this.carrierCache;
138
151
  }
@@ -257,7 +270,9 @@ class ParcelClient {
257
270
  return;
258
271
  }
259
272
  const code = res.statusCode === 401 ? "INVALID_API_KEY" : res.statusCode === 403 ? "FORBIDDEN" : "HTTP_ERROR";
260
- (_b = this.log) == null ? void 0 : _b.debug(`HTTP ${method} ${path} \u2192 ${res.statusCode} ${code} (body=${raw.substring(0, 200)})`);
273
+ (_b = this.log) == null ? void 0 : _b.debug(
274
+ `HTTP ${method} ${path} \u2192 ${res.statusCode} ${code} (body=${(0, import_coerce.oneLine)(raw.substring(0, 200))})`
275
+ );
261
276
  reject(apiError(`HTTP ${res.statusCode}: ${res.statusMessage}`, code));
262
277
  return;
263
278
  }
@@ -266,8 +281,8 @@ class ParcelClient {
266
281
  (_c = this.log) == null ? void 0 : _c.debug(`HTTP ${method} ${path} \u2192 ${res.statusCode} (${Date.now() - startedAt}ms, ${bodyBytes}B)`);
267
282
  resolve(parsed);
268
283
  } catch {
269
- (_d = this.log) == null ? void 0 : _d.debug(`HTTP JSON parse fail ${path}: ${raw.substring(0, 200)}`);
270
- reject(new Error(`JSON parse error: ${raw.substring(0, 200)}`));
284
+ (_d = this.log) == null ? void 0 : _d.debug(`HTTP JSON parse fail ${path}: ${(0, import_coerce.oneLine)(raw.substring(0, 200))}`);
285
+ reject(new Error(`JSON parse error (${raw.length} bytes)`));
271
286
  }
272
287
  });
273
288
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/parcel-client.ts"],
4
- "sourcesContent": ["import * as http from \"node:http\";\nimport * as https from \"node:https\";\nimport { isTrueish } from \"./coerce\";\nimport type { ParcelApiResponse, ParcelDelivery, AddDeliveryRequest, AddDeliveryResponse, CarrierMap } from \"./types\";\n\nconst API_BASE = \"https://api.parcel.app/external\";\nconst REQUEST_TIMEOUT = 15_000;\n\n/**\n * v0.4.3: optional logger injected by the adapter so the HTTPS client can\n * trace its own request/response lifecycle. When omitted (e.g. in tests),\n * every `this.log?.debug(...)` call is a no-op \u2014 keeps the bare-`apiKey`\n * constructor signature backward-compatible.\n */\nexport interface ParcelClientLogger {\n /** Adapter debug log. Called at most once per request/response decision. */\n debug(message: string): void;\n}\n/**\n * v0.4.2 (P9): hard cap on response body size. parcel.app deliveries lists\n * are tiny (~1 kB per package, max ~50 packages = 50 kB), so a 1 MiB cap is\n * 20\u00D7 the realistic max while still defending against a runaway response.\n */\nconst MAX_BODY_BYTES = 1 << 20; // 1 MiB\n\n/**\n * Build an `Error` carrying a `code` (and optional extra fields such as\n * `retryAfterSeconds`). Centralizes the `new Error(...) as Error & { code }`\n * + `err.code = \u2026` pattern that was repeated at every throw/reject site.\n *\n * @param message Human-readable error message.\n * @param code Machine-readable error code used by the adapter for classification.\n * @param extra Optional additional own-properties to attach to the error.\n */\nfunction apiError(message: string, code: string, extra?: Record<string, unknown>): Error & { code: string } {\n const err = new Error(message) as Error & { code: string };\n err.code = code;\n if (extra) {\n Object.assign(err, extra);\n }\n return err;\n}\n\n/** HTTP client for the parcel.app API */\nexport class ParcelClient {\n private apiKey: string;\n private carrierCache: CarrierMap | null = null;\n /**\n * v0.7.2: in-flight fetch for the carrier list. The per-delivery updates run\n * in parallel (Promise.all) and each resolves carrier names \u2014 without this\n * mutex the first poll with N packages fired N identical concurrent fetches\n * of the static 447-entry file (and a persistently failing endpoint was\n * retried N times per poll). Same pattern as beszel's auth mutex (B1).\n */\n private carrierFetchInFlight: Promise<CarrierMap> | null = null;\n /**\n * v0.4.2 (P1): per-request AbortController. `cancelAll()` aborts every\n * pending HTTPS request \u2014 called from the adapter's `onUnload` so a slow\n * parcel.app endpoint can't keep the adapter alive past js-controller's\n * 4-second kill deadline.\n */\n private readonly inflight = new Set<AbortController>();\n /** v0.4.3: optional logger for the HTTPS-layer trace. See {@link ParcelClientLogger}. */\n private readonly log?: ParcelClientLogger;\n /** API base URL. Overridable so tests can run the real `request()` against a local mock server. */\n private readonly baseUrl: string;\n\n /**\n * @param apiKey The parcel.app API key\n * @param log Optional adapter logger for HTTPS-layer trace (v0.4.3)\n * @param baseUrl API base URL \u2014 defaults to the production endpoint; overridden in tests\n */\n constructor(apiKey: string, log?: ParcelClientLogger, baseUrl: string = API_BASE) {\n this.apiKey = apiKey;\n this.log = log;\n this.baseUrl = baseUrl;\n }\n\n /**\n * v0.4.2 (P1): abort every in-flight HTTPS request. Idempotent.\n */\n cancelAll(): void {\n // v0.4.3 (A12): trace the shutdown anchor so the adapter log shows\n // exactly how many HTTPS calls were aborted at unload.\n this.log?.debug(`cancelAll: aborting ${this.inflight.size} inflight requests`);\n for (const ctrl of this.inflight) {\n ctrl.abort();\n }\n }\n\n /**\n * Fetch deliveries from parcel.app.\n *\n * @param filterMode Filter active or recent deliveries\n */\n async getDeliveries(filterMode: \"active\" | \"recent\" = \"active\"): Promise<ParcelDelivery[]> {\n const response = await this.request<ParcelApiResponse>(\"GET\", `/deliveries/?filter_mode=${filterMode}`, true);\n\n // API-drift guard: response may be null or a non-object\n if (!response || typeof response !== \"object\") {\n // v0.4.3 (A11a): trace malformed-response drift before throwing.\n this.log?.debug(`API drift: malformed response (got ${typeof response})`);\n throw apiError(\"API error: malformed response\", \"API_ERROR\");\n }\n\n if (!isTrueish(response.success)) {\n const rawMsg = typeof response.error_message === \"string\" ? response.error_message : \"\";\n // v0.4.3 (A11b): trace API-side error before throwing. An invalid key is\n // reported via HTTP 401 (handled in request()), not via a body field \u2014\n // so a `success:false` body is always a generic API_ERROR.\n this.log?.debug(`API drift: success=false, msg='${rawMsg}'`);\n throw apiError(`API error: ${rawMsg || \"UNKNOWN\"}`, \"API_ERROR\");\n }\n\n // API-drift guard: deliveries must be an array\n return Array.isArray(response.deliveries) ? response.deliveries : [];\n }\n\n /**\n * Add a new delivery to parcel.app.\n *\n * @param delivery The delivery to add\n */\n async addDelivery(delivery: AddDeliveryRequest): Promise<AddDeliveryResponse> {\n return this.request<AddDeliveryResponse>(\"POST\", \"/add-delivery/\", true, delivery);\n }\n\n /** Get carrier names (cached after first call; concurrent callers share one fetch) */\n async getCarrierNames(): Promise<CarrierMap> {\n if (this.carrierCache) {\n return this.carrierCache;\n }\n // v0.7.2: share one in-flight fetch between the parallel per-delivery\n // updates instead of firing N identical requests on the first poll.\n if (!this.carrierFetchInFlight) {\n this.carrierFetchInFlight = this.fetchCarrierNames().finally(() => {\n this.carrierFetchInFlight = null;\n });\n }\n return this.carrierFetchInFlight;\n }\n\n /** One actual carrier-list fetch. Failure \u2192 empty map, NOT cached (retry next poll). */\n private async fetchCarrierNames(): Promise<CarrierMap> {\n try {\n const raw = await this.request<unknown>(\"GET\", \"/supported_carriers.json\", false);\n // API-drift guard: must be a plain object (not null, array, or primitive)\n if (raw && typeof raw === \"object\" && !Array.isArray(raw)) {\n this.carrierCache = raw as CarrierMap;\n // v0.4.3 (D1): trace the one-time cache fill so a successful warm-up\n // is visible in the debug log (happens once per adapter restart).\n this.log?.debug(`carriers: fetched ${Object.keys(this.carrierCache).length} entries`);\n return this.carrierCache;\n }\n // v0.4.3 (D3): non-object drift \u2014 supported_carriers.json returned\n // something that isn't an object. Empty map is returned, NOT cached.\n this.log?.debug(\n `carriers: drift (got ${Array.isArray(raw) ? \"array\" : typeof raw}, expected object), kept empty`,\n );\n return {};\n } catch (err) {\n // v0.4.3 (D2): trace the fetch-fail so the empty-map fallback isn't\n // silent. NOT cached \u2014 next poll retries; the trace then shows the\n // retry, too. Without this the user sees carrier codes instead of\n // names with no log entry explaining why.\n const msg = err instanceof Error ? err.message : String(err);\n this.log?.debug(`carriers: fetch failed (kept empty, will retry): ${msg}`);\n // Return empty map but don't cache it \u2014 allow retry next time\n return {};\n }\n }\n\n /**\n * Resolve a carrier code to a display name.\n *\n * @param carrierCode The carrier code from API\n */\n async getCarrierName(carrierCode: unknown): Promise<string> {\n // API-drift guard: non-string codes fall back to \"UNKNOWN\"\n if (typeof carrierCode !== \"string\" || carrierCode.length === 0) {\n // v0.4.3 (D4): trace non-string code drift. Helps diagnose \"all my\n // packages show UNKNOWN carrier\" reports.\n this.log?.debug(`getCarrierName: non-string code (got ${typeof carrierCode}), returning UNKNOWN`);\n return \"UNKNOWN\";\n }\n const carriers = await this.getCarrierNames();\n const mapped = carriers[carrierCode];\n return typeof mapped === \"string\" && mapped.length > 0 ? mapped : carrierCode.toUpperCase();\n }\n\n /** Test if the API key is valid */\n async testConnection(): Promise<{ success: boolean; message: string }> {\n try {\n await this.getDeliveries(\"active\");\n return { success: true, message: \"Connection successful\" };\n } catch (err) {\n const error = err as Error & { code?: string };\n if (error.code === \"INVALID_API_KEY\") {\n return { success: false, message: \"Invalid API key\" };\n }\n return { success: false, message: error.message };\n }\n }\n\n /**\n * Execute an HTTP request against the parcel.app API.\n *\n * @param method HTTP method\n * @param path API path\n * @param authenticated Whether to send the API key\n * @param body Optional request body\n */\n private request<T>(method: string, path: string, authenticated: boolean, body?: unknown): Promise<T> {\n // v0.4.3 (A0): start timestamp for elapsed-ms in the success/timeout/error\n // log lines. One LOC, no behavior change.\n const startedAt = Date.now();\n // v0.4.3 (A1): trace request entry. ~144 calls/day at the default 10-min\n // poll interval \u2014 acceptable at debug.\n this.log?.debug(`HTTP ${method} ${path}`);\n return new Promise((resolve, reject) => {\n // v0.4.2 (E3): URL-shape validation defensive \u2014 paths are hardcoded\n // upstream but a future caller could pass garbage; surface a clear\n // error class instead of a TypeError thrown sync from the executor.\n let url: URL;\n try {\n url = new URL(`${this.baseUrl}${path}`);\n } catch {\n // v0.4.3 (A10): trace invalid-URL drift before throwing.\n this.log?.debug(`HTTP invalid URL: ${this.baseUrl}${path}`);\n reject(apiError(`Invalid URL: ${this.baseUrl}${path}`, \"INVALID_URL\"));\n return;\n }\n\n const headers: Record<string, string> = {};\n if (authenticated) {\n headers[\"api-key\"] = this.apiKey;\n }\n if (body) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n const options: https.RequestOptions = {\n hostname: url.hostname,\n port: url.port || 443,\n path: url.pathname + url.search,\n method,\n headers,\n timeout: REQUEST_TIMEOUT,\n };\n\n // v0.4.2 (P1): per-request AbortController. `cancelAll()` (called\n // from `onUnload`) aborts everything pending without waiting for\n // the configured timeout.\n const ctrl = new AbortController();\n this.inflight.add(ctrl);\n const cleanup = (): void => {\n this.inflight.delete(ctrl);\n };\n\n // Pick transport from the URL protocol so tests can run the real\n // request() against a local http mock server; production is always https.\n const transportRequest: (\n opts: https.RequestOptions,\n callback: (res: http.IncomingMessage) => void,\n ) => http.ClientRequest = url.protocol === \"http:\" ? http.request : https.request;\n\n const req = transportRequest(options, res => {\n const chunks: Buffer[] = [];\n let bodyBytes = 0;\n let oversized = false;\n\n res.on(\"error\", err => {\n cleanup();\n reject(err);\n });\n res.on(\"data\", (chunk: Buffer) => {\n if (oversized) {\n return;\n }\n bodyBytes += chunk.length;\n // v0.4.2 (P9): drop oversized responses so a compromised or\n // misconfigured endpoint can't OOM the adapter. Reject with the\n // stable BODY_TOO_LARGE code here, then destroy WITHOUT an error so\n // req.on(\"error\") doesn't fire a second, codeless rejection (the\n // earlier `req.destroy(Error)` preempted the end-handler's code).\n if (bodyBytes > MAX_BODY_BYTES) {\n oversized = true;\n // v0.4.3 (A9): trace the oversize-drop before destroying.\n this.log?.debug(`HTTP body oversized ${path}: dropping at ${bodyBytes}B`);\n cleanup();\n reject(apiError(\"Response body too large\", \"BODY_TOO_LARGE\"));\n req.destroy();\n return;\n }\n chunks.push(chunk);\n });\n res.on(\"end\", () => {\n if (oversized) {\n return; // already cleaned up + rejected in the data handler\n }\n cleanup();\n const raw = Buffer.concat(chunks).toString(\"utf-8\");\n\n if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {\n if (res.statusCode === 429) {\n // v0.4.2 (P6): clamp Retry-After parser. Bogus values (0,\n // negative, NaN) used to fall through `>0` and default to\n // 5 min \u2014 keep that, but also reject infinity/extreme.\n const retryAfter = parseInt(res.headers[\"retry-after\"] || \"\", 10);\n const retryAfterSeconds =\n Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(24 * 3600, retryAfter) : 5 * 60;\n // v0.4.3 (A4): trace 429 with the parsed retry-after.\n this.log?.debug(`HTTP 429 ${path} \u2192 retry-after=${retryAfterSeconds}s`);\n reject(apiError(\"Rate limit exceeded\", \"RATE_LIMITED\", { retryAfterSeconds }));\n return;\n }\n // v0.4.2 (P3): split 401 (invalid key) from 403 (permission /\n // no premium). Adapter treats them differently \u2014 INVALID_API_KEY\n // says \"fix the key\", FORBIDDEN says \"fix the account\".\n const code =\n res.statusCode === 401 ? \"INVALID_API_KEY\" : res.statusCode === 403 ? \"FORBIDDEN\" : \"HTTP_ERROR\";\n // v0.4.3 (A3): trace 4xx/5xx with body-snippet for diagnosis.\n this.log?.debug(`HTTP ${method} ${path} \u2192 ${res.statusCode} ${code} (body=${raw.substring(0, 200)})`);\n reject(apiError(`HTTP ${res.statusCode}: ${res.statusMessage}`, code));\n return;\n }\n\n try {\n const parsed = JSON.parse(raw) as T;\n // v0.4.3 (A2): trace successful response with elapsed-ms + bytes.\n this.log?.debug(`HTTP ${method} ${path} \u2192 ${res.statusCode} (${Date.now() - startedAt}ms, ${bodyBytes}B)`);\n resolve(parsed);\n } catch {\n // v0.4.3 (A8): trace JSON parse-fail with snippet.\n this.log?.debug(`HTTP JSON parse fail ${path}: ${raw.substring(0, 200)}`);\n reject(new Error(`JSON parse error: ${raw.substring(0, 200)}`));\n }\n });\n });\n\n ctrl.signal.addEventListener(\"abort\", () => {\n // v0.4.3: A6 deliberately omitted \u2014 `req.destroy(Error)` propagates\n // through `req.on(\"error\")` below where A7 already logs it.\n req.destroy(new Error(\"Request aborted\"));\n });\n\n req.on(\"timeout\", () => {\n req.destroy();\n cleanup();\n // v0.4.3 (A5): trace timeout with elapsed-ms.\n this.log?.debug(`HTTP timeout ${method} ${path} (${Date.now() - startedAt}ms)`);\n reject(new Error(\"Request timeout\"));\n });\n\n req.on(\"error\", err => {\n cleanup();\n // v0.4.3 (A7): trace network / abort / TLS / DNS errors with elapsed.\n // Also catches the abort case (req.destroy(Error(\"Request aborted\")))\n // \u2014 A6 deliberately not emitted to avoid double-log.\n this.log?.debug(`HTTP error ${method} ${path} (${Date.now() - startedAt}ms): ${err.message}`);\n reject(err);\n });\n\n if (body) {\n req.write(JSON.stringify(body));\n }\n req.end();\n });\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import * as http from \"node:http\";\nimport * as https from \"node:https\";\nimport { isTrueish, oneLine } from \"./coerce\";\nimport type { ParcelApiResponse, ParcelDelivery, AddDeliveryRequest, AddDeliveryResponse, CarrierMap } from \"./types\";\n\nconst API_BASE = \"https://api.parcel.app/external\";\nconst REQUEST_TIMEOUT = 15_000;\n\n/**\n * v0.4.3: optional logger injected by the adapter so the HTTPS client can\n * trace its own request/response lifecycle. When omitted (e.g. in tests),\n * every `this.log?.debug(...)` call is a no-op \u2014 keeps the bare-`apiKey`\n * constructor signature backward-compatible.\n */\nexport interface ParcelClientLogger {\n /** Adapter debug log. Called per request/response outcome (drift, status, parse, oversize) \u2014 low-frequency tracing. */\n debug(message: string): void;\n}\n/**\n * v0.4.2 (P9): hard cap on response body size. parcel.app deliveries lists\n * are tiny (~1 kB per package, max ~50 packages = 50 kB), so a 1 MiB cap is\n * 20\u00D7 the realistic max while still defending against a runaway response.\n */\nconst MAX_BODY_BYTES = 1 << 20; // 1 MiB\n\n/**\n * Build an `Error` carrying a `code` (and optional extra fields such as\n * `retryAfterSeconds`). Centralizes the `new Error(...) as Error & { code }`\n * + `err.code = \u2026` pattern that was repeated at every throw/reject site.\n *\n * @param message Human-readable error message.\n * @param code Machine-readable error code used by the adapter for classification.\n * @param extra Optional additional own-properties to attach to the error.\n */\nfunction apiError(message: string, code: string, extra?: Record<string, unknown>): Error & { code: string } {\n const err = new Error(message) as Error & { code: string };\n err.code = code;\n if (extra) {\n Object.assign(err, extra);\n }\n return err;\n}\n\n/** HTTP client for the parcel.app API */\nexport class ParcelClient {\n private apiKey: string;\n private carrierCache: CarrierMap | null = null;\n /**\n * v0.7.2: in-flight fetch for the carrier list. The per-delivery updates run\n * in parallel (Promise.all) and each resolves carrier names \u2014 without this\n * mutex the first poll with N packages fired N identical concurrent fetches\n * of the static carrier-list file (and a persistently failing endpoint was\n * retried N times per poll). Same pattern as beszel's auth mutex (B1).\n */\n private carrierFetchInFlight: Promise<CarrierMap> | null = null;\n /**\n * v0.4.2 (P1): per-request AbortController. `cancelAll()` aborts every\n * pending HTTPS request \u2014 called from the adapter's `onUnload` so a slow\n * parcel.app endpoint can't keep the adapter alive past js-controller's\n * 4-second kill deadline.\n */\n private readonly inflight = new Set<AbortController>();\n /** v0.4.3: optional logger for the HTTPS-layer trace. See {@link ParcelClientLogger}. */\n private readonly log?: ParcelClientLogger;\n /** API base URL. Overridable so tests can run the real `request()` against a local mock server. */\n private readonly baseUrl: string;\n\n /**\n * @param apiKey The parcel.app API key\n * @param log Optional adapter logger for HTTPS-layer trace (v0.4.3)\n * @param baseUrl API base URL \u2014 defaults to the production endpoint; overridden in tests\n */\n constructor(apiKey: string, log?: ParcelClientLogger, baseUrl: string = API_BASE) {\n this.apiKey = apiKey;\n this.log = log;\n this.baseUrl = baseUrl;\n }\n\n /**\n * v0.4.2 (P1): abort every in-flight HTTPS request. Idempotent.\n */\n cancelAll(): void {\n // v0.4.3 (A12): trace the shutdown anchor so the adapter log shows\n // exactly how many HTTPS calls were aborted at unload.\n this.log?.debug(`cancelAll: aborting ${this.inflight.size} inflight requests`);\n for (const ctrl of this.inflight) {\n ctrl.abort();\n }\n }\n\n /**\n * Fetch deliveries from parcel.app.\n *\n * @param filterMode Filter active or recent deliveries\n */\n async getDeliveries(filterMode: \"active\" | \"recent\" = \"active\"): Promise<ParcelDelivery[]> {\n const response = await this.request<ParcelApiResponse>(\"GET\", `/deliveries/?filter_mode=${filterMode}`, true);\n\n // API-drift guard: response may be null or a non-object\n if (!response || typeof response !== \"object\") {\n // v0.4.3 (A11a): trace malformed-response drift before throwing.\n this.log?.debug(`API drift: malformed response (got ${typeof response})`);\n throw apiError(\"API error: malformed response\", \"API_ERROR\");\n }\n\n if (!isTrueish(response.success)) {\n const rawMsg = typeof response.error_message === \"string\" ? response.error_message : \"\";\n // v0.4.3 (A11b): trace API-side error before throwing. An invalid key is\n // reported via HTTP 401 (handled in request()), not via a body field \u2014\n // so a `success:false` body is always a generic API_ERROR.\n this.log?.debug(`API drift: success=false, msg='${rawMsg}'`);\n throw apiError(`API error: ${rawMsg || \"UNKNOWN\"}`, \"API_ERROR\");\n }\n\n // API-drift guard. An absent OR null `deliveries` is the API's \"no\n // deliveries\" shape \u2192 [] (zero active packages is the common state; a false\n // throw there would flip the adapter to disconnected on every poll). Only a\n // PRESENT, NON-NULL, wrong-typed value (string/number/object/boolean) is\n // real drift \u2014 throw so the poll keeps the existing states stale instead of\n // reading garbage as \"zero deliveries\" and deleting every package's states.\n if (response.deliveries == null) {\n return [];\n }\n if (!Array.isArray(response.deliveries)) {\n this.log?.debug(`API drift: deliveries not an array (got ${typeof response.deliveries})`);\n throw apiError(\"API error: deliveries not an array\", \"API_ERROR\");\n }\n return response.deliveries;\n }\n\n /**\n * Add a new delivery to parcel.app.\n *\n * @param delivery The delivery to add\n */\n async addDelivery(delivery: AddDeliveryRequest): Promise<AddDeliveryResponse> {\n return this.request<AddDeliveryResponse>(\"POST\", \"/add-delivery/\", true, delivery);\n }\n\n /** Get carrier names (cached after first call; concurrent callers share one fetch) */\n async getCarrierNames(): Promise<CarrierMap> {\n if (this.carrierCache) {\n return this.carrierCache;\n }\n // v0.7.2: share one in-flight fetch between the parallel per-delivery\n // updates instead of firing N identical requests on the first poll.\n if (!this.carrierFetchInFlight) {\n this.carrierFetchInFlight = this.fetchCarrierNames().finally(() => {\n this.carrierFetchInFlight = null;\n });\n }\n return this.carrierFetchInFlight;\n }\n\n /** One actual carrier-list fetch. Failure \u2192 empty map, NOT cached (retry next poll). */\n private async fetchCarrierNames(): Promise<CarrierMap> {\n try {\n const raw = await this.request<unknown>(\"GET\", \"/supported_carriers.json\", false);\n // API-drift guard: must be a plain object (not null, array, or primitive)\n if (raw && typeof raw === \"object\" && !Array.isArray(raw)) {\n // v0.9.0 (C6): keep only string-valued entries instead of asserting the\n // whole object is Record<string,string>. A drifted non-string value is\n // dropped here, so the cache is honestly typed (no `as CarrierMap`).\n const clean: CarrierMap = {};\n for (const [code, name] of Object.entries(raw)) {\n if (typeof name === \"string\") {\n clean[code] = name;\n }\n }\n this.carrierCache = clean;\n // v0.4.3 (D1): trace the one-time cache fill so a successful warm-up\n // is visible in the debug log (happens once per adapter restart).\n this.log?.debug(`carriers: fetched ${Object.keys(this.carrierCache).length} entries`);\n return this.carrierCache;\n }\n // v0.4.3 (D3): non-object drift \u2014 supported_carriers.json returned\n // something that isn't an object. Empty map is returned, NOT cached.\n this.log?.debug(\n `carriers: drift (got ${Array.isArray(raw) ? \"array\" : typeof raw}, expected object), kept empty`,\n );\n return {};\n } catch (err) {\n // v0.4.3 (D2): trace the fetch-fail so the empty-map fallback isn't\n // silent. NOT cached \u2014 next poll retries; the trace then shows the\n // retry, too. Without this the user sees carrier codes instead of\n // names with no log entry explaining why.\n const msg = err instanceof Error ? err.message : String(err);\n this.log?.debug(`carriers: fetch failed (kept empty, will retry): ${msg}`);\n // Return empty map but don't cache it \u2014 allow retry next time\n return {};\n }\n }\n\n /**\n * Resolve a carrier code to a display name.\n *\n * @param carrierCode The carrier code from API\n */\n async getCarrierName(carrierCode: unknown): Promise<string> {\n // API-drift guard: non-string codes fall back to \"UNKNOWN\"\n if (typeof carrierCode !== \"string\" || carrierCode.length === 0) {\n // v0.4.3 (D4): trace non-string code drift. Helps diagnose \"all my\n // packages show UNKNOWN carrier\" reports.\n this.log?.debug(`getCarrierName: non-string code (got ${typeof carrierCode}), returning UNKNOWN`);\n return \"UNKNOWN\";\n }\n const carriers = await this.getCarrierNames();\n const mapped = carriers[carrierCode];\n return typeof mapped === \"string\" && mapped.length > 0 ? mapped : carrierCode.toUpperCase();\n }\n\n /** Test if the API key is valid */\n async testConnection(): Promise<{ success: boolean; message: string }> {\n try {\n await this.getDeliveries(\"active\");\n return { success: true, message: \"Connection successful\" };\n } catch (err) {\n const error = err as Error & { code?: string };\n if (error.code === \"INVALID_API_KEY\") {\n return { success: false, message: \"Invalid API key\" };\n }\n return { success: false, message: error.message };\n }\n }\n\n /**\n * Execute an HTTP request against the parcel.app API.\n *\n * @param method HTTP method\n * @param path API path\n * @param authenticated Whether to send the API key\n * @param body Optional request body\n */\n private request<T>(method: string, path: string, authenticated: boolean, body?: unknown): Promise<T> {\n // v0.4.3 (A0): start timestamp for elapsed-ms in the success/timeout/error\n // log lines. One LOC, no behavior change.\n const startedAt = Date.now();\n // v0.4.3 (A1): trace request entry. ~144 calls/day at the default 10-min\n // poll interval \u2014 acceptable at debug.\n this.log?.debug(`HTTP ${method} ${path}`);\n return new Promise((resolve, reject) => {\n // v0.4.2 (E3): URL-shape validation defensive \u2014 paths are hardcoded\n // upstream but a future caller could pass garbage; surface a clear\n // error class instead of a TypeError thrown sync from the executor.\n let url: URL;\n try {\n url = new URL(`${this.baseUrl}${path}`);\n } catch {\n // v0.4.3 (A10): trace invalid-URL drift before throwing.\n this.log?.debug(`HTTP invalid URL: ${this.baseUrl}${path}`);\n reject(apiError(`Invalid URL: ${this.baseUrl}${path}`, \"INVALID_URL\"));\n return;\n }\n\n const headers: Record<string, string> = {};\n if (authenticated) {\n headers[\"api-key\"] = this.apiKey;\n }\n if (body) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n const options: https.RequestOptions = {\n hostname: url.hostname,\n port: url.port || 443,\n path: url.pathname + url.search,\n method,\n headers,\n timeout: REQUEST_TIMEOUT,\n };\n\n // v0.4.2 (P1): per-request AbortController. `cancelAll()` (called\n // from `onUnload`) aborts everything pending without waiting for\n // the configured timeout.\n const ctrl = new AbortController();\n this.inflight.add(ctrl);\n const cleanup = (): void => {\n this.inflight.delete(ctrl);\n };\n\n // Pick transport from the URL protocol so tests can run the real\n // request() against a local http mock server; production is always https.\n const transportRequest: (\n opts: https.RequestOptions,\n callback: (res: http.IncomingMessage) => void,\n ) => http.ClientRequest = url.protocol === \"http:\" ? http.request : https.request;\n\n const req = transportRequest(options, res => {\n const chunks: Buffer[] = [];\n let bodyBytes = 0;\n let oversized = false;\n\n res.on(\"error\", err => {\n cleanup();\n reject(err);\n });\n res.on(\"data\", (chunk: Buffer) => {\n if (oversized) {\n return;\n }\n bodyBytes += chunk.length;\n // v0.4.2 (P9): drop oversized responses so a compromised or\n // misconfigured endpoint can't OOM the adapter. Reject with the\n // stable BODY_TOO_LARGE code here, then destroy WITHOUT an error so\n // req.on(\"error\") doesn't fire a second, codeless rejection (the\n // earlier `req.destroy(Error)` preempted the end-handler's code).\n if (bodyBytes > MAX_BODY_BYTES) {\n oversized = true;\n // v0.4.3 (A9): trace the oversize-drop before destroying.\n this.log?.debug(`HTTP body oversized ${path}: dropping at ${bodyBytes}B`);\n cleanup();\n reject(apiError(\"Response body too large\", \"BODY_TOO_LARGE\"));\n req.destroy();\n return;\n }\n chunks.push(chunk);\n });\n res.on(\"end\", () => {\n if (oversized) {\n return; // already cleaned up + rejected in the data handler\n }\n cleanup();\n // The MAX_BODY_BYTES cap above bounds `chunks`, so concat stays well\n // under Buffer's max length and toString won't throw here.\n const raw = Buffer.concat(chunks).toString(\"utf-8\");\n\n if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {\n if (res.statusCode === 429) {\n // v0.4.2 (P6): clamp Retry-After parser. Bogus values (0,\n // negative, NaN) used to fall through `>0` and default to\n // 5 min \u2014 keep that, but also reject infinity/extreme.\n const retryAfter = parseInt(res.headers[\"retry-after\"] || \"\", 10);\n const retryAfterSeconds =\n Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(24 * 3600, retryAfter) : 5 * 60;\n // v0.4.3 (A4): trace 429 with the parsed retry-after.\n this.log?.debug(`HTTP 429 ${path} \u2192 retry-after=${retryAfterSeconds}s`);\n reject(apiError(\"Rate limit exceeded\", \"RATE_LIMITED\", { retryAfterSeconds }));\n return;\n }\n // v0.4.2 (P3): split 401 (invalid key) from 403 (permission /\n // no premium). Adapter treats them differently \u2014 INVALID_API_KEY\n // says \"fix the key\", FORBIDDEN says \"fix the account\".\n const code =\n res.statusCode === 401 ? \"INVALID_API_KEY\" : res.statusCode === 403 ? \"FORBIDDEN\" : \"HTTP_ERROR\";\n // v0.4.3 (A3): trace 4xx/5xx with body-snippet for diagnosis.\n this.log?.debug(\n `HTTP ${method} ${path} \u2192 ${res.statusCode} ${code} (body=${oneLine(raw.substring(0, 200))})`,\n );\n reject(apiError(`HTTP ${res.statusCode}: ${res.statusMessage}`, code));\n return;\n }\n\n try {\n const parsed = JSON.parse(raw) as T;\n // v0.4.3 (A2): trace successful response with elapsed-ms + bytes.\n this.log?.debug(`HTTP ${method} ${path} \u2192 ${res.statusCode} (${Date.now() - startedAt}ms, ${bodyBytes}B)`);\n resolve(parsed);\n } catch {\n // v0.4.3 (A8): trace JSON parse-fail with snippet (debug only).\n this.log?.debug(`HTTP JSON parse fail ${path}: ${oneLine(raw.substring(0, 200))}`);\n // v0.9.0 (S1): keep the raw body OUT of the Error message \u2014 it\n // bubbles to a poll error-log; a malformed PII-bearing body must\n // not reach error level. The snippet stays in the debug line above.\n reject(new Error(`JSON parse error (${raw.length} bytes)`));\n }\n });\n });\n\n ctrl.signal.addEventListener(\"abort\", () => {\n // v0.4.3: A6 deliberately omitted \u2014 `req.destroy(Error)` propagates\n // through `req.on(\"error\")` below where A7 already logs it.\n req.destroy(new Error(\"Request aborted\"));\n });\n\n req.on(\"timeout\", () => {\n req.destroy();\n cleanup();\n // v0.4.3 (A5): trace timeout with elapsed-ms.\n this.log?.debug(`HTTP timeout ${method} ${path} (${Date.now() - startedAt}ms)`);\n reject(new Error(\"Request timeout\"));\n });\n\n req.on(\"error\", err => {\n cleanup();\n // v0.4.3 (A7): trace network / abort / TLS / DNS errors with elapsed.\n // Also catches the abort case (req.destroy(Error(\"Request aborted\")))\n // \u2014 A6 deliberately not emitted to avoid double-log.\n this.log?.debug(`HTTP error ${method} ${path} (${Date.now() - startedAt}ms): ${err.message}`);\n reject(err);\n });\n\n if (body) {\n req.write(JSON.stringify(body));\n }\n req.end();\n });\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["_a"]
7
7
  }
@@ -131,7 +131,7 @@ class StateManager {
131
131
  if (owner !== void 0 && owner !== rawKey) {
132
132
  const suffixed = `${id}__${StateManager.shortHash(rawKey)}`;
133
133
  this.adapter.log.debug(
134
- `packageId collision: bare='${id}' owner='${owner}' new='${rawKey}' \u2192 suffixed='${suffixed}'`
134
+ `packageId collision: bare='${id}' owner='${(0, import_coerce.oneLine)(owner)}' new='${(0, import_coerce.oneLine)(rawKey)}' \u2192 suffixed='${suffixed}'`
135
135
  );
136
136
  this.idOwner.set(suffixed, rawKey);
137
137
  return suffixed;
@@ -164,7 +164,7 @@ class StateManager {
164
164
  }
165
165
  /**
166
166
  * v0.4.2 (S3): which raw-tracking-key currently "owns" each sanitized id
167
- * within the running poll. Cleared via `resetIdOwners()` between polls so
167
+ * within the running poll. Cleared via `resetPollState()` between polls so
168
168
  * the same delivery keeps its bare id as long as it's unique.
169
169
  */
170
170
  idOwner = /* @__PURE__ */ new Map();
@@ -283,11 +283,14 @@ class StateManager {
283
283
  ]);
284
284
  }
285
285
  /**
286
- * Remove deliveries that are no longer active.
286
+ * Remove deliveries that are no longer present in the API response.
287
287
  *
288
- * @param activeIds List of currently active package IDs
288
+ * @param keepIds Package IDs the API still returns this poll (kept). Every
289
+ * currently-known delivery NOT in this set is removed. The caller passes
290
+ * ALL visible package ids, not only the ones whose state-write succeeded —
291
+ * a transient write failure must not delete a still-present package.
289
292
  */
290
- async cleanupDeliveries(activeIds) {
293
+ async cleanupDeliveries(keepIds) {
291
294
  if (this.knownDeliveryIds === null) {
292
295
  const objects = await this.adapter.getObjectViewAsync("system", "device", {
293
296
  startkey: `${this.adapter.namespace}.deliveries.`,
@@ -308,8 +311,9 @@ class StateManager {
308
311
  }
309
312
  }
310
313
  }
311
- const activeSet = new Set(activeIds);
312
- const toDelete = [...this.knownDeliveryIds].filter((pkgId) => !activeSet.has(pkgId));
314
+ const keepSet = new Set(keepIds);
315
+ const toDelete = [...this.knownDeliveryIds].filter((pkgId) => !keepSet.has(pkgId));
316
+ const toDeleteSet = new Set(toDelete);
313
317
  await Promise.all(
314
318
  toDelete.map(async (pkgId) => {
315
319
  const relativeId = `deliveries.${pkgId}`;
@@ -317,14 +321,17 @@ class StateManager {
317
321
  this.adapter.log.debug(`Removed stale delivery: ${relativeId}`);
318
322
  this.deviceWritten.delete(pkgId);
319
323
  this.valuesSig.delete(pkgId);
320
- for (const id of [...this.createdIds]) {
321
- if (id === relativeId || id.startsWith(`${relativeId}.`)) {
322
- this.createdIds.delete(id);
323
- }
324
- }
325
324
  })
326
325
  );
327
- this.knownDeliveryIds = new Set(activeSet);
326
+ if (toDeleteSet.size > 0) {
327
+ for (const id of [...this.createdIds]) {
328
+ const pkgId = id.startsWith("deliveries.") ? id.slice("deliveries.".length).split(".")[0] : "";
329
+ if (toDeleteSet.has(pkgId)) {
330
+ this.createdIds.delete(id);
331
+ }
332
+ }
333
+ }
334
+ this.knownDeliveryIds = new Set(keepSet);
328
335
  }
329
336
  /**
330
337
  * Parse a parcel.app expected-date string to LOCAL epoch-millis.
@@ -348,18 +355,20 @@ class StateManager {
348
355
  return null;
349
356
  }
350
357
  const hasClock = m[4] !== void 0;
351
- const date = new Date(
352
- Number(m[1]),
353
- Number(m[2]) - 1,
354
- Number(m[3]),
355
- hasClock ? Number(m[4]) : 0,
356
- hasClock ? Number(m[5]) : 0,
357
- m[6] !== void 0 ? Number(m[6]) : 0
358
- );
359
- if (Number.isNaN(date.getTime())) {
358
+ const year = Number(m[1]);
359
+ const month = Number(m[2]);
360
+ const day = Number(m[3]);
361
+ const hour = hasClock ? Number(m[4]) : 0;
362
+ const min = hasClock ? Number(m[5]) : 0;
363
+ const sec = m[6] !== void 0 ? Number(m[6]) : 0;
364
+ if (month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || min > 59 || sec > 59) {
360
365
  return null;
361
366
  }
362
- const hasTime = hasClock && !(Number(m[4]) === 0 && Number(m[5]) === 0 && (m[6] === void 0 || Number(m[6]) === 0));
367
+ const date = new Date(year, month - 1, day, hour, min, sec);
368
+ if (Number.isNaN(date.getTime()) || date.getMonth() !== month - 1 || date.getDate() !== day) {
369
+ return null;
370
+ }
371
+ const hasTime = hasClock && !(hour === 0 && min === 0 && sec === 0);
363
372
  return { ms: date.getTime(), hasTime };
364
373
  }
365
374
  /**
@@ -407,6 +416,44 @@ class StateManager {
407
416
  const d = new Date(ms);
408
417
  return `${d.getHours().toString().padStart(2, "0")}:${d.getMinutes().toString().padStart(2, "0")}`;
409
418
  }
419
+ /**
420
+ * Local "MM-DD HH:MM" — used when a window spans more than one calendar day.
421
+ *
422
+ * @param ms Epoch milliseconds
423
+ */
424
+ static formatDateHHMM(ms) {
425
+ const d = new Date(ms);
426
+ const mm = (d.getMonth() + 1).toString().padStart(2, "0");
427
+ const dd = d.getDate().toString().padStart(2, "0");
428
+ return `${mm}-${dd} ${StateManager.formatHHMM(ms)}`;
429
+ }
430
+ /**
431
+ * Whether two epoch-millis fall on the same LOCAL calendar day.
432
+ *
433
+ * @param aMs First epoch milliseconds
434
+ * @param bMs Second epoch milliseconds
435
+ */
436
+ static sameLocalDay(aMs, bMs) {
437
+ const a = new Date(aMs);
438
+ const b = new Date(bMs);
439
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
440
+ }
441
+ /**
442
+ * Format a start→end window as a local string. A real end (> start) on the
443
+ * SAME day renders "HH:MM - HH:MM"; an end on a LATER day carries the date on
444
+ * both sides ("12-06 14:30 - 12-08 18:30") so a multi-day window is not shown
445
+ * as if it were same-day. No end, or an end <= start (reversed/equal), renders
446
+ * just the start.
447
+ *
448
+ * @param startMs Window start (epoch ms)
449
+ * @param endMs Window end (epoch ms) or null
450
+ */
451
+ static formatWindow(startMs, endMs) {
452
+ if (endMs === null || endMs <= startMs) {
453
+ return StateManager.formatHHMM(startMs);
454
+ }
455
+ return StateManager.sameLocalDay(startMs, endMs) ? `${StateManager.formatHHMM(startMs)} - ${StateManager.formatHHMM(endMs)}` : `${StateManager.formatDateHHMM(startMs)} - ${StateManager.formatDateHHMM(endMs)}`;
456
+ }
410
457
  /**
411
458
  * Calculate a delivery time-window string from the resolved expected bounds.
412
459
  *
@@ -418,8 +465,7 @@ class StateManager {
418
465
  if (!bounds) {
419
466
  return "";
420
467
  }
421
- const start = StateManager.formatHHMM(bounds.start);
422
- return bounds.end !== null ? `${start} - ${StateManager.formatHHMM(bounds.end)}` : start;
468
+ return StateManager.formatWindow(bounds.start, bounds.end);
423
469
  }
424
470
  /**
425
471
  * Days from today to the expected delivery date. Returns null when the
@@ -529,8 +575,7 @@ class StateManager {
529
575
  var _a;
530
576
  return (_a = b.end) != null ? _a : b.start;
531
577
  }));
532
- const startStr = StateManager.formatHHMM(minStart);
533
- return maxEnd > minStart ? `${startStr} - ${StateManager.formatHHMM(maxEnd)}` : startStr;
578
+ return StateManager.formatWindow(minStart, maxEnd);
534
579
  }
535
580
  /**
536
581
  * Create/extend a read-only state and set its value. Skips the
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/state-manager.ts"],
4
- "sourcesContent": ["import { I18n, type AdapterInstance } from \"@iobroker/adapter-core\";\nimport { coerceFiniteNumber } from \"./coerce\";\nimport { tName } from \"./i18n\";\nimport type { ParcelDelivery, ParcelEvent } from \"./types\";\nimport { STATUS_LABELS, SUPPORTED_LANGUAGES, FALLBACK_LANGUAGE, UNKNOWN_STATUS_CODE } from \"./types\";\n\n/** Status codes that have expected delivery date/time */\nconst TRACKABLE_STATUSES = new Set([2, 4, 8]);\n\n/**\n * Upper bound for the `deliveries.*` object-view range query: the highest BMP\n * code unit, so the range covers every possible sanitized package id.\n */\nconst ID_RANGE_END = \"\uFFFF\";\n\n/**\n * Resolve a language code to one that has labels. Falls back to English\n * when the system language is not one of the supported ioBroker languages.\n *\n * @param language Raw language code (e.g. from system.config.language)\n */\nexport function resolveLanguage(language: unknown): string {\n if (typeof language === \"string\" && SUPPORTED_LANGUAGES.includes(language)) {\n return language;\n }\n return FALLBACK_LANGUAGE;\n}\n\n/** Manages ioBroker states for parcel deliveries */\nexport class StateManager {\n private adapter: AdapterInstance;\n private language: string;\n /**\n * Cache of state IDs that have already passed `setObjectNotExistsAsync`.\n * Skips repeat DB lookups on the hot path \u2014 each poll touches ~11 states\n * per delivery, and most deliveries see no schema change between polls.\n * On `cleanupDeliveries`, IDs of removed packages are dropped so a re-add\n * triggers a fresh creation.\n */\n private readonly createdIds = new Set<string>();\n\n /**\n * v0.7.2: last-written device-object signature per package id (the name\n * source: description + tracking number). `updateDelivery` used to\n * extendObject the device on EVERY poll \u2014 one object write + objectChange\n * event per package per minute for data that practically never changes.\n * Now the write happens only when the signature differs.\n */\n private readonly deviceWritten = new Map<string, string>();\n\n /**\n * v0.7.2: signature of the last-written state values per package id.\n * `lastUpdated` is only refreshed when at least one sibling value actually\n * changed \u2014 before, a fresh ISO timestamp fired one guaranteed state event\n * per package per poll, defeating the v0.5.3 skip-unchanged optimization\n * for that state. Semantics: `lastUpdated` = \"when the tracking data last\n * changed\", not \"when the adapter last polled\".\n */\n private readonly valuesSig = new Map<string, string>();\n\n /**\n * v0.7.2: package ids known to exist as device objects. Filled from the\n * object view ONCE after adapter start (reconciles leftovers from previous\n * runs), afterwards maintained in memory \u2014 `cleanupDeliveries` no longer\n * needs a DB round-trip per poll.\n */\n private knownDeliveryIds: Set<string> | null = null;\n\n /**\n * @param adapter The ioBroker adapter instance\n * @param language Language code from system.config.language (falls back to English)\n */\n constructor(adapter: AdapterInstance, language: string) {\n this.adapter = adapter;\n this.language = resolveLanguage(language);\n }\n\n /**\n * Sanitize a string for use as ioBroker object ID (see adapter.FORBIDDEN_CHARS).\n * API-drift guard: returns \"unknown\" for non-string input.\n *\n * @param name Raw value to sanitize (any type)\n */\n sanitize(name: unknown): string {\n if (typeof name !== \"string\") {\n return \"unknown\";\n }\n return (\n name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\")\n .slice(0, 50) || \"unknown\"\n );\n }\n\n /**\n * Parse the status code from a delivery. The API sends an int; we also accept\n * a numeric string and fall back to the \"unknown\" sentinel (-1) for drift.\n *\n * @param delivery The delivery to parse\n */\n parseStatus(delivery: ParcelDelivery): number {\n const raw = delivery.status_code as unknown;\n if (typeof raw === \"number\" && Number.isFinite(raw)) {\n return Math.trunc(raw);\n }\n if (typeof raw === \"string\") {\n const n = parseInt(raw, 10);\n if (Number.isFinite(n)) {\n return n;\n }\n }\n // API drift (non-numeric / non-string status_code). Return a visible\n // \"unknown\" sentinel instead of 0 (Delivered) \u2014 otherwise a garbage\n // status_code would silently filter the package out and remove it in\n // autoRemove mode. The active filter is `status !== 0`, so -1 stays visible.\n this.adapter.log.debug(\n `parseStatus drift: ${JSON.stringify(raw)} (type ${typeof raw}) \u2192 ${UNKNOWN_STATUS_CODE} (unknown, kept visible)`,\n );\n return UNKNOWN_STATUS_CODE;\n }\n\n /**\n * Build a unique package ID from a delivery.\n *\n * v0.4.2 (S3): when the bare `sanitize(tracking_number)` collides with\n * another active package (e.g. two trackings differ only in special\n * chars that strip down to the same id), append a stable hash of the\n * full tracking number so both end up at distinct state IDs.\n *\n * @param delivery The delivery to build an ID for\n */\n packageId(delivery: ParcelDelivery): string {\n let id = this.sanitize(delivery.tracking_number);\n // API-drift guard: only string values extend the id\n if (typeof delivery.extra_information === \"string\" && delivery.extra_information.length > 0) {\n id += `_${this.sanitize(delivery.extra_information)}`;\n }\n // v0.4.2 (S3): collision suffix when two distinct (raw) trackings would\n // collapse to the same id. Bare id is kept as long as it's unique\n // within this poll (back-compat with existing installs).\n const owner = this.idOwner.get(id);\n const rawKey = StateManager.rawIdKey(delivery);\n if (owner !== undefined && owner !== rawKey) {\n const suffixed = `${id}__${StateManager.shortHash(rawKey)}`;\n // v0.4.3 (C3): trace the collision-suffix path. Rare event but the\n // resulting state-id divergence is hard to diagnose without a log.\n this.adapter.log.debug(\n `packageId collision: bare='${id}' owner='${owner}' new='${rawKey}' \u2192 suffixed='${suffixed}'`,\n );\n this.idOwner.set(suffixed, rawKey);\n return suffixed;\n }\n this.idOwner.set(id, rawKey);\n return id;\n }\n\n /**\n * v0.4.2 (S3): build a stable raw-key for collision tracking.\n *\n * @param delivery The delivery whose raw tracking identifies it.\n */\n private static rawIdKey(delivery: ParcelDelivery): string {\n const t = typeof delivery.tracking_number === \"string\" ? delivery.tracking_number : \"\";\n const e = typeof delivery.extra_information === \"string\" ? delivery.extra_information : \"\";\n return `${t}\\u0000${e}`;\n }\n\n /**\n * v0.4.2 (S3): FNV-1a 32-bit short hash \u2192 6 hex chars.\n *\n * @param s Input string to hash.\n */\n private static shortHash(s: string): string {\n let h = 0x811c9dc5;\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return (h >>> 0).toString(16).padStart(8, \"0\").slice(0, 6);\n }\n\n /**\n * v0.4.2 (S3): which raw-tracking-key currently \"owns\" each sanitized id\n * within the running poll. Cleared via `resetIdOwners()` between polls so\n * the same delivery keeps its bare id as long as it's unique.\n */\n private readonly idOwner = new Map<string, string>();\n\n /**\n * v0.4.2 (S3): reset the per-poll collision tracker. Call from main.ts\n * before iterating deliveries so the bare id always wins for the first\n * occurrence in each poll.\n */\n resetPollState(): void {\n this.idOwner.clear();\n }\n\n /**\n * Update or create all states for a delivery.\n *\n * @param delivery The delivery data from API\n * @param carrierName Resolved carrier display name\n * @param precomputedId Optional package id from the caller's deterministic\n * pre-pass. Falls back to computing it here when called directly (tests).\n */\n async updateDelivery(delivery: ParcelDelivery, carrierName: string, precomputedId?: string): Promise<void> {\n const pkgId = precomputedId ?? this.packageId(delivery);\n const devicePath = `deliveries.${pkgId}`;\n\n const description = typeof delivery.description === \"string\" ? delivery.description : \"\";\n const trackingNumber = typeof delivery.tracking_number === \"string\" ? delivery.tracking_number : \"\";\n const extraInfo = typeof delivery.extra_information === \"string\" ? delivery.extra_information : \"\";\n\n // v0.7.2: only write the device object when its name source changed \u2014\n // extendObject on every poll meant one object write + objectChange event\n // per package per minute for data that practically never changes.\n const deviceSig = `${description} ${trackingNumber}`;\n if (this.deviceWritten.get(pkgId) !== deviceSig) {\n await this.adapter.extendObjectAsync(\n devicePath,\n {\n type: \"device\",\n common: {\n name: description || `Package ${trackingNumber || pkgId}`,\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n this.deviceWritten.set(pkgId, deviceSig);\n }\n this.knownDeliveryIds?.add(pkgId);\n\n const statusCode = this.parseStatus(delivery);\n const labels = STATUS_LABELS[this.language];\n let statusText = labels[statusCode];\n if (!statusText) {\n // v0.4.3 (E3): trace unknown status-code (API drift). A future\n // parcel.app status (e.g. 9, 10) would render as \"Unknown (N)\"\n // without any log clue that the codes table is out of date.\n this.adapter.log.debug(`status code ${statusCode} not in STATUS_LABELS[${this.language}], using fallback`);\n statusText = `Unknown (${statusCode})`;\n }\n\n const deliveryWindow = this.calculateDeliveryWindow(delivery, statusCode);\n const deliveryEstimate = this.calculateDeliveryEstimate(delivery, statusCode);\n const lastEvent = this.formatLastEvent(delivery);\n const lastLocation = this.extractLastLocation(delivery);\n\n await Promise.all([\n this.createAndSet(`${devicePath}.carrier`, tName(\"carrier\"), \"string\", \"text\", carrierName),\n this.createAndSet(`${devicePath}.status`, tName(\"status\"), \"string\", \"text\", statusText),\n this.createAndSet(`${devicePath}.statusCode`, tName(\"statusCode\"), \"number\", \"value\", statusCode),\n this.createAndSet(`${devicePath}.description`, tName(\"description\"), \"string\", \"text\", description),\n this.createAndSet(`${devicePath}.trackingNumber`, tName(\"trackingNumber\"), \"string\", \"text\", trackingNumber),\n this.createAndSet(`${devicePath}.extraInfo`, tName(\"extraInfo\"), \"string\", \"text\", extraInfo),\n this.createAndSet(`${devicePath}.deliveryWindow`, tName(\"deliveryWindow\"), \"string\", \"text\", deliveryWindow),\n this.createAndSet(\n `${devicePath}.deliveryEstimate`,\n tName(\"deliveryEstimate\"),\n \"string\",\n \"text\",\n deliveryEstimate,\n ),\n this.createAndSet(`${devicePath}.lastEvent`, tName(\"lastEvent\"), \"string\", \"text\", lastEvent),\n this.createAndSet(`${devicePath}.lastLocation`, tName(\"lastLocation\"), \"string\", \"text\", lastLocation),\n ]);\n\n // v0.7.2: `lastUpdated` = \"when the tracking data last CHANGED\". Writing a\n // fresh timestamp every poll fired one guaranteed state event per package\n // per poll and defeated the skip-unchanged optimization for this state.\n const sig = JSON.stringify([\n carrierName,\n statusText,\n statusCode,\n description,\n trackingNumber,\n extraInfo,\n deliveryWindow,\n deliveryEstimate,\n lastEvent,\n lastLocation,\n ]);\n if (this.valuesSig.get(pkgId) !== sig) {\n this.valuesSig.set(pkgId, sig);\n await this.createAndSet(\n `${devicePath}.lastUpdated`,\n tName(\"lastUpdated\"),\n \"string\",\n \"date\",\n new Date().toISOString(),\n );\n }\n }\n\n /**\n * Update summary states. Expects already-filtered active deliveries.\n * The `summary` channel itself is declared via io-package.json instanceObjects.\n *\n * @param activeDeliveries Only active (non-delivered) deliveries\n */\n async updateSummary(activeDeliveries: ParcelDelivery[]): Promise<void> {\n const todayDeliveries = activeDeliveries.filter(d => this.isToday(d, this.parseStatus(d)));\n // v0.4.3 (E1): trace summary refresh \u2014 ~144/day at the default poll\n // interval, kept short (counts only).\n this.adapter.log.debug(\n `updateSummary: ${activeDeliveries.length} active, ${todayDeliveries.length} expected today`,\n );\n\n await Promise.all([\n this.createAndSet(\"summary.activeCount\", tName(\"activeCount\"), \"number\", \"value\", activeDeliveries.length),\n this.createAndSet(\"summary.todayCount\", tName(\"todayCount\"), \"number\", \"value\", todayDeliveries.length),\n this.createAndSet(\n \"summary.deliveryWindow\",\n tName(\"summaryDeliveryWindow\"),\n \"string\",\n \"text\",\n this.calculateCombinedWindow(todayDeliveries),\n ),\n ]);\n }\n\n /**\n * Remove deliveries that are no longer active.\n *\n * @param activeIds List of currently active package IDs\n */\n async cleanupDeliveries(activeIds: string[]): Promise<void> {\n // v0.7.2: the object view is queried only ONCE after adapter start to\n // reconcile leftovers from previous runs; afterwards the in-memory set\n // (maintained by updateDelivery + this prune) replaces the per-poll DB\n // round-trip.\n if (this.knownDeliveryIds === null) {\n const objects = await this.adapter.getObjectViewAsync(\"system\", \"device\", {\n startkey: `${this.adapter.namespace}.deliveries.`,\n endkey: `${this.adapter.namespace}.deliveries.${ID_RANGE_END}`,\n });\n if (!objects?.rows) {\n // v0.4.3 (E2): trace the no-op path \u2014 happens on fresh installs or\n // when getObjectViewAsync returns falsy. Without this the early-return\n // is invisible (and the known-set stays unseeded for the next poll).\n this.adapter.log.debug(\"cleanupDeliveries: no objects view available, skipping\");\n return;\n }\n this.knownDeliveryIds = new Set<string>();\n for (const row of objects.rows) {\n const relativeId = row.id.replace(`${this.adapter.namespace}.`, \"\");\n if (relativeId.startsWith(\"deliveries.\")) {\n // Direct device segment only (`deliveries.<pkgId>`).\n const pkgId = relativeId.slice(\"deliveries.\".length).split(\".\")[0];\n if (pkgId) {\n this.knownDeliveryIds.add(pkgId);\n }\n }\n }\n }\n\n const activeSet = new Set(activeIds);\n // v0.4.2 (S1): collect first, then delete in parallel. Earlier each\n // stale package took a sequential broker round-trip.\n const toDelete = [...this.knownDeliveryIds].filter(pkgId => !activeSet.has(pkgId));\n\n await Promise.all(\n toDelete.map(async pkgId => {\n const relativeId = `deliveries.${pkgId}`;\n await this.adapter.delObjectAsync(relativeId, { recursive: true });\n this.adapter.log.debug(`Removed stale delivery: ${relativeId}`);\n this.deviceWritten.delete(pkgId);\n this.valuesSig.delete(pkgId);\n // v0.4.2 (S2): snapshot to array first \u2014 defensive against any future\n // engine that diverges from spec on Set.delete during for-of iteration.\n for (const id of [...this.createdIds]) {\n if (id === relativeId || id.startsWith(`${relativeId}.`)) {\n this.createdIds.delete(id);\n }\n }\n }),\n );\n this.knownDeliveryIds = new Set(activeSet);\n }\n\n /**\n * Parse a parcel.app expected-date string to LOCAL epoch-millis.\n *\n * The API delivers `date_expected`/`date_expected_end` \"without specific\n * timezone information\"; parse with explicit local calendar components so the\n * value lands on the intended local day/time (`new Date(\"YYYY-MM-DD\")` would\n * be UTC midnight). `hasTime` is false for a bare date or a midnight time\n * (a day, not an hour-window). Ambiguous carrier formats (dotted, weekday\n * names) are deliberately NOT guessed \u2014 they return null rather than risk a\n * wrong date.\n *\n * @param value Raw date/time string from the API\n */\n private static parseExpectedToMs(value: unknown): { ms: number; hasTime: boolean } | null {\n if (typeof value !== \"string\") {\n return null;\n }\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})(?:[ T](\\d{2}):(\\d{2})(?::(\\d{2}))?)?$/.exec(value.trim());\n if (!m) {\n return null;\n }\n const hasClock = m[4] !== undefined;\n const date = new Date(\n Number(m[1]),\n Number(m[2]) - 1,\n Number(m[3]),\n hasClock ? Number(m[4]) : 0,\n hasClock ? Number(m[5]) : 0,\n m[6] !== undefined ? Number(m[6]) : 0,\n );\n if (Number.isNaN(date.getTime())) {\n return null;\n }\n const hasTime =\n hasClock && !(Number(m[4]) === 0 && Number(m[5]) === 0 && (m[6] === undefined || Number(m[6]) === 0));\n return { ms: date.getTime(), hasTime };\n }\n\n /**\n * Resolve a delivery's expected window to epoch-millis bounds. Returns null\n * for non-trackable status or when there is no usable start time.\n *\n * Prefers the Unix timestamp fields; for carriers that report the window only\n * as a date/time string (`date_expected`/`date_expected_end`) it falls back to\n * those \u2014 but only when the string carries a real time-of-day (a bare date or\n * midnight is a day, not an hour-window). Carrier-agnostic.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private windowBoundsMs(delivery: ParcelDelivery, statusCode: number): { start: number; end: number | null } | null {\n if (!TRACKABLE_STATUSES.has(statusCode)) {\n return null;\n }\n const toMs = (timestamp: unknown): number | null => {\n const ts = coerceFiniteNumber(timestamp);\n if (ts === null || ts <= 0) {\n return null;\n }\n const ms = ts * 1000;\n return Number.isNaN(new Date(ms).getTime()) ? null : ms;\n };\n const dateMs = (value: unknown): number | null => {\n const parsed = StateManager.parseExpectedToMs(value);\n return parsed && parsed.hasTime ? parsed.ms : null;\n };\n const start = toMs(delivery.timestamp_expected) ?? dateMs(delivery.date_expected);\n if (start === null) {\n return null;\n }\n const end = toMs(delivery.timestamp_expected_end) ?? dateMs(delivery.date_expected_end);\n return { start, end };\n }\n\n /**\n * Format epoch-millis as local HH:MM.\n *\n * @param ms Epoch milliseconds\n */\n private static formatHHMM(ms: number): string {\n const d = new Date(ms);\n return `${d.getHours().toString().padStart(2, \"0\")}:${d.getMinutes().toString().padStart(2, \"0\")}`;\n }\n\n /**\n * Calculate a delivery time-window string from the resolved expected bounds.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private calculateDeliveryWindow(delivery: ParcelDelivery, statusCode: number): string {\n const bounds = this.windowBoundsMs(delivery, statusCode);\n if (!bounds) {\n return \"\";\n }\n const start = StateManager.formatHHMM(bounds.start);\n return bounds.end !== null ? `${start} - ${StateManager.formatHHMM(bounds.end)}` : start;\n }\n\n /**\n * Days from today to the expected delivery date. Returns null when the\n * delivery has no usable expected date or is in a non-trackable status.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private computeDiffDays(delivery: ParcelDelivery, statusCode: number): number | null {\n if (!TRACKABLE_STATUSES.has(statusCode)) {\n return null;\n }\n\n let expectedDate: Date | null = null;\n const ts = coerceFiniteNumber(delivery.timestamp_expected);\n if (ts !== null && ts > 0) {\n expectedDate = new Date(ts * 1000);\n } else {\n // Shares the window's date parser (one source of format-truth). Only the\n // calendar day matters here, so the time-of-day flag is ignored; the\n // local-component parse keeps the day timezone-stable.\n const parsed = StateManager.parseExpectedToMs(delivery.date_expected);\n expectedDate = parsed ? new Date(parsed.ms) : null;\n }\n\n if (!expectedDate || isNaN(expectedDate.getTime())) {\n return null;\n }\n\n const now = new Date();\n const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n const expectedStart = new Date(expectedDate.getFullYear(), expectedDate.getMonth(), expectedDate.getDate());\n return Math.round((expectedStart.getTime() - todayStart.getTime()) / (1000 * 60 * 60 * 24));\n }\n\n /**\n * Calculate human-readable delivery estimate.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private calculateDeliveryEstimate(delivery: ParcelDelivery, statusCode: number): string {\n const diffDays = this.computeDiffDays(delivery, statusCode);\n if (diffDays === null) {\n return \"\";\n }\n if (diffDays < 0) {\n return I18n.translate(\"estimateOverdue\");\n }\n if (diffDays === 0) {\n return I18n.translate(\"estimateToday\");\n }\n if (diffDays === 1) {\n return I18n.translate(\"estimateTomorrow\");\n }\n return I18n.translate(\"estimateDays\").replace(\"%d\", String(diffDays));\n }\n\n /**\n * Whether the delivery is expected today. Language-agnostic, used by the\n * summary filter so `todayCount` works across all languages.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private isToday(delivery: ParcelDelivery, statusCode: number): boolean {\n return this.computeDiffDays(delivery, statusCode) === 0;\n }\n\n private getLatestEvent(delivery: ParcelDelivery): ParcelEvent | null {\n if (!Array.isArray(delivery.events) || delivery.events.length === 0) {\n return null;\n }\n const latest = delivery.events[0];\n if (!latest || typeof latest !== \"object\") {\n return null;\n }\n return latest;\n }\n\n private formatLastEvent(delivery: ParcelDelivery): string {\n const latest = this.getLatestEvent(delivery);\n if (!latest) {\n return \"\";\n }\n const parts: string[] = [];\n if (typeof latest.event === \"string\" && latest.event.length > 0) {\n parts.push(latest.event);\n }\n if (typeof latest.date === \"string\" && latest.date.length > 0) {\n parts.push(latest.date);\n }\n return parts.join(\" - \");\n }\n\n private extractLastLocation(delivery: ParcelDelivery): string {\n const latest = this.getLatestEvent(delivery);\n if (!latest) {\n return \"\";\n }\n return typeof latest.location === \"string\" ? latest.location : \"\";\n }\n\n /**\n * Combined delivery window for today's packages: earliest start to latest\n * end across all windows. Computed from the raw millis (not the formatted\n * strings) so the latest end always wins \u2014 fixes the earlier bug where the\n * end of the latest-*starting* window was used instead of the maximum end.\n *\n * @param todayDeliveries Deliveries expected today\n */\n private calculateCombinedWindow(todayDeliveries: ParcelDelivery[]): string {\n const bounds = todayDeliveries\n .map(d => this.windowBoundsMs(d, this.parseStatus(d)))\n .filter((b): b is { start: number; end: number | null } => b !== null);\n\n if (bounds.length === 0) {\n return \"\";\n }\n\n const minStart = Math.min(...bounds.map(b => b.start));\n const maxEnd = Math.max(...bounds.map(b => b.end ?? b.start));\n const startStr = StateManager.formatHHMM(minStart);\n return maxEnd > minStart ? `${startStr} - ${StateManager.formatHHMM(maxEnd)}` : startStr;\n }\n\n /**\n * Create/extend a read-only state and set its value. Skips the\n * `setObjectNotExistsAsync` round-trip once the ID is in the cache \u2014\n * states are static after first creation; only the value changes per poll.\n *\n * @param id State ID relative to adapter namespace\n * @param name Display name (translation object or plain string)\n * @param type Value type\n * @param role ioBroker role\n * @param val Value to set\n */\n private async createAndSet(\n id: string,\n name: ioBroker.StringOrTranslated,\n type: ioBroker.CommonType,\n role: string,\n val: ioBroker.StateValue,\n ): Promise<void> {\n if (!this.createdIds.has(id)) {\n await this.adapter.setObjectNotExistsAsync(id, {\n type: \"state\",\n common: { name, type, role, read: true, write: false },\n native: {},\n });\n this.createdIds.add(id);\n }\n await this.adapter.setStateChangedAsync(id, { val, ack: true });\n }\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAA2C;AAC3C,oBAAmC;AACnC,kBAAsB;AAEtB,mBAA2F;AAG3F,MAAM,qBAAqB,oBAAI,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC;AAM5C,MAAM,eAAe;AAQd,SAAS,gBAAgB,UAA2B;AACzD,MAAI,OAAO,aAAa,YAAY,iCAAoB,SAAS,QAAQ,GAAG;AAC1E,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGO,MAAM,aAAa;AAAA,EAChB;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQS,aAAa,oBAAI,IAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS7B,gBAAgB,oBAAI,IAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUxC,YAAY,oBAAI,IAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ7C,mBAAuC;AAAA;AAAA;AAAA;AAAA;AAAA,EAM/C,YAAY,SAA0B,UAAkB;AACtD,SAAK,UAAU;AACf,SAAK,WAAW,gBAAgB,QAAQ;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,SAAS,MAAuB;AAC9B,QAAI,OAAO,SAAS,UAAU;AAC5B,aAAO;AAAA,IACT;AACA,WACE,KACG,YAAY,EACZ,QAAQ,eAAe,GAAG,EAC1B,QAAQ,YAAY,EAAE,EACtB,MAAM,GAAG,EAAE,KAAK;AAAA,EAEvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,YAAY,UAAkC;AAC5C,UAAM,MAAM,SAAS;AACrB,QAAI,OAAO,QAAQ,YAAY,OAAO,SAAS,GAAG,GAAG;AACnD,aAAO,KAAK,MAAM,GAAG;AAAA,IACvB;AACA,QAAI,OAAO,QAAQ,UAAU;AAC3B,YAAM,IAAI,SAAS,KAAK,EAAE;AAC1B,UAAI,OAAO,SAAS,CAAC,GAAG;AACtB,eAAO;AAAA,MACT;AAAA,IACF;AAKA,SAAK,QAAQ,IAAI;AAAA,MACf,sBAAsB,KAAK,UAAU,GAAG,CAAC,UAAU,OAAO,GAAG,YAAO,gCAAmB;AAAA,IACzF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,UAAU,UAAkC;AAC1C,QAAI,KAAK,KAAK,SAAS,SAAS,eAAe;AAE/C,QAAI,OAAO,SAAS,sBAAsB,YAAY,SAAS,kBAAkB,SAAS,GAAG;AAC3F,YAAM,IAAI,KAAK,SAAS,SAAS,iBAAiB,CAAC;AAAA,IACrD;AAIA,UAAM,QAAQ,KAAK,QAAQ,IAAI,EAAE;AACjC,UAAM,SAAS,aAAa,SAAS,QAAQ;AAC7C,QAAI,UAAU,UAAa,UAAU,QAAQ;AAC3C,YAAM,WAAW,GAAG,EAAE,KAAK,aAAa,UAAU,MAAM,CAAC;AAGzD,WAAK,QAAQ,IAAI;AAAA,QACf,8BAA8B,EAAE,YAAY,KAAK,UAAU,MAAM,sBAAiB,QAAQ;AAAA,MAC5F;AACA,WAAK,QAAQ,IAAI,UAAU,MAAM;AACjC,aAAO;AAAA,IACT;AACA,SAAK,QAAQ,IAAI,IAAI,MAAM;AAC3B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAe,SAAS,UAAkC;AACxD,UAAM,IAAI,OAAO,SAAS,oBAAoB,WAAW,SAAS,kBAAkB;AACpF,UAAM,IAAI,OAAO,SAAS,sBAAsB,WAAW,SAAS,oBAAoB;AACxF,WAAO,GAAG,CAAC,KAAS,CAAC;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAe,UAAU,GAAmB;AAC1C,QAAI,IAAI;AACR,aAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AACjC,WAAK,EAAE,WAAW,CAAC;AACnB,UAAI,KAAK,KAAK,GAAG,QAAU;AAAA,IAC7B;AACA,YAAQ,MAAM,GAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,EAAE,MAAM,GAAG,CAAC;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOiB,UAAU,oBAAI,IAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOnD,iBAAuB;AACrB,SAAK,QAAQ,MAAM;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,eAAe,UAA0B,aAAqB,eAAuC;AA/M7G;AAgNI,UAAM,QAAQ,wCAAiB,KAAK,UAAU,QAAQ;AACtD,UAAM,aAAa,cAAc,KAAK;AAEtC,UAAM,cAAc,OAAO,SAAS,gBAAgB,WAAW,SAAS,cAAc;AACtF,UAAM,iBAAiB,OAAO,SAAS,oBAAoB,WAAW,SAAS,kBAAkB;AACjG,UAAM,YAAY,OAAO,SAAS,sBAAsB,WAAW,SAAS,oBAAoB;AAKhG,UAAM,YAAY,GAAG,WAAW,IAAI,cAAc;AAClD,QAAI,KAAK,cAAc,IAAI,KAAK,MAAM,WAAW;AAC/C,YAAM,KAAK,QAAQ;AAAA,QACjB;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,QAAQ;AAAA,YACN,MAAM,eAAe,WAAW,kBAAkB,KAAK;AAAA,UACzD;AAAA,UACA,QAAQ,CAAC;AAAA,QACX;AAAA,QACA,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE;AAAA,MACnC;AACA,WAAK,cAAc,IAAI,OAAO,SAAS;AAAA,IACzC;AACA,eAAK,qBAAL,mBAAuB,IAAI;AAE3B,UAAM,aAAa,KAAK,YAAY,QAAQ;AAC5C,UAAM,SAAS,2BAAc,KAAK,QAAQ;AAC1C,QAAI,aAAa,OAAO,UAAU;AAClC,QAAI,CAAC,YAAY;AAIf,WAAK,QAAQ,IAAI,MAAM,eAAe,UAAU,yBAAyB,KAAK,QAAQ,mBAAmB;AACzG,mBAAa,YAAY,UAAU;AAAA,IACrC;AAEA,UAAM,iBAAiB,KAAK,wBAAwB,UAAU,UAAU;AACxE,UAAM,mBAAmB,KAAK,0BAA0B,UAAU,UAAU;AAC5E,UAAM,YAAY,KAAK,gBAAgB,QAAQ;AAC/C,UAAM,eAAe,KAAK,oBAAoB,QAAQ;AAEtD,UAAM,QAAQ,IAAI;AAAA,MAChB,KAAK,aAAa,GAAG,UAAU,gBAAY,mBAAM,SAAS,GAAG,UAAU,QAAQ,WAAW;AAAA,MAC1F,KAAK,aAAa,GAAG,UAAU,eAAW,mBAAM,QAAQ,GAAG,UAAU,QAAQ,UAAU;AAAA,MACvF,KAAK,aAAa,GAAG,UAAU,mBAAe,mBAAM,YAAY,GAAG,UAAU,SAAS,UAAU;AAAA,MAChG,KAAK,aAAa,GAAG,UAAU,oBAAgB,mBAAM,aAAa,GAAG,UAAU,QAAQ,WAAW;AAAA,MAClG,KAAK,aAAa,GAAG,UAAU,uBAAmB,mBAAM,gBAAgB,GAAG,UAAU,QAAQ,cAAc;AAAA,MAC3G,KAAK,aAAa,GAAG,UAAU,kBAAc,mBAAM,WAAW,GAAG,UAAU,QAAQ,SAAS;AAAA,MAC5F,KAAK,aAAa,GAAG,UAAU,uBAAmB,mBAAM,gBAAgB,GAAG,UAAU,QAAQ,cAAc;AAAA,MAC3G,KAAK;AAAA,QACH,GAAG,UAAU;AAAA,YACb,mBAAM,kBAAkB;AAAA,QACxB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA,KAAK,aAAa,GAAG,UAAU,kBAAc,mBAAM,WAAW,GAAG,UAAU,QAAQ,SAAS;AAAA,MAC5F,KAAK,aAAa,GAAG,UAAU,qBAAiB,mBAAM,cAAc,GAAG,UAAU,QAAQ,YAAY;AAAA,IACvG,CAAC;AAKD,UAAM,MAAM,KAAK,UAAU;AAAA,MACzB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,QAAI,KAAK,UAAU,IAAI,KAAK,MAAM,KAAK;AACrC,WAAK,UAAU,IAAI,OAAO,GAAG;AAC7B,YAAM,KAAK;AAAA,QACT,GAAG,UAAU;AAAA,YACb,mBAAM,aAAa;AAAA,QACnB;AAAA,QACA;AAAA,SACA,oBAAI,KAAK,GAAE,YAAY;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,cAAc,kBAAmD;AACrE,UAAM,kBAAkB,iBAAiB,OAAO,OAAK,KAAK,QAAQ,GAAG,KAAK,YAAY,CAAC,CAAC,CAAC;AAGzF,SAAK,QAAQ,IAAI;AAAA,MACf,kBAAkB,iBAAiB,MAAM,YAAY,gBAAgB,MAAM;AAAA,IAC7E;AAEA,UAAM,QAAQ,IAAI;AAAA,MAChB,KAAK,aAAa,2BAAuB,mBAAM,aAAa,GAAG,UAAU,SAAS,iBAAiB,MAAM;AAAA,MACzG,KAAK,aAAa,0BAAsB,mBAAM,YAAY,GAAG,UAAU,SAAS,gBAAgB,MAAM;AAAA,MACtG,KAAK;AAAA,QACH;AAAA,YACA,mBAAM,uBAAuB;AAAA,QAC7B;AAAA,QACA;AAAA,QACA,KAAK,wBAAwB,eAAe;AAAA,MAC9C;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,kBAAkB,WAAoC;AAK1D,QAAI,KAAK,qBAAqB,MAAM;AAClC,YAAM,UAAU,MAAM,KAAK,QAAQ,mBAAmB,UAAU,UAAU;AAAA,QACxE,UAAU,GAAG,KAAK,QAAQ,SAAS;AAAA,QACnC,QAAQ,GAAG,KAAK,QAAQ,SAAS,eAAe,YAAY;AAAA,MAC9D,CAAC;AACD,UAAI,EAAC,mCAAS,OAAM;AAIlB,aAAK,QAAQ,IAAI,MAAM,wDAAwD;AAC/E;AAAA,MACF;AACA,WAAK,mBAAmB,oBAAI,IAAY;AACxC,iBAAW,OAAO,QAAQ,MAAM;AAC9B,cAAM,aAAa,IAAI,GAAG,QAAQ,GAAG,KAAK,QAAQ,SAAS,KAAK,EAAE;AAClE,YAAI,WAAW,WAAW,aAAa,GAAG;AAExC,gBAAM,QAAQ,WAAW,MAAM,cAAc,MAAM,EAAE,MAAM,GAAG,EAAE,CAAC;AACjE,cAAI,OAAO;AACT,iBAAK,iBAAiB,IAAI,KAAK;AAAA,UACjC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,YAAY,IAAI,IAAI,SAAS;AAGnC,UAAM,WAAW,CAAC,GAAG,KAAK,gBAAgB,EAAE,OAAO,WAAS,CAAC,UAAU,IAAI,KAAK,CAAC;AAEjF,UAAM,QAAQ;AAAA,MACZ,SAAS,IAAI,OAAM,UAAS;AAC1B,cAAM,aAAa,cAAc,KAAK;AACtC,cAAM,KAAK,QAAQ,eAAe,YAAY,EAAE,WAAW,KAAK,CAAC;AACjE,aAAK,QAAQ,IAAI,MAAM,2BAA2B,UAAU,EAAE;AAC9D,aAAK,cAAc,OAAO,KAAK;AAC/B,aAAK,UAAU,OAAO,KAAK;AAG3B,mBAAW,MAAM,CAAC,GAAG,KAAK,UAAU,GAAG;AACrC,cAAI,OAAO,cAAc,GAAG,WAAW,GAAG,UAAU,GAAG,GAAG;AACxD,iBAAK,WAAW,OAAO,EAAE;AAAA,UAC3B;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AACA,SAAK,mBAAmB,IAAI,IAAI,SAAS;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,OAAe,kBAAkB,OAAyD;AACxF,QAAI,OAAO,UAAU,UAAU;AAC7B,aAAO;AAAA,IACT;AACA,UAAM,IAAI,iEAAiE,KAAK,MAAM,KAAK,CAAC;AAC5F,QAAI,CAAC,GAAG;AACN,aAAO;AAAA,IACT;AACA,UAAM,WAAW,EAAE,CAAC,MAAM;AAC1B,UAAM,OAAO,IAAI;AAAA,MACf,OAAO,EAAE,CAAC,CAAC;AAAA,MACX,OAAO,EAAE,CAAC,CAAC,IAAI;AAAA,MACf,OAAO,EAAE,CAAC,CAAC;AAAA,MACX,WAAW,OAAO,EAAE,CAAC,CAAC,IAAI;AAAA,MAC1B,WAAW,OAAO,EAAE,CAAC,CAAC,IAAI;AAAA,MAC1B,EAAE,CAAC,MAAM,SAAY,OAAO,EAAE,CAAC,CAAC,IAAI;AAAA,IACtC;AACA,QAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,GAAG;AAChC,aAAO;AAAA,IACT;AACA,UAAM,UACJ,YAAY,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC,CAAC,MAAM,MAAM,EAAE,CAAC,MAAM,UAAa,OAAO,EAAE,CAAC,CAAC,MAAM;AACpG,WAAO,EAAE,IAAI,KAAK,QAAQ,GAAG,QAAQ;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcQ,eAAe,UAA0B,YAAkE;AAjbrH;AAkbI,QAAI,CAAC,mBAAmB,IAAI,UAAU,GAAG;AACvC,aAAO;AAAA,IACT;AACA,UAAM,OAAO,CAAC,cAAsC;AAClD,YAAM,SAAK,kCAAmB,SAAS;AACvC,UAAI,OAAO,QAAQ,MAAM,GAAG;AAC1B,eAAO;AAAA,MACT;AACA,YAAM,KAAK,KAAK;AAChB,aAAO,OAAO,MAAM,IAAI,KAAK,EAAE,EAAE,QAAQ,CAAC,IAAI,OAAO;AAAA,IACvD;AACA,UAAM,SAAS,CAAC,UAAkC;AAChD,YAAM,SAAS,aAAa,kBAAkB,KAAK;AACnD,aAAO,UAAU,OAAO,UAAU,OAAO,KAAK;AAAA,IAChD;AACA,UAAM,SAAQ,UAAK,SAAS,kBAAkB,MAAhC,YAAqC,OAAO,SAAS,aAAa;AAChF,QAAI,UAAU,MAAM;AAClB,aAAO;AAAA,IACT;AACA,UAAM,OAAM,UAAK,SAAS,sBAAsB,MAApC,YAAyC,OAAO,SAAS,iBAAiB;AACtF,WAAO,EAAE,OAAO,IAAI;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAe,WAAW,IAAoB;AAC5C,UAAM,IAAI,IAAI,KAAK,EAAE;AACrB,WAAO,GAAG,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,GAAG,GAAG,CAAC;AAAA,EAClG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,wBAAwB,UAA0B,YAA4B;AACpF,UAAM,SAAS,KAAK,eAAe,UAAU,UAAU;AACvD,QAAI,CAAC,QAAQ;AACX,aAAO;AAAA,IACT;AACA,UAAM,QAAQ,aAAa,WAAW,OAAO,KAAK;AAClD,WAAO,OAAO,QAAQ,OAAO,GAAG,KAAK,MAAM,aAAa,WAAW,OAAO,GAAG,CAAC,KAAK;AAAA,EACrF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,gBAAgB,UAA0B,YAAmC;AACnF,QAAI,CAAC,mBAAmB,IAAI,UAAU,GAAG;AACvC,aAAO;AAAA,IACT;AAEA,QAAI,eAA4B;AAChC,UAAM,SAAK,kCAAmB,SAAS,kBAAkB;AACzD,QAAI,OAAO,QAAQ,KAAK,GAAG;AACzB,qBAAe,IAAI,KAAK,KAAK,GAAI;AAAA,IACnC,OAAO;AAIL,YAAM,SAAS,aAAa,kBAAkB,SAAS,aAAa;AACpE,qBAAe,SAAS,IAAI,KAAK,OAAO,EAAE,IAAI;AAAA,IAChD;AAEA,QAAI,CAAC,gBAAgB,MAAM,aAAa,QAAQ,CAAC,GAAG;AAClD,aAAO;AAAA,IACT;AAEA,UAAM,MAAM,oBAAI,KAAK;AACrB,UAAM,aAAa,IAAI,KAAK,IAAI,YAAY,GAAG,IAAI,SAAS,GAAG,IAAI,QAAQ,CAAC;AAC5E,UAAM,gBAAgB,IAAI,KAAK,aAAa,YAAY,GAAG,aAAa,SAAS,GAAG,aAAa,QAAQ,CAAC;AAC1G,WAAO,KAAK,OAAO,cAAc,QAAQ,IAAI,WAAW,QAAQ,MAAM,MAAO,KAAK,KAAK,GAAG;AAAA,EAC5F;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,0BAA0B,UAA0B,YAA4B;AACtF,UAAM,WAAW,KAAK,gBAAgB,UAAU,UAAU;AAC1D,QAAI,aAAa,MAAM;AACrB,aAAO;AAAA,IACT;AACA,QAAI,WAAW,GAAG;AAChB,aAAO,yBAAK,UAAU,iBAAiB;AAAA,IACzC;AACA,QAAI,aAAa,GAAG;AAClB,aAAO,yBAAK,UAAU,eAAe;AAAA,IACvC;AACA,QAAI,aAAa,GAAG;AAClB,aAAO,yBAAK,UAAU,kBAAkB;AAAA,IAC1C;AACA,WAAO,yBAAK,UAAU,cAAc,EAAE,QAAQ,MAAM,OAAO,QAAQ,CAAC;AAAA,EACtE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,QAAQ,UAA0B,YAA6B;AACrE,WAAO,KAAK,gBAAgB,UAAU,UAAU,MAAM;AAAA,EACxD;AAAA,EAEQ,eAAe,UAA8C;AACnE,QAAI,CAAC,MAAM,QAAQ,SAAS,MAAM,KAAK,SAAS,OAAO,WAAW,GAAG;AACnE,aAAO;AAAA,IACT;AACA,UAAM,SAAS,SAAS,OAAO,CAAC;AAChC,QAAI,CAAC,UAAU,OAAO,WAAW,UAAU;AACzC,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,gBAAgB,UAAkC;AACxD,UAAM,SAAS,KAAK,eAAe,QAAQ;AAC3C,QAAI,CAAC,QAAQ;AACX,aAAO;AAAA,IACT;AACA,UAAM,QAAkB,CAAC;AACzB,QAAI,OAAO,OAAO,UAAU,YAAY,OAAO,MAAM,SAAS,GAAG;AAC/D,YAAM,KAAK,OAAO,KAAK;AAAA,IACzB;AACA,QAAI,OAAO,OAAO,SAAS,YAAY,OAAO,KAAK,SAAS,GAAG;AAC7D,YAAM,KAAK,OAAO,IAAI;AAAA,IACxB;AACA,WAAO,MAAM,KAAK,KAAK;AAAA,EACzB;AAAA,EAEQ,oBAAoB,UAAkC;AAC5D,UAAM,SAAS,KAAK,eAAe,QAAQ;AAC3C,QAAI,CAAC,QAAQ;AACX,aAAO;AAAA,IACT;AACA,WAAO,OAAO,OAAO,aAAa,WAAW,OAAO,WAAW;AAAA,EACjE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,wBAAwB,iBAA2C;AACzE,UAAM,SAAS,gBACZ,IAAI,OAAK,KAAK,eAAe,GAAG,KAAK,YAAY,CAAC,CAAC,CAAC,EACpD,OAAO,CAAC,MAAkD,MAAM,IAAI;AAEvE,QAAI,OAAO,WAAW,GAAG;AACvB,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,KAAK,IAAI,GAAG,OAAO,IAAI,OAAK,EAAE,KAAK,CAAC;AACrD,UAAM,SAAS,KAAK,IAAI,GAAG,OAAO,IAAI,OAAE;AA1lB5C;AA0lB+C,qBAAE,QAAF,YAAS,EAAE;AAAA,KAAK,CAAC;AAC5D,UAAM,WAAW,aAAa,WAAW,QAAQ;AACjD,WAAO,SAAS,WAAW,GAAG,QAAQ,MAAM,aAAa,WAAW,MAAM,CAAC,KAAK;AAAA,EAClF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAc,aACZ,IACA,MACA,MACA,MACA,KACe;AACf,QAAI,CAAC,KAAK,WAAW,IAAI,EAAE,GAAG;AAC5B,YAAM,KAAK,QAAQ,wBAAwB,IAAI;AAAA,QAC7C,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,MAAM,MAAM,MAAM,MAAM,OAAO,MAAM;AAAA,QACrD,QAAQ,CAAC;AAAA,MACX,CAAC;AACD,WAAK,WAAW,IAAI,EAAE;AAAA,IACxB;AACA,UAAM,KAAK,QAAQ,qBAAqB,IAAI,EAAE,KAAK,KAAK,KAAK,CAAC;AAAA,EAChE;AACF;",
4
+ "sourcesContent": ["import { I18n, type AdapterInstance } from \"@iobroker/adapter-core\";\nimport { coerceFiniteNumber, oneLine } from \"./coerce\";\nimport { tName } from \"./i18n\";\nimport type { ParcelDelivery, ParcelEvent } from \"./types\";\nimport { STATUS_LABELS, SUPPORTED_LANGUAGES, FALLBACK_LANGUAGE, UNKNOWN_STATUS_CODE } from \"./types\";\n\n/** Status codes that have expected delivery date/time */\nconst TRACKABLE_STATUSES = new Set([2, 4, 8]);\n\n/**\n * Upper bound for the `deliveries.*` object-view range query: the highest BMP\n * code unit, so the range covers every possible sanitized package id.\n */\nconst ID_RANGE_END = \"\uFFFF\";\n\n/**\n * Resolve a language code to one that has labels. Falls back to English\n * when the system language is not one of the supported ioBroker languages.\n *\n * @param language Raw language code (e.g. from system.config.language)\n */\nexport function resolveLanguage(language: unknown): string {\n if (typeof language === \"string\" && SUPPORTED_LANGUAGES.includes(language)) {\n return language;\n }\n return FALLBACK_LANGUAGE;\n}\n\n/** Manages ioBroker states for parcel deliveries */\nexport class StateManager {\n private adapter: AdapterInstance;\n private language: string;\n /**\n * Cache of state IDs that have already passed `setObjectNotExistsAsync`.\n * Skips repeat DB lookups on the hot path \u2014 each poll touches ~11 states\n * per delivery, and most deliveries see no schema change between polls.\n * On `cleanupDeliveries`, IDs of removed packages are dropped so a re-add\n * triggers a fresh creation.\n */\n private readonly createdIds = new Set<string>();\n\n /**\n * v0.7.2: last-written device-object signature per package id (the name\n * source: description + tracking number). `updateDelivery` used to\n * extendObject the device on EVERY poll \u2014 one object write + objectChange\n * event per package per minute for data that practically never changes.\n * Now the write happens only when the signature differs.\n */\n private readonly deviceWritten = new Map<string, string>();\n\n /**\n * v0.7.2: signature of the last-written state values per package id.\n * `lastUpdated` is only refreshed when at least one sibling value actually\n * changed \u2014 before, a fresh ISO timestamp fired one guaranteed state event\n * per package per poll, defeating the v0.5.3 skip-unchanged optimization\n * for that state. Semantics: `lastUpdated` = \"when the tracking data last\n * changed\", not \"when the adapter last polled\".\n */\n private readonly valuesSig = new Map<string, string>();\n\n /**\n * v0.7.2: package ids known to exist as device objects. Filled from the\n * object view ONCE after adapter start (reconciles leftovers from previous\n * runs), afterwards maintained in memory \u2014 `cleanupDeliveries` no longer\n * needs a DB round-trip per poll.\n */\n private knownDeliveryIds: Set<string> | null = null;\n\n /**\n * @param adapter The ioBroker adapter instance\n * @param language Language code from system.config.language (falls back to English)\n */\n constructor(adapter: AdapterInstance, language: string) {\n this.adapter = adapter;\n this.language = resolveLanguage(language);\n }\n\n /**\n * Sanitize a string for use as ioBroker object ID (see adapter.FORBIDDEN_CHARS).\n * API-drift guard: returns \"unknown\" for non-string input.\n *\n * @param name Raw value to sanitize (any type)\n */\n sanitize(name: unknown): string {\n if (typeof name !== \"string\") {\n return \"unknown\";\n }\n return (\n name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\")\n .slice(0, 50) || \"unknown\"\n );\n }\n\n /**\n * Parse the status code from a delivery. The API sends an int; we also accept\n * a numeric string and fall back to the \"unknown\" sentinel (-1) for drift.\n *\n * @param delivery The delivery to parse\n */\n parseStatus(delivery: ParcelDelivery): number {\n const raw = delivery.status_code;\n if (typeof raw === \"number\" && Number.isFinite(raw)) {\n return Math.trunc(raw);\n }\n if (typeof raw === \"string\") {\n const n = parseInt(raw, 10);\n if (Number.isFinite(n)) {\n return n;\n }\n }\n // API drift (non-numeric / non-string status_code). Return a visible\n // \"unknown\" sentinel instead of 0 (Delivered) \u2014 otherwise a garbage\n // status_code would silently filter the package out and remove it in\n // autoRemove mode. The active filter is `status !== 0`, so -1 stays visible.\n this.adapter.log.debug(\n `parseStatus drift: ${JSON.stringify(raw)} (type ${typeof raw}) \u2192 ${UNKNOWN_STATUS_CODE} (unknown, kept visible)`,\n );\n return UNKNOWN_STATUS_CODE;\n }\n\n /**\n * Build a unique package ID from a delivery.\n *\n * v0.4.2 (S3): when the bare `sanitize(tracking_number)` collides with\n * another active package (e.g. two trackings differ only in special\n * chars that strip down to the same id), append a stable hash of the\n * full tracking number so both end up at distinct state IDs.\n *\n * @param delivery The delivery to build an ID for\n */\n packageId(delivery: ParcelDelivery): string {\n let id = this.sanitize(delivery.tracking_number);\n // API-drift guard: only string values extend the id\n if (typeof delivery.extra_information === \"string\" && delivery.extra_information.length > 0) {\n id += `_${this.sanitize(delivery.extra_information)}`;\n }\n // v0.4.2 (S3): collision suffix when two distinct (raw) trackings would\n // collapse to the same id. Bare id is kept as long as it's unique\n // within this poll (back-compat with existing installs).\n const owner = this.idOwner.get(id);\n const rawKey = StateManager.rawIdKey(delivery);\n if (owner !== undefined && owner !== rawKey) {\n const suffixed = `${id}__${StateManager.shortHash(rawKey)}`;\n // v0.4.3 (C3): trace the collision-suffix path. Rare event but the\n // resulting state-id divergence is hard to diagnose without a log.\n this.adapter.log.debug(\n `packageId collision: bare='${id}' owner='${oneLine(owner)}' new='${oneLine(rawKey)}' \u2192 suffixed='${suffixed}'`,\n );\n this.idOwner.set(suffixed, rawKey);\n return suffixed;\n }\n this.idOwner.set(id, rawKey);\n return id;\n }\n\n /**\n * v0.4.2 (S3): build a stable raw-key for collision tracking.\n *\n * @param delivery The delivery whose raw tracking identifies it.\n */\n private static rawIdKey(delivery: ParcelDelivery): string {\n const t = typeof delivery.tracking_number === \"string\" ? delivery.tracking_number : \"\";\n const e = typeof delivery.extra_information === \"string\" ? delivery.extra_information : \"\";\n return `${t}\\u0000${e}`;\n }\n\n /**\n * v0.4.2 (S3): FNV-1a 32-bit short hash \u2192 6 hex chars.\n *\n * @param s Input string to hash.\n */\n private static shortHash(s: string): string {\n let h = 0x811c9dc5;\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return (h >>> 0).toString(16).padStart(8, \"0\").slice(0, 6);\n }\n\n /**\n * v0.4.2 (S3): which raw-tracking-key currently \"owns\" each sanitized id\n * within the running poll. Cleared via `resetPollState()` between polls so\n * the same delivery keeps its bare id as long as it's unique.\n */\n private readonly idOwner = new Map<string, string>();\n\n /**\n * v0.4.2 (S3): reset the per-poll collision tracker. Call from main.ts\n * before iterating deliveries so the bare id always wins for the first\n * occurrence in each poll.\n */\n resetPollState(): void {\n this.idOwner.clear();\n }\n\n /**\n * Update or create all states for a delivery.\n *\n * @param delivery The delivery data from API\n * @param carrierName Resolved carrier display name\n * @param precomputedId Optional package id from the caller's deterministic\n * pre-pass. Falls back to computing it here when called directly (tests).\n */\n async updateDelivery(delivery: ParcelDelivery, carrierName: string, precomputedId?: string): Promise<void> {\n const pkgId = precomputedId ?? this.packageId(delivery);\n const devicePath = `deliveries.${pkgId}`;\n\n const description = typeof delivery.description === \"string\" ? delivery.description : \"\";\n const trackingNumber = typeof delivery.tracking_number === \"string\" ? delivery.tracking_number : \"\";\n const extraInfo = typeof delivery.extra_information === \"string\" ? delivery.extra_information : \"\";\n\n // v0.7.2: only write the device object when its name source changed \u2014\n // extendObject on every poll meant one object write + objectChange event\n // per package per minute for data that practically never changes.\n const deviceSig = `${description} ${trackingNumber}`;\n if (this.deviceWritten.get(pkgId) !== deviceSig) {\n await this.adapter.extendObjectAsync(\n devicePath,\n {\n type: \"device\",\n common: {\n name: description || `Package ${trackingNumber || pkgId}`,\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n this.deviceWritten.set(pkgId, deviceSig);\n }\n this.knownDeliveryIds?.add(pkgId);\n\n const statusCode = this.parseStatus(delivery);\n const labels = STATUS_LABELS[this.language];\n let statusText = labels[statusCode];\n if (!statusText) {\n // v0.4.3 (E3): trace unknown status-code (API drift). A future\n // parcel.app status (e.g. 9, 10) would render as \"Unknown (N)\"\n // without any log clue that the codes table is out of date.\n this.adapter.log.debug(`status code ${statusCode} not in STATUS_LABELS[${this.language}], using fallback`);\n statusText = `Unknown (${statusCode})`;\n }\n\n const deliveryWindow = this.calculateDeliveryWindow(delivery, statusCode);\n const deliveryEstimate = this.calculateDeliveryEstimate(delivery, statusCode);\n const lastEvent = this.formatLastEvent(delivery);\n const lastLocation = this.extractLastLocation(delivery);\n\n await Promise.all([\n this.createAndSet(`${devicePath}.carrier`, tName(\"carrier\"), \"string\", \"text\", carrierName),\n this.createAndSet(`${devicePath}.status`, tName(\"status\"), \"string\", \"text\", statusText),\n this.createAndSet(`${devicePath}.statusCode`, tName(\"statusCode\"), \"number\", \"value\", statusCode),\n this.createAndSet(`${devicePath}.description`, tName(\"description\"), \"string\", \"text\", description),\n this.createAndSet(`${devicePath}.trackingNumber`, tName(\"trackingNumber\"), \"string\", \"text\", trackingNumber),\n this.createAndSet(`${devicePath}.extraInfo`, tName(\"extraInfo\"), \"string\", \"text\", extraInfo),\n this.createAndSet(`${devicePath}.deliveryWindow`, tName(\"deliveryWindow\"), \"string\", \"text\", deliveryWindow),\n this.createAndSet(\n `${devicePath}.deliveryEstimate`,\n tName(\"deliveryEstimate\"),\n \"string\",\n \"text\",\n deliveryEstimate,\n ),\n this.createAndSet(`${devicePath}.lastEvent`, tName(\"lastEvent\"), \"string\", \"text\", lastEvent),\n this.createAndSet(`${devicePath}.lastLocation`, tName(\"lastLocation\"), \"string\", \"text\", lastLocation),\n ]);\n\n // v0.7.2: `lastUpdated` = \"when the tracking data last CHANGED\". Writing a\n // fresh timestamp every poll fired one guaranteed state event per package\n // per poll and defeated the skip-unchanged optimization for this state.\n const sig = JSON.stringify([\n carrierName,\n statusText,\n statusCode,\n description,\n trackingNumber,\n extraInfo,\n deliveryWindow,\n deliveryEstimate,\n lastEvent,\n lastLocation,\n ]);\n if (this.valuesSig.get(pkgId) !== sig) {\n this.valuesSig.set(pkgId, sig);\n await this.createAndSet(\n `${devicePath}.lastUpdated`,\n tName(\"lastUpdated\"),\n \"string\",\n \"date\",\n new Date().toISOString(),\n );\n }\n }\n\n /**\n * Update summary states. Expects already-filtered active deliveries.\n * The `summary` channel itself is declared via io-package.json instanceObjects.\n *\n * @param activeDeliveries Only active (non-delivered) deliveries\n */\n async updateSummary(activeDeliveries: ParcelDelivery[]): Promise<void> {\n const todayDeliveries = activeDeliveries.filter(d => this.isToday(d, this.parseStatus(d)));\n // v0.4.3 (E1): trace summary refresh \u2014 ~144/day at the default poll\n // interval, kept short (counts only).\n this.adapter.log.debug(\n `updateSummary: ${activeDeliveries.length} active, ${todayDeliveries.length} expected today`,\n );\n\n await Promise.all([\n this.createAndSet(\"summary.activeCount\", tName(\"activeCount\"), \"number\", \"value\", activeDeliveries.length),\n this.createAndSet(\"summary.todayCount\", tName(\"todayCount\"), \"number\", \"value\", todayDeliveries.length),\n this.createAndSet(\n \"summary.deliveryWindow\",\n tName(\"summaryDeliveryWindow\"),\n \"string\",\n \"text\",\n this.calculateCombinedWindow(todayDeliveries),\n ),\n ]);\n }\n\n /**\n * Remove deliveries that are no longer present in the API response.\n *\n * @param keepIds Package IDs the API still returns this poll (kept). Every\n * currently-known delivery NOT in this set is removed. The caller passes\n * ALL visible package ids, not only the ones whose state-write succeeded \u2014\n * a transient write failure must not delete a still-present package.\n */\n async cleanupDeliveries(keepIds: string[]): Promise<void> {\n // v0.7.2: the object view is queried only ONCE after adapter start to\n // reconcile leftovers from previous runs; afterwards the in-memory set\n // (maintained by updateDelivery + this prune) replaces the per-poll DB\n // round-trip.\n if (this.knownDeliveryIds === null) {\n const objects = await this.adapter.getObjectViewAsync(\"system\", \"device\", {\n startkey: `${this.adapter.namespace}.deliveries.`,\n endkey: `${this.adapter.namespace}.deliveries.${ID_RANGE_END}`,\n });\n if (!objects?.rows) {\n // v0.4.3 (E2): trace the no-op path \u2014 happens on fresh installs or\n // when getObjectViewAsync returns falsy. Without this the early-return\n // is invisible (and the known-set stays unseeded for the next poll).\n this.adapter.log.debug(\"cleanupDeliveries: no objects view available, skipping\");\n return;\n }\n this.knownDeliveryIds = new Set<string>();\n for (const row of objects.rows) {\n const relativeId = row.id.replace(`${this.adapter.namespace}.`, \"\");\n if (relativeId.startsWith(\"deliveries.\")) {\n // Direct device segment only (`deliveries.<pkgId>`).\n const pkgId = relativeId.slice(\"deliveries.\".length).split(\".\")[0];\n if (pkgId) {\n this.knownDeliveryIds.add(pkgId);\n }\n }\n }\n }\n\n const keepSet = new Set(keepIds);\n // v0.4.2 (S1): collect first, then delete in parallel. Earlier each\n // stale package took a sequential broker round-trip.\n const toDelete = [...this.knownDeliveryIds].filter(pkgId => !keepSet.has(pkgId));\n const toDeleteSet = new Set(toDelete);\n\n await Promise.all(\n toDelete.map(async pkgId => {\n const relativeId = `deliveries.${pkgId}`;\n await this.adapter.delObjectAsync(relativeId, { recursive: true });\n this.adapter.log.debug(`Removed stale delivery: ${relativeId}`);\n this.deviceWritten.delete(pkgId);\n this.valuesSig.delete(pkgId);\n }),\n );\n\n // v0.9.0 (S2): prune createdIds for every removed package in ONE pass over\n // the set. This used to be nested inside the delete-map above \u2014 O(deleted \u00D7\n // created) with a fresh `[...createdIds]` spread per deleted package; now it\n // is O(created). A createdId is `deliveries.<pkgId>` or\n // `deliveries.<pkgId>.<state>` \u2014 extract the pkgId and drop it if removed.\n if (toDeleteSet.size > 0) {\n for (const id of [...this.createdIds]) {\n const pkgId = id.startsWith(\"deliveries.\") ? id.slice(\"deliveries.\".length).split(\".\")[0] : \"\";\n if (toDeleteSet.has(pkgId)) {\n this.createdIds.delete(id);\n }\n }\n }\n this.knownDeliveryIds = new Set(keepSet);\n }\n\n /**\n * Parse a parcel.app expected-date string to LOCAL epoch-millis.\n *\n * The API delivers `date_expected`/`date_expected_end` \"without specific\n * timezone information\"; parse with explicit local calendar components so the\n * value lands on the intended local day/time (`new Date(\"YYYY-MM-DD\")` would\n * be UTC midnight). `hasTime` is false for a bare date or a midnight time\n * (a day, not an hour-window). Ambiguous carrier formats (dotted, weekday\n * names) are deliberately NOT guessed \u2014 they return null rather than risk a\n * wrong date.\n *\n * @param value Raw date/time string from the API\n */\n private static parseExpectedToMs(value: unknown): { ms: number; hasTime: boolean } | null {\n if (typeof value !== \"string\") {\n return null;\n }\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})(?:[ T](\\d{2}):(\\d{2})(?::(\\d{2}))?)?$/.exec(value.trim());\n if (!m) {\n return null;\n }\n const hasClock = m[4] !== undefined;\n const year = Number(m[1]);\n const month = Number(m[2]); // 1-12\n const day = Number(m[3]);\n const hour = hasClock ? Number(m[4]) : 0;\n const min = hasClock ? Number(m[5]) : 0;\n const sec = m[6] !== undefined ? Number(m[6]) : 0;\n // Range-validate the components. The regex only checks digit COUNT, not\n // value range, and `new Date(2026, 12, 40, 25, \u2026)` silently ROLLS OVER to a\n // wrong date (getTime() is NOT NaN). Reject out-of-range rather than guess.\n if (month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || min > 59 || sec > 59) {\n return null;\n }\n const date = new Date(year, month - 1, day, hour, min, sec);\n // Catch day-of-month overflow the range check misses (Feb 30, Apr 31, \u2026):\n // a real date round-trips the month and day it was built from.\n if (Number.isNaN(date.getTime()) || date.getMonth() !== month - 1 || date.getDate() !== day) {\n return null;\n }\n const hasTime = hasClock && !(hour === 0 && min === 0 && sec === 0);\n return { ms: date.getTime(), hasTime };\n }\n\n /**\n * Resolve a delivery's expected window to epoch-millis bounds. Returns null\n * for non-trackable status or when there is no usable start time.\n *\n * Prefers the Unix timestamp fields; for carriers that report the window only\n * as a date/time string (`date_expected`/`date_expected_end`) it falls back to\n * those \u2014 but only when the string carries a real time-of-day (a bare date or\n * midnight is a day, not an hour-window). Carrier-agnostic.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private windowBoundsMs(delivery: ParcelDelivery, statusCode: number): { start: number; end: number | null } | null {\n if (!TRACKABLE_STATUSES.has(statusCode)) {\n return null;\n }\n const toMs = (timestamp: unknown): number | null => {\n const ts = coerceFiniteNumber(timestamp);\n if (ts === null || ts <= 0) {\n return null;\n }\n const ms = ts * 1000;\n return Number.isNaN(new Date(ms).getTime()) ? null : ms;\n };\n const dateMs = (value: unknown): number | null => {\n const parsed = StateManager.parseExpectedToMs(value);\n return parsed && parsed.hasTime ? parsed.ms : null;\n };\n const start = toMs(delivery.timestamp_expected) ?? dateMs(delivery.date_expected);\n if (start === null) {\n return null;\n }\n const end = toMs(delivery.timestamp_expected_end) ?? dateMs(delivery.date_expected_end);\n return { start, end };\n }\n\n /**\n * Format epoch-millis as local HH:MM.\n *\n * @param ms Epoch milliseconds\n */\n private static formatHHMM(ms: number): string {\n const d = new Date(ms);\n return `${d.getHours().toString().padStart(2, \"0\")}:${d.getMinutes().toString().padStart(2, \"0\")}`;\n }\n\n /**\n * Local \"MM-DD HH:MM\" \u2014 used when a window spans more than one calendar day.\n *\n * @param ms Epoch milliseconds\n */\n private static formatDateHHMM(ms: number): string {\n const d = new Date(ms);\n const mm = (d.getMonth() + 1).toString().padStart(2, \"0\");\n const dd = d.getDate().toString().padStart(2, \"0\");\n return `${mm}-${dd} ${StateManager.formatHHMM(ms)}`;\n }\n\n /**\n * Whether two epoch-millis fall on the same LOCAL calendar day.\n *\n * @param aMs First epoch milliseconds\n * @param bMs Second epoch milliseconds\n */\n private static sameLocalDay(aMs: number, bMs: number): boolean {\n const a = new Date(aMs);\n const b = new Date(bMs);\n return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();\n }\n\n /**\n * Format a start\u2192end window as a local string. A real end (> start) on the\n * SAME day renders \"HH:MM - HH:MM\"; an end on a LATER day carries the date on\n * both sides (\"12-06 14:30 - 12-08 18:30\") so a multi-day window is not shown\n * as if it were same-day. No end, or an end <= start (reversed/equal), renders\n * just the start.\n *\n * @param startMs Window start (epoch ms)\n * @param endMs Window end (epoch ms) or null\n */\n private static formatWindow(startMs: number, endMs: number | null): string {\n if (endMs === null || endMs <= startMs) {\n return StateManager.formatHHMM(startMs);\n }\n return StateManager.sameLocalDay(startMs, endMs)\n ? `${StateManager.formatHHMM(startMs)} - ${StateManager.formatHHMM(endMs)}`\n : `${StateManager.formatDateHHMM(startMs)} - ${StateManager.formatDateHHMM(endMs)}`;\n }\n\n /**\n * Calculate a delivery time-window string from the resolved expected bounds.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private calculateDeliveryWindow(delivery: ParcelDelivery, statusCode: number): string {\n const bounds = this.windowBoundsMs(delivery, statusCode);\n if (!bounds) {\n return \"\";\n }\n return StateManager.formatWindow(bounds.start, bounds.end);\n }\n\n /**\n * Days from today to the expected delivery date. Returns null when the\n * delivery has no usable expected date or is in a non-trackable status.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private computeDiffDays(delivery: ParcelDelivery, statusCode: number): number | null {\n if (!TRACKABLE_STATUSES.has(statusCode)) {\n return null;\n }\n\n let expectedDate: Date | null = null;\n const ts = coerceFiniteNumber(delivery.timestamp_expected);\n if (ts !== null && ts > 0) {\n expectedDate = new Date(ts * 1000);\n } else {\n // Shares the window's date parser (one source of format-truth). Only the\n // calendar day matters here, so the time-of-day flag is ignored; the\n // local-component parse keeps the day timezone-stable.\n const parsed = StateManager.parseExpectedToMs(delivery.date_expected);\n expectedDate = parsed ? new Date(parsed.ms) : null;\n }\n\n if (!expectedDate || isNaN(expectedDate.getTime())) {\n return null;\n }\n\n const now = new Date();\n const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n const expectedStart = new Date(expectedDate.getFullYear(), expectedDate.getMonth(), expectedDate.getDate());\n return Math.round((expectedStart.getTime() - todayStart.getTime()) / (1000 * 60 * 60 * 24));\n }\n\n /**\n * Calculate human-readable delivery estimate.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private calculateDeliveryEstimate(delivery: ParcelDelivery, statusCode: number): string {\n const diffDays = this.computeDiffDays(delivery, statusCode);\n if (diffDays === null) {\n return \"\";\n }\n if (diffDays < 0) {\n return I18n.translate(\"estimateOverdue\");\n }\n if (diffDays === 0) {\n return I18n.translate(\"estimateToday\");\n }\n if (diffDays === 1) {\n return I18n.translate(\"estimateTomorrow\");\n }\n return I18n.translate(\"estimateDays\").replace(\"%d\", String(diffDays));\n }\n\n /**\n * Whether the delivery is expected today. Language-agnostic, used by the\n * summary filter so `todayCount` works across all languages.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private isToday(delivery: ParcelDelivery, statusCode: number): boolean {\n return this.computeDiffDays(delivery, statusCode) === 0;\n }\n\n private getLatestEvent(delivery: ParcelDelivery): ParcelEvent | null {\n if (!Array.isArray(delivery.events) || delivery.events.length === 0) {\n return null;\n }\n const latest = delivery.events[0];\n if (!latest || typeof latest !== \"object\") {\n return null;\n }\n return latest;\n }\n\n private formatLastEvent(delivery: ParcelDelivery): string {\n const latest = this.getLatestEvent(delivery);\n if (!latest) {\n return \"\";\n }\n const parts: string[] = [];\n if (typeof latest.event === \"string\" && latest.event.length > 0) {\n parts.push(latest.event);\n }\n if (typeof latest.date === \"string\" && latest.date.length > 0) {\n parts.push(latest.date);\n }\n return parts.join(\" - \");\n }\n\n private extractLastLocation(delivery: ParcelDelivery): string {\n const latest = this.getLatestEvent(delivery);\n if (!latest) {\n return \"\";\n }\n return typeof latest.location === \"string\" ? latest.location : \"\";\n }\n\n /**\n * Combined delivery window for today's packages: earliest start to latest\n * end across all windows. Computed from the raw millis (not the formatted\n * strings) so the latest end always wins \u2014 fixes the earlier bug where the\n * end of the latest-*starting* window was used instead of the maximum end.\n *\n * @param todayDeliveries Deliveries expected today\n */\n private calculateCombinedWindow(todayDeliveries: ParcelDelivery[]): string {\n const bounds = todayDeliveries\n .map(d => this.windowBoundsMs(d, this.parseStatus(d)))\n .filter((b): b is { start: number; end: number | null } => b !== null);\n\n if (bounds.length === 0) {\n return \"\";\n }\n\n const minStart = Math.min(...bounds.map(b => b.start));\n const maxEnd = Math.max(...bounds.map(b => b.end ?? b.start));\n return StateManager.formatWindow(minStart, maxEnd);\n }\n\n /**\n * Create/extend a read-only state and set its value. Skips the\n * `setObjectNotExistsAsync` round-trip once the ID is in the cache \u2014\n * states are static after first creation; only the value changes per poll.\n *\n * @param id State ID relative to adapter namespace\n * @param name Display name (translation object or plain string)\n * @param type Value type\n * @param role ioBroker role\n * @param val Value to set\n */\n private async createAndSet(\n id: string,\n name: ioBroker.StringOrTranslated,\n type: ioBroker.CommonType,\n role: string,\n val: ioBroker.StateValue,\n ): Promise<void> {\n if (!this.createdIds.has(id)) {\n await this.adapter.setObjectNotExistsAsync(id, {\n type: \"state\",\n common: { name, type, role, read: true, write: false },\n native: {},\n });\n this.createdIds.add(id);\n }\n await this.adapter.setStateChangedAsync(id, { val, ack: true });\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/types.ts"],
4
- "sourcesContent": ["/** API response from parcel.app deliveries endpoint */\nexport interface ParcelApiResponse {\n /** Whether the request was successful */\n success: boolean;\n /** Error message if request failed */\n error_message?: string;\n /** List of deliveries */\n deliveries?: ParcelDelivery[];\n}\n\n/** Single delivery from the parcel.app API */\nexport interface ParcelDelivery {\n /** Carrier identifier */\n carrier_code: string;\n /** User-defined description */\n description: string;\n /** Status code (int 0-8; API sends a number \u2014 parseStatus also tolerates a numeric string for drift safety) */\n status_code: number;\n /** Tracking number */\n tracking_number: string;\n /** Extra info (postal code, email) */\n extra_information?: string;\n /** Expected delivery date/time as a string, without timezone (carrier-dependent format) */\n date_expected?: string;\n /** End of the delivery window as a date/time string (present when the carrier reports a range) */\n date_expected_end?: string;\n /** Expected delivery Unix timestamp (only when the carrier provides full date/time/timezone) */\n timestamp_expected?: number;\n /** Expected delivery end Unix timestamp */\n timestamp_expected_end?: number;\n /** Tracking events, newest first (confirmed across multiple parcel.app clients) */\n events?: ParcelEvent[];\n}\n\n/** Single tracking event */\nexport interface ParcelEvent {\n /** Event description */\n event: string;\n /** Event date string */\n date: string;\n /** Event location */\n location?: string;\n}\n\n/** Request body for adding a delivery */\nexport interface AddDeliveryRequest {\n /** Tracking number to add */\n tracking_number: string;\n /** Carrier code */\n carrier_code: string;\n /** User description */\n description: string;\n /** Tracking language */\n language?: string;\n /** Send push confirmation */\n send_push_confirmation?: boolean;\n}\n\n/** Add delivery API response */\nexport interface AddDeliveryResponse {\n /** Whether the delivery was added */\n success: boolean;\n /** Error message if failed */\n error_message?: string;\n}\n\n/** Carrier names mapping (carrier_code \u2192 display name) */\nexport type CarrierMap = Record<string, string>;\n\n/** Delivery status labels for status codes 0-8, keyed by ioBroker language code */\nexport const STATUS_LABELS: Record<string, Record<number, string>> = {\n de: {\n 0: \"Zugestellt\",\n 1: \"Eingefroren\",\n 2: \"Unterwegs\",\n 3: \"Abholung erwartet\",\n 4: \"In Zustellung\",\n 5: \"Nicht gefunden\",\n 6: \"Zustellversuch gescheitert\",\n 7: \"Ausnahme\",\n 8: \"Registriert\",\n },\n en: {\n 0: \"Delivered\",\n 1: \"Frozen\",\n 2: \"In Transit\",\n 3: \"Awaiting Pickup\",\n 4: \"Out for Delivery\",\n 5: \"Not Found\",\n 6: \"Delivery Attempt Failed\",\n 7: \"Exception\",\n 8: \"Info Received\",\n },\n ru: {\n 0: \"\u0414\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043E\",\n 1: \"\u0417\u0430\u043C\u043E\u0440\u043E\u0436\u0435\u043D\u043E\",\n 2: \"\u0412 \u043F\u0443\u0442\u0438\",\n 3: \"\u041E\u0436\u0438\u0434\u0430\u0435\u0442 \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u0438\u044F\",\n 4: \"\u0414\u043E\u0441\u0442\u0430\u0432\u043B\u044F\u0435\u0442\u0441\u044F\",\n 5: \"\u041D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E\",\n 6: \"\u041D\u0435\u0443\u0434\u0430\u0447\u043D\u0430\u044F \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0430\",\n 7: \"\u0418\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435\",\n 8: \"\u0417\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043E\",\n },\n pt: {\n 0: \"Entregue\",\n 1: \"Congelado\",\n 2: \"Em tr\u00E2nsito\",\n 3: \"Aguardando recolha\",\n 4: \"Em entrega\",\n 5: \"N\u00E3o encontrado\",\n 6: \"Tentativa de entrega falhou\",\n 7: \"Exce\u00E7\u00E3o\",\n 8: \"Registado\",\n },\n nl: {\n 0: \"Bezorgd\",\n 1: \"Bevroren\",\n 2: \"Onderweg\",\n 3: \"Wacht op ophaling\",\n 4: \"Wordt bezorgd\",\n 5: \"Niet gevonden\",\n 6: \"Bezorgpoging mislukt\",\n 7: \"Uitzondering\",\n 8: \"Geregistreerd\",\n },\n fr: {\n 0: \"Livr\u00E9\",\n 1: \"Gel\u00E9\",\n 2: \"En transit\",\n 3: \"En attente de retrait\",\n 4: \"En cours de livraison\",\n 5: \"Introuvable\",\n 6: \"\u00C9chec de la livraison\",\n 7: \"Exception\",\n 8: \"Enregistr\u00E9\",\n },\n it: {\n 0: \"Consegnato\",\n 1: \"Congelato\",\n 2: \"In transito\",\n 3: \"In attesa di ritiro\",\n 4: \"In consegna\",\n 5: \"Non trovato\",\n 6: \"Consegna fallita\",\n 7: \"Eccezione\",\n 8: \"Registrato\",\n },\n es: {\n 0: \"Entregado\",\n 1: \"Congelado\",\n 2: \"En tr\u00E1nsito\",\n 3: \"Esperando recogida\",\n 4: \"En reparto\",\n 5: \"No encontrado\",\n 6: \"Intento de entrega fallido\",\n 7: \"Excepci\u00F3n\",\n 8: \"Registrado\",\n },\n pl: {\n 0: \"Dostarczone\",\n 1: \"Zamro\u017Cone\",\n 2: \"W drodze\",\n 3: \"Oczekuje na odbi\u00F3r\",\n 4: \"W dor\u0119czeniu\",\n 5: \"Nie znaleziono\",\n 6: \"Nieudana pr\u00F3ba dor\u0119czenia\",\n 7: \"Wyj\u0105tek\",\n 8: \"Zarejestrowane\",\n },\n uk: {\n 0: \"\u0414\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043E\",\n 1: \"\u0417\u0430\u043C\u043E\u0440\u043E\u0436\u0435\u043D\u043E\",\n 2: \"\u0412 \u0434\u043E\u0440\u043E\u0437\u0456\",\n 3: \"\u041E\u0447\u0456\u043A\u0443\u0454 \u043E\u0442\u0440\u0438\u043C\u0430\u043D\u043D\u044F\",\n 4: \"\u0414\u043E\u0441\u0442\u0430\u0432\u043B\u044F\u0454\u0442\u044C\u0441\u044F\",\n 5: \"\u041D\u0435 \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E\",\n 6: \"\u041D\u0435\u0432\u0434\u0430\u043B\u0430 \u0441\u043F\u0440\u043E\u0431\u0430 \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0438\",\n 7: \"\u0412\u0438\u043D\u044F\u0442\u043E\u043A\",\n 8: \"\u0417\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u043E\u0432\u0430\u043D\u043E\",\n },\n \"zh-cn\": {\n 0: \"\u5DF2\u9001\u8FBE\",\n 1: \"\u5DF2\u51BB\u7ED3\",\n 2: \"\u8FD0\u8F93\u4E2D\",\n 3: \"\u7B49\u5F85\u53D6\u4EF6\",\n 4: \"\u6D3E\u9001\u4E2D\",\n 5: \"\u672A\u627E\u5230\",\n 6: \"\u6D3E\u9001\u5931\u8D25\",\n 7: \"\u5F02\u5E38\",\n 8: \"\u5DF2\u767B\u8BB0\",\n },\n};\n\n/** Language codes the adapter generates state labels for */\nexport const SUPPORTED_LANGUAGES = Object.keys(STATUS_LABELS);\n\n/** Fallback language used when system.config.language is outside SUPPORTED_LANGUAGES */\nexport const FALLBACK_LANGUAGE = \"en\";\n\n/**\n * Sentinel status code for unparseable API drift. Distinct from 0 (Delivered)\n * so a garbage `status_code` keeps the package visible (the active filter is\n * `status !== 0`) and renders as \"Unknown\" instead of silently dropping it.\n */\nexport const UNKNOWN_STATUS_CODE = -1;\n\n// Augment the ioBroker global namespace\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace ioBroker {\n interface AdapterConfig {\n /** parcel.app API key */\n apiKey: string;\n /** Polling interval in minutes */\n pollInterval: number;\n /** Automatically remove delivered packages from states */\n autoRemoveDelivered: boolean;\n }\n }\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsEO,MAAM,gBAAwD;AAAA,EACnE,IAAI;AAAA,IACF,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAAA,EACA,IAAI;AAAA,IACF,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAAA,EACA,IAAI;AAAA,IACF,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAAA,EACA,IAAI;AAAA,IACF,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAAA,EACA,IAAI;AAAA,IACF,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAAA,EACA,IAAI;AAAA,IACF,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAAA,EACA,IAAI;AAAA,IACF,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAAA,EACA,IAAI;AAAA,IACF,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAAA,EACA,IAAI;AAAA,IACF,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAAA,EACA,IAAI;AAAA,IACF,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAAA,EACA,SAAS;AAAA,IACP,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AACF;AAGO,MAAM,sBAAsB,OAAO,KAAK,aAAa;AAGrD,MAAM,oBAAoB;AAO1B,MAAM,sBAAsB;",
4
+ "sourcesContent": ["/** API response from parcel.app deliveries endpoint */\nexport interface ParcelApiResponse {\n /** Whether the request was successful */\n success: boolean;\n /** Error message if request failed */\n error_message?: string;\n /** List of deliveries */\n deliveries?: ParcelDelivery[];\n}\n\n/** Single delivery from the parcel.app API */\nexport interface ParcelDelivery {\n /** Carrier identifier (optional: the API normally always sends it, but the adapter guards against drift) */\n carrier_code?: string;\n /** User-defined description (optional: guarded against drift) */\n description?: string;\n /**\n * Status code (int 0-8). The API sends a number; the type also admits a\n * numeric string, which `parseStatus` tolerates for drift safety. Widening\n * the type here lets `parseStatus` narrow with plain `typeof` guards instead\n * of an `as unknown` cast that hid the real runtime shape.\n */\n status_code: number | string;\n /** Tracking number (optional: guarded against drift) */\n tracking_number?: string;\n /** Extra info (postal code, email) */\n extra_information?: string;\n /** Expected delivery date/time as a string, without timezone (carrier-dependent format) */\n date_expected?: string;\n /** End of the delivery window as a date/time string (present when the carrier reports a range) */\n date_expected_end?: string;\n /** Expected delivery Unix timestamp (only when the carrier provides full date/time/timezone) */\n timestamp_expected?: number;\n /** Expected delivery end Unix timestamp */\n timestamp_expected_end?: number;\n /** Tracking events, newest first (confirmed across multiple parcel.app clients) */\n events?: ParcelEvent[];\n}\n\n/** Single tracking event */\nexport interface ParcelEvent {\n /** Event description */\n event: string;\n /** Event date string */\n date: string;\n /** Event location */\n location?: string;\n}\n\n/** Request body for adding a delivery */\nexport interface AddDeliveryRequest {\n /** Tracking number to add */\n tracking_number: string;\n /** Carrier code */\n carrier_code: string;\n /** User description */\n description: string;\n /** Tracking language */\n language?: string;\n /** Send push confirmation */\n send_push_confirmation?: boolean;\n}\n\n/** Add delivery API response */\nexport interface AddDeliveryResponse {\n /** Whether the delivery was added */\n success: boolean;\n /** Error message if failed */\n error_message?: string;\n}\n\n/** Carrier names mapping (carrier_code \u2192 display name) */\nexport type CarrierMap = Record<string, string>;\n\n/** Delivery status labels for status codes 0-8, keyed by ioBroker language code */\nexport const STATUS_LABELS: Record<string, Record<number, string>> = {\n de: {\n 0: \"Zugestellt\",\n 1: \"Eingefroren\",\n 2: \"Unterwegs\",\n 3: \"Abholung erwartet\",\n 4: \"In Zustellung\",\n 5: \"Nicht gefunden\",\n 6: \"Zustellversuch gescheitert\",\n 7: \"Ausnahme\",\n 8: \"Registriert\",\n },\n en: {\n 0: \"Delivered\",\n 1: \"Frozen\",\n 2: \"In Transit\",\n 3: \"Awaiting Pickup\",\n 4: \"Out for Delivery\",\n 5: \"Not Found\",\n 6: \"Delivery Attempt Failed\",\n 7: \"Exception\",\n 8: \"Info Received\",\n },\n ru: {\n 0: \"\u0414\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043E\",\n 1: \"\u0417\u0430\u043C\u043E\u0440\u043E\u0436\u0435\u043D\u043E\",\n 2: \"\u0412 \u043F\u0443\u0442\u0438\",\n 3: \"\u041E\u0436\u0438\u0434\u0430\u0435\u0442 \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u0438\u044F\",\n 4: \"\u0414\u043E\u0441\u0442\u0430\u0432\u043B\u044F\u0435\u0442\u0441\u044F\",\n 5: \"\u041D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E\",\n 6: \"\u041D\u0435\u0443\u0434\u0430\u0447\u043D\u0430\u044F \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0430\",\n 7: \"\u0418\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435\",\n 8: \"\u0417\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043E\",\n },\n pt: {\n 0: \"Entregue\",\n 1: \"Congelado\",\n 2: \"Em tr\u00E2nsito\",\n 3: \"Aguardando recolha\",\n 4: \"Em entrega\",\n 5: \"N\u00E3o encontrado\",\n 6: \"Tentativa de entrega falhou\",\n 7: \"Exce\u00E7\u00E3o\",\n 8: \"Registado\",\n },\n nl: {\n 0: \"Bezorgd\",\n 1: \"Bevroren\",\n 2: \"Onderweg\",\n 3: \"Wacht op ophaling\",\n 4: \"Wordt bezorgd\",\n 5: \"Niet gevonden\",\n 6: \"Bezorgpoging mislukt\",\n 7: \"Uitzondering\",\n 8: \"Geregistreerd\",\n },\n fr: {\n 0: \"Livr\u00E9\",\n 1: \"Gel\u00E9\",\n 2: \"En transit\",\n 3: \"En attente de retrait\",\n 4: \"En cours de livraison\",\n 5: \"Introuvable\",\n 6: \"\u00C9chec de la livraison\",\n 7: \"Exception\",\n 8: \"Enregistr\u00E9\",\n },\n it: {\n 0: \"Consegnato\",\n 1: \"Congelato\",\n 2: \"In transito\",\n 3: \"In attesa di ritiro\",\n 4: \"In consegna\",\n 5: \"Non trovato\",\n 6: \"Consegna fallita\",\n 7: \"Eccezione\",\n 8: \"Registrato\",\n },\n es: {\n 0: \"Entregado\",\n 1: \"Congelado\",\n 2: \"En tr\u00E1nsito\",\n 3: \"Esperando recogida\",\n 4: \"En reparto\",\n 5: \"No encontrado\",\n 6: \"Intento de entrega fallido\",\n 7: \"Excepci\u00F3n\",\n 8: \"Registrado\",\n },\n pl: {\n 0: \"Dostarczone\",\n 1: \"Zamro\u017Cone\",\n 2: \"W drodze\",\n 3: \"Oczekuje na odbi\u00F3r\",\n 4: \"W dor\u0119czeniu\",\n 5: \"Nie znaleziono\",\n 6: \"Nieudana pr\u00F3ba dor\u0119czenia\",\n 7: \"Wyj\u0105tek\",\n 8: \"Zarejestrowane\",\n },\n uk: {\n 0: \"\u0414\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043E\",\n 1: \"\u0417\u0430\u043C\u043E\u0440\u043E\u0436\u0435\u043D\u043E\",\n 2: \"\u0412 \u0434\u043E\u0440\u043E\u0437\u0456\",\n 3: \"\u041E\u0447\u0456\u043A\u0443\u0454 \u043E\u0442\u0440\u0438\u043C\u0430\u043D\u043D\u044F\",\n 4: \"\u0414\u043E\u0441\u0442\u0430\u0432\u043B\u044F\u0454\u0442\u044C\u0441\u044F\",\n 5: \"\u041D\u0435 \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E\",\n 6: \"\u041D\u0435\u0432\u0434\u0430\u043B\u0430 \u0441\u043F\u0440\u043E\u0431\u0430 \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0438\",\n 7: \"\u0412\u0438\u043D\u044F\u0442\u043E\u043A\",\n 8: \"\u0417\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u043E\u0432\u0430\u043D\u043E\",\n },\n \"zh-cn\": {\n 0: \"\u5DF2\u9001\u8FBE\",\n 1: \"\u5DF2\u51BB\u7ED3\",\n 2: \"\u8FD0\u8F93\u4E2D\",\n 3: \"\u7B49\u5F85\u53D6\u4EF6\",\n 4: \"\u6D3E\u9001\u4E2D\",\n 5: \"\u672A\u627E\u5230\",\n 6: \"\u6D3E\u9001\u5931\u8D25\",\n 7: \"\u5F02\u5E38\",\n 8: \"\u5DF2\u767B\u8BB0\",\n },\n};\n\n/** Language codes the adapter generates state labels for */\nexport const SUPPORTED_LANGUAGES = Object.keys(STATUS_LABELS);\n\n/** Fallback language used when system.config.language is outside SUPPORTED_LANGUAGES */\nexport const FALLBACK_LANGUAGE = \"en\";\n\n/**\n * Sentinel status code for unparseable API drift. Distinct from 0 (Delivered)\n * so a garbage `status_code` keeps the package visible (the active filter is\n * `status !== 0`) and renders as \"Unknown\" instead of silently dropping it.\n */\nexport const UNKNOWN_STATUS_CODE = -1;\n\n// Augment the ioBroker global namespace\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace ioBroker {\n interface AdapterConfig {\n /** parcel.app API key */\n apiKey: string;\n /** Polling interval in minutes */\n pollInterval: number;\n /** Automatically remove delivered packages from states */\n autoRemoveDelivered: boolean;\n }\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA2EO,MAAM,gBAAwD;AAAA,EACnE,IAAI;AAAA,IACF,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAAA,EACA,IAAI;AAAA,IACF,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAAA,EACA,IAAI;AAAA,IACF,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAAA,EACA,IAAI;AAAA,IACF,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAAA,EACA,IAAI;AAAA,IACF,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAAA,EACA,IAAI;AAAA,IACF,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAAA,EACA,IAAI;AAAA,IACF,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAAA,EACA,IAAI;AAAA,IACF,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAAA,EACA,IAAI;AAAA,IACF,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAAA,EACA,IAAI;AAAA,IACF,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAAA,EACA,SAAS;AAAA,IACP,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AACF;AAGO,MAAM,sBAAsB,OAAO,KAAK,aAAa;AAGrD,MAAM,oBAAoB;AAO1B,MAAM,sBAAsB;",
6
6
  "names": []
7
7
  }
package/build/main.js CHANGED
@@ -42,6 +42,10 @@ const MAX_POLL_INTERVAL = 60;
42
42
  const DEFAULT_POLL_INTERVAL = 10;
43
43
  const MIN_POLL_GAP_MS = 6e4;
44
44
  const MIN_API_KEY_LENGTH = 10;
45
+ const MAX_ADD_FIELD_LEN = 512;
46
+ const UPDATE_BATCH_SIZE = 25;
47
+ const MAX_ADDS_PER_WINDOW = 20;
48
+ const ADD_WINDOW_MS = 6e4;
45
49
  class ParcelappAdapter extends utils.Adapter {
46
50
  client = null;
47
51
  stateManager = null;
@@ -61,7 +65,14 @@ class ParcelappAdapter extends utils.Adapter {
61
65
  lastPollTime = 0;
62
66
  rateLimitedUntil = 0;
63
67
  lastErrorCode = "";
68
+ /**
69
+ * Package ids (not raw tracking numbers) whose last updateDelivery failed.
70
+ * Keyed like the states so the dedup survives a sanitize collision or a
71
+ * missing tracking number; pruned each poll against the visible pkgIds.
72
+ */
64
73
  failedDeliveries = /* @__PURE__ */ new Set();
74
+ /** Timestamps of recent addDelivery POSTs — the S4 throttle window. */
75
+ addTimestamps = [];
65
76
  /**
66
77
  * v0.4.4: short-lived test-clients spawned from `checkConnection` admin
67
78
  * messages. The prod-`this.client` is what `onUnload` cancels, so these
@@ -98,7 +109,11 @@ class ParcelappAdapter extends utils.Adapter {
98
109
  }
99
110
  this.client = this.makeClient(apiKey.trim());
100
111
  this.stateManager = this.makeStateManager(language);
101
- await this.cleanupObsoleteStates();
112
+ try {
113
+ await this.cleanupObsoleteStates();
114
+ } catch (err) {
115
+ this.log.warn(`cleanupObsoleteStates failed (continuing): ${(0, import_coerce.errText)(err)}`);
116
+ }
102
117
  await this.poll();
103
118
  const interval = ParcelappAdapter.coercePollInterval(this.config.pollInterval);
104
119
  this.log.debug(`pollInterval: raw=${JSON.stringify(this.config.pollInterval)} resolved=${interval}min`);
@@ -188,6 +203,16 @@ class ParcelappAdapter extends utils.Adapter {
188
203
  );
189
204
  return;
190
205
  }
206
+ if (msg.tracking_number.length > MAX_ADD_FIELD_LEN || msg.carrier_code.length > MAX_ADD_FIELD_LEN || msg.description.length > MAX_ADD_FIELD_LEN || typeof msg.language === "string" && msg.language.length > MAX_ADD_FIELD_LEN) {
207
+ this.log.debug("addDelivery: a field exceeds the maximum length");
208
+ this.sendTo(
209
+ obj.from,
210
+ obj.command,
211
+ { success: false, error_message: `each field must be at most ${MAX_ADD_FIELD_LEN} characters` },
212
+ obj.callback
213
+ );
214
+ return;
215
+ }
191
216
  const request = {
192
217
  tracking_number: msg.tracking_number,
193
218
  carrier_code: msg.carrier_code,
@@ -199,6 +224,24 @@ class ParcelappAdapter extends utils.Adapter {
199
224
  if (typeof msg.send_push_confirmation === "boolean") {
200
225
  request.send_push_confirmation = msg.send_push_confirmation;
201
226
  }
227
+ const nowMs = Date.now();
228
+ this.addTimestamps = this.addTimestamps.filter((t) => nowMs - t < ADD_WINDOW_MS);
229
+ if (this.addTimestamps.length >= MAX_ADDS_PER_WINDOW) {
230
+ this.log.warn(
231
+ `addDelivery throttled: more than ${MAX_ADDS_PER_WINDOW} requests within ${ADD_WINDOW_MS / 1e3}s`
232
+ );
233
+ this.sendTo(
234
+ obj.from,
235
+ obj.command,
236
+ {
237
+ success: false,
238
+ error_message: `too many addDelivery requests; max ${MAX_ADDS_PER_WINDOW} per ${ADD_WINDOW_MS / 1e3}s`
239
+ },
240
+ obj.callback
241
+ );
242
+ return;
243
+ }
244
+ this.addTimestamps.push(nowMs);
202
245
  const addResult = await this.client.addDelivery(request);
203
246
  this.log.debug(`addDelivery: '${request.tracking_number}' result=${addResult.success ? "ok" : "fail"}`);
204
247
  this.sendTo(obj.from, obj.command, addResult, obj.callback);
@@ -285,36 +328,40 @@ class ParcelappAdapter extends utils.Adapter {
285
328
  const visibleDeliveries = autoRemoveMode ? activeDeliveries : deliveries;
286
329
  this.stateManager.resetPollState();
287
330
  const pkgIds = visibleDeliveries.map((d) => this.stateManager.packageId(d));
288
- const idResults = await Promise.all(
289
- visibleDeliveries.map(async (delivery, index) => {
290
- const pkgId = pkgIds[index];
291
- try {
292
- this.log.debug(
293
- `updateDelivery: '${delivery.tracking_number}' carrier=${delivery.carrier_code} status=${delivery.status_code}`
294
- );
295
- const carrierName = await this.client.getCarrierName(delivery.carrier_code);
296
- await this.stateManager.updateDelivery(delivery, carrierName, pkgId);
297
- this.failedDeliveries.delete(delivery.tracking_number);
298
- return pkgId;
299
- } catch (err) {
300
- const msg = (0, import_coerce.errText)(err);
301
- if (this.failedDeliveries.has(delivery.tracking_number)) {
302
- this.log.debug(`Failed to update "${delivery.tracking_number}": ${msg}`);
303
- } else {
304
- this.log.warn(`Failed to update '${delivery.tracking_number}': ${msg}`);
305
- this.failedDeliveries.add(delivery.tracking_number);
331
+ if (visibleDeliveries.length > UPDATE_BATCH_SIZE) {
332
+ this.log.debug(`Updating ${visibleDeliveries.length} deliveries in batches of ${UPDATE_BATCH_SIZE}`);
333
+ }
334
+ for (let start = 0; start < visibleDeliveries.length; start += UPDATE_BATCH_SIZE) {
335
+ const batch = visibleDeliveries.slice(start, start + UPDATE_BATCH_SIZE);
336
+ await Promise.all(
337
+ batch.map(async (delivery, offset) => {
338
+ var _a2, _b;
339
+ const pkgId = pkgIds[start + offset];
340
+ const tracking = (0, import_coerce.oneLine)((_a2 = delivery.tracking_number) != null ? _a2 : "");
341
+ const carrier = (0, import_coerce.oneLine)((_b = delivery.carrier_code) != null ? _b : "");
342
+ try {
343
+ this.log.debug(`updateDelivery: '${tracking}' carrier=${carrier} status=${delivery.status_code}`);
344
+ const carrierName = await this.client.getCarrierName(delivery.carrier_code);
345
+ await this.stateManager.updateDelivery(delivery, carrierName, pkgId);
346
+ this.failedDeliveries.delete(pkgId);
347
+ } catch (err) {
348
+ const msg = (0, import_coerce.errText)(err);
349
+ if (this.failedDeliveries.has(pkgId)) {
350
+ this.log.debug(`Failed to update '${tracking}': ${msg}`);
351
+ } else {
352
+ this.log.warn(`Failed to update '${tracking}': ${msg}`);
353
+ this.failedDeliveries.add(pkgId);
354
+ }
306
355
  }
307
- return null;
308
- }
309
- })
310
- );
311
- const activeIds = idResults.filter((id) => id !== null);
312
- await this.stateManager.cleanupDeliveries(activeIds);
356
+ })
357
+ );
358
+ }
359
+ await this.stateManager.cleanupDeliveries(pkgIds);
313
360
  await this.stateManager.updateSummary(activeDeliveries);
314
- const seenTracking = new Set(visibleDeliveries.map((d) => d.tracking_number));
315
- for (const tracking of [...this.failedDeliveries]) {
316
- if (!seenTracking.has(tracking)) {
317
- this.failedDeliveries.delete(tracking);
361
+ const seenPkgIds = new Set(pkgIds);
362
+ for (const id of [...this.failedDeliveries]) {
363
+ if (!seenPkgIds.has(id)) {
364
+ this.failedDeliveries.delete(id);
318
365
  }
319
366
  }
320
367
  this.log.debug(`Polled ${visibleDeliveries.length} deliveries (${activeDeliveries.length} active)`);
package/build/main.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/main.ts"],
4
- "sourcesContent": ["import * as utils from \"@iobroker/adapter-core\";\nimport { I18n } from \"@iobroker/adapter-core\";\nimport { join } from \"node:path\";\nimport { coerceClampedInt, errText } from \"./lib/coerce\";\nimport { ParcelClient } from \"./lib/parcel-client\";\nimport { resolveLanguage, StateManager } from \"./lib/state-manager\";\nimport type { AddDeliveryRequest } from \"./lib/types\";\n\nconst MIN_POLL_INTERVAL = 5;\nconst MAX_POLL_INTERVAL = 60;\nconst DEFAULT_POLL_INTERVAL = 10;\nconst MIN_POLL_GAP_MS = 60_000; // Minimum 60s between polls\n/** v0.4.2 (M6): minimum length for an apiKey value to even be considered valid. */\nconst MIN_API_KEY_LENGTH = 10;\n\n/**\n * ioBroker adapter for parcel.app package tracking. Exported so the\n * orchestration unit tests can drive its lifecycle/poll handlers directly.\n */\nexport class ParcelappAdapter extends utils.Adapter {\n private client: ParcelClient | null = null;\n private stateManager: StateManager | null = null;\n /**\n * Factories for the HTTP client + state manager \u2014 default to the real\n * constructors. Test seams (fleet pattern): unit tests replace these with\n * fakes to exercise the poll orchestration (throttle/force/rate-limit\n * interplay, error routing, failure dedup) without real network.\n *\n * @param apiKey parcel.app API key\n */\n private makeClient: (apiKey: string) => ParcelClient = apiKey =>\n new ParcelClient(apiKey, { debug: (m: string) => this.log.debug(m) });\n /** @param language Raw system language (resolution happens in StateManager) */\n private makeStateManager: (language: string) => StateManager = language => new StateManager(this, language);\n private pollTimer: ioBroker.Interval | undefined = undefined;\n private isPolling = false;\n private lastPollTime = 0;\n private rateLimitedUntil = 0;\n private lastErrorCode = \"\";\n private failedDeliveries = new Set<string>();\n /**\n * v0.4.4: short-lived test-clients spawned from `checkConnection` admin\n * messages. The prod-`this.client` is what `onUnload` cancels, so these\n * need their own registry to be reachable at shutdown. Without this, an\n * admin clicking \"Test Connection\" right before adapter-stop could keep\n * the process alive past js-controller's 4-second kill deadline.\n */\n private testClients = new Set<ParcelClient>();\n\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({\n ...options,\n name: \"parcelapp\",\n });\n this.on(\"ready\", this.onReady.bind(this));\n this.on(\"unload\", this.onUnload.bind(this));\n this.on(\"message\", this.onMessage.bind(this));\n }\n\n private async onReady(): Promise<void> {\n try {\n await I18n.init(join(this.adapterDir, \"admin\"), this);\n this.log.debug(\n `onReady: starting (pollInterval=${JSON.stringify(this.config.pollInterval)}, autoRemoveDelivered=${this.config.autoRemoveDelivered})`,\n );\n\n const sysConfig = await this.getForeignObjectAsync(\"system.config\");\n const language = (sysConfig?.common as { language?: string } | undefined)?.language ?? \"\";\n // v0.7.2: the fallback resolution lives in StateManager (resolveLanguage)\n // \u2014 log the value that is actually used, not a dead local field.\n this.log.debug(`system language: '${language}' \u2192 using '${resolveLanguage(language)}'`);\n\n await this.setStateAsync(\"info.connection\", { val: false, ack: true });\n\n const { apiKey } = this.config;\n if (!apiKey || apiKey.trim().length < MIN_API_KEY_LENGTH) {\n this.log.error(\"No valid API key configured \u2014 please enter your parcel.app API key in the adapter settings\");\n return;\n }\n\n this.client = this.makeClient(apiKey.trim());\n this.stateManager = this.makeStateManager(language);\n\n await this.cleanupObsoleteStates();\n\n await this.poll();\n\n const interval = ParcelappAdapter.coercePollInterval(this.config.pollInterval);\n this.log.debug(`pollInterval: raw=${JSON.stringify(this.config.pollInterval)} resolved=${interval}min`);\n const intervalMs = interval * 60 * 1000;\n this.pollTimer = this.setInterval(() => {\n void this.poll().catch(err => this.log.error(`Scheduled poll failed: ${errText(err)}`));\n }, intervalMs);\n\n this.log.info(`Parcel tracking started \u2014 polling every ${interval} minutes`);\n } catch (err: unknown) {\n this.log.error(`onReady failed: ${errText(err)}`);\n }\n }\n\n /**\n * v0.4.2 (M5+X5): delegate to the shared `coerceClampedInt` helper.\n *\n * @param raw Raw `pollInterval` from admin config (number or numeric string).\n */\n private static coercePollInterval(raw: unknown): number {\n return coerceClampedInt(raw, MIN_POLL_INTERVAL, MAX_POLL_INTERVAL, DEFAULT_POLL_INTERVAL);\n }\n\n private onUnload(callback: () => void): void {\n try {\n if (this.pollTimer) {\n this.clearInterval(this.pollTimer);\n this.pollTimer = undefined;\n }\n // v0.4.2 (M11+P1): cancel every in-flight HTTPS request so a slow\n // parcel.app endpoint doesn't keep the adapter alive past\n // js-controller's 4-second kill deadline.\n this.client?.cancelAll();\n // v0.4.4: also abort any short-lived test-client (from checkConnection)\n // whose HTTPS-request might still be inflight at shutdown \u2014 the prod\n // `this.client.cancelAll()` only touches the production-client.\n for (const tc of this.testClients) {\n tc.cancelAll();\n }\n this.testClients.clear();\n // v0.4.2 (M10): explicit `.catch(() => {})` on the fire-and-forget so\n // a broker-already-down doesn't leak as an unhandled rejection.\n void this.setState(\"info.connection\", { val: false, ack: true }).catch(() => {\n /* broker is shutting down \u2014 ignore */\n });\n } catch (err) {\n // v0.4.3 (G4): replace silent `// ignore` with a trace so shutdown\n // errors leave a debug breadcrumb. Broker-already-down errors here\n // are expected \u2014 debug-level keeps them out of the user log.\n this.log.debug(`onUnload error (ignored): ${errText(err)}`);\n }\n callback();\n }\n\n private async onMessage(obj: ioBroker.Message): Promise<void> {\n // v0.4.3 (F1): entry log BEFORE the early-return \u2014 broadcast messages\n // without callback wouldn't be visible otherwise.\n this.log.debug(`onMessage: command='${obj?.command}' from='${obj?.from}' has-callback=${!!obj?.callback}`);\n if (!obj?.command || !obj.callback) {\n return;\n }\n\n try {\n switch (obj.command) {\n case \"checkConnection\": {\n const msg = obj.message as { apiKey?: string };\n const key = msg?.apiKey?.trim() || \"\";\n if (!key || key.length < MIN_API_KEY_LENGTH) {\n // v0.4.3 (F2): trace the reject before sendTo.\n this.log.debug(\"checkConnection: apiKey too short\");\n this.sendTo(obj.from, obj.command, { success: false, message: \"API key is too short\" }, obj.callback);\n return;\n }\n // v0.4.3: same debug-logger as the prod client so checkConnection\n // failures get the same HTTPS-layer trace (via the makeClient seam).\n const testClient = this.makeClient(key);\n // v0.4.4: register test-client so onUnload can abort its inflight\n // HTTPS-request \u2014 the adapter's `this.client.cancelAll()` only\n // touches the prod-client, not these short-lived test-clients.\n this.testClients.add(testClient);\n try {\n const result = await testClient.testConnection();\n // v0.4.3 (F3): trace checkConnection result.\n this.log.debug(`checkConnection: result=${result.success ? \"ok\" : \"fail\"} (${result.message})`);\n this.sendTo(obj.from, obj.command, result, obj.callback);\n } finally {\n this.testClients.delete(testClient);\n }\n break;\n }\n case \"addDelivery\": {\n if (!this.client) {\n // v0.4.3 (F4): trace addDelivery-before-init.\n this.log.debug(\"addDelivery: adapter not initialized\");\n this.sendTo(\n obj.from,\n obj.command,\n { success: false, error_message: \"Adapter not initialized\" },\n obj.callback,\n );\n return;\n }\n // v0.7.2: obj.message is `unknown`-shaped \u2014 a script calling\n // sendTo(\"parcelapp\", \"addDelivery\", null) used to surface as an\n // ugly TypeError through the catch instead of a clear validation\n // message. Coerce to a plain object and validate required fields.\n const raw = obj.message;\n const msg =\n raw !== null && typeof raw === \"object\" && !Array.isArray(raw) ? (raw as Record<string, unknown>) : {};\n if (\n typeof msg.tracking_number !== \"string\" ||\n msg.tracking_number.length === 0 ||\n typeof msg.carrier_code !== \"string\" ||\n msg.carrier_code.length === 0 ||\n typeof msg.description !== \"string\" ||\n msg.description.length === 0\n ) {\n this.log.debug(\"addDelivery: missing tracking_number/carrier_code/description in message\");\n this.sendTo(\n obj.from,\n obj.command,\n { success: false, error_message: \"tracking_number, carrier_code and description are required\" },\n obj.callback,\n );\n return;\n }\n // Pass the optional API fields through when the caller supplies them\n // (language: ISO 639-1 two-letter code; send_push_confirmation: push\n // notification once the delivery is added).\n const request: AddDeliveryRequest = {\n tracking_number: msg.tracking_number,\n carrier_code: msg.carrier_code,\n description: msg.description,\n };\n if (typeof msg.language === \"string\" && msg.language.length > 0) {\n request.language = msg.language;\n }\n if (typeof msg.send_push_confirmation === \"boolean\") {\n request.send_push_confirmation = msg.send_push_confirmation;\n }\n const addResult = await this.client.addDelivery(request);\n // v0.4.3 (F5): trace addDelivery result with the tracking number.\n this.log.debug(`addDelivery: '${request.tracking_number}' result=${addResult.success ? \"ok\" : \"fail\"}`);\n this.sendTo(obj.from, obj.command, addResult, obj.callback);\n if (addResult.success) {\n // C5: force bypasses the 60s throttle so the next poll runs right\n // away; the rate-limit cooldown still applies. Note: the GET returns\n // a cached list and a newly added package has no tracking data for\n // ~45-90 min, so this poll usually won't surface it yet.\n void this.poll({ force: true }).catch(err =>\n this.log.error(`Poll after addDelivery failed: ${errText(err)}`),\n );\n }\n break;\n }\n default:\n // v0.4.3 (F6): trace unknown command before sendTo.\n this.log.debug(`onMessage: unknown command '${obj.command}'`);\n this.sendTo(obj.from, obj.command, { error: \"Unknown command\" }, obj.callback);\n }\n } catch (err) {\n // v0.4.3 (F7): trace catch so the debug log shows what failed.\n // The sendTo back to the caller is preserved unchanged.\n this.log.debug(`onMessage: '${obj.command}' failed: ${errText(err)}`);\n this.sendTo(obj.from, obj.command, { success: false, error_message: errText(err) }, obj.callback);\n }\n }\n\n private async cleanupObsoleteStates(): Promise<void> {\n const obsoleteStates = [\n \"summary.json\", // removed in 0.2.0\n ];\n for (const stateId of obsoleteStates) {\n const obj = await this.getObjectAsync(stateId);\n if (obj) {\n await this.delObjectAsync(stateId);\n this.log.debug(`Removed obsolete state: ${stateId}`);\n }\n }\n }\n\n /**\n * Classify an error for deduplication and log-level decisions.\n *\n * @param error The error to classify\n */\n private classifyError(error: Error & { code?: string }): string {\n if (error.code === \"RATE_LIMITED\") {\n return \"RATE_LIMITED\";\n }\n if (error.code === \"INVALID_API_KEY\") {\n return \"INVALID_API_KEY\";\n }\n // v0.4.2 (P3): 403 is a permission issue, distinct from invalid api-key.\n if (error.code === \"FORBIDDEN\") {\n return \"FORBIDDEN\";\n }\n // Network errors: DNS, connection refused, no internet\n if (\n error.code === \"ENOTFOUND\" ||\n error.code === \"ECONNREFUSED\" ||\n error.code === \"ECONNRESET\" ||\n error.code === \"ENETUNREACH\" ||\n error.code === \"EHOSTUNREACH\" ||\n error.code === \"EAI_AGAIN\"\n ) {\n return \"NETWORK\";\n }\n if (error.message.includes(\"timeout\") || error.code === \"ETIMEDOUT\") {\n return \"TIMEOUT\";\n }\n return error.code || \"UNKNOWN\";\n }\n\n private async poll(options: { force?: boolean } = {}): Promise<void> {\n if (this.isPolling || !this.client || !this.stateManager) {\n return;\n }\n\n const now = Date.now();\n // v0.4.3 (B1): poll-entry anchor \u2014 visible after the re-entry guard but\n // before the rate-limit/throttle skips. Shows mode + current error state\n // so the debug log gives context for whatever follows.\n const autoRemoveMode = this.config.autoRemoveDelivered !== false;\n this.log.debug(`poll: starting (autoRemove=${autoRemoveMode}, lastErrorCode='${this.lastErrorCode}')`);\n\n // Skip if rate limited\n if (now < this.rateLimitedUntil) {\n const waitMin = Math.ceil((this.rateLimitedUntil - now) / 60_000);\n this.log.debug(`Skipping poll \u2014 rate limited for ${waitMin} more minute(s)`);\n return;\n }\n\n // Throttle: minimum gap between polls. A forced poll (e.g. from\n // addDelivery) bypasses this so a freshly added package shows up\n // immediately; the rate-limit cooldown above still applies to protect\n // the API.\n if (!options.force && now - this.lastPollTime < MIN_POLL_GAP_MS) {\n this.log.debug(\"Skipping poll \u2014 too soon after last poll\");\n return;\n }\n\n this.isPolling = true;\n this.lastPollTime = now;\n try {\n // When keeping delivered packages, use \"recent\" to get them from API\n const deliveries = await this.client.getDeliveries(autoRemoveMode ? \"active\" : \"recent\");\n\n // Reset error state on success\n this.rateLimitedUntil = 0;\n if (this.lastErrorCode) {\n this.log.info(\"Connection restored\");\n this.lastErrorCode = \"\";\n }\n await this.setStateChangedAsync(\"info.connection\", { val: true, ack: true });\n\n // Split into active (non-delivered) and visible (what gets states)\n const activeDeliveries = deliveries.filter(d => this.stateManager!.parseStatus(d) !== 0);\n const visibleDeliveries = autoRemoveMode ? activeDeliveries : deliveries;\n\n // v0.4.2 (S3): reset the per-poll collision tracker, then compute every\n // package id in a deterministic sequential pre-pass (stable array order)\n // BEFORE the parallel updates \u2014 collision-suffixing is then deterministic\n // and packageId runs exactly once per delivery instead of twice.\n this.stateManager.resetPollState();\n const pkgIds = visibleDeliveries.map(d => this.stateManager!.packageId(d));\n\n // v0.4.2 (M4): per-delivery updates run in parallel, each wrapped in\n // try/catch so one bad delivery doesn't poison the others.\n const idResults = await Promise.all(\n visibleDeliveries.map(async (delivery, index) => {\n const pkgId = pkgIds[index];\n try {\n // v0.4.3 (C1): per-delivery entry. ~10 packages \u00D7 144 polls/day\n // = ~1440 debug lines/day \u2014 acceptable at debug-level. Line stays\n // short (tracking + carrier + status only, no full delivery JSON).\n this.log.debug(\n `updateDelivery: '${delivery.tracking_number}' carrier=${delivery.carrier_code} status=${delivery.status_code}`,\n );\n const carrierName = await this.client!.getCarrierName(delivery.carrier_code);\n await this.stateManager!.updateDelivery(delivery, carrierName, pkgId);\n this.failedDeliveries.delete(delivery.tracking_number);\n return pkgId;\n } catch (err) {\n const msg = errText(err);\n if (this.failedDeliveries.has(delivery.tracking_number)) {\n this.log.debug(`Failed to update \"${delivery.tracking_number}\": ${msg}`);\n } else {\n this.log.warn(`Failed to update '${delivery.tracking_number}': ${msg}`);\n this.failedDeliveries.add(delivery.tracking_number);\n }\n return null;\n }\n }),\n );\n const activeIds = idResults.filter((id): id is string => id !== null);\n\n // Cleanup stale deliveries\n await this.stateManager.cleanupDeliveries(activeIds);\n\n // Update summary (always uses active/non-delivered)\n await this.stateManager.updateSummary(activeDeliveries);\n\n // Keep failedDeliveries bounded: drop entries for trackings no longer\n // present, so packages that vanish from the API don't linger forever.\n const seenTracking = new Set(visibleDeliveries.map(d => d.tracking_number));\n for (const tracking of [...this.failedDeliveries]) {\n if (!seenTracking.has(tracking)) {\n this.failedDeliveries.delete(tracking);\n }\n }\n\n this.log.debug(`Polled ${visibleDeliveries.length} deliveries (${activeDeliveries.length} active)`);\n } catch (err) {\n const error = err as Error & {\n code?: string;\n retryAfterSeconds?: number;\n };\n\n // Classify the error\n const errorCode = this.classifyError(error);\n const isRepeat = errorCode === this.lastErrorCode;\n this.lastErrorCode = errorCode;\n\n if (error.code === \"RATE_LIMITED\") {\n // v0.4.2 (M9): clamp Retry-After value into [60s, 24h]. A bogus 0,\n // negative, or fractional value used to either wipe the cooldown\n // (set rateLimitedUntil to past) or set it for fractions of a\n // second \u2014 neither is the intended behavior.\n const rawCooldown = error.retryAfterSeconds ?? 0;\n const cooldownSec =\n Number.isFinite(rawCooldown) && rawCooldown > 0\n ? Math.min(24 * 3600, Math.max(60, Math.floor(rawCooldown)))\n : 5 * 60;\n this.rateLimitedUntil = Date.now() + cooldownSec * 1000;\n this.log.warn(`Rate limit hit \u2014 pausing API requests for ${Math.ceil(cooldownSec / 60)} minute(s)`);\n } else if (error.code === \"FORBIDDEN\") {\n // v0.4.2 (P3): 403 is a permission issue (e.g. Premium subscription\n // expired). Reauth wouldn't help \u2014 surface a clear hint.\n this.log.error(\n \"parcel.app returned 403 Forbidden \u2014 your account may not have an active Premium subscription, or the API key was revoked. Check your account on parcelapp.net.\",\n );\n } else if (error.code === \"INVALID_API_KEY\") {\n // Always log \u2014 user must fix config\n this.log.error(\"Invalid API key \u2014 please check your parcel.app API key\");\n } else if (isRepeat) {\n // Same error as last time \u2014 don't spam the log\n this.log.debug(`Poll failed (ongoing): ${error.message}`);\n } else if (errorCode === \"NETWORK\") {\n this.log.warn(\"Cannot reach parcel.app API \u2014 will keep retrying\");\n } else if (errorCode === \"TIMEOUT\") {\n this.log.warn(\"API request timeout \u2014 will retry next cycle\");\n } else {\n this.log.error(`Poll failed: ${error.message}`);\n }\n\n // C2: setStateChangedAsync avoids redundant `false` writes on sustained\n // failure. The `.catch` keeps poll() from rejecting when the broker is\n // already down, so the fire-and-forget callers never see an unhandled\n // rejection (no global process handler needed).\n await this.setStateChangedAsync(\"info.connection\", { val: false, ack: true }).catch(() => {\n /* broker shutting down \u2014 ignore */\n });\n } finally {\n this.isPolling = false;\n }\n }\n}\n\nif (require.main !== module) {\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new ParcelappAdapter(options);\n} else {\n (() => new ParcelappAdapter())();\n}\n"],
5
- "mappings": "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",
6
- "names": []
4
+ "sourcesContent": ["import * as utils from \"@iobroker/adapter-core\";\nimport { I18n } from \"@iobroker/adapter-core\";\nimport { join } from \"node:path\";\nimport { coerceClampedInt, errText, oneLine } from \"./lib/coerce\";\nimport { ParcelClient } from \"./lib/parcel-client\";\nimport { resolveLanguage, StateManager } from \"./lib/state-manager\";\nimport type { AddDeliveryRequest } from \"./lib/types\";\n\nconst MIN_POLL_INTERVAL = 5;\nconst MAX_POLL_INTERVAL = 60;\nconst DEFAULT_POLL_INTERVAL = 10;\nconst MIN_POLL_GAP_MS = 60_000; // Minimum 60s between polls\n/** v0.4.2 (M6): minimum length for an apiKey value to even be considered valid. */\nconst MIN_API_KEY_LENGTH = 10;\n// v0.9.0 (S5): cap addDelivery field lengths. A sendTo caller is local, but a\n// runaway script must not push a multi-MB POST body to parcel.app. Identifiers\n// are short; this is generous for a description.\nconst MAX_ADD_FIELD_LEN = 512;\n// v0.9.0 (S3): cap the per-poll broker fan-out. Updates run in batches of this\n// size instead of all deliveries at once, so an abnormally large API response\n// can't flood the broker with thousands of concurrent writes.\nconst UPDATE_BATCH_SIZE = 25;\n// v0.9.0 (S4): client-side throttle on addDelivery POSTs. parcel.app enforces\n// ~20 POST/day server-side; this caps a runaway/buggy script's burst so it can't\n// hammer the API. The window is generous enough never to block a real batch-add\n// (the daily server limit is the real cap).\nconst MAX_ADDS_PER_WINDOW = 20;\nconst ADD_WINDOW_MS = 60_000;\n\n/**\n * ioBroker adapter for parcel.app package tracking. Exported so the\n * orchestration unit tests can drive its lifecycle/poll handlers directly.\n */\nexport class ParcelappAdapter extends utils.Adapter {\n private client: ParcelClient | null = null;\n private stateManager: StateManager | null = null;\n /**\n * Factories for the HTTP client + state manager \u2014 default to the real\n * constructors. Test seams (fleet pattern): unit tests replace these with\n * fakes to exercise the poll orchestration (throttle/force/rate-limit\n * interplay, error routing, failure dedup) without real network.\n *\n * @param apiKey parcel.app API key\n */\n private makeClient: (apiKey: string) => ParcelClient = apiKey =>\n new ParcelClient(apiKey, { debug: (m: string) => this.log.debug(m) });\n /** @param language Raw system language (resolution happens in StateManager) */\n private makeStateManager: (language: string) => StateManager = language => new StateManager(this, language);\n private pollTimer: ioBroker.Interval | undefined = undefined;\n private isPolling = false;\n private lastPollTime = 0;\n private rateLimitedUntil = 0;\n private lastErrorCode = \"\";\n /**\n * Package ids (not raw tracking numbers) whose last updateDelivery failed.\n * Keyed like the states so the dedup survives a sanitize collision or a\n * missing tracking number; pruned each poll against the visible pkgIds.\n */\n private failedDeliveries = new Set<string>();\n /** Timestamps of recent addDelivery POSTs \u2014 the S4 throttle window. */\n private addTimestamps: number[] = [];\n /**\n * v0.4.4: short-lived test-clients spawned from `checkConnection` admin\n * messages. The prod-`this.client` is what `onUnload` cancels, so these\n * need their own registry to be reachable at shutdown. Without this, an\n * admin clicking \"Test Connection\" right before adapter-stop could keep\n * the process alive past js-controller's 4-second kill deadline.\n */\n private testClients = new Set<ParcelClient>();\n\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({\n ...options,\n name: \"parcelapp\",\n });\n this.on(\"ready\", this.onReady.bind(this));\n this.on(\"unload\", this.onUnload.bind(this));\n this.on(\"message\", this.onMessage.bind(this));\n }\n\n private async onReady(): Promise<void> {\n try {\n await I18n.init(join(this.adapterDir, \"admin\"), this);\n this.log.debug(\n `onReady: starting (pollInterval=${JSON.stringify(this.config.pollInterval)}, autoRemoveDelivered=${this.config.autoRemoveDelivered})`,\n );\n\n const sysConfig = await this.getForeignObjectAsync(\"system.config\");\n const language = (sysConfig?.common as { language?: string } | undefined)?.language ?? \"\";\n // v0.7.2: the fallback resolution lives in StateManager (resolveLanguage)\n // \u2014 log the value that is actually used, not a dead local field.\n this.log.debug(`system language: '${language}' \u2192 using '${resolveLanguage(language)}'`);\n\n await this.setStateAsync(\"info.connection\", { val: false, ack: true });\n\n const { apiKey } = this.config;\n if (!apiKey || apiKey.trim().length < MIN_API_KEY_LENGTH) {\n this.log.error(\"No valid API key configured \u2014 please enter your parcel.app API key in the adapter settings\");\n return;\n }\n\n this.client = this.makeClient(apiKey.trim());\n this.stateManager = this.makeStateManager(language);\n\n try {\n await this.cleanupObsoleteStates();\n } catch (err) {\n // C8: a cleanup failure must not abort startup. Without this guard the\n // outer catch would skip arming the poll interval below, and the adapter\n // would never poll until a manual restart. Degrade to a warning.\n this.log.warn(`cleanupObsoleteStates failed (continuing): ${errText(err)}`);\n }\n\n await this.poll();\n\n const interval = ParcelappAdapter.coercePollInterval(this.config.pollInterval);\n this.log.debug(`pollInterval: raw=${JSON.stringify(this.config.pollInterval)} resolved=${interval}min`);\n const intervalMs = interval * 60 * 1000;\n this.pollTimer = this.setInterval(() => {\n void this.poll().catch(err => this.log.error(`Scheduled poll failed: ${errText(err)}`));\n }, intervalMs);\n\n this.log.info(`Parcel tracking started \u2014 polling every ${interval} minutes`);\n } catch (err: unknown) {\n this.log.error(`onReady failed: ${errText(err)}`);\n }\n }\n\n /**\n * v0.4.2 (M5+X5): delegate to the shared `coerceClampedInt` helper.\n *\n * @param raw Raw `pollInterval` from admin config (number or numeric string).\n */\n private static coercePollInterval(raw: unknown): number {\n return coerceClampedInt(raw, MIN_POLL_INTERVAL, MAX_POLL_INTERVAL, DEFAULT_POLL_INTERVAL);\n }\n\n private onUnload(callback: () => void): void {\n try {\n if (this.pollTimer) {\n this.clearInterval(this.pollTimer);\n this.pollTimer = undefined;\n }\n // v0.4.2 (M11+P1): cancel every in-flight HTTPS request so a slow\n // parcel.app endpoint doesn't keep the adapter alive past\n // js-controller's 4-second kill deadline.\n this.client?.cancelAll();\n // v0.4.4: also abort any short-lived test-client (from checkConnection)\n // whose HTTPS-request might still be inflight at shutdown \u2014 the prod\n // `this.client.cancelAll()` only touches the production-client.\n for (const tc of this.testClients) {\n tc.cancelAll();\n }\n this.testClients.clear();\n // v0.4.2 (M10): explicit `.catch(() => {})` on the fire-and-forget so\n // a broker-already-down doesn't leak as an unhandled rejection.\n void this.setState(\"info.connection\", { val: false, ack: true }).catch(() => {\n /* broker is shutting down \u2014 ignore */\n });\n } catch (err) {\n // v0.4.3 (G4): replace silent `// ignore` with a trace so shutdown\n // errors leave a debug breadcrumb. Broker-already-down errors here\n // are expected \u2014 debug-level keeps them out of the user log.\n this.log.debug(`onUnload error (ignored): ${errText(err)}`);\n }\n callback();\n }\n\n private async onMessage(obj: ioBroker.Message): Promise<void> {\n // v0.4.3 (F1): entry log BEFORE the early-return \u2014 broadcast messages\n // without callback wouldn't be visible otherwise.\n this.log.debug(`onMessage: command='${obj?.command}' from='${obj?.from}' has-callback=${!!obj?.callback}`);\n if (!obj?.command || !obj.callback) {\n return;\n }\n\n try {\n switch (obj.command) {\n case \"checkConnection\": {\n const msg = obj.message as { apiKey?: string };\n const key = msg?.apiKey?.trim() || \"\";\n if (!key || key.length < MIN_API_KEY_LENGTH) {\n // v0.4.3 (F2): trace the reject before sendTo.\n this.log.debug(\"checkConnection: apiKey too short\");\n this.sendTo(obj.from, obj.command, { success: false, message: \"API key is too short\" }, obj.callback);\n return;\n }\n // v0.4.3: same debug-logger as the prod client so checkConnection\n // failures get the same HTTPS-layer trace (via the makeClient seam).\n const testClient = this.makeClient(key);\n // v0.4.4: register test-client so onUnload can abort its inflight\n // HTTPS-request \u2014 the adapter's `this.client.cancelAll()` only\n // touches the prod-client, not these short-lived test-clients.\n this.testClients.add(testClient);\n try {\n const result = await testClient.testConnection();\n // v0.4.3 (F3): trace checkConnection result.\n this.log.debug(`checkConnection: result=${result.success ? \"ok\" : \"fail\"} (${result.message})`);\n this.sendTo(obj.from, obj.command, result, obj.callback);\n } finally {\n this.testClients.delete(testClient);\n }\n break;\n }\n case \"addDelivery\": {\n if (!this.client) {\n // v0.4.3 (F4): trace addDelivery-before-init.\n this.log.debug(\"addDelivery: adapter not initialized\");\n this.sendTo(\n obj.from,\n obj.command,\n { success: false, error_message: \"Adapter not initialized\" },\n obj.callback,\n );\n return;\n }\n // v0.7.2: obj.message is `unknown`-shaped \u2014 a script calling\n // sendTo(\"parcelapp\", \"addDelivery\", null) used to surface as an\n // ugly TypeError through the catch instead of a clear validation\n // message. Coerce to a plain object and validate required fields.\n const raw = obj.message;\n const msg =\n raw !== null && typeof raw === \"object\" && !Array.isArray(raw) ? (raw as Record<string, unknown>) : {};\n if (\n typeof msg.tracking_number !== \"string\" ||\n msg.tracking_number.length === 0 ||\n typeof msg.carrier_code !== \"string\" ||\n msg.carrier_code.length === 0 ||\n typeof msg.description !== \"string\" ||\n msg.description.length === 0\n ) {\n this.log.debug(\"addDelivery: missing tracking_number/carrier_code/description in message\");\n this.sendTo(\n obj.from,\n obj.command,\n { success: false, error_message: \"tracking_number, carrier_code and description are required\" },\n obj.callback,\n );\n return;\n }\n // v0.9.0 (S5): cap field lengths so a runaway local script can't push\n // a multi-MB POST body to parcel.app. Checked after the required-field\n // guard above so the two validation messages stay distinct.\n if (\n msg.tracking_number.length > MAX_ADD_FIELD_LEN ||\n msg.carrier_code.length > MAX_ADD_FIELD_LEN ||\n msg.description.length > MAX_ADD_FIELD_LEN ||\n (typeof msg.language === \"string\" && msg.language.length > MAX_ADD_FIELD_LEN)\n ) {\n this.log.debug(\"addDelivery: a field exceeds the maximum length\");\n this.sendTo(\n obj.from,\n obj.command,\n { success: false, error_message: `each field must be at most ${MAX_ADD_FIELD_LEN} characters` },\n obj.callback,\n );\n return;\n }\n // Pass the optional API fields through when the caller supplies them\n // (language: ISO 639-1 two-letter code; send_push_confirmation: push\n // notification once the delivery is added).\n const request: AddDeliveryRequest = {\n tracking_number: msg.tracking_number,\n carrier_code: msg.carrier_code,\n description: msg.description,\n };\n if (typeof msg.language === \"string\" && msg.language.length > 0) {\n request.language = msg.language;\n }\n if (typeof msg.send_push_confirmation === \"boolean\") {\n request.send_push_confirmation = msg.send_push_confirmation;\n }\n // v0.9.0 (S4): throttle addDelivery POSTs. parcel.app caps ~20/day\n // server-side; this stops a runaway/buggy script from hammering the API\n // with a burst. Record the attempt before the await so concurrent\n // callers count too.\n const nowMs = Date.now();\n this.addTimestamps = this.addTimestamps.filter(t => nowMs - t < ADD_WINDOW_MS);\n if (this.addTimestamps.length >= MAX_ADDS_PER_WINDOW) {\n this.log.warn(\n `addDelivery throttled: more than ${MAX_ADDS_PER_WINDOW} requests within ${ADD_WINDOW_MS / 1000}s`,\n );\n this.sendTo(\n obj.from,\n obj.command,\n {\n success: false,\n error_message: `too many addDelivery requests; max ${MAX_ADDS_PER_WINDOW} per ${ADD_WINDOW_MS / 1000}s`,\n },\n obj.callback,\n );\n return;\n }\n this.addTimestamps.push(nowMs);\n const addResult = await this.client.addDelivery(request);\n // v0.4.3 (F5): trace addDelivery result with the tracking number.\n this.log.debug(`addDelivery: '${request.tracking_number}' result=${addResult.success ? \"ok\" : \"fail\"}`);\n this.sendTo(obj.from, obj.command, addResult, obj.callback);\n if (addResult.success) {\n // C5: force bypasses the 60s throttle so the next poll runs right\n // away; the rate-limit cooldown still applies. Note: the GET returns\n // a cached list and a newly added package has no tracking data for\n // ~45-90 min, so this poll usually won't surface it yet.\n void this.poll({ force: true }).catch(err =>\n this.log.error(`Poll after addDelivery failed: ${errText(err)}`),\n );\n }\n break;\n }\n default:\n // v0.4.3 (F6): trace unknown command before sendTo.\n this.log.debug(`onMessage: unknown command '${obj.command}'`);\n this.sendTo(obj.from, obj.command, { error: \"Unknown command\" }, obj.callback);\n }\n } catch (err) {\n // v0.4.3 (F7): trace catch so the debug log shows what failed.\n // The sendTo back to the caller is preserved unchanged.\n this.log.debug(`onMessage: '${obj.command}' failed: ${errText(err)}`);\n this.sendTo(obj.from, obj.command, { success: false, error_message: errText(err) }, obj.callback);\n }\n }\n\n private async cleanupObsoleteStates(): Promise<void> {\n const obsoleteStates = [\n \"summary.json\", // removed in 0.2.0\n ];\n for (const stateId of obsoleteStates) {\n const obj = await this.getObjectAsync(stateId);\n if (obj) {\n await this.delObjectAsync(stateId);\n this.log.debug(`Removed obsolete state: ${stateId}`);\n }\n }\n }\n\n /**\n * Classify an error for deduplication and log-level decisions.\n *\n * @param error The error to classify\n */\n private classifyError(error: Error & { code?: string }): string {\n if (error.code === \"RATE_LIMITED\") {\n return \"RATE_LIMITED\";\n }\n if (error.code === \"INVALID_API_KEY\") {\n return \"INVALID_API_KEY\";\n }\n // v0.4.2 (P3): 403 is a permission issue, distinct from invalid api-key.\n if (error.code === \"FORBIDDEN\") {\n return \"FORBIDDEN\";\n }\n // Network errors: DNS, connection refused, no internet\n if (\n error.code === \"ENOTFOUND\" ||\n error.code === \"ECONNREFUSED\" ||\n error.code === \"ECONNRESET\" ||\n error.code === \"ENETUNREACH\" ||\n error.code === \"EHOSTUNREACH\" ||\n error.code === \"EAI_AGAIN\"\n ) {\n return \"NETWORK\";\n }\n if (error.message.includes(\"timeout\") || error.code === \"ETIMEDOUT\") {\n return \"TIMEOUT\";\n }\n return error.code || \"UNKNOWN\";\n }\n\n private async poll(options: { force?: boolean } = {}): Promise<void> {\n if (this.isPolling || !this.client || !this.stateManager) {\n return;\n }\n\n const now = Date.now();\n // v0.4.3 (B1): poll-entry anchor \u2014 visible after the re-entry guard but\n // before the rate-limit/throttle skips. Shows mode + current error state\n // so the debug log gives context for whatever follows.\n const autoRemoveMode = this.config.autoRemoveDelivered !== false;\n this.log.debug(`poll: starting (autoRemove=${autoRemoveMode}, lastErrorCode='${this.lastErrorCode}')`);\n\n // Skip if rate limited\n if (now < this.rateLimitedUntil) {\n const waitMin = Math.ceil((this.rateLimitedUntil - now) / 60_000);\n this.log.debug(`Skipping poll \u2014 rate limited for ${waitMin} more minute(s)`);\n return;\n }\n\n // Throttle: minimum gap between polls. A forced poll (e.g. from\n // addDelivery) bypasses this so a freshly added package shows up\n // immediately; the rate-limit cooldown above still applies to protect\n // the API.\n if (!options.force && now - this.lastPollTime < MIN_POLL_GAP_MS) {\n this.log.debug(\"Skipping poll \u2014 too soon after last poll\");\n return;\n }\n\n this.isPolling = true;\n this.lastPollTime = now;\n try {\n // When keeping delivered packages, use \"recent\" to get them from API\n const deliveries = await this.client.getDeliveries(autoRemoveMode ? \"active\" : \"recent\");\n\n // Reset error state on success\n this.rateLimitedUntil = 0;\n if (this.lastErrorCode) {\n this.log.info(\"Connection restored\");\n this.lastErrorCode = \"\";\n }\n await this.setStateChangedAsync(\"info.connection\", { val: true, ack: true });\n\n // Split into active (non-delivered) and visible (what gets states)\n const activeDeliveries = deliveries.filter(d => this.stateManager!.parseStatus(d) !== 0);\n const visibleDeliveries = autoRemoveMode ? activeDeliveries : deliveries;\n\n // v0.4.2 (S3): reset the per-poll collision tracker, then compute every\n // package id in a deterministic sequential pre-pass (stable array order)\n // BEFORE the parallel updates \u2014 collision-suffixing is then deterministic\n // and packageId runs exactly once per delivery instead of twice.\n this.stateManager.resetPollState();\n const pkgIds = visibleDeliveries.map(d => this.stateManager!.packageId(d));\n\n // v0.4.2 (M4): per-delivery updates run in parallel, each wrapped in\n // try/catch so one bad delivery doesn't poison the others.\n // v0.9.0 (S3): process the updates in bounded batches instead of one\n // broker fan-out for ALL deliveries at once. The keep-set is still every\n // visible pkgId (computed above), so this only caps concurrency \u2014 it never\n // drops a package. A normal poll (a handful of packages) is a single batch.\n if (visibleDeliveries.length > UPDATE_BATCH_SIZE) {\n this.log.debug(`Updating ${visibleDeliveries.length} deliveries in batches of ${UPDATE_BATCH_SIZE}`);\n }\n for (let start = 0; start < visibleDeliveries.length; start += UPDATE_BATCH_SIZE) {\n const batch = visibleDeliveries.slice(start, start + UPDATE_BATCH_SIZE);\n await Promise.all(\n batch.map(async (delivery, offset) => {\n const pkgId = pkgIds[start + offset];\n // Pre-sanitize the externally-sourced strings once for logging. The\n // fields are optional (the API can drop them), so default to \"\" before\n // oneLine flattens them onto a single log line.\n const tracking = oneLine(delivery.tracking_number ?? \"\");\n const carrier = oneLine(delivery.carrier_code ?? \"\");\n try {\n // v0.4.3 (C1): per-delivery entry. ~10 packages \u00D7 144 polls/day\n // = ~1440 debug lines/day \u2014 acceptable at debug-level. Line stays\n // short (tracking + carrier + status only, no full delivery JSON).\n this.log.debug(`updateDelivery: '${tracking}' carrier=${carrier} status=${delivery.status_code}`);\n const carrierName = await this.client!.getCarrierName(delivery.carrier_code);\n await this.stateManager!.updateDelivery(delivery, carrierName, pkgId);\n this.failedDeliveries.delete(pkgId);\n } catch (err) {\n const msg = errText(err);\n if (this.failedDeliveries.has(pkgId)) {\n this.log.debug(`Failed to update '${tracking}': ${msg}`);\n } else {\n this.log.warn(`Failed to update '${tracking}': ${msg}`);\n this.failedDeliveries.add(pkgId);\n }\n }\n }),\n );\n }\n\n // v0.9.0 (C1): keep-set = EVERY package the API still returns this poll\n // (pkgIds), NOT only the writes that just succeeded. A transient\n // updateDelivery failure leaves a package's states stale, but it must not\n // drop the package from cleanup \u2014 that would delete a still-present\n // package's states (and any user-set device name) at green info.connection.\n await this.stateManager.cleanupDeliveries(pkgIds);\n\n // Update summary (always uses active/non-delivered)\n await this.stateManager.updateSummary(activeDeliveries);\n\n // Keep failedDeliveries bounded: drop entries for package ids no longer\n // present, so packages that vanish from the API don't linger forever.\n const seenPkgIds = new Set(pkgIds);\n for (const id of [...this.failedDeliveries]) {\n if (!seenPkgIds.has(id)) {\n this.failedDeliveries.delete(id);\n }\n }\n\n this.log.debug(`Polled ${visibleDeliveries.length} deliveries (${activeDeliveries.length} active)`);\n } catch (err) {\n const error = err as Error & {\n code?: string;\n retryAfterSeconds?: number;\n };\n\n // Classify the error\n const errorCode = this.classifyError(error);\n const isRepeat = errorCode === this.lastErrorCode;\n this.lastErrorCode = errorCode;\n\n if (error.code === \"RATE_LIMITED\") {\n // v0.4.2 (M9): clamp Retry-After value into [60s, 24h]. A bogus 0,\n // negative, or fractional value used to either wipe the cooldown\n // (set rateLimitedUntil to past) or set it for fractions of a\n // second \u2014 neither is the intended behavior.\n const rawCooldown = error.retryAfterSeconds ?? 0;\n const cooldownSec =\n Number.isFinite(rawCooldown) && rawCooldown > 0\n ? Math.min(24 * 3600, Math.max(60, Math.floor(rawCooldown)))\n : 5 * 60;\n this.rateLimitedUntil = Date.now() + cooldownSec * 1000;\n this.log.warn(`Rate limit hit \u2014 pausing API requests for ${Math.ceil(cooldownSec / 60)} minute(s)`);\n } else if (error.code === \"FORBIDDEN\") {\n // v0.4.2 (P3): 403 is a permission issue (e.g. Premium subscription\n // expired). Reauth wouldn't help \u2014 surface a clear hint.\n this.log.error(\n \"parcel.app returned 403 Forbidden \u2014 your account may not have an active Premium subscription, or the API key was revoked. Check your account on parcelapp.net.\",\n );\n } else if (error.code === \"INVALID_API_KEY\") {\n // Always log \u2014 user must fix config\n this.log.error(\"Invalid API key \u2014 please check your parcel.app API key\");\n } else if (isRepeat) {\n // Same error as last time \u2014 don't spam the log\n this.log.debug(`Poll failed (ongoing): ${error.message}`);\n } else if (errorCode === \"NETWORK\") {\n this.log.warn(\"Cannot reach parcel.app API \u2014 will keep retrying\");\n } else if (errorCode === \"TIMEOUT\") {\n this.log.warn(\"API request timeout \u2014 will retry next cycle\");\n } else {\n this.log.error(`Poll failed: ${error.message}`);\n }\n\n // C2: setStateChangedAsync avoids redundant `false` writes on sustained\n // failure. The `.catch` keeps poll() from rejecting when the broker is\n // already down, so the fire-and-forget callers never see an unhandled\n // rejection (no global process handler needed).\n await this.setStateChangedAsync(\"info.connection\", { val: false, ack: true }).catch(() => {\n /* broker shutting down \u2014 ignore */\n });\n } finally {\n this.isPolling = false;\n }\n }\n}\n\nif (require.main !== module) {\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new ParcelappAdapter(options);\n} else {\n (() => new ParcelappAdapter())();\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["_a"]
7
7
  }
package/io-package.json CHANGED
@@ -1,8 +1,21 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "parcelapp",
4
- "version": "0.8.0",
4
+ "version": "0.9.0",
5
5
  "news": {
6
+ "0.9.0": {
7
+ "en": "Tracked packages no longer disappear after a temporary error or an unexpected API response, and multi-day delivery windows now show the date on each side.",
8
+ "de": "Verfolgte Pakete verschwinden nicht mehr nach einem vorübergehenden Fehler oder einer unerwarteten API-Antwort, und mehrtägige Zustellfenster zeigen jetzt auf beiden Seiten das Datum.",
9
+ "ru": "Отслеживаемые посылки больше не исчезают после временной ошибки или неожиданного ответа API, а многодневные окна доставки теперь показывают дату с обеих сторон.",
10
+ "pt": "Os pacotes monitorizados já não desaparecem após um erro temporário ou uma resposta inesperada da API, e as janelas de entrega de vários dias mostram agora a data em cada lado.",
11
+ "nl": "Gevolgde pakketten verdwijnen niet meer na een tijdelijke fout of een onverwacht API-antwoord, en bezorgvensters over meerdere dagen tonen nu aan beide kanten de datum.",
12
+ "fr": "Les colis suivis ne disparaissent plus après une erreur temporaire ou une réponse inattendue de l'API, et les créneaux de livraison sur plusieurs jours affichent désormais la date de chaque côté.",
13
+ "it": "I pacchi tracciati non scompaiono più dopo un errore temporaneo o una risposta API imprevista, e le finestre di consegna su più giorni ora mostrano la data su entrambi i lati.",
14
+ "es": "Los paquetes rastreados ya no desaparecen tras un error temporal o una respuesta inesperada de la API, y las ventanas de entrega de varios días ahora muestran la fecha en cada lado.",
15
+ "pl": "Śledzone paczki nie znikają już po tymczasowym błędzie lub nieoczekiwanej odpowiedzi API, a wielodniowe okna dostawy pokazują teraz datę po obu stronach.",
16
+ "uk": "Відстежувані посилки більше не зникають після тимчасової помилки або неочікуваної відповіді API, а багатоденні вікна доставки тепер показують дату з обох боків.",
17
+ "zh-cn": "跟踪的包裹在临时错误或意外的 API 响应后不再消失,跨多天的派送时段现在会在两侧显示日期。"
18
+ },
6
19
  "0.8.0": {
7
20
  "en": "The delivery window is now also shown for carriers that report it only as a date/time range, not just as a Unix timestamp.",
8
21
  "de": "Das Zustellfenster wird jetzt auch für Anbieter angezeigt, die es nur als Datums-/Zeitbereich melden, nicht nur als Unix-Zeitstempel.",
@@ -80,19 +93,6 @@
80
93
  "pl": "Zmniejszono niepotrzebne zdarzenia zmiany stanu, pomijając zapisy gdy wartość się nie zmieniła.",
81
94
  "uk": "Зменшено непотрібні події зміни стану шляхом пропуску запису при незміненому значенні.",
82
95
  "zh-cn": "通过在值未更改时跳过写入来减少不必要的状态变更事件。"
83
- },
84
- "0.5.2": {
85
- "en": "Changelog rewritten in user-centric style across all versions.",
86
- "de": "Changelog in benutzerorientiertem Stil über alle Versionen neu geschrieben.",
87
- "ru": "Журнал изменений переписан в пользовательском стиле для всех версий.",
88
- "pt": "Registo de alterações reescrito em estilo centrado no utilizador em todas as versões.",
89
- "nl": "Changelog herschreven in gebruikersgerichte stijl voor alle versies.",
90
- "fr": "Journal des modifications réécrit dans un style centré sur l'utilisateur pour toutes les versions.",
91
- "it": "Registro delle modifiche riscritto in stile orientato all'utente per tutte le versioni.",
92
- "es": "Registro de cambios reescrito en estilo centrado en el usuario en todas las versiones.",
93
- "pl": "Dziennik zmian przepisany w stylu zorientowanym na użytkownika we wszystkich wersjach.",
94
- "uk": "Журнал змін переписано у стилі, орієнтованому на користувача, для всіх версій.",
95
- "zh-cn": "所有版本的更新日志已重写为以用户为中心的风格。"
96
96
  }
97
97
  },
98
98
  "plugins": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.parcelapp",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "ioBroker adapter for the parcel.app API",
5
5
  "author": {
6
6
  "name": "krobi",
@@ -30,7 +30,7 @@
30
30
  "node": ">=22"
31
31
  },
32
32
  "dependencies": {
33
- "@iobroker/adapter-core": "^3.3.2"
33
+ "@iobroker/adapter-core": "^3.4.1"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@alcalzone/release-script": "^5.2.1",