iobroker.parcelapp 0.11.0 → 0.11.1
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 +4 -4
- package/build/lib/state-manager.js +40 -10
- package/build/lib/state-manager.js.map +2 -2
- package/io-package.json +14 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -142,6 +142,10 @@ sendTo("parcelapp.0", "addDelivery", {
|
|
|
142
142
|
Placeholder for the next version (at the beginning of the line):
|
|
143
143
|
### **WORK IN PROGRESS**
|
|
144
144
|
-->
|
|
145
|
+
### 0.11.1 (2026-09-04)
|
|
146
|
+
|
|
147
|
+
- Fixed: The last-changed timestamp of a package kept its old label and had no description as long as the package did not move.
|
|
148
|
+
|
|
145
149
|
### 0.11.0 (2026-09-04)
|
|
146
150
|
|
|
147
151
|
- Fixed: Since version 0.10.3 the Test Connection button gave no response at all, and packages added from a script never showed up — both work again.
|
|
@@ -165,10 +169,6 @@ sendTo("parcelapp.0", "addDelivery", {
|
|
|
165
169
|
|
|
166
170
|
- Changed: Internal cleanup. No user-facing changes.
|
|
167
171
|
|
|
168
|
-
### 0.10.1 (2026-07-13) — stable
|
|
169
|
-
|
|
170
|
-
- Internal refactoring. No user-facing changes.
|
|
171
|
-
|
|
172
172
|
[Older changelogs can be found there](CHANGELOG_OLD.md)
|
|
173
173
|
|
|
174
174
|
## Support
|
|
@@ -264,14 +264,20 @@ class StateManager {
|
|
|
264
264
|
const changed = await Promise.all(
|
|
265
265
|
stateDefs.map(([id, name, type, role, val, desc]) => this.createAndSet(id, name, type, role, val, desc))
|
|
266
266
|
);
|
|
267
|
+
await this.ensureStateObject(
|
|
268
|
+
`${devicePath}.lastUpdated`,
|
|
269
|
+
(0, import_i18n.tName)("lastUpdated"),
|
|
270
|
+
"string",
|
|
271
|
+
"date",
|
|
272
|
+
(0, import_i18n.tName)("descLastUpdated")
|
|
273
|
+
);
|
|
267
274
|
if (changed.some(Boolean)) {
|
|
268
275
|
await this.createAndSet(
|
|
269
276
|
`${devicePath}.lastUpdated`,
|
|
270
277
|
(0, import_i18n.tName)("lastUpdated"),
|
|
271
278
|
"string",
|
|
272
279
|
"date",
|
|
273
|
-
(/* @__PURE__ */ new Date()).toISOString()
|
|
274
|
-
(0, import_i18n.tName)("descLastUpdated")
|
|
280
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
275
281
|
);
|
|
276
282
|
}
|
|
277
283
|
}
|
|
@@ -634,15 +640,39 @@ class StateManager {
|
|
|
634
640
|
* state was new) — the DB-backed "did anything change" signal driving
|
|
635
641
|
* `lastUpdated` (v0.10.0, M5)
|
|
636
642
|
*/
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
643
|
+
/**
|
|
644
|
+
* Write a state's OBJECT once per process — name, description, type and role.
|
|
645
|
+
*
|
|
646
|
+
* Split out of {@link createAndSet} in v0.11.1 because `lastUpdated` writes its VALUE only
|
|
647
|
+
* when the tracking data actually changed. With the object write welded to the value write,
|
|
648
|
+
* its object was refreshed only on that same condition — so on a quiet installation, where no
|
|
649
|
+
* package moves for days, the datapoint kept the `common` it was created with forever. Measured
|
|
650
|
+
* on a live install right after the v0.11.0 upgrade: all four `lastUpdated` states still
|
|
651
|
+
* carried no description while their 24 siblings already had one. Same class as
|
|
652
|
+
* `reference_abgeleiteter_wert_nur_bei_aenderung` — a write path behind a condition looks alive
|
|
653
|
+
* because the condition is usually true, and an outdated name in the tree is what gives it away.
|
|
654
|
+
*
|
|
655
|
+
* The object write is unconditional now; only the VALUE keeps its condition.
|
|
656
|
+
*
|
|
657
|
+
* @param id State ID relative to adapter namespace
|
|
658
|
+
* @param name Display name (translation object or plain string)
|
|
659
|
+
* @param type Value type
|
|
660
|
+
* @param role ioBroker role
|
|
661
|
+
* @param desc Short explanation, or undefined where there is nothing to explain
|
|
662
|
+
*/
|
|
663
|
+
async ensureStateObject(id, name, type, role, desc) {
|
|
664
|
+
if (this.createdIds.has(id)) {
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
const common = { name, type, role, read: true, write: false };
|
|
668
|
+
if (desc !== void 0) {
|
|
669
|
+
common.desc = desc;
|
|
645
670
|
}
|
|
671
|
+
await this.adapter.extendObject(id, { type: "state", common, native: {} });
|
|
672
|
+
this.createdIds.add(id);
|
|
673
|
+
}
|
|
674
|
+
async createAndSet(id, name, type, role, val, desc) {
|
|
675
|
+
await this.ensureStateObject(id, name, type, role, desc);
|
|
646
676
|
const result = await this.adapter.setStateChangedAsync(id, { val, ack: true });
|
|
647
677
|
return typeof result === "object" && result !== null && result.notChanged === false;
|
|
648
678
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/state-manager.ts"],
|
|
4
|
-
"sourcesContent": ["import { type AdapterInstance } from \"@iobroker/adapter-core\";\nimport { coerceFiniteNumber, oneLine } from \"./coerce\";\nimport { packageName, statusLabel, tName, tText } from \"./i18n\";\nimport type { ParcelDelivery, ParcelEvent } from \"./types\";\nimport { UNKNOWN_STATUS_CODE } from \"./types\";\n\n/** Status codes that have expected delivery date/time: 2=In Transit, 4=Out for Delivery, 8=Info Received */\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/** Max length of a sanitized package-id segment (collision suffix handles truncation clashes). */\nconst MAX_ID_LENGTH = 50;\n\n/**\n * v0.10.0 (I2): cap the parallel recursive deletes in cleanupDeliveries the\n * same way main.ts caps the update fan-out \u2014 a poll that suddenly loses many\n * packages must not flood the broker in one burst.\n */\nconst DELETE_BATCH_SIZE = 25;\n\n/** Manages ioBroker states for parcel deliveries */\nexport class StateManager {\n private adapter: AdapterInstance;\n /**\n * Cache of state IDs whose object has already been written this process.\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.10.0 (DP-5): package ids whose device object was ensured this process.\n * Replaces the former description+tracking signature map: with\n * `preserve: { common: [\"name\"] }` a rewrite never changed an existing\n * object's name anyway, so ensuring existence ONCE per process is the\n * honest version of what the signature cache actually did.\n */\n private readonly deviceEnsured = new Set<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 * 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 * L1: per-delivery-object memo for `parseStatus`. A poll parses the SAME\n * delivery object at up to four sites (active filter, updateDelivery,\n * updateSummary's isToday filter, combined window); memoizing keyed by the\n * object means the parse \u2014 and its drift debug line \u2014 runs ONCE per delivery\n * instead of per site. No reset needed: each poll's deliveries are fresh\n * objects (JSON.parse) and GC'd afterwards, and nothing holds a long-lived\n * delivery reference (idOwner/failedDeliveries/knownDeliveryIds store strings).\n */\n private readonly statusMemo = new WeakMap<ParcelDelivery, number>();\n\n /**\n * @param adapter The ioBroker adapter instance\n */\n constructor(adapter: AdapterInstance) {\n this.adapter = adapter;\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, MAX_ID_LENGTH) || \"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 // L1: memoize per delivery object so the parse (and its drift log) runs\n // once per delivery per poll, not once per call site.\n const memoized = this.statusMemo.get(delivery);\n if (memoized !== undefined) {\n return memoized;\n }\n const code = this.computeStatus(delivery);\n this.statusMemo.set(delivery, code);\n return code;\n }\n\n /**\n * Parse the status code without memoization. Split from {@link parseStatus}\n * (L1) so the memo wraps a single pure computation \u2014 including the one-per-\n * delivery drift log.\n *\n * @param delivery The delivery to parse\n */\n private computeStatus(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): 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 * Extract the package-id segment from a relative object id\n * (`deliveries.<pkgId>` or `deliveries.<pkgId>.<state>`); \"\" when the id is\n * outside the deliveries tree. Single source for the id-schema knowledge\n * (v0.10.0, L15).\n *\n * @param relativeId Object id relative to the adapter namespace\n */\n private static pkgIdOf(relativeId: string): string {\n return relativeId.startsWith(\"deliveries.\") ? relativeId.slice(\"deliveries.\".length).split(\".\")[0] : \"\";\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 pkgId Package id from the caller's deterministic pre-pass \u2014 always\n * computed via `packageId()` so the collision suffixing stays deterministic\n * (v0.10.0, L11: no test-only fallback path anymore).\n */\n async updateDelivery(delivery: ParcelDelivery, carrierName: string, pkgId: string): Promise<void> {\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.10.0 (DP-5): ensure the device object once per process. `preserve:\n // name` keeps an existing name (user renames win), so the name \u2014 localized\n // fallback when the API sends no description (L18) \u2014 only matters at\n // first creation; the former per-change rewrite never had a visible effect.\n if (!this.deviceEnsured.has(pkgId)) {\n await this.adapter.extendObject(\n devicePath,\n {\n type: \"device\",\n common: {\n name: description || packageName(trackingNumber || pkgId),\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n this.deviceEnsured.add(pkgId);\n }\n this.knownDeliveryIds?.add(pkgId);\n\n const statusCode = this.parseStatus(delivery);\n let statusText = statusLabel(statusCode);\n if (statusText === undefined) {\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 label table is out of date.\n this.adapter.log.debug(`status code ${statusCode} has no status_* label, 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 // v0.10.0 (M5): ONE definition list drives the writes AND the lastUpdated\n // decision \u2014 the former parallel JSON.stringify signature array was a\n // silent drift trap (a new field added to one list but not the other).\n // `desc` is an EXPLANATION, only where the name alone does not give it \u2014\n // `undefined` means \"nothing to explain here\", and an invented sentence\n // would be worse than none (fleet standard, 2026-09-02).\n const stateDefs: [\n id: string,\n name: ioBroker.StringOrTranslated,\n type: ioBroker.CommonType,\n role: string,\n val: ioBroker.StateValue,\n desc: ioBroker.StringOrTranslated | undefined,\n ][] = [\n [`${devicePath}.carrier`, tName(\"carrier\"), \"string\", \"text\", carrierName, undefined],\n [`${devicePath}.status`, tName(\"status\"), \"string\", \"text\", statusText, undefined],\n [`${devicePath}.statusCode`, tName(\"statusCode\"), \"number\", \"value\", statusCode, tName(\"descStatusCode\")],\n [`${devicePath}.description`, tName(\"description\"), \"string\", \"text\", description, undefined],\n [`${devicePath}.trackingNumber`, tName(\"trackingNumber\"), \"string\", \"text\", trackingNumber, undefined],\n [`${devicePath}.extraInfo`, tName(\"extraInfo\"), \"string\", \"text\", extraInfo, tName(\"descExtraInfo\")],\n [\n `${devicePath}.deliveryWindow`,\n tName(\"deliveryWindow\"),\n \"string\",\n \"text\",\n deliveryWindow,\n tName(\"descDeliveryWindow\"),\n ],\n [\n `${devicePath}.deliveryEstimate`,\n tName(\"deliveryEstimate\"),\n \"string\",\n \"text\",\n deliveryEstimate,\n tName(\"descDeliveryEstimate\"),\n ],\n [`${devicePath}.lastEvent`, tName(\"lastEvent\"), \"string\", \"text\", lastEvent, tName(\"descLastEvent\")],\n [`${devicePath}.lastLocation`, tName(\"lastLocation\"), \"string\", \"text\", lastLocation, undefined],\n ];\n const changed = await Promise.all(\n stateDefs.map(([id, name, type, role, val, desc]) => this.createAndSet(id, name, type, role, val, desc)),\n );\n\n // v0.10.0 (M5): `lastUpdated` = \"when the tracking data last CHANGED\".\n // The decision now rides on the broker's own setStateChanged answer\n // (notChanged=false \u21D2 a sibling value really differed in the DB), so an\n // adapter restart no longer stamps every package with a fresh timestamp \u2014\n // the old in-memory signature map always missed on the first poll after a\n // restart, and it was updated BEFORE its write survived (ASYNC-6).\n if (changed.some(Boolean)) {\n await this.createAndSet(\n `${devicePath}.lastUpdated`,\n tName(\"lastUpdated\"),\n \"string\",\n \"date\",\n new Date().toISOString(),\n tName(\"descLastUpdated\"),\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(\n \"summary.activeCount\",\n tName(\"activeCount\"),\n \"number\",\n \"value\",\n activeDeliveries.length,\n tName(\"descActiveCount\"),\n ),\n this.createAndSet(\n \"summary.todayCount\",\n tName(\"todayCount\"),\n \"number\",\n \"value\",\n todayDeliveries.length,\n tName(\"descTodayCount\"),\n ),\n this.createAndSet(\n \"summary.deliveryWindow\",\n tName(\"summaryDeliveryWindow\"),\n \"string\",\n \"text\",\n this.calculateCombinedWindow(todayDeliveries),\n tName(\"descSummaryDeliveryWindow\"),\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 // The range query guarantees the namespace prefix \u2014 cut it instead of\n // pattern-replacing (v0.10.0, KISS-13).\n const pkgId = StateManager.pkgIdOf(row.id.slice(this.adapter.namespace.length + 1));\n if (pkgId) {\n this.knownDeliveryIds.add(pkgId);\n }\n }\n }\n\n const keepSet = new Set(keepIds);\n // v0.4.2 (S1): collect first, then delete in parallel \u2014 capped in batches\n // (v0.10.0, I2) like the update fan-out in main.ts.\n const toDelete = [...this.knownDeliveryIds].filter(pkgId => !keepSet.has(pkgId));\n const toDeleteSet = new Set(toDelete);\n\n for (let start = 0; start < toDelete.length; start += DELETE_BATCH_SIZE) {\n const batch = toDelete.slice(start, start + DELETE_BATCH_SIZE);\n await Promise.all(\n batch.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.deviceEnsured.delete(pkgId);\n }),\n );\n }\n\n // v0.9.0 (S2): prune createdIds for every removed package in ONE pass over\n // the set \u2014 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 if (toDeleteSet.has(StateManager.pkgIdOf(id))) {\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 || Number.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 tText(\"estimateOverdue\");\n }\n if (diffDays === 0) {\n return tText(\"estimateToday\");\n }\n if (diffDays === 1) {\n return tText(\"estimateTomorrow\");\n }\n return tText(\"estimateDays\", 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 // L3: fold instead of Math.min/max(...spread). The bounds array is capped\n // by the 1 MiB response limit, but a spread over a large array can still hit\n // V8's argument-count limit (RangeError); reduce is O(n) and unbounded-safe\n // \u2014 consistent with beszel's computeMaxTemp hardening.\n const minStart = bounds.reduce((m, b) => (b.start < m ? b.start : m), bounds[0].start);\n const maxEnd = bounds.reduce((m, b) => {\n const e = b.end ?? b.start;\n return e > m ? e : m;\n }, bounds[0].end ?? bounds[0].start);\n return StateManager.formatWindow(minStart, maxEnd);\n }\n\n /**\n * Create/refresh a read-only state and set its value. Runs the object write\n * once per ID per process (the cache skips the repeat round-trip on the hot\n * path); only the value changes per poll.\n *\n * v0.11.0: `extendObject` instead of `setObjectNotExistsAsync`. The old call\n * only ever touched an object that did not exist yet, so a changed name,\n * description, role or type reached FRESH installs only \u2014 an existing tree\n * kept the text it was created with, while manifest, linter, type check and\n * the name gate all stayed green. Measured on a live install: the three\n * permanent `summary.*` states still carried their plain-English pre-i18n\n * names, while the per-package states looked correct only because packages\n * are deleted and recreated. `extendObject` merges, so a user-set `custom`\n * (history/logging) survives \u2014 the name deliberately does NOT: the adapter\n * owns the names of its own states.\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 * @param desc Short explanation, or undefined where there is nothing to explain\n * @returns true when the broker actually wrote the value (it differed or the\n * state was new) \u2014 the DB-backed \"did anything change\" signal driving\n * `lastUpdated` (v0.10.0, M5)\n */\n private async createAndSet(\n id: string,\n name: ioBroker.StringOrTranslated,\n type: ioBroker.CommonType,\n role: string,\n val: ioBroker.StateValue,\n desc?: ioBroker.StringOrTranslated,\n ): Promise<boolean> {\n if (!this.createdIds.has(id)) {\n const common: ioBroker.StateCommon = { name, type, role, read: true, write: false };\n if (desc !== undefined) {\n common.desc = desc;\n }\n await this.adapter.extendObject(id, { type: \"state\", common, native: {} });\n this.createdIds.add(id);\n }\n // The bundled @iobroker/types 7.1.2 types this promise as `string`, but\n // js-controller \u22657.2.2 (our dependency floor) resolves { id, notChanged }\n // \u2014 verified at v7.2.2: adapter.ts invokes the callback with\n // (null, res.id, res.notChanged) and tools.promisify(['id','notChanged'])\n // builds the object from exactly these named args. Narrow locally instead\n // of trusting the stale published type.\n const result: unknown = await this.adapter.setStateChangedAsync(id, { val, ack: true });\n // Only an explicit notChanged=false counts as a write \u2014 anything else\n // (missing field, drifted runtime) must not fake \"changed\" on every poll.\n return typeof result === \"object\" && result !== null && (result as { notChanged?: unknown }).notChanged === false;\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { type AdapterInstance } from \"@iobroker/adapter-core\";\nimport { coerceFiniteNumber, oneLine } from \"./coerce\";\nimport { packageName, statusLabel, tName, tText } from \"./i18n\";\nimport type { ParcelDelivery, ParcelEvent } from \"./types\";\nimport { UNKNOWN_STATUS_CODE } from \"./types\";\n\n/** Status codes that have expected delivery date/time: 2=In Transit, 4=Out for Delivery, 8=Info Received */\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/** Max length of a sanitized package-id segment (collision suffix handles truncation clashes). */\nconst MAX_ID_LENGTH = 50;\n\n/**\n * v0.10.0 (I2): cap the parallel recursive deletes in cleanupDeliveries the\n * same way main.ts caps the update fan-out \u2014 a poll that suddenly loses many\n * packages must not flood the broker in one burst.\n */\nconst DELETE_BATCH_SIZE = 25;\n\n/** Manages ioBroker states for parcel deliveries */\nexport class StateManager {\n private adapter: AdapterInstance;\n /**\n * Cache of state IDs whose object has already been written this process.\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.10.0 (DP-5): package ids whose device object was ensured this process.\n * Replaces the former description+tracking signature map: with\n * `preserve: { common: [\"name\"] }` a rewrite never changed an existing\n * object's name anyway, so ensuring existence ONCE per process is the\n * honest version of what the signature cache actually did.\n */\n private readonly deviceEnsured = new Set<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 * 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 * L1: per-delivery-object memo for `parseStatus`. A poll parses the SAME\n * delivery object at up to four sites (active filter, updateDelivery,\n * updateSummary's isToday filter, combined window); memoizing keyed by the\n * object means the parse \u2014 and its drift debug line \u2014 runs ONCE per delivery\n * instead of per site. No reset needed: each poll's deliveries are fresh\n * objects (JSON.parse) and GC'd afterwards, and nothing holds a long-lived\n * delivery reference (idOwner/failedDeliveries/knownDeliveryIds store strings).\n */\n private readonly statusMemo = new WeakMap<ParcelDelivery, number>();\n\n /**\n * @param adapter The ioBroker adapter instance\n */\n constructor(adapter: AdapterInstance) {\n this.adapter = adapter;\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, MAX_ID_LENGTH) || \"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 // L1: memoize per delivery object so the parse (and its drift log) runs\n // once per delivery per poll, not once per call site.\n const memoized = this.statusMemo.get(delivery);\n if (memoized !== undefined) {\n return memoized;\n }\n const code = this.computeStatus(delivery);\n this.statusMemo.set(delivery, code);\n return code;\n }\n\n /**\n * Parse the status code without memoization. Split from {@link parseStatus}\n * (L1) so the memo wraps a single pure computation \u2014 including the one-per-\n * delivery drift log.\n *\n * @param delivery The delivery to parse\n */\n private computeStatus(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): 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 * Extract the package-id segment from a relative object id\n * (`deliveries.<pkgId>` or `deliveries.<pkgId>.<state>`); \"\" when the id is\n * outside the deliveries tree. Single source for the id-schema knowledge\n * (v0.10.0, L15).\n *\n * @param relativeId Object id relative to the adapter namespace\n */\n private static pkgIdOf(relativeId: string): string {\n return relativeId.startsWith(\"deliveries.\") ? relativeId.slice(\"deliveries.\".length).split(\".\")[0] : \"\";\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 pkgId Package id from the caller's deterministic pre-pass \u2014 always\n * computed via `packageId()` so the collision suffixing stays deterministic\n * (v0.10.0, L11: no test-only fallback path anymore).\n */\n async updateDelivery(delivery: ParcelDelivery, carrierName: string, pkgId: string): Promise<void> {\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.10.0 (DP-5): ensure the device object once per process. `preserve:\n // name` keeps an existing name (user renames win), so the name \u2014 localized\n // fallback when the API sends no description (L18) \u2014 only matters at\n // first creation; the former per-change rewrite never had a visible effect.\n if (!this.deviceEnsured.has(pkgId)) {\n await this.adapter.extendObject(\n devicePath,\n {\n type: \"device\",\n common: {\n name: description || packageName(trackingNumber || pkgId),\n },\n native: {},\n },\n { preserve: { common: [\"name\"] } },\n );\n this.deviceEnsured.add(pkgId);\n }\n this.knownDeliveryIds?.add(pkgId);\n\n const statusCode = this.parseStatus(delivery);\n let statusText = statusLabel(statusCode);\n if (statusText === undefined) {\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 label table is out of date.\n this.adapter.log.debug(`status code ${statusCode} has no status_* label, 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 // v0.10.0 (M5): ONE definition list drives the writes AND the lastUpdated\n // decision \u2014 the former parallel JSON.stringify signature array was a\n // silent drift trap (a new field added to one list but not the other).\n // `desc` is an EXPLANATION, only where the name alone does not give it \u2014\n // `undefined` means \"nothing to explain here\", and an invented sentence\n // would be worse than none (fleet standard, 2026-09-02).\n const stateDefs: [\n id: string,\n name: ioBroker.StringOrTranslated,\n type: ioBroker.CommonType,\n role: string,\n val: ioBroker.StateValue,\n desc: ioBroker.StringOrTranslated | undefined,\n ][] = [\n [`${devicePath}.carrier`, tName(\"carrier\"), \"string\", \"text\", carrierName, undefined],\n [`${devicePath}.status`, tName(\"status\"), \"string\", \"text\", statusText, undefined],\n [`${devicePath}.statusCode`, tName(\"statusCode\"), \"number\", \"value\", statusCode, tName(\"descStatusCode\")],\n [`${devicePath}.description`, tName(\"description\"), \"string\", \"text\", description, undefined],\n [`${devicePath}.trackingNumber`, tName(\"trackingNumber\"), \"string\", \"text\", trackingNumber, undefined],\n [`${devicePath}.extraInfo`, tName(\"extraInfo\"), \"string\", \"text\", extraInfo, tName(\"descExtraInfo\")],\n [\n `${devicePath}.deliveryWindow`,\n tName(\"deliveryWindow\"),\n \"string\",\n \"text\",\n deliveryWindow,\n tName(\"descDeliveryWindow\"),\n ],\n [\n `${devicePath}.deliveryEstimate`,\n tName(\"deliveryEstimate\"),\n \"string\",\n \"text\",\n deliveryEstimate,\n tName(\"descDeliveryEstimate\"),\n ],\n [`${devicePath}.lastEvent`, tName(\"lastEvent\"), \"string\", \"text\", lastEvent, tName(\"descLastEvent\")],\n [`${devicePath}.lastLocation`, tName(\"lastLocation\"), \"string\", \"text\", lastLocation, undefined],\n ];\n const changed = await Promise.all(\n stateDefs.map(([id, name, type, role, val, desc]) => this.createAndSet(id, name, type, role, val, desc)),\n );\n\n // v0.10.0 (M5): `lastUpdated` = \"when the tracking data last CHANGED\".\n // The decision now rides on the broker's own setStateChanged answer\n // (notChanged=false \u21D2 a sibling value really differed in the DB), so an\n // adapter restart no longer stamps every package with a fresh timestamp \u2014\n // the old in-memory signature map always missed on the first poll after a\n // restart, and it was updated BEFORE its write survived (ASYNC-6).\n // The OBJECT is refreshed on every poll cycle (once per process, the cache sees to that) \u2014\n // only the VALUE stays behind the change condition. Welding the two together left this\n // datapoint with a stale `common` forever on a quiet installation (v0.11.1).\n await this.ensureStateObject(\n `${devicePath}.lastUpdated`,\n tName(\"lastUpdated\"),\n \"string\",\n \"date\",\n tName(\"descLastUpdated\"),\n );\n if (changed.some(Boolean)) {\n // No `desc` argument here on purpose: ensureStateObject above already wrote the object and\n // put the id in the cache, so anything passed along would be dead weight.\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(\n \"summary.activeCount\",\n tName(\"activeCount\"),\n \"number\",\n \"value\",\n activeDeliveries.length,\n tName(\"descActiveCount\"),\n ),\n this.createAndSet(\n \"summary.todayCount\",\n tName(\"todayCount\"),\n \"number\",\n \"value\",\n todayDeliveries.length,\n tName(\"descTodayCount\"),\n ),\n this.createAndSet(\n \"summary.deliveryWindow\",\n tName(\"summaryDeliveryWindow\"),\n \"string\",\n \"text\",\n this.calculateCombinedWindow(todayDeliveries),\n tName(\"descSummaryDeliveryWindow\"),\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 // The range query guarantees the namespace prefix \u2014 cut it instead of\n // pattern-replacing (v0.10.0, KISS-13).\n const pkgId = StateManager.pkgIdOf(row.id.slice(this.adapter.namespace.length + 1));\n if (pkgId) {\n this.knownDeliveryIds.add(pkgId);\n }\n }\n }\n\n const keepSet = new Set(keepIds);\n // v0.4.2 (S1): collect first, then delete in parallel \u2014 capped in batches\n // (v0.10.0, I2) like the update fan-out in main.ts.\n const toDelete = [...this.knownDeliveryIds].filter(pkgId => !keepSet.has(pkgId));\n const toDeleteSet = new Set(toDelete);\n\n for (let start = 0; start < toDelete.length; start += DELETE_BATCH_SIZE) {\n const batch = toDelete.slice(start, start + DELETE_BATCH_SIZE);\n await Promise.all(\n batch.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.deviceEnsured.delete(pkgId);\n }),\n );\n }\n\n // v0.9.0 (S2): prune createdIds for every removed package in ONE pass over\n // the set \u2014 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 if (toDeleteSet.has(StateManager.pkgIdOf(id))) {\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 || Number.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 tText(\"estimateOverdue\");\n }\n if (diffDays === 0) {\n return tText(\"estimateToday\");\n }\n if (diffDays === 1) {\n return tText(\"estimateTomorrow\");\n }\n return tText(\"estimateDays\", 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 // L3: fold instead of Math.min/max(...spread). The bounds array is capped\n // by the 1 MiB response limit, but a spread over a large array can still hit\n // V8's argument-count limit (RangeError); reduce is O(n) and unbounded-safe\n // \u2014 consistent with beszel's computeMaxTemp hardening.\n const minStart = bounds.reduce((m, b) => (b.start < m ? b.start : m), bounds[0].start);\n const maxEnd = bounds.reduce((m, b) => {\n const e = b.end ?? b.start;\n return e > m ? e : m;\n }, bounds[0].end ?? bounds[0].start);\n return StateManager.formatWindow(minStart, maxEnd);\n }\n\n /**\n * Create/refresh a read-only state and set its value. Runs the object write\n * once per ID per process (the cache skips the repeat round-trip on the hot\n * path); only the value changes per poll.\n *\n * v0.11.0: `extendObject` instead of `setObjectNotExistsAsync`. The old call\n * only ever touched an object that did not exist yet, so a changed name,\n * description, role or type reached FRESH installs only \u2014 an existing tree\n * kept the text it was created with, while manifest, linter, type check and\n * the name gate all stayed green. Measured on a live install: the three\n * permanent `summary.*` states still carried their plain-English pre-i18n\n * names, while the per-package states looked correct only because packages\n * are deleted and recreated. `extendObject` merges, so a user-set `custom`\n * (history/logging) survives \u2014 the name deliberately does NOT: the adapter\n * owns the names of its own states.\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 * @param desc Short explanation, or undefined where there is nothing to explain\n * @returns true when the broker actually wrote the value (it differed or the\n * state was new) \u2014 the DB-backed \"did anything change\" signal driving\n * `lastUpdated` (v0.10.0, M5)\n */\n /**\n * Write a state's OBJECT once per process \u2014 name, description, type and role.\n *\n * Split out of {@link createAndSet} in v0.11.1 because `lastUpdated` writes its VALUE only\n * when the tracking data actually changed. With the object write welded to the value write,\n * its object was refreshed only on that same condition \u2014 so on a quiet installation, where no\n * package moves for days, the datapoint kept the `common` it was created with forever. Measured\n * on a live install right after the v0.11.0 upgrade: all four `lastUpdated` states still\n * carried no description while their 24 siblings already had one. Same class as\n * `reference_abgeleiteter_wert_nur_bei_aenderung` \u2014 a write path behind a condition looks alive\n * because the condition is usually true, and an outdated name in the tree is what gives it away.\n *\n * The object write is unconditional now; only the VALUE keeps its condition.\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 desc Short explanation, or undefined where there is nothing to explain\n */\n private async ensureStateObject(\n id: string,\n name: ioBroker.StringOrTranslated,\n type: ioBroker.CommonType,\n role: string,\n desc?: ioBroker.StringOrTranslated,\n ): Promise<void> {\n if (this.createdIds.has(id)) {\n return;\n }\n const common: ioBroker.StateCommon = { name, type, role, read: true, write: false };\n if (desc !== undefined) {\n common.desc = desc;\n }\n await this.adapter.extendObject(id, { type: \"state\", common, native: {} });\n this.createdIds.add(id);\n }\n\n private async createAndSet(\n id: string,\n name: ioBroker.StringOrTranslated,\n type: ioBroker.CommonType,\n role: string,\n val: ioBroker.StateValue,\n desc?: ioBroker.StringOrTranslated,\n ): Promise<boolean> {\n await this.ensureStateObject(id, name, type, role, desc);\n // The bundled @iobroker/types 7.1.2 types this promise as `string`, but\n // js-controller \u22657.2.2 (our dependency floor) resolves { id, notChanged }\n // \u2014 verified at v7.2.2: adapter.ts invokes the callback with\n // (null, res.id, res.notChanged) and tools.promisify(['id','notChanged'])\n // builds the object from exactly these named args. Narrow locally instead\n // of trusting the stale published type.\n const result: unknown = await this.adapter.setStateChangedAsync(id, { val, ack: true });\n // Only an explicit notChanged=false counts as a write \u2014 anything else\n // (missing field, drifted runtime) must not fake \"changed\" on every poll.\n return typeof result === \"object\" && result !== null && (result as { notChanged?: unknown }).notChanged === false;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
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.11.
|
|
4
|
+
"version": "0.11.1",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.11.1": {
|
|
7
|
+
"en": "The last-changed timestamp of a package kept its old label and had no description as long as the package did not move.",
|
|
8
|
+
"de": "Der Zeitstempel der letzten Änderung einer Sendung behielt seine alte Bezeichnung und hatte keine Beschreibung, solange sich die Sendung nicht bewegte.",
|
|
9
|
+
"ru": "Отметка времени последнего изменения посылки сохраняла старое название и не имела описания, пока посылка не двигалась.",
|
|
10
|
+
"pt": "O carimbo da última alteração de uma encomenda mantinha o rótulo antigo e não tinha descrição enquanto a encomenda não se movia.",
|
|
11
|
+
"nl": "Het tijdstempel van de laatste wijziging van een zending behield zijn oude naam en had geen omschrijving zolang de zending niet bewoog.",
|
|
12
|
+
"fr": "L'horodatage de la dernière modification d'un colis gardait son ancien libellé et n'avait pas de description tant que le colis ne bougeait pas.",
|
|
13
|
+
"it": "Il timestamp dell'ultima modifica di una spedizione manteneva la vecchia etichetta e non aveva descrizione finché il pacco non si muoveva.",
|
|
14
|
+
"es": "La marca de tiempo del último cambio de un paquete conservaba su etiqueta antigua y no tenía descripción mientras el paquete no se movía.",
|
|
15
|
+
"pl": "Znacznik czasu ostatniej zmiany przesyłki zachowywał starą nazwę i nie miał opisu, dopóki przesyłka się nie poruszyła.",
|
|
16
|
+
"uk": "Позначка часу останньої зміни посилки зберігала стару назву й не мала опису, доки посилка не рухалася.",
|
|
17
|
+
"zh-cn": "只要包裹没有变动,其最后更新时间戳就一直保留旧名称且没有说明。"
|
|
18
|
+
},
|
|
6
19
|
"0.11.0": {
|
|
7
20
|
"en": "Since 0.10.3 the Test Connection button gave no response, and packages added from a script never showed up — both work again.\nAn update now reaches every datapoint of an existing installation: names and new short descriptions arrive in your system language.\nDetailed user documentation in English and German, plus two leftover settings from much older versions are cleaned up.",
|
|
8
21
|
"de": "Seit 0.10.3 reagierte der Verbindungstest-Knopf gar nicht mehr, und per Skript hinzugefügte Pakete tauchten nie auf — beides geht wieder.\nEin Update erreicht jetzt jeden Datenpunkt einer bestehenden Anlage: Namen und neue kurze Beschreibungen kommen in deiner Systemsprache an.\nAusführliche Nutzerdoku auf Englisch und Deutsch, und zwei Altlasten aus viel älteren Versionen werden aufgeräumt.",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
93
|
"pl": "Test połączenia w adminie zgłasza teraz prawdziwe błędy.\nSpokojniejsze logi, stabilny wskaźnik połączenia i automatyczne odzyskiwanie po błędach.",
|
|
81
94
|
"uk": "Тест з'єднання в адмінці тепер показує реальні помилки.\nМенше повторів у лозі, стабільний індикатор зв'язку, автоматичне відновлення після збоїв.",
|
|
82
95
|
"zh-cn": "管理界面的连接测试现在会报告真实错误。\n日志更安静,连接状态更稳定,请求卡住或启动失败后可自动恢复。"
|
|
83
|
-
},
|
|
84
|
-
"0.9.0": {
|
|
85
|
-
"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.",
|
|
86
|
-
"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.",
|
|
87
|
-
"ru": "Отслеживаемые посылки больше не исчезают после временной ошибки или неожиданного ответа API, а многодневные окна доставки теперь показывают дату с обеих сторон.",
|
|
88
|
-
"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.",
|
|
89
|
-
"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.",
|
|
90
|
-
"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é.",
|
|
91
|
-
"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.",
|
|
92
|
-
"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.",
|
|
93
|
-
"pl": "Śledzone paczki nie znikają już po tymczasowym błędzie lub nieoczekiwanej odpowiedzi API, a wielodniowe okna dostawy pokazują teraz datę po obu stronach.",
|
|
94
|
-
"uk": "Відстежувані посилки більше не зникають після тимчасової помилки або неочікуваної відповіді API, а багатоденні вікна доставки тепер показують дату з обох боків.",
|
|
95
|
-
"zh-cn": "跟踪的包裹在临时错误或意外的 API 响应后不再消失,跨多天的派送时段现在会在两侧显示日期。"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"plugins": {
|