iobroker.parcelapp 0.5.1 → 0.5.3

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
@@ -123,26 +123,25 @@ The delivery is added to your parcel.app account and immediately appears in ioBr
123
123
  Placeholder for the next version (at the beginning of the line):
124
124
  ### **WORK IN PROGRESS**
125
125
  -->
126
- ### 0.5.1 (2026-05-23)
126
+ ### 0.5.3 (2026-05-23)
127
127
 
128
- - CI check-and-lint updated to Node.js 24
128
+ - Reduced unnecessary state-change events by skipping writes when the value has not changed.
129
129
 
130
- ### 0.5.0 (2026-05-22)
130
+ ### 0.5.2 (2026-05-23)
131
131
 
132
- - User-modified state names are no longer overwritten on adapter restart
133
- - State name translations migrated to standard ioBroker i18n framework
132
+ - Changelog rewritten in user-centric style across all versions.
134
133
 
135
- ### 0.4.9 (2026-05-21)
134
+ ### 0.5.1 (2026-05-23)
136
135
 
137
- - Use community-standard event handler pattern
136
+ - Internal cleanup. No user-facing changes.
138
137
 
139
- ### 0.4.8 (2026-05-19)
138
+ ### 0.5.0 (2026-05-22)
140
139
 
141
- - Code quality enforced with standard formatting.
140
+ - User-modified state names are no longer overwritten on adapter restart
142
141
 
143
- ### 0.4.7 (2026-05-17)
142
+ ### 0.4.9 (2026-05-21)
144
143
 
145
- - Internal cleanup. No user-facing changes.
144
+ - Improved error handling and stability.
146
145
 
147
146
  Older entries are in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
148
147
 
@@ -435,7 +435,7 @@ class StateManager {
435
435
  });
436
436
  this.createdIds.add(id);
437
437
  }
438
- await this.adapter.setStateAsync(id, { val, ack: true });
438
+ await this.adapter.setStateChangedAsync(id, { val, ack: true });
439
439
  }
440
440
  }
441
441
  // Annotate the CommonJS export names for ESM import in node:
@@ -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 } from \"./types\";\n\n/** Status codes that have expected delivery date/time */\nconst TRACKABLE_STATUSES = new Set([2, 4, 8]);\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 * @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. API documents `status_code` as\n * a numeric string, but we accept numbers too and fall back to 0 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 return Number.isFinite(n) ? n : 0;\n }\n // v0.4.3 (C2): trace status_code drift (non-string/non-number). Without\n // this, the fallback to 0 (\"delivered\") silently makes packages disappear\n // from active states with no log entry explaining why.\n this.adapter.log.debug(`parseStatus drift: ${JSON.stringify(raw)} (type ${typeof raw}) \u2192 0 (delivered fallback)`);\n return 0;\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 */\n async updateDelivery(delivery: ParcelDelivery, carrierName: string): Promise<void> {\n const pkgId = 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 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\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 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(\n `${devicePath}.deliveryWindow`,\n tName(\"deliveryWindow\"),\n \"string\",\n \"text\",\n this.calculateDeliveryWindow(delivery, statusCode),\n ),\n this.createAndSet(\n `${devicePath}.deliveryEstimate`,\n tName(\"deliveryEstimate\"),\n \"string\",\n \"text\",\n this.calculateDeliveryEstimate(delivery, statusCode),\n ),\n this.createAndSet(\n `${devicePath}.lastEvent`,\n tName(\"lastEvent\"),\n \"string\",\n \"text\",\n this.formatLastEvent(delivery),\n ),\n this.createAndSet(\n `${devicePath}.lastLocation`,\n tName(\"lastLocation\"),\n \"string\",\n \"text\",\n this.extractLastLocation(delivery),\n ),\n this.createAndSet(`${devicePath}.lastUpdated`, tName(\"lastUpdated\"), \"string\", \"date\", new Date().toISOString()),\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 const activeSet = new Set(activeIds.map(id => `deliveries.${id}`));\n\n const objects = await this.adapter.getObjectViewAsync(\"system\", \"device\", {\n startkey: `${this.adapter.namespace}.deliveries.`,\n endkey: `${this.adapter.namespace}.deliveries.\u9999`,\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.\n this.adapter.log.debug(\"cleanupDeliveries: no objects view available, skipping\");\n return;\n }\n\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: string[] = [];\n for (const row of objects.rows) {\n const relativeId = row.id.replace(`${this.adapter.namespace}.`, \"\");\n if (relativeId.startsWith(\"deliveries.\") && !activeSet.has(relativeId)) {\n toDelete.push(relativeId);\n }\n }\n\n await Promise.all(\n toDelete.map(async relativeId => {\n await this.adapter.delObjectAsync(relativeId, { recursive: true });\n this.adapter.log.debug(`Removed stale delivery: ${relativeId}`);\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 }\n\n /**\n * Calculate delivery time window \u2014 only from Unix timestamps.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private calculateDeliveryWindow(delivery: ParcelDelivery, statusCode: number): string {\n if (!TRACKABLE_STATUSES.has(statusCode)) {\n return \"\";\n }\n\n const formatTime = (timestamp: unknown): string | null => {\n const ts = coerceFiniteNumber(timestamp);\n if (ts === null || ts <= 0) {\n return null;\n }\n const d = new Date(ts * 1000);\n if (Number.isNaN(d.getTime())) {\n return null;\n }\n return `${d.getHours().toString().padStart(2, \"0\")}:${d.getMinutes().toString().padStart(2, \"0\")}`;\n };\n\n const start = formatTime(delivery.timestamp_expected);\n const end = formatTime(delivery.timestamp_expected_end);\n\n if (!start) {\n return \"\";\n }\n return end ? `${start} - ${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 if (typeof delivery.date_expected === \"string\" && delivery.date_expected.length > 0) {\n expectedDate = new Date(delivery.date_expected);\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 * Calculate combined delivery window for today's packages.\n *\n * @param todayDeliveries Deliveries expected today\n */\n private calculateCombinedWindow(todayDeliveries: ParcelDelivery[]): string {\n const windows = todayDeliveries\n .map(d => this.calculateDeliveryWindow(d, this.parseStatus(d)))\n .filter(w => w.length > 0);\n\n if (windows.length === 0) {\n return \"\";\n }\n if (windows.length === 1) {\n return windows[0];\n }\n\n const times: {\n /** Window start */ start: string;\n /** Window end */ end: string;\n }[] = [];\n for (const w of windows) {\n const match = w.match(/(\\d{2}:\\d{2})(?:\\s*-\\s*(\\d{2}:\\d{2}))?/);\n if (match) {\n times.push({ start: match[1], end: match[2] || match[1] });\n }\n }\n\n if (times.length === 0) {\n return \"\";\n }\n\n times.sort((a, b) => a.start.localeCompare(b.start));\n return `${times[0].start} - ${times[times.length - 1].end}`;\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.setStateAsync(id, { val, ack: true });\n }\n}\n"],
5
- "mappings": "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",
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 } from \"./types\";\n\n/** Status codes that have expected delivery date/time */\nconst TRACKABLE_STATUSES = new Set([2, 4, 8]);\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 * @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. API documents `status_code` as\n * a numeric string, but we accept numbers too and fall back to 0 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 return Number.isFinite(n) ? n : 0;\n }\n // v0.4.3 (C2): trace status_code drift (non-string/non-number). Without\n // this, the fallback to 0 (\"delivered\") silently makes packages disappear\n // from active states with no log entry explaining why.\n this.adapter.log.debug(`parseStatus drift: ${JSON.stringify(raw)} (type ${typeof raw}) \u2192 0 (delivered fallback)`);\n return 0;\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 */\n async updateDelivery(delivery: ParcelDelivery, carrierName: string): Promise<void> {\n const pkgId = 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 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\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 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(\n `${devicePath}.deliveryWindow`,\n tName(\"deliveryWindow\"),\n \"string\",\n \"text\",\n this.calculateDeliveryWindow(delivery, statusCode),\n ),\n this.createAndSet(\n `${devicePath}.deliveryEstimate`,\n tName(\"deliveryEstimate\"),\n \"string\",\n \"text\",\n this.calculateDeliveryEstimate(delivery, statusCode),\n ),\n this.createAndSet(\n `${devicePath}.lastEvent`,\n tName(\"lastEvent\"),\n \"string\",\n \"text\",\n this.formatLastEvent(delivery),\n ),\n this.createAndSet(\n `${devicePath}.lastLocation`,\n tName(\"lastLocation\"),\n \"string\",\n \"text\",\n this.extractLastLocation(delivery),\n ),\n this.createAndSet(`${devicePath}.lastUpdated`, tName(\"lastUpdated\"), \"string\", \"date\", new Date().toISOString()),\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 const activeSet = new Set(activeIds.map(id => `deliveries.${id}`));\n\n const objects = await this.adapter.getObjectViewAsync(\"system\", \"device\", {\n startkey: `${this.adapter.namespace}.deliveries.`,\n endkey: `${this.adapter.namespace}.deliveries.\u9999`,\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.\n this.adapter.log.debug(\"cleanupDeliveries: no objects view available, skipping\");\n return;\n }\n\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: string[] = [];\n for (const row of objects.rows) {\n const relativeId = row.id.replace(`${this.adapter.namespace}.`, \"\");\n if (relativeId.startsWith(\"deliveries.\") && !activeSet.has(relativeId)) {\n toDelete.push(relativeId);\n }\n }\n\n await Promise.all(\n toDelete.map(async relativeId => {\n await this.adapter.delObjectAsync(relativeId, { recursive: true });\n this.adapter.log.debug(`Removed stale delivery: ${relativeId}`);\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 }\n\n /**\n * Calculate delivery time window \u2014 only from Unix timestamps.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private calculateDeliveryWindow(delivery: ParcelDelivery, statusCode: number): string {\n if (!TRACKABLE_STATUSES.has(statusCode)) {\n return \"\";\n }\n\n const formatTime = (timestamp: unknown): string | null => {\n const ts = coerceFiniteNumber(timestamp);\n if (ts === null || ts <= 0) {\n return null;\n }\n const d = new Date(ts * 1000);\n if (Number.isNaN(d.getTime())) {\n return null;\n }\n return `${d.getHours().toString().padStart(2, \"0\")}:${d.getMinutes().toString().padStart(2, \"0\")}`;\n };\n\n const start = formatTime(delivery.timestamp_expected);\n const end = formatTime(delivery.timestamp_expected_end);\n\n if (!start) {\n return \"\";\n }\n return end ? `${start} - ${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 if (typeof delivery.date_expected === \"string\" && delivery.date_expected.length > 0) {\n expectedDate = new Date(delivery.date_expected);\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 * Calculate combined delivery window for today's packages.\n *\n * @param todayDeliveries Deliveries expected today\n */\n private calculateCombinedWindow(todayDeliveries: ParcelDelivery[]): string {\n const windows = todayDeliveries\n .map(d => this.calculateDeliveryWindow(d, this.parseStatus(d)))\n .filter(w => w.length > 0);\n\n if (windows.length === 0) {\n return \"\";\n }\n if (windows.length === 1) {\n return windows[0];\n }\n\n const times: {\n /** Window start */ start: string;\n /** Window end */ end: string;\n }[] = [];\n for (const w of windows) {\n const match = w.match(/(\\d{2}:\\d{2})(?:\\s*-\\s*(\\d{2}:\\d{2}))?/);\n if (match) {\n times.push({ start: match[1], end: match[2] || match[1] });\n }\n }\n\n if (times.length === 0) {\n return \"\";\n }\n\n times.sort((a, b) => a.start.localeCompare(b.start));\n return `${times[0].start} - ${times[times.length - 1].end}`;\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
  }
package/build/main.js CHANGED
@@ -260,7 +260,7 @@ class ParcelappAdapter extends utils.Adapter {
260
260
  this.log.info("Connection restored");
261
261
  this.lastErrorCode = "";
262
262
  }
263
- await this.setStateAsync("info.connection", { val: true, ack: true });
263
+ await this.setStateChangedAsync("info.connection", { val: true, ack: true });
264
264
  const activeDeliveries = deliveries.filter((d) => this.stateManager.parseStatus(d) !== 0);
265
265
  const visibleDeliveries = autoRemoveMode ? activeDeliveries : deliveries;
266
266
  this.stateManager.resetPollState();
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 { StateManager } from \"./lib/state-manager\";\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/** ioBroker adapter for parcel.app package tracking */\nclass ParcelappAdapter extends utils.Adapter {\n private client: ParcelClient | null = null;\n private stateManager: StateManager | null = null;\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 private unhandledRejectionHandler: ((reason: unknown) => void) | null = null;\n private uncaughtExceptionHandler: ((err: Error) => void) | null = null;\n /** ioBroker system language \u2014 read once in `onReady` from `system.config`. EN fallback. */\n private systemLang: string = \"en\";\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 // Safety net for fire-and-forget paths (e.g. `void this.poll()`).\n this.unhandledRejectionHandler = (reason: unknown) => {\n this.log.error(`Unhandled rejection: ${errText(reason)}`);\n this.terminate?.(11);\n };\n this.uncaughtExceptionHandler = (err: Error) => {\n this.log.error(`Uncaught exception: ${errText(err)}`);\n this.terminate?.(11);\n };\n process.on(\"unhandledRejection\", this.unhandledRejectionHandler);\n process.on(\"uncaughtException\", this.uncaughtExceptionHandler);\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 if (typeof language === \"string\" && language.length > 0) {\n this.systemLang = language;\n }\n this.log.debug(`system language: '${language}' \u2192 using '${this.systemLang}'`);\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 = new ParcelClient(apiKey.trim(), { debug: (m: string) => this.log.debug(m) });\n this.stateManager = new StateManager(this, 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(() => void this.poll(), 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 if (this.unhandledRejectionHandler) {\n process.off(\"unhandledRejection\", this.unhandledRejectionHandler);\n this.unhandledRejectionHandler = null;\n }\n if (this.uncaughtExceptionHandler) {\n process.off(\"uncaughtException\", this.uncaughtExceptionHandler);\n this.uncaughtExceptionHandler = null;\n }\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.\n const testClient = new ParcelClient(key, { debug: (m: string) => this.log.debug(m) });\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 const request = obj.message as {\n tracking_number: string;\n carrier_code: string;\n description: string;\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 void this.poll();\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(): 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\n if (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.setStateAsync(\"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 per-poll collision tracker so the bare-id wins\n // for the first occurrence in this poll (deterministic, back-compat).\n this.stateManager.resetPollState();\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 => {\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);\n this.failedDeliveries.delete(delivery.tracking_number);\n return this.stateManager!.packageId(delivery);\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 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 await this.setStateAsync(\"info.connection\", { val: false, ack: true });\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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",
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 { StateManager } from \"./lib/state-manager\";\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/** ioBroker adapter for parcel.app package tracking */\nclass ParcelappAdapter extends utils.Adapter {\n private client: ParcelClient | null = null;\n private stateManager: StateManager | null = null;\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 private unhandledRejectionHandler: ((reason: unknown) => void) | null = null;\n private uncaughtExceptionHandler: ((err: Error) => void) | null = null;\n /** ioBroker system language \u2014 read once in `onReady` from `system.config`. EN fallback. */\n private systemLang: string = \"en\";\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 // Safety net for fire-and-forget paths (e.g. `void this.poll()`).\n this.unhandledRejectionHandler = (reason: unknown) => {\n this.log.error(`Unhandled rejection: ${errText(reason)}`);\n this.terminate?.(11);\n };\n this.uncaughtExceptionHandler = (err: Error) => {\n this.log.error(`Uncaught exception: ${errText(err)}`);\n this.terminate?.(11);\n };\n process.on(\"unhandledRejection\", this.unhandledRejectionHandler);\n process.on(\"uncaughtException\", this.uncaughtExceptionHandler);\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 if (typeof language === \"string\" && language.length > 0) {\n this.systemLang = language;\n }\n this.log.debug(`system language: '${language}' \u2192 using '${this.systemLang}'`);\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 = new ParcelClient(apiKey.trim(), { debug: (m: string) => this.log.debug(m) });\n this.stateManager = new StateManager(this, 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(() => void this.poll(), 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 if (this.unhandledRejectionHandler) {\n process.off(\"unhandledRejection\", this.unhandledRejectionHandler);\n this.unhandledRejectionHandler = null;\n }\n if (this.uncaughtExceptionHandler) {\n process.off(\"uncaughtException\", this.uncaughtExceptionHandler);\n this.uncaughtExceptionHandler = null;\n }\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.\n const testClient = new ParcelClient(key, { debug: (m: string) => this.log.debug(m) });\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 const request = obj.message as {\n tracking_number: string;\n carrier_code: string;\n description: string;\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 void this.poll();\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(): 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\n if (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 per-poll collision tracker so the bare-id wins\n // for the first occurrence in this poll (deterministic, back-compat).\n this.stateManager.resetPollState();\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 => {\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);\n this.failedDeliveries.delete(delivery.tracking_number);\n return this.stateManager!.packageId(delivery);\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 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 await this.setStateAsync(\"info.connection\", { val: false, ack: true });\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
6
  "names": []
7
7
  }
package/io-package.json CHANGED
@@ -1,59 +1,85 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "parcelapp",
4
- "version": "0.5.1",
4
+ "version": "0.5.3",
5
5
  "news": {
6
+ "0.5.3": {
7
+ "en": "Reduced unnecessary state-change events by skipping writes when the value has not changed.",
8
+ "de": "Unnötige State-Change-Events reduziert, indem Schreibvorgänge bei unverändertem Wert übersprungen werden.",
9
+ "ru": "Сокращены ненужные события изменения состояний путём пропуска записи при неизменённом значении.",
10
+ "pt": "Eventos de mudança de estado desnecessários reduzidos ao ignorar escritas quando o valor não mudou.",
11
+ "nl": "Onnodige state-change-events verminderd door schrijfacties over te slaan als de waarde niet is gewijzigd.",
12
+ "fr": "Réduction des événements de changement d'état inutiles en ignorant les écritures lorsque la valeur n'a pas changé.",
13
+ "it": "Ridotti gli eventi di cambio stato non necessari saltando le scritture quando il valore non è cambiato.",
14
+ "es": "Reducidos los eventos de cambio de estado innecesarios omitiendo escrituras cuando el valor no ha cambiado.",
15
+ "pl": "Zmniejszono niepotrzebne zdarzenia zmiany stanu, pomijając zapisy gdy wartość się nie zmieniła.",
16
+ "uk": "Зменшено непотрібні події зміни стану шляхом пропуску запису при незміненому значенні.",
17
+ "zh-cn": "通过在值未更改时跳过写入来减少不必要的状态变更事件。"
18
+ },
19
+ "0.5.2": {
20
+ "en": "Changelog rewritten in user-centric style across all versions.",
21
+ "de": "Changelog in benutzerorientiertem Stil über alle Versionen neu geschrieben.",
22
+ "ru": "Журнал изменений переписан в пользовательском стиле для всех версий.",
23
+ "pt": "Registo de alterações reescrito em estilo centrado no utilizador em todas as versões.",
24
+ "nl": "Changelog herschreven in gebruikersgerichte stijl voor alle versies.",
25
+ "fr": "Journal des modifications réécrit dans un style centré sur l'utilisateur pour toutes les versions.",
26
+ "it": "Registro delle modifiche riscritto in stile orientato all'utente per tutte le versioni.",
27
+ "es": "Registro de cambios reescrito en estilo centrado en el usuario en todas las versiones.",
28
+ "pl": "Dziennik zmian przepisany w stylu zorientowanym na użytkownika we wszystkich wersjach.",
29
+ "uk": "Журнал змін переписано у стилі, орієнтованому на користувача, для всіх версій.",
30
+ "zh-cn": "所有版本的更新日志已重写为以用户为中心的风格。"
31
+ },
6
32
  "0.5.1": {
7
- "en": "CI check-and-lint updated to Node.js 24",
8
- "de": "CI check-and-lint auf Node.js 24 aktualisiert",
9
- "ru": "CI check-and-lint обновлён до Node.js 24",
10
- "pt": "CI check-and-lint atualizado para Node.js 24",
11
- "nl": "CI check-and-lint bijgewerkt naar Node.js 24",
12
- "fr": "CI check-and-lint mis à jour vers Node.js 24",
13
- "it": "CI check-and-lint aggiornato a Node.js 24",
14
- "es": "CI check-and-lint actualizado a Node.js 24",
15
- "pl": "CI check-and-lint zaktualizowano do Node.js 24",
16
- "uk": "CI check-and-lint оновлено до Node.js 24",
17
- "zh-cn": "CI check-and-lint 更新至 Node.js 24"
33
+ "en": "Internal cleanup. No user-facing changes.",
34
+ "de": "Interne Bereinigung. Keine Änderungen für Benutzer.",
35
+ "ru": "Внутренняя очистка. Нет изменений для пользователей.",
36
+ "pt": "Limpeza interna. Sem alterações para o utilizador.",
37
+ "nl": "Interne opruiming. Geen wijzigingen voor gebruikers.",
38
+ "fr": "Nettoyage interne. Aucun changement pour l'utilisateur.",
39
+ "it": "Pulizia interna. Nessuna modifica per l'utente.",
40
+ "es": "Limpieza interna. Sin cambios para el usuario.",
41
+ "pl": "Wewnętrzne porządki. Brak zmian dla użytkownika.",
42
+ "uk": "Внутрішнє очищення. Без змін для користувачів.",
43
+ "zh-cn": "内部清理。无用户可见的更改。"
18
44
  },
19
45
  "0.5.0": {
20
- "en": "Preserve user-modified state names; state translations use standard ioBroker i18n",
21
- "de": "Vom Benutzer geänderte Datenpunktnamen bleiben erhalten; Übersetzungen nutzen Standard-ioBroker-i18n",
22
- "ru": "Пользовательские имена точек данных сохраняются; переводы используют стандартный i18n ioBroker",
23
- "pt": "Nomes de pontos de dados modificados pelo utilizador são preservados; traduções usam i18n padrão do ioBroker",
24
- "nl": "Door gebruiker gewijzigde namen van datapunten blijven behouden; vertalingen gebruiken standaard ioBroker-i18n",
25
- "fr": "Les noms de points de données modifiés par l'utilisateur sont conservés ; les traductions utilisent l'i18n standard d'ioBroker",
26
- "it": "I nomi dei punti dati modificati dall'utente vengono preservati; le traduzioni usano l'i18n standard di ioBroker",
27
- "es": "Los nombres de puntos de datos modificados por el usuario se conservan; las traducciones usan i18n estándar de ioBroker",
28
- "pl": "Nazwy punktów danych zmienione przez użytkownika są zachowywane; tłumaczenia używają standardowego i18n ioBroker",
29
- "uk": "Змінені користувачем назви точок даних зберігаються; переклади використовують стандартний i18n ioBroker",
30
- "zh-cn": "保留用户修改的数据点名称;翻译使用标准 ioBroker i18n"
46
+ "en": "User-modified state names are no longer overwritten on adapter restart",
47
+ "de": "Vom Benutzer geänderte Datenpunktnamen werden beim Adapter-Neustart nicht mehr überschrieben",
48
+ "ru": "Имена состояний, изменённые пользователем, больше не перезаписываются при перезапуске адаптера",
49
+ "pt": "Nomes de estados modificados pelo utilizador não são sobrescritos ao reiniciar o adaptador",
50
+ "nl": "Door de gebruiker aangepaste statusnamen worden niet meer overschreven bij herstart van de adapter",
51
+ "fr": "Les noms d'états modifiés par l'utilisateur ne sont plus écrasés au redémarrage de l'adaptateur",
52
+ "it": "I nomi degli stati modificati dall'utente non vengono più sovrascritti al riavvio dell'adattatore",
53
+ "es": "Los nombres de estados modificados por el usuario ya no se sobrescriben al reiniciar el adaptador",
54
+ "pl": "Nazwy stanów zmienione przez użytkownika nie już nadpisywane przy restarcie adaptera",
55
+ "uk": "Назви станів, змінені користувачем, більше не перезаписуються при перезапуску адаптера",
56
+ "zh-cn": "用户修改的状态名称在适配器重启时不再被覆盖"
31
57
  },
32
58
  "0.4.9": {
33
- "en": "Use community-standard event handler pattern",
34
- "de": "Community-Standard Event-Handler-Pattern verwenden",
35
- "ru": "Использование стандартного паттерна обработчика событий сообщества",
36
- "pt": "Usar padrão de manipulador de eventos padrão da comunidade",
37
- "nl": "Community-standaard event handler patroon gebruiken",
38
- "fr": "Utiliser le modèle de gestionnaire d'événements standard de la communauté",
39
- "it": "Utilizzare il pattern standard della community per i gestori degli eventi",
40
- "es": "Usar el patrón de manejador de eventos estándar de la comunidad",
41
- "pl": "Użyj standardowego wzorca obsługi zdarzeń społeczności",
42
- "uk": "Використання стандартного шаблону обробника подій спільноти",
43
- "zh-cn": "使用社区标准事件处理程序模式"
59
+ "en": "Improved error handling and stability.",
60
+ "de": "Verbesserte Fehlerbehandlung und Stabilität.",
61
+ "ru": "Улучшена обработка ошибок и стабильность.",
62
+ "pt": "Melhor tratamento de erros e estabilidade.",
63
+ "nl": "Verbeterde foutafhandeling en stabiliteit.",
64
+ "fr": "Amélioration de la gestion des erreurs et de la stabilité.",
65
+ "it": "Migliorata la gestione degli errori e la stabilità.",
66
+ "es": "Mejora del manejo de errores y la estabilidad.",
67
+ "pl": "Poprawiona obsługa błędów i stabilność.",
68
+ "uk": "Покращено обробку помилок та стабільність.",
69
+ "zh-cn": "改进了错误处理和稳定性。"
44
70
  },
45
71
  "0.4.8": {
46
- "en": "Code quality enforced with standard formatting.",
47
- "de": "Codequalität mit standardisierter Formatierung durchgesetzt.",
48
- "ru": "Обеспечено качество кода со стандартным форматированием.",
49
- "pt": "Qualidade do código reforçada com formatação padrão.",
50
- "nl": "Codekwaliteit afgedwongen met standaardopmaak.",
51
- "fr": "Qualité du code appliquée avec un formatage standard.",
52
- "it": "Qualità del codice applicata con formattazione standard.",
53
- "es": "Calidad del código aplicada con formato estándar.",
54
- "pl": "Jakość kodu wymuszona ze standardowym formatowaniem.",
55
- "uk": "Якість коду забезпечена стандартним форматуванням.",
56
- "zh-cn": "通过标准格式强制执行代码质量。"
72
+ "en": "Internal cleanup. No user-facing changes.",
73
+ "de": "Interne Bereinigung. Keine Änderungen für Benutzer.",
74
+ "ru": "Внутренняя очистка. Нет изменений для пользователей.",
75
+ "pt": "Limpeza interna. Sem alterações para o utilizador.",
76
+ "nl": "Interne opruiming. Geen wijzigingen voor gebruikers.",
77
+ "fr": "Nettoyage interne. Aucun changement pour l'utilisateur.",
78
+ "it": "Pulizia interna. Nessuna modifica per l'utente.",
79
+ "es": "Limpieza interna. Sin cambios para el usuario.",
80
+ "pl": "Wewnętrzne porządki. Brak zmian dla użytkownika.",
81
+ "uk": "Внутрішнє очищення. Без змін для користувачів.",
82
+ "zh-cn": "内部清理。无用户可见的更改。"
57
83
  },
58
84
  "0.4.7": {
59
85
  "en": "Internal cleanup. No user-facing changes.",
@@ -67,32 +93,6 @@
67
93
  "pl": "Wewnętrzne porządki. Brak widocznych zmian dla użytkownika.",
68
94
  "uk": "Внутрішнє прибирання. Без видимих змін для користувача.",
69
95
  "zh-cn": "内部清理。无用户可见的更改。"
70
- },
71
- "0.4.6": {
72
- "en": "Localized adapter info and connection status labels into 11 languages.",
73
- "de": "Adapter-Info und Verbindungsstatus-Labels in 11 Sprachen lokalisiert.",
74
- "ru": "Локализованы метки информации адаптера и статуса подключения на 11 языков.",
75
- "pt": "Etiquetas de informação do adaptador e estado de ligação localizadas em 11 idiomas.",
76
- "nl": "Adapterinformatie- en verbindingsstatuslabels gelokaliseerd in 11 talen.",
77
- "fr": "Libellés d'information adaptateur et état de connexion localisés en 11 langues.",
78
- "it": "Etichette informazioni adattatore e stato connessione localizzate in 11 lingue.",
79
- "es": "Etiquetas de información del adaptador y estado de conexión localizadas en 11 idiomas.",
80
- "pl": "Zlokalizowano etykiety informacji adaptera i statusu połączenia w 11 językach.",
81
- "uk": "Локалізовано мітки інформації адаптера та стану з'єднання 11 мовами.",
82
- "zh-cn": "将适配器信息和连接状态标签本地化为11种语言。"
83
- },
84
- "0.4.5": {
85
- "en": "Fixed adapter icon not displaying in some admin environments (Content Security Policy).",
86
- "de": "Adapter-Icon wird jetzt auch in Admin-Umgebungen mit Content Security Policy korrekt angezeigt.",
87
- "ru": "Исправлено отображение иконки адаптера в средах администрирования с Content Security Policy.",
88
- "pt": "Corrigido o ícone do adaptador que não era exibido em alguns ambientes de administração (Content Security Policy).",
89
- "nl": "Adapter-icoon wordt nu correct weergegeven in admin-omgevingen met Content Security Policy.",
90
- "fr": "Correction de l'icône de l'adaptateur qui ne s'affichait pas dans certains environnements d'administration (Content Security Policy).",
91
- "it": "Corretta l'icona dell'adattatore che non veniva visualizzata in alcuni ambienti di amministrazione (Content Security Policy).",
92
- "es": "Corregido el icono del adaptador que no se mostraba en algunos entornos de administración (Content Security Policy).",
93
- "pl": "Naprawiono wyświetlanie ikony adaptera w niektórych środowiskach administracyjnych (Content Security Policy).",
94
- "uk": "Виправлено відображення іконки адаптера в деяких середовищах адміністрування (Content Security Policy).",
95
- "zh-cn": "修复了在某些管理环境中适配器图标因内容安全策略而无法显示的问题。"
96
96
  }
97
97
  },
98
98
  "titleLang": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.parcelapp",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "ioBroker adapter for the parcel.app API",
5
5
  "author": {
6
6
  "name": "krobi",