iobroker.parcelapp 0.2.17 → 0.2.18
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 +8 -3
- package/build/lib/parcel-client.js +4 -19
- package/build/lib/parcel-client.js.map +2 -2
- package/build/lib/state-manager.js +14 -86
- package/build/lib/state-manager.js.map +2 -2
- package/build/main.js +17 -61
- package/build/main.js.map +2 -2
- package/io-package.json +14 -14
- package/package.json +24 -2
package/README.md
CHANGED
|
@@ -115,6 +115,14 @@ The delivery is added to your parcel.app account and immediately appears in ioBr
|
|
|
115
115
|
---
|
|
116
116
|
|
|
117
117
|
## Changelog
|
|
118
|
+
### 0.2.18 (2026-04-28)
|
|
119
|
+
- Audit cleanup against the upstream `ioBroker.example/TypeScript` full standard:
|
|
120
|
+
- `@types/node` rolled back from `^25.6.0` to `^20.19.24` so type defs match `engines.node: ">=20"` (otherwise Node 21+-only APIs would type-check on Node 20 and crash at runtime)
|
|
121
|
+
- Dependabot now ignores major bumps for `@types/node`, `typescript`, `eslint` so the runtime/toolchain pinning cannot drift via auto-merge
|
|
122
|
+
- `nyc` config + `coverage` script added (matches upstream template)
|
|
123
|
+
- `prettier.config.mjs` made explicit with project-style overrides (Spaces 2-wide, double quotes) instead of relying on missing config
|
|
124
|
+
- Orphan `.github/auto-merge.yml` removed (the active workflow is `automerge-dependabot.yml` using `gh pr merge`, the old yml was never read)
|
|
125
|
+
|
|
118
126
|
### 0.2.17 (2026-04-28)
|
|
119
127
|
- Test setup migrated to the upstream `ioBroker.example/TypeScript` standard: tests now live next to source as `src/**/*.test.ts` and run directly via `ts-node/register`, no separate test-build. Removes `tsconfig.test.json` + `build-test/` per latest-repo review feedback.
|
|
120
128
|
|
|
@@ -134,9 +142,6 @@ The delivery is added to your parcel.app account and immediately appears in ioBr
|
|
|
134
142
|
- Localize status labels and delivery estimates to all 11 ioBroker languages via `system.config.language`; the per-instance `Status Language` option is removed.
|
|
135
143
|
- Fix `summary.todayCount` for non-DE/EN languages (filter compared estimate strings against `heute`/`today`, so it always returned zero elsewhere).
|
|
136
144
|
|
|
137
|
-
### 0.2.13 (2026-04-19)
|
|
138
|
-
- Latest-repo review compliance: `common.messagebox=true` added because the admin-UI `Check Connection` and `Add Delivery` buttons route through `onMessage`. Runtime behaviour unchanged.
|
|
139
|
-
|
|
140
145
|
## Support
|
|
141
146
|
|
|
142
147
|
- [ioBroker Forum](https://forum.iobroker.net/)
|
|
@@ -60,11 +60,7 @@ class ParcelClient {
|
|
|
60
60
|
* @param filterMode Filter active or recent deliveries
|
|
61
61
|
*/
|
|
62
62
|
async getDeliveries(filterMode = "active") {
|
|
63
|
-
const response = await this.request(
|
|
64
|
-
"GET",
|
|
65
|
-
`/deliveries/?filter_mode=${filterMode}`,
|
|
66
|
-
true
|
|
67
|
-
);
|
|
63
|
+
const response = await this.request("GET", `/deliveries/?filter_mode=${filterMode}`, true);
|
|
68
64
|
if (!response || typeof response !== "object") {
|
|
69
65
|
const err = new Error("API error: malformed response");
|
|
70
66
|
err.code = "API_ERROR";
|
|
@@ -86,12 +82,7 @@ class ParcelClient {
|
|
|
86
82
|
* @param delivery The delivery to add
|
|
87
83
|
*/
|
|
88
84
|
async addDelivery(delivery) {
|
|
89
|
-
return this.request(
|
|
90
|
-
"POST",
|
|
91
|
-
"/add-delivery/",
|
|
92
|
-
true,
|
|
93
|
-
delivery
|
|
94
|
-
);
|
|
85
|
+
return this.request("POST", "/add-delivery/", true, delivery);
|
|
95
86
|
}
|
|
96
87
|
/** Get carrier names (cached after first call) */
|
|
97
88
|
async getCarrierNames() {
|
|
@@ -99,11 +90,7 @@ class ParcelClient {
|
|
|
99
90
|
return this.carrierCache;
|
|
100
91
|
}
|
|
101
92
|
try {
|
|
102
|
-
const raw = await this.request(
|
|
103
|
-
"GET",
|
|
104
|
-
"/supported_carriers.json",
|
|
105
|
-
false
|
|
106
|
-
);
|
|
93
|
+
const raw = await this.request("GET", "/supported_carriers.json", false);
|
|
107
94
|
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
108
95
|
this.carrierCache = raw;
|
|
109
96
|
} else {
|
|
@@ -181,9 +168,7 @@ class ParcelClient {
|
|
|
181
168
|
reject(err2);
|
|
182
169
|
return;
|
|
183
170
|
}
|
|
184
|
-
const err = new Error(
|
|
185
|
-
`HTTP ${res.statusCode}: ${res.statusMessage}`
|
|
186
|
-
);
|
|
171
|
+
const err = new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`);
|
|
187
172
|
err.code = res.statusCode === 401 || res.statusCode === 403 ? "INVALID_API_KEY" : "HTTP_ERROR";
|
|
188
173
|
reject(err);
|
|
189
174
|
return;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/parcel-client.ts"],
|
|
4
|
-
"sourcesContent": ["import * as https from \"node:https\";\nimport type {
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAAuB;
|
|
4
|
+
"sourcesContent": ["import * as https from \"node:https\";\nimport type { ParcelApiResponse, ParcelDelivery, AddDeliveryRequest, AddDeliveryResponse, CarrierMap } from \"./types\";\n\nconst API_BASE = \"https://api.parcel.app/external\";\nconst REQUEST_TIMEOUT = 15_000;\n\n/**\n * Coerce API-drift boolean responses. parcel.app should return a real boolean\n * for `success`, but the guard accepts common string/number encodings too.\n *\n * @param v Value to interpret as a success flag\n */\nfunction isTrueish(v: unknown): boolean {\n if (typeof v === \"boolean\") {\n return v;\n }\n if (typeof v === \"number\") {\n return v === 1;\n }\n if (typeof v === \"string\") {\n const s = v.toLowerCase();\n return s === \"true\" || s === \"1\";\n }\n return false;\n}\n\n/** HTTP client for the parcel.app API */\nexport class ParcelClient {\n private apiKey: string;\n private carrierCache: CarrierMap | null = null;\n\n /** @param apiKey The parcel.app API key */\n constructor(apiKey: string) {\n this.apiKey = apiKey;\n }\n\n /**\n * Fetch deliveries from parcel.app.\n *\n * @param filterMode Filter active or recent deliveries\n */\n async getDeliveries(filterMode: \"active\" | \"recent\" = \"active\"): Promise<ParcelDelivery[]> {\n const response = await this.request<ParcelApiResponse>(\"GET\", `/deliveries/?filter_mode=${filterMode}`, true);\n\n // API-drift guard: response may be null or a non-object\n if (!response || typeof response !== \"object\") {\n const err = new Error(\"API error: malformed response\") as Error & {\n code: string;\n };\n err.code = \"API_ERROR\";\n throw err;\n }\n\n if (!isTrueish(response.success)) {\n const rawCode = typeof response.error_code === \"string\" ? response.error_code : \"\";\n const rawMsg = typeof response.error_message === \"string\" ? response.error_message : \"\";\n const code = rawCode || rawMsg || \"UNKNOWN\";\n const err = new Error(`API error: ${rawMsg || code}`) as Error & {\n code: string;\n };\n err.code = rawCode === \"INVALID_API_KEY\" ? \"INVALID_API_KEY\" : \"API_ERROR\";\n throw err;\n }\n\n // API-drift guard: deliveries must be an array\n return Array.isArray(response.deliveries) ? response.deliveries : [];\n }\n\n /**\n * Add a new delivery to parcel.app.\n *\n * @param delivery The delivery to add\n */\n async addDelivery(delivery: AddDeliveryRequest): Promise<AddDeliveryResponse> {\n return this.request<AddDeliveryResponse>(\"POST\", \"/add-delivery/\", true, delivery);\n }\n\n /** Get carrier names (cached after first call) */\n async getCarrierNames(): Promise<CarrierMap> {\n if (this.carrierCache) {\n return this.carrierCache;\n }\n\n try {\n const raw = await this.request<unknown>(\"GET\", \"/supported_carriers.json\", false);\n // API-drift guard: must be a plain object (not null, array, or primitive)\n if (raw && typeof raw === \"object\" && !Array.isArray(raw)) {\n this.carrierCache = raw as CarrierMap;\n } else {\n return {};\n }\n } catch {\n // Return empty map but don't cache it \u2014 allow retry next time\n return {};\n }\n\n return this.carrierCache;\n }\n\n /**\n * Resolve a carrier code to a display name.\n *\n * @param carrierCode The carrier code from API\n */\n async getCarrierName(carrierCode: unknown): Promise<string> {\n // API-drift guard: non-string codes fall back to \"UNKNOWN\"\n if (typeof carrierCode !== \"string\" || carrierCode.length === 0) {\n return \"UNKNOWN\";\n }\n const carriers = await this.getCarrierNames();\n const mapped = carriers[carrierCode];\n return typeof mapped === \"string\" && mapped.length > 0 ? mapped : carrierCode.toUpperCase();\n }\n\n /** Test if the API key is valid */\n async testConnection(): Promise<{ success: boolean; message: string }> {\n try {\n await this.getDeliveries(\"active\");\n return { success: true, message: \"Connection successful\" };\n } catch (err) {\n const error = err as Error & { code?: string };\n if (error.code === \"INVALID_API_KEY\") {\n return { success: false, message: \"Invalid API key\" };\n }\n return { success: false, message: error.message };\n }\n }\n\n /**\n * Execute an HTTP request against the parcel.app API.\n *\n * @param method HTTP method\n * @param path API path\n * @param authenticated Whether to send the API key\n * @param body Optional request body\n */\n private request<T>(method: string, path: string, authenticated: boolean, body?: unknown): Promise<T> {\n return new Promise((resolve, reject) => {\n const url = new URL(`${API_BASE}${path}`);\n\n const headers: Record<string, string> = {};\n if (authenticated) {\n headers[\"api-key\"] = this.apiKey;\n }\n if (body) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n const options: https.RequestOptions = {\n hostname: url.hostname,\n port: url.port || 443,\n path: url.pathname + url.search,\n method,\n headers,\n timeout: REQUEST_TIMEOUT,\n };\n\n const req = https.request(options, res => {\n const chunks: Buffer[] = [];\n\n res.on(\"error\", err => reject(err));\n res.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n res.on(\"end\", () => {\n const raw = Buffer.concat(chunks).toString(\"utf-8\");\n\n if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {\n if (res.statusCode === 429) {\n const retryAfter = parseInt(res.headers[\"retry-after\"] || \"\", 10);\n const err = new Error(\"Rate limit exceeded\") as Error & {\n code: string;\n retryAfterSeconds: number;\n };\n err.code = \"RATE_LIMITED\";\n // Use Retry-After header or default to 5 minutes\n err.retryAfterSeconds = retryAfter > 0 ? retryAfter : 5 * 60;\n reject(err);\n return;\n }\n const err = new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`) as Error & { code: string };\n err.code = res.statusCode === 401 || res.statusCode === 403 ? \"INVALID_API_KEY\" : \"HTTP_ERROR\";\n reject(err);\n return;\n }\n\n try {\n resolve(JSON.parse(raw) as T);\n } catch {\n reject(new Error(`JSON parse error: ${raw.substring(0, 200)}`));\n }\n });\n });\n\n req.on(\"timeout\", () => {\n req.destroy();\n reject(new Error(\"Request timeout\"));\n });\n\n req.on(\"error\", err => reject(err));\n\n if (body) {\n req.write(JSON.stringify(body));\n }\n req.end();\n });\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["err"]
|
|
7
7
|
}
|
|
@@ -183,48 +183,12 @@ class StateManager {
|
|
|
183
183
|
const labels = import_types.STATUS_LABELS[this.language];
|
|
184
184
|
const statusText = labels[statusCode] || `Unknown (${statusCode})`;
|
|
185
185
|
await Promise.all([
|
|
186
|
-
this.createAndSet(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
),
|
|
193
|
-
this.createAndSet(
|
|
194
|
-
`${devicePath}.status`,
|
|
195
|
-
"Status",
|
|
196
|
-
"string",
|
|
197
|
-
"text",
|
|
198
|
-
statusText
|
|
199
|
-
),
|
|
200
|
-
this.createAndSet(
|
|
201
|
-
`${devicePath}.statusCode`,
|
|
202
|
-
"Status Code",
|
|
203
|
-
"number",
|
|
204
|
-
"value",
|
|
205
|
-
statusCode
|
|
206
|
-
),
|
|
207
|
-
this.createAndSet(
|
|
208
|
-
`${devicePath}.description`,
|
|
209
|
-
"Description",
|
|
210
|
-
"string",
|
|
211
|
-
"text",
|
|
212
|
-
description
|
|
213
|
-
),
|
|
214
|
-
this.createAndSet(
|
|
215
|
-
`${devicePath}.trackingNumber`,
|
|
216
|
-
"Tracking Number",
|
|
217
|
-
"string",
|
|
218
|
-
"text",
|
|
219
|
-
trackingNumber
|
|
220
|
-
),
|
|
221
|
-
this.createAndSet(
|
|
222
|
-
`${devicePath}.extraInfo`,
|
|
223
|
-
"Extra Information",
|
|
224
|
-
"string",
|
|
225
|
-
"text",
|
|
226
|
-
extraInfo
|
|
227
|
-
),
|
|
186
|
+
this.createAndSet(`${devicePath}.carrier`, "Carrier", "string", "text", carrierName),
|
|
187
|
+
this.createAndSet(`${devicePath}.status`, "Status", "string", "text", statusText),
|
|
188
|
+
this.createAndSet(`${devicePath}.statusCode`, "Status Code", "number", "value", statusCode),
|
|
189
|
+
this.createAndSet(`${devicePath}.description`, "Description", "string", "text", description),
|
|
190
|
+
this.createAndSet(`${devicePath}.trackingNumber`, "Tracking Number", "string", "text", trackingNumber),
|
|
191
|
+
this.createAndSet(`${devicePath}.extraInfo`, "Extra Information", "string", "text", extraInfo),
|
|
228
192
|
this.createAndSet(
|
|
229
193
|
`${devicePath}.deliveryWindow`,
|
|
230
194
|
"Delivery Window",
|
|
@@ -239,13 +203,7 @@ class StateManager {
|
|
|
239
203
|
"text",
|
|
240
204
|
this.calculateDeliveryEstimate(delivery, statusCode)
|
|
241
205
|
),
|
|
242
|
-
this.createAndSet(
|
|
243
|
-
`${devicePath}.lastEvent`,
|
|
244
|
-
"Last Event",
|
|
245
|
-
"string",
|
|
246
|
-
"text",
|
|
247
|
-
this.formatLastEvent(delivery)
|
|
248
|
-
),
|
|
206
|
+
this.createAndSet(`${devicePath}.lastEvent`, "Last Event", "string", "text", this.formatLastEvent(delivery)),
|
|
249
207
|
this.createAndSet(
|
|
250
208
|
`${devicePath}.lastLocation`,
|
|
251
209
|
"Last Location",
|
|
@@ -253,13 +211,7 @@ class StateManager {
|
|
|
253
211
|
"text",
|
|
254
212
|
this.extractLastLocation(delivery)
|
|
255
213
|
),
|
|
256
|
-
this.createAndSet(
|
|
257
|
-
`${devicePath}.lastUpdated`,
|
|
258
|
-
"Last Updated",
|
|
259
|
-
"string",
|
|
260
|
-
"date",
|
|
261
|
-
(/* @__PURE__ */ new Date()).toISOString()
|
|
262
|
-
)
|
|
214
|
+
this.createAndSet(`${devicePath}.lastUpdated`, "Last Updated", "string", "date", (/* @__PURE__ */ new Date()).toISOString())
|
|
263
215
|
]);
|
|
264
216
|
}
|
|
265
217
|
/**
|
|
@@ -269,24 +221,10 @@ class StateManager {
|
|
|
269
221
|
* @param activeDeliveries Only active (non-delivered) deliveries
|
|
270
222
|
*/
|
|
271
223
|
async updateSummary(activeDeliveries) {
|
|
272
|
-
const todayDeliveries = activeDeliveries.filter(
|
|
273
|
-
(d) => this.isToday(d, this.parseStatus(d))
|
|
274
|
-
);
|
|
224
|
+
const todayDeliveries = activeDeliveries.filter((d) => this.isToday(d, this.parseStatus(d)));
|
|
275
225
|
await Promise.all([
|
|
276
|
-
this.createAndSet(
|
|
277
|
-
|
|
278
|
-
"Active Deliveries",
|
|
279
|
-
"number",
|
|
280
|
-
"value",
|
|
281
|
-
activeDeliveries.length
|
|
282
|
-
),
|
|
283
|
-
this.createAndSet(
|
|
284
|
-
"summary.todayCount",
|
|
285
|
-
"Deliveries Today",
|
|
286
|
-
"number",
|
|
287
|
-
"value",
|
|
288
|
-
todayDeliveries.length
|
|
289
|
-
),
|
|
226
|
+
this.createAndSet("summary.activeCount", "Active Deliveries", "number", "value", activeDeliveries.length),
|
|
227
|
+
this.createAndSet("summary.todayCount", "Deliveries Today", "number", "value", todayDeliveries.length),
|
|
290
228
|
this.createAndSet(
|
|
291
229
|
"summary.deliveryWindow",
|
|
292
230
|
"Combined Delivery Window",
|
|
@@ -365,19 +303,9 @@ class StateManager {
|
|
|
365
303
|
return null;
|
|
366
304
|
}
|
|
367
305
|
const now = /* @__PURE__ */ new Date();
|
|
368
|
-
const todayStart = new Date(
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
now.getDate()
|
|
372
|
-
);
|
|
373
|
-
const expectedStart = new Date(
|
|
374
|
-
expectedDate.getFullYear(),
|
|
375
|
-
expectedDate.getMonth(),
|
|
376
|
-
expectedDate.getDate()
|
|
377
|
-
);
|
|
378
|
-
return Math.round(
|
|
379
|
-
(expectedStart.getTime() - todayStart.getTime()) / (1e3 * 60 * 60 * 24)
|
|
380
|
-
);
|
|
306
|
+
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
307
|
+
const expectedStart = new Date(expectedDate.getFullYear(), expectedDate.getMonth(), expectedDate.getDate());
|
|
308
|
+
return Math.round((expectedStart.getTime() - todayStart.getTime()) / (1e3 * 60 * 60 * 24));
|
|
381
309
|
}
|
|
382
310
|
/**
|
|
383
311
|
* Calculate human-readable delivery estimate.
|
|
@@ -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 type { ParcelDelivery } 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 * Coerce a value to a finite number. Accepts numbers and numeric strings.\n * Returns null for anything else \u2014 used to guard against API drift.\n *\n * @param v Value to coerce\n */\nfunction coerceNumber(v: unknown): number | null {\n if (typeof v === \"number\" && Number.isFinite(v)) {\n return v;\n }\n if (typeof v === \"string\" && v.length > 0) {\n const n = parseFloat(v);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\n/** Delivery-estimate labels keyed by language code. Keys must match STATUS_LABELS. */\nconst ESTIMATE_LABELS: Record<string, Record<string, string>> = {\n de: {\n overdue: \"\u00FCberf\u00E4llig\",\n today: \"heute\",\n tomorrow: \"morgen\",\n days: \"in %d Tagen\",\n },\n en: {\n overdue: \"overdue\",\n today: \"today\",\n tomorrow: \"tomorrow\",\n days: \"in %d days\",\n },\n ru: {\n overdue: \"\u043F\u0440\u043E\u0441\u0440\u043E\u0447\u0435\u043D\u043E\",\n today: \"\u0441\u0435\u0433\u043E\u0434\u043D\u044F\",\n tomorrow: \"\u0437\u0430\u0432\u0442\u0440\u0430\",\n days: \"\u0447\u0435\u0440\u0435\u0437 %d \u0434\u043D.\",\n },\n pt: {\n overdue: \"atrasado\",\n today: \"hoje\",\n tomorrow: \"amanh\u00E3\",\n days: \"em %d dias\",\n },\n nl: {\n overdue: \"te laat\",\n today: \"vandaag\",\n tomorrow: \"morgen\",\n days: \"over %d dagen\",\n },\n fr: {\n overdue: \"en retard\",\n today: \"aujourd'hui\",\n tomorrow: \"demain\",\n days: \"dans %d jours\",\n },\n it: {\n overdue: \"in ritardo\",\n today: \"oggi\",\n tomorrow: \"domani\",\n days: \"tra %d giorni\",\n },\n es: {\n overdue: \"atrasado\",\n today: \"hoy\",\n tomorrow: \"ma\u00F1ana\",\n days: \"en %d d\u00EDas\",\n },\n pl: {\n overdue: \"zaleg\u0142e\",\n today: \"dzisiaj\",\n tomorrow: \"jutro\",\n days: \"za %d dni\",\n },\n uk: {\n overdue: \"\u043F\u0440\u043E\u0441\u0442\u0440\u043E\u0447\u0435\u043D\u043E\",\n today: \"\u0441\u044C\u043E\u0433\u043E\u0434\u043D\u0456\",\n tomorrow: \"\u0437\u0430\u0432\u0442\u0440\u0430\",\n days: \"\u0447\u0435\u0440\u0435\u0437 %d \u0434\u043D.\",\n },\n \"zh-cn\": {\n overdue: \"\u5DF2\u903E\u671F\",\n today: \"\u4ECA\u5929\",\n tomorrow: \"\u660E\u5929\",\n days: \"%d \u5929\u540E\",\n },\n};\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 /**\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 return 0;\n }\n\n /**\n * Build a unique package ID from a delivery.\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 (\n typeof delivery.extra_information === \"string\" &&\n delivery.extra_information.length > 0\n ) {\n id += `_${this.sanitize(delivery.extra_information)}`;\n }\n return id;\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(\n delivery: ParcelDelivery,\n carrierName: string,\n ): Promise<void> {\n const pkgId = this.packageId(delivery);\n const devicePath = `deliveries.${pkgId}`;\n\n const description =\n typeof delivery.description === \"string\" ? delivery.description : \"\";\n const trackingNumber =\n typeof delivery.tracking_number === \"string\"\n ? delivery.tracking_number\n : \"\";\n const extraInfo =\n typeof delivery.extra_information === \"string\"\n ? delivery.extra_information\n : \"\";\n\n await this.adapter.extendObjectAsync(devicePath, {\n type: \"device\",\n common: {\n name: description || `Package ${trackingNumber || pkgId}`,\n },\n native: {},\n });\n\n const statusCode = this.parseStatus(delivery);\n const labels = STATUS_LABELS[this.language];\n const statusText = labels[statusCode] || `Unknown (${statusCode})`;\n\n await Promise.all([\n this.createAndSet(\n `${devicePath}.carrier`,\n \"Carrier\",\n \"string\",\n \"text\",\n carrierName,\n ),\n this.createAndSet(\n `${devicePath}.status`,\n \"Status\",\n \"string\",\n \"text\",\n statusText,\n ),\n this.createAndSet(\n `${devicePath}.statusCode`,\n \"Status Code\",\n \"number\",\n \"value\",\n statusCode,\n ),\n this.createAndSet(\n `${devicePath}.description`,\n \"Description\",\n \"string\",\n \"text\",\n description,\n ),\n this.createAndSet(\n `${devicePath}.trackingNumber`,\n \"Tracking Number\",\n \"string\",\n \"text\",\n trackingNumber,\n ),\n this.createAndSet(\n `${devicePath}.extraInfo`,\n \"Extra Information\",\n \"string\",\n \"text\",\n extraInfo,\n ),\n this.createAndSet(\n `${devicePath}.deliveryWindow`,\n \"Delivery Window\",\n \"string\",\n \"text\",\n this.calculateDeliveryWindow(delivery, statusCode),\n ),\n this.createAndSet(\n `${devicePath}.deliveryEstimate`,\n \"Delivery Estimate\",\n \"string\",\n \"text\",\n this.calculateDeliveryEstimate(delivery, statusCode),\n ),\n this.createAndSet(\n `${devicePath}.lastEvent`,\n \"Last Event\",\n \"string\",\n \"text\",\n this.formatLastEvent(delivery),\n ),\n this.createAndSet(\n `${devicePath}.lastLocation`,\n \"Last Location\",\n \"string\",\n \"text\",\n this.extractLastLocation(delivery),\n ),\n this.createAndSet(\n `${devicePath}.lastUpdated`,\n \"Last Updated\",\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) =>\n this.isToday(d, this.parseStatus(d)),\n );\n\n await Promise.all([\n this.createAndSet(\n \"summary.activeCount\",\n \"Active Deliveries\",\n \"number\",\n \"value\",\n activeDeliveries.length,\n ),\n this.createAndSet(\n \"summary.todayCount\",\n \"Deliveries Today\",\n \"number\",\n \"value\",\n todayDeliveries.length,\n ),\n this.createAndSet(\n \"summary.deliveryWindow\",\n \"Combined Delivery Window\",\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\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 await this.adapter.delObjectAsync(relativeId, { recursive: true });\n this.adapter.log.debug(`Removed stale delivery: ${relativeId}`);\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(\n delivery: ParcelDelivery,\n statusCode: number,\n ): string {\n if (!TRACKABLE_STATUSES.has(statusCode)) {\n return \"\";\n }\n\n const formatTime = (timestamp: unknown): string | null => {\n const ts = coerceNumber(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(\n delivery: ParcelDelivery,\n statusCode: number,\n ): number | null {\n if (!TRACKABLE_STATUSES.has(statusCode)) {\n return null;\n }\n\n let expectedDate: Date | null = null;\n const ts = coerceNumber(delivery.timestamp_expected);\n if (ts !== null && ts > 0) {\n expectedDate = new Date(ts * 1000);\n } else if (\n typeof delivery.date_expected === \"string\" &&\n delivery.date_expected.length > 0\n ) {\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(\n now.getFullYear(),\n now.getMonth(),\n now.getDate(),\n );\n const expectedStart = new Date(\n expectedDate.getFullYear(),\n expectedDate.getMonth(),\n expectedDate.getDate(),\n );\n return Math.round(\n (expectedStart.getTime() - todayStart.getTime()) / (1000 * 60 * 60 * 24),\n );\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(\n delivery: ParcelDelivery,\n statusCode: number,\n ): string {\n const diffDays = this.computeDiffDays(delivery, statusCode);\n if (diffDays === null) {\n return \"\";\n }\n const l = ESTIMATE_LABELS[this.language];\n if (diffDays < 0) {\n return l.overdue;\n }\n if (diffDays === 0) {\n return l.today;\n }\n if (diffDays === 1) {\n return l.tomorrow;\n }\n return l.days.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 /**\n * Format the latest tracking event.\n *\n * @param delivery The delivery data\n */\n private formatLastEvent(delivery: ParcelDelivery): string {\n if (!Array.isArray(delivery.events) || delivery.events.length === 0) {\n return \"\";\n }\n const latest = delivery.events[0];\n if (!latest || typeof latest !== \"object\") {\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 /**\n * Extract location from latest event.\n *\n * @param delivery The delivery data\n */\n private extractLastLocation(delivery: ParcelDelivery): string {\n if (!Array.isArray(delivery.events) || delivery.events.length === 0) {\n return \"\";\n }\n const latest = delivery.events[0];\n if (!latest || typeof latest !== \"object\") {\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.\n *\n * @param id State ID relative to adapter namespace\n * @param name Display name\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: string,\n type: ioBroker.CommonType,\n role: string,\n val: ioBroker.StateValue,\n ): Promise<void> {\n await this.adapter.setObjectNotExistsAsync(id, {\n type: \"state\",\n common: { name, type, role, read: true, write: false },\n native: {},\n });\n await this.adapter.setStateAsync(id, { val, ack: true });\n }\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import type { AdapterInstance } from \"@iobroker/adapter-core\";\nimport type { ParcelDelivery } 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 * Coerce a value to a finite number. Accepts numbers and numeric strings.\n * Returns null for anything else \u2014 used to guard against API drift.\n *\n * @param v Value to coerce\n */\nfunction coerceNumber(v: unknown): number | null {\n if (typeof v === \"number\" && Number.isFinite(v)) {\n return v;\n }\n if (typeof v === \"string\" && v.length > 0) {\n const n = parseFloat(v);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\n/** Delivery-estimate labels keyed by language code. Keys must match STATUS_LABELS. */\nconst ESTIMATE_LABELS: Record<string, Record<string, string>> = {\n de: {\n overdue: \"\u00FCberf\u00E4llig\",\n today: \"heute\",\n tomorrow: \"morgen\",\n days: \"in %d Tagen\",\n },\n en: {\n overdue: \"overdue\",\n today: \"today\",\n tomorrow: \"tomorrow\",\n days: \"in %d days\",\n },\n ru: {\n overdue: \"\u043F\u0440\u043E\u0441\u0440\u043E\u0447\u0435\u043D\u043E\",\n today: \"\u0441\u0435\u0433\u043E\u0434\u043D\u044F\",\n tomorrow: \"\u0437\u0430\u0432\u0442\u0440\u0430\",\n days: \"\u0447\u0435\u0440\u0435\u0437 %d \u0434\u043D.\",\n },\n pt: {\n overdue: \"atrasado\",\n today: \"hoje\",\n tomorrow: \"amanh\u00E3\",\n days: \"em %d dias\",\n },\n nl: {\n overdue: \"te laat\",\n today: \"vandaag\",\n tomorrow: \"morgen\",\n days: \"over %d dagen\",\n },\n fr: {\n overdue: \"en retard\",\n today: \"aujourd'hui\",\n tomorrow: \"demain\",\n days: \"dans %d jours\",\n },\n it: {\n overdue: \"in ritardo\",\n today: \"oggi\",\n tomorrow: \"domani\",\n days: \"tra %d giorni\",\n },\n es: {\n overdue: \"atrasado\",\n today: \"hoy\",\n tomorrow: \"ma\u00F1ana\",\n days: \"en %d d\u00EDas\",\n },\n pl: {\n overdue: \"zaleg\u0142e\",\n today: \"dzisiaj\",\n tomorrow: \"jutro\",\n days: \"za %d dni\",\n },\n uk: {\n overdue: \"\u043F\u0440\u043E\u0441\u0442\u0440\u043E\u0447\u0435\u043D\u043E\",\n today: \"\u0441\u044C\u043E\u0433\u043E\u0434\u043D\u0456\",\n tomorrow: \"\u0437\u0430\u0432\u0442\u0440\u0430\",\n days: \"\u0447\u0435\u0440\u0435\u0437 %d \u0434\u043D.\",\n },\n \"zh-cn\": {\n overdue: \"\u5DF2\u903E\u671F\",\n today: \"\u4ECA\u5929\",\n tomorrow: \"\u660E\u5929\",\n days: \"%d \u5929\u540E\",\n },\n};\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 /**\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 return 0;\n }\n\n /**\n * Build a unique package ID from a delivery.\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 return id;\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(devicePath, {\n type: \"device\",\n common: {\n name: description || `Package ${trackingNumber || pkgId}`,\n },\n native: {},\n });\n\n const statusCode = this.parseStatus(delivery);\n const labels = STATUS_LABELS[this.language];\n const statusText = labels[statusCode] || `Unknown (${statusCode})`;\n\n await Promise.all([\n this.createAndSet(`${devicePath}.carrier`, \"Carrier\", \"string\", \"text\", carrierName),\n this.createAndSet(`${devicePath}.status`, \"Status\", \"string\", \"text\", statusText),\n this.createAndSet(`${devicePath}.statusCode`, \"Status Code\", \"number\", \"value\", statusCode),\n this.createAndSet(`${devicePath}.description`, \"Description\", \"string\", \"text\", description),\n this.createAndSet(`${devicePath}.trackingNumber`, \"Tracking Number\", \"string\", \"text\", trackingNumber),\n this.createAndSet(`${devicePath}.extraInfo`, \"Extra Information\", \"string\", \"text\", extraInfo),\n this.createAndSet(\n `${devicePath}.deliveryWindow`,\n \"Delivery Window\",\n \"string\",\n \"text\",\n this.calculateDeliveryWindow(delivery, statusCode),\n ),\n this.createAndSet(\n `${devicePath}.deliveryEstimate`,\n \"Delivery Estimate\",\n \"string\",\n \"text\",\n this.calculateDeliveryEstimate(delivery, statusCode),\n ),\n this.createAndSet(`${devicePath}.lastEvent`, \"Last Event\", \"string\", \"text\", this.formatLastEvent(delivery)),\n this.createAndSet(\n `${devicePath}.lastLocation`,\n \"Last Location\",\n \"string\",\n \"text\",\n this.extractLastLocation(delivery),\n ),\n this.createAndSet(`${devicePath}.lastUpdated`, \"Last Updated\", \"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\n await Promise.all([\n this.createAndSet(\"summary.activeCount\", \"Active Deliveries\", \"number\", \"value\", activeDeliveries.length),\n this.createAndSet(\"summary.todayCount\", \"Deliveries Today\", \"number\", \"value\", todayDeliveries.length),\n this.createAndSet(\n \"summary.deliveryWindow\",\n \"Combined Delivery Window\",\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\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 await this.adapter.delObjectAsync(relativeId, { recursive: true });\n this.adapter.log.debug(`Removed stale delivery: ${relativeId}`);\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 = coerceNumber(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 = coerceNumber(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 const l = ESTIMATE_LABELS[this.language];\n if (diffDays < 0) {\n return l.overdue;\n }\n if (diffDays === 0) {\n return l.today;\n }\n if (diffDays === 1) {\n return l.tomorrow;\n }\n return l.days.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 /**\n * Format the latest tracking event.\n *\n * @param delivery The delivery data\n */\n private formatLastEvent(delivery: ParcelDelivery): string {\n if (!Array.isArray(delivery.events) || delivery.events.length === 0) {\n return \"\";\n }\n const latest = delivery.events[0];\n if (!latest || typeof latest !== \"object\") {\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 /**\n * Extract location from latest event.\n *\n * @param delivery The delivery data\n */\n private extractLastLocation(delivery: ParcelDelivery): string {\n if (!Array.isArray(delivery.events) || delivery.events.length === 0) {\n return \"\";\n }\n const latest = delivery.events[0];\n if (!latest || typeof latest !== \"object\") {\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.\n *\n * @param id State ID relative to adapter namespace\n * @param name Display name\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: string,\n type: ioBroker.CommonType,\n role: string,\n val: ioBroker.StateValue,\n ): Promise<void> {\n await this.adapter.setObjectNotExistsAsync(id, {\n type: \"state\",\n common: { name, type, role, read: true, write: false },\n native: {},\n });\n await this.adapter.setStateAsync(id, { val, ack: true });\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/build/main.js
CHANGED
|
@@ -49,15 +49,11 @@ class ParcelappAdapter extends utils.Adapter {
|
|
|
49
49
|
name: "parcelapp"
|
|
50
50
|
});
|
|
51
51
|
this.on("ready", () => {
|
|
52
|
-
this.onReady().catch(
|
|
53
|
-
(err) => this.log.error(`onReady failed: ${errText(err)}`)
|
|
54
|
-
);
|
|
52
|
+
this.onReady().catch((err) => this.log.error(`onReady failed: ${errText(err)}`));
|
|
55
53
|
});
|
|
56
54
|
this.on("unload", this.onUnload.bind(this));
|
|
57
55
|
this.on("message", (obj) => {
|
|
58
|
-
this.onMessage(obj).catch(
|
|
59
|
-
(err) => this.log.error(`onMessage failed: ${errText(err)}`)
|
|
60
|
-
);
|
|
56
|
+
this.onMessage(obj).catch((err) => this.log.error(`onMessage failed: ${errText(err)}`));
|
|
61
57
|
});
|
|
62
58
|
this.unhandledRejectionHandler = (reason) => {
|
|
63
59
|
this.log.error(`Unhandled rejection: ${errText(reason)}`);
|
|
@@ -73,9 +69,7 @@ class ParcelappAdapter extends utils.Adapter {
|
|
|
73
69
|
await this.setStateAsync("info.connection", { val: false, ack: true });
|
|
74
70
|
const { apiKey } = this.config;
|
|
75
71
|
if (!apiKey || apiKey.trim().length < 10) {
|
|
76
|
-
this.log.error(
|
|
77
|
-
"No valid API key configured \u2014 please enter your parcel.app API key in the adapter settings"
|
|
78
|
-
);
|
|
72
|
+
this.log.error("No valid API key configured \u2014 please enter your parcel.app API key in the adapter settings");
|
|
79
73
|
return;
|
|
80
74
|
}
|
|
81
75
|
const sysConfig = await this.getForeignObjectAsync("system.config");
|
|
@@ -86,16 +80,11 @@ class ParcelappAdapter extends utils.Adapter {
|
|
|
86
80
|
await this.poll();
|
|
87
81
|
const interval = Math.max(
|
|
88
82
|
MIN_POLL_INTERVAL,
|
|
89
|
-
Math.min(
|
|
90
|
-
MAX_POLL_INTERVAL,
|
|
91
|
-
(_c = this.config.pollInterval) != null ? _c : DEFAULT_POLL_INTERVAL
|
|
92
|
-
)
|
|
83
|
+
Math.min(MAX_POLL_INTERVAL, (_c = this.config.pollInterval) != null ? _c : DEFAULT_POLL_INTERVAL)
|
|
93
84
|
);
|
|
94
85
|
const intervalMs = interval * 60 * 1e3;
|
|
95
86
|
this.pollTimer = this.setInterval(() => void this.poll(), intervalMs);
|
|
96
|
-
this.log.info(
|
|
97
|
-
`Parcel tracking started \u2014 polling every ${interval} minutes`
|
|
98
|
-
);
|
|
87
|
+
this.log.info(`Parcel tracking started \u2014 polling every ${interval} minutes`);
|
|
99
88
|
}
|
|
100
89
|
onUnload(callback) {
|
|
101
90
|
try {
|
|
@@ -127,12 +116,7 @@ class ParcelappAdapter extends utils.Adapter {
|
|
|
127
116
|
const msg = obj.message;
|
|
128
117
|
const key = ((_a = msg == null ? void 0 : msg.apiKey) == null ? void 0 : _a.trim()) || "";
|
|
129
118
|
if (!key || key.length < 10) {
|
|
130
|
-
this.sendTo(
|
|
131
|
-
obj.from,
|
|
132
|
-
obj.command,
|
|
133
|
-
{ success: false, message: "API key is too short" },
|
|
134
|
-
obj.callback
|
|
135
|
-
);
|
|
119
|
+
this.sendTo(obj.from, obj.command, { success: false, message: "API key is too short" }, obj.callback);
|
|
136
120
|
return;
|
|
137
121
|
}
|
|
138
122
|
const testClient = new import_parcel_client.ParcelClient(key);
|
|
@@ -159,21 +143,11 @@ class ParcelappAdapter extends utils.Adapter {
|
|
|
159
143
|
break;
|
|
160
144
|
}
|
|
161
145
|
default:
|
|
162
|
-
this.sendTo(
|
|
163
|
-
obj.from,
|
|
164
|
-
obj.command,
|
|
165
|
-
{ error: "Unknown command" },
|
|
166
|
-
obj.callback
|
|
167
|
-
);
|
|
146
|
+
this.sendTo(obj.from, obj.command, { error: "Unknown command" }, obj.callback);
|
|
168
147
|
}
|
|
169
148
|
} catch (err) {
|
|
170
149
|
const msg = err instanceof Error ? err.message : String(err);
|
|
171
|
-
this.sendTo(
|
|
172
|
-
obj.from,
|
|
173
|
-
obj.command,
|
|
174
|
-
{ success: false, error_message: msg },
|
|
175
|
-
obj.callback
|
|
176
|
-
);
|
|
150
|
+
this.sendTo(obj.from, obj.command, { success: false, error_message: msg }, obj.callback);
|
|
177
151
|
}
|
|
178
152
|
}
|
|
179
153
|
async cleanupObsoleteStates() {
|
|
@@ -216,9 +190,7 @@ class ParcelappAdapter extends utils.Adapter {
|
|
|
216
190
|
const now = Date.now();
|
|
217
191
|
if (now < this.rateLimitedUntil) {
|
|
218
192
|
const waitMin = Math.ceil((this.rateLimitedUntil - now) / 6e4);
|
|
219
|
-
this.log.debug(
|
|
220
|
-
`Skipping poll \u2014 rate limited for ${waitMin} more minute(s)`
|
|
221
|
-
);
|
|
193
|
+
this.log.debug(`Skipping poll \u2014 rate limited for ${waitMin} more minute(s)`);
|
|
222
194
|
return;
|
|
223
195
|
}
|
|
224
196
|
if (now - this.lastPollTime < MIN_POLL_GAP_MS) {
|
|
@@ -229,47 +201,35 @@ class ParcelappAdapter extends utils.Adapter {
|
|
|
229
201
|
this.lastPollTime = now;
|
|
230
202
|
try {
|
|
231
203
|
const autoRemove = this.config.autoRemoveDelivered !== false;
|
|
232
|
-
const deliveries = await this.client.getDeliveries(
|
|
233
|
-
autoRemove ? "active" : "recent"
|
|
234
|
-
);
|
|
204
|
+
const deliveries = await this.client.getDeliveries(autoRemove ? "active" : "recent");
|
|
235
205
|
this.rateLimitedUntil = 0;
|
|
236
206
|
if (this.lastErrorCode) {
|
|
237
207
|
this.log.info("Connection restored");
|
|
238
208
|
this.lastErrorCode = "";
|
|
239
209
|
}
|
|
240
210
|
await this.setStateAsync("info.connection", { val: true, ack: true });
|
|
241
|
-
const activeDeliveries = deliveries.filter(
|
|
242
|
-
(d) => this.stateManager.parseStatus(d) !== 0
|
|
243
|
-
);
|
|
211
|
+
const activeDeliveries = deliveries.filter((d) => this.stateManager.parseStatus(d) !== 0);
|
|
244
212
|
const visibleDeliveries = autoRemove ? activeDeliveries : deliveries;
|
|
245
213
|
const activeIds = [];
|
|
246
214
|
for (const delivery of visibleDeliveries) {
|
|
247
215
|
try {
|
|
248
|
-
const carrierName = await this.client.getCarrierName(
|
|
249
|
-
delivery.carrier_code
|
|
250
|
-
);
|
|
216
|
+
const carrierName = await this.client.getCarrierName(delivery.carrier_code);
|
|
251
217
|
await this.stateManager.updateDelivery(delivery, carrierName);
|
|
252
218
|
activeIds.push(this.stateManager.packageId(delivery));
|
|
253
219
|
this.failedDeliveries.delete(delivery.tracking_number);
|
|
254
220
|
} catch (err) {
|
|
255
221
|
const msg = err instanceof Error ? err.message : String(err);
|
|
256
222
|
if (this.failedDeliveries.has(delivery.tracking_number)) {
|
|
257
|
-
this.log.debug(
|
|
258
|
-
`Failed to update "${delivery.tracking_number}": ${msg}`
|
|
259
|
-
);
|
|
223
|
+
this.log.debug(`Failed to update "${delivery.tracking_number}": ${msg}`);
|
|
260
224
|
} else {
|
|
261
|
-
this.log.warn(
|
|
262
|
-
`Failed to update "${delivery.tracking_number}": ${msg}`
|
|
263
|
-
);
|
|
225
|
+
this.log.warn(`Failed to update "${delivery.tracking_number}": ${msg}`);
|
|
264
226
|
this.failedDeliveries.add(delivery.tracking_number);
|
|
265
227
|
}
|
|
266
228
|
}
|
|
267
229
|
}
|
|
268
230
|
await this.stateManager.cleanupDeliveries(activeIds);
|
|
269
231
|
await this.stateManager.updateSummary(activeDeliveries);
|
|
270
|
-
this.log.debug(
|
|
271
|
-
`Polled ${visibleDeliveries.length} deliveries (${activeDeliveries.length} active)`
|
|
272
|
-
);
|
|
232
|
+
this.log.debug(`Polled ${visibleDeliveries.length} deliveries (${activeDeliveries.length} active)`);
|
|
273
233
|
} catch (err) {
|
|
274
234
|
const error = err;
|
|
275
235
|
const errorCode = this.classifyError(error);
|
|
@@ -278,13 +238,9 @@ class ParcelappAdapter extends utils.Adapter {
|
|
|
278
238
|
if (error.code === "RATE_LIMITED") {
|
|
279
239
|
const cooldownSec = error.retryAfterSeconds || 5 * 60;
|
|
280
240
|
this.rateLimitedUntil = Date.now() + cooldownSec * 1e3;
|
|
281
|
-
this.log.warn(
|
|
282
|
-
`Rate limit hit \u2014 pausing API requests for ${Math.ceil(cooldownSec / 60)} minute(s)`
|
|
283
|
-
);
|
|
241
|
+
this.log.warn(`Rate limit hit \u2014 pausing API requests for ${Math.ceil(cooldownSec / 60)} minute(s)`);
|
|
284
242
|
} else if (error.code === "INVALID_API_KEY") {
|
|
285
|
-
this.log.error(
|
|
286
|
-
"Invalid API key \u2014 please check your parcel.app API key"
|
|
287
|
-
);
|
|
243
|
+
this.log.error("Invalid API key \u2014 please check your parcel.app API key");
|
|
288
244
|
} else if (isRepeat) {
|
|
289
245
|
this.log.debug(`Poll failed (ongoing): ${error.message}`);
|
|
290
246
|
} else if (errorCode === "NETWORK") {
|
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 { 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\n/**\n * Extract a log-friendly message from an unknown error value.\n *\n * @param err Value caught in a promise rejection (may or may not be an Error)\n */\nfunction errText(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\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 private unhandledRejectionHandler: ((reason: unknown) => void) | null = null;\n private uncaughtExceptionHandler: ((err: Error) => void) | null = null;\n\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({\n ...options,\n name: \"parcelapp\",\n });\n // Wrap async handlers with .catch() so a rejection can never become an\n // unhandled promise rejection (which would SIGKILL the adapter and trap\n // js-controller in a restart loop without any stack trace).\n this.on(\"ready\", () => {\n this.onReady().catch((err) =>\n this.log.error(`onReady failed: ${errText(err)}`),\n );\n });\n this.on(\"unload\", this.onUnload.bind(this));\n this.on(\"message\", (obj) => {\n this.onMessage(obj).catch((err) =>\n this.log.error(`onMessage failed: ${errText(err)}`),\n );\n });\n // Last-line-of-defence against unhandled rejections / sync throws from\n // fire-and-forget paths (e.g. `void this.poll()`). The per-handler\n // .catch() wrappers cover the documented async paths; this catches\n // anything that slips past during refactors.\n this.unhandledRejectionHandler = (reason: unknown) => {\n this.log.error(`Unhandled rejection: ${errText(reason)}`);\n };\n this.uncaughtExceptionHandler = (err: Error) => {\n this.log.error(`Uncaught exception: ${errText(err)}`);\n };\n process.on(\"unhandledRejection\", this.unhandledRejectionHandler);\n process.on(\"uncaughtException\", this.uncaughtExceptionHandler);\n }\n\n private async onReady(): Promise<void> {\n await this.setStateAsync(\"info.connection\", { val: false, ack: true });\n\n // Validate config\n const { apiKey } = this.config;\n if (!apiKey || apiKey.trim().length < 10) {\n this.log.error(\n \"No valid API key configured \u2014 please enter your parcel.app API key in the adapter settings\",\n );\n return;\n }\n\n // Pick the label language from the ioBroker system configuration.\n // StateManager falls back to English if the language is unsupported.\n const sysConfig = await this.getForeignObjectAsync(\"system.config\");\n const language =\n (sysConfig?.common as { language?: string } | undefined)?.language ?? \"\";\n\n // Initialize\n this.client = new ParcelClient(apiKey.trim());\n this.stateManager = new StateManager(this, language);\n\n // Cleanup obsolete states\n await this.cleanupObsoleteStates();\n\n // Initial poll\n await this.poll();\n\n // Set up recurring poll\n const interval = Math.max(\n MIN_POLL_INTERVAL,\n Math.min(\n MAX_POLL_INTERVAL,\n this.config.pollInterval ?? DEFAULT_POLL_INTERVAL,\n ),\n );\n const intervalMs = interval * 60 * 1000;\n this.pollTimer = this.setInterval(() => void this.poll(), intervalMs);\n\n this.log.info(\n `Parcel tracking started \u2014 polling every ${interval} minutes`,\n );\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 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 void this.setState(\"info.connection\", { val: false, ack: true });\n } catch {\n // ignore\n }\n callback();\n }\n\n private async onMessage(obj: ioBroker.Message): Promise<void> {\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 < 10) {\n this.sendTo(\n obj.from,\n obj.command,\n { success: false, message: \"API key is too short\" },\n obj.callback,\n );\n return;\n }\n const testClient = new ParcelClient(key);\n const result = await testClient.testConnection();\n this.sendTo(obj.from, obj.command, result, obj.callback);\n break;\n }\n case \"addDelivery\": {\n if (!this.client) {\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 this.sendTo(obj.from, obj.command, addResult, obj.callback);\n if (addResult.success) {\n void this.poll();\n }\n break;\n }\n default:\n this.sendTo(\n obj.from,\n obj.command,\n { error: \"Unknown command\" },\n obj.callback,\n );\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n this.sendTo(\n obj.from,\n obj.command,\n { success: false, error_message: msg },\n obj.callback,\n );\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 // 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\n // Skip if rate limited\n if (now < this.rateLimitedUntil) {\n const waitMin = Math.ceil((this.rateLimitedUntil - now) / 60_000);\n this.log.debug(\n `Skipping poll \u2014 rate limited for ${waitMin} more minute(s)`,\n );\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 autoRemove = this.config.autoRemoveDelivered !== false;\n const deliveries = await this.client.getDeliveries(\n autoRemove ? \"active\" : \"recent\",\n );\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(\n (d) => this.stateManager!.parseStatus(d) !== 0,\n );\n const visibleDeliveries = autoRemove ? activeDeliveries : deliveries;\n\n // Update each delivery (isolated: one failure must not block others)\n const activeIds: string[] = [];\n for (const delivery of visibleDeliveries) {\n try {\n const carrierName = await this.client.getCarrierName(\n delivery.carrier_code,\n );\n await this.stateManager.updateDelivery(delivery, carrierName);\n activeIds.push(this.stateManager.packageId(delivery));\n this.failedDeliveries.delete(delivery.tracking_number);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (this.failedDeliveries.has(delivery.tracking_number)) {\n this.log.debug(\n `Failed to update \"${delivery.tracking_number}\": ${msg}`,\n );\n } else {\n this.log.warn(\n `Failed to update \"${delivery.tracking_number}\": ${msg}`,\n );\n this.failedDeliveries.add(delivery.tracking_number);\n }\n }\n }\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(\n `Polled ${visibleDeliveries.length} deliveries (${activeDeliveries.length} active)`,\n );\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 const cooldownSec = error.retryAfterSeconds || 5 * 60;\n this.rateLimitedUntil = Date.now() + cooldownSec * 1000;\n this.log.warn(\n `Rate limit hit \u2014 pausing API requests for ${Math.ceil(cooldownSec / 60)} minute(s)`,\n );\n } else if (error.code === \"INVALID_API_KEY\") {\n // Always log \u2014 user must fix config\n this.log.error(\n \"Invalid API key \u2014 please check your parcel.app API key\",\n );\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) =>\n new ParcelappAdapter(options);\n} else {\n (() => new ParcelappAdapter())();\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;AAAA,YAAuB;AACvB,2BAA6B;AAC7B,2BAA6B;AAE7B,MAAM,oBAAoB;AAC1B,MAAM,oBAAoB;AAC1B,MAAM,wBAAwB;AAC9B,MAAM,kBAAkB;AAOxB,SAAS,QAAQ,KAAsB;AACrC,SAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACxD;AAGA,MAAM,yBAAyB,MAAM,QAAQ;AAAA,EACnC,SAA8B;AAAA,EAC9B,eAAoC;AAAA,EACpC,YAA2C;AAAA,EAC3C,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,mBAAmB;AAAA,EACnB,gBAAgB;AAAA,EAChB,mBAAmB,oBAAI,IAAY;AAAA,EACnC,4BAAgE;AAAA,EAChE,2BAA0D;AAAA;AAAA,EAG3D,YAAY,UAAyC,CAAC,GAAG;AAC9D,UAAM;AAAA,MACJ,GAAG;AAAA,MACH,MAAM;AAAA,IACR,CAAC;AAID,SAAK,GAAG,SAAS,MAAM;AACrB,WAAK,QAAQ,EAAE
|
|
4
|
+
"sourcesContent": ["import * as utils from \"@iobroker/adapter-core\";\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\n/**\n * Extract a log-friendly message from an unknown error value.\n *\n * @param err Value caught in a promise rejection (may or may not be an Error)\n */\nfunction errText(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\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 private unhandledRejectionHandler: ((reason: unknown) => void) | null = null;\n private uncaughtExceptionHandler: ((err: Error) => void) | null = null;\n\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({\n ...options,\n name: \"parcelapp\",\n });\n // Wrap async handlers with .catch() so a rejection can never become an\n // unhandled promise rejection (which would SIGKILL the adapter and trap\n // js-controller in a restart loop without any stack trace).\n this.on(\"ready\", () => {\n this.onReady().catch(err => this.log.error(`onReady failed: ${errText(err)}`));\n });\n this.on(\"unload\", this.onUnload.bind(this));\n this.on(\"message\", obj => {\n this.onMessage(obj).catch(err => this.log.error(`onMessage failed: ${errText(err)}`));\n });\n // Last-line-of-defence against unhandled rejections / sync throws from\n // fire-and-forget paths (e.g. `void this.poll()`). The per-handler\n // .catch() wrappers cover the documented async paths; this catches\n // anything that slips past during refactors.\n this.unhandledRejectionHandler = (reason: unknown) => {\n this.log.error(`Unhandled rejection: ${errText(reason)}`);\n };\n this.uncaughtExceptionHandler = (err: Error) => {\n this.log.error(`Uncaught exception: ${errText(err)}`);\n };\n process.on(\"unhandledRejection\", this.unhandledRejectionHandler);\n process.on(\"uncaughtException\", this.uncaughtExceptionHandler);\n }\n\n private async onReady(): Promise<void> {\n await this.setStateAsync(\"info.connection\", { val: false, ack: true });\n\n // Validate config\n const { apiKey } = this.config;\n if (!apiKey || apiKey.trim().length < 10) {\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 // Pick the label language from the ioBroker system configuration.\n // StateManager falls back to English if the language is unsupported.\n const sysConfig = await this.getForeignObjectAsync(\"system.config\");\n const language = (sysConfig?.common as { language?: string } | undefined)?.language ?? \"\";\n\n // Initialize\n this.client = new ParcelClient(apiKey.trim());\n this.stateManager = new StateManager(this, language);\n\n // Cleanup obsolete states\n await this.cleanupObsoleteStates();\n\n // Initial poll\n await this.poll();\n\n // Set up recurring poll\n const interval = Math.max(\n MIN_POLL_INTERVAL,\n Math.min(MAX_POLL_INTERVAL, this.config.pollInterval ?? DEFAULT_POLL_INTERVAL),\n );\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 }\n\n private onUnload(callback: () => void): void {\n try {\n if (this.pollTimer) {\n this.clearInterval(this.pollTimer);\n this.pollTimer = undefined;\n }\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 void this.setState(\"info.connection\", { val: false, ack: true });\n } catch {\n // ignore\n }\n callback();\n }\n\n private async onMessage(obj: ioBroker.Message): Promise<void> {\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 < 10) {\n this.sendTo(obj.from, obj.command, { success: false, message: \"API key is too short\" }, obj.callback);\n return;\n }\n const testClient = new ParcelClient(key);\n const result = await testClient.testConnection();\n this.sendTo(obj.from, obj.command, result, obj.callback);\n break;\n }\n case \"addDelivery\": {\n if (!this.client) {\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 this.sendTo(obj.from, obj.command, addResult, obj.callback);\n if (addResult.success) {\n void this.poll();\n }\n break;\n }\n default:\n this.sendTo(obj.from, obj.command, { error: \"Unknown command\" }, obj.callback);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n this.sendTo(obj.from, obj.command, { success: false, error_message: msg }, 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 // 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\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 autoRemove = this.config.autoRemoveDelivered !== false;\n const deliveries = await this.client.getDeliveries(autoRemove ? \"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 = autoRemove ? activeDeliveries : deliveries;\n\n // Update each delivery (isolated: one failure must not block others)\n const activeIds: string[] = [];\n for (const delivery of visibleDeliveries) {\n try {\n const carrierName = await this.client.getCarrierName(delivery.carrier_code);\n await this.stateManager.updateDelivery(delivery, carrierName);\n activeIds.push(this.stateManager.packageId(delivery));\n this.failedDeliveries.delete(delivery.tracking_number);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(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 }\n }\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 const cooldownSec = error.retryAfterSeconds || 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 === \"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,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "parcelapp",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.18",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.2.18": {
|
|
7
|
+
"en": "Audit cleanup against the upstream `ioBroker.example/TypeScript` full standard:\n`@types/node` rolled back from `^25.6.0` to `^20.19.24` so type defs match `engines.node: \">=20\"` (otherwise Node 21+-only APIs would type-check on Node 20 and crash at runtime)\nDependabot now ignores major bumps for `@types/node`, `typescript`, `eslint` so the runtime/toolchain pinning cannot drift via auto-merge\n`nyc` config + `coverage` script added (matches upstream template)\n`prettier.config.mjs` made explicit with project-style overrides (Spaces 2-wide, double quotes) instead of relying on missing config\nOrphan `.github/auto-merge.yml` removed (the active workflow is `automerge-dependabot.yml` using `gh pr merge`, the old yml was never read)",
|
|
8
|
+
"de": "Audit-Aufräumung gegen den vorgeschalteten `ioBroker.example/TypeScript` Vollstandard:\n`@types/node` rollte zurück von `^25.6.0` zu `^20.19.24` so type defs match `engines.node: \">=20\" ` (sonst Node 21+-only APIs würden Typ-Check auf Node 20 und Crash bei Laufzeit)\nDependabot ignoriert nun große Beulen für `@types/node`, `typescript`, `eslint`, so dass die Runtime/toolchain Pinning nicht über Auto-Merge driften kann\n`nyc` config + `coverage` skript hinzugefügt (matches vor vorlage)\n`prettier.config.mjs` explizit gemacht mit Projekt-Stil Overrides (Spaces 2-wide, doppelte Zitate) anstatt auf fehlende config verlassen\nOrphan `.github/auto-merge.yml` entfernt (der aktive Workflow ist `automerge-dependabot.yml` mit `gh pr merge`, die alte yml wurde nie gelesen)",
|
|
9
|
+
"ru": "Очистка аудита по полному стандарту «ioBroker.example/TypeScript»:\n«@types/node» откатился от «^25.6.0» к «^20.19.24», поэтому параметры типа соответствуют «engines.node:>=20» (иначе API Node 21+ будут проверять тип на Node 20 и падать во время выполнения)\nDependabot теперь игнорирует основные удары для «@types/node», «typescript», «eslint», поэтому время выполнения / закрепление цепочки не может дрейфовать через автоматическое слияние\n'nyc' config + 'coverage' script add (совпадает с шаблоном upstream)\n«prettier.config.mjs» в прямом эфире с надстройками в стиле проекта (пространства 2, двойные цитаты) вместо того, чтобы полагаться на отсутствующий конфигуратор\nOrphan '.github/auto-merge.yml' удален (активный рабочий процесс - 'automerge-dependabot.yml' с использованием 'gh pr merge', старый yml никогда не читался)",
|
|
10
|
+
"pt": "Limpeza de auditoria contra o padrão completo 'ioBroker.example/TypeScript` upstream:\n`@types/node` rolled back from `^25.6.0` to `^20.19.24` então digite defs match `engines.node: \">=20\"` (caso contrário Node 21+-only APIs iria digitar-check on Node 20 e bater em tempo de execução)\nDependebot agora ignora grandes problemas para `@types/node`, `typescript`, `eslint` para que o spining runtime/toolchain não possa derivar através da fusão automática\n'nyc' config + 'coverage' script adicionado (corresponde ao modelo upstream)\n`prettier.config.mjs` explicitado com sobreposições de estilo de projeto (Espaços 2-largura, aspas duplas) em vez de confiar na configuração ausente\nÓrfão `.github/auto-merge.yml` removido (o fluxo de trabalho ativo é `automerge-dependabot.yml` usando `gh pr merge`, o yml antigo nunca foi lido)",
|
|
11
|
+
"nl": "Audit cleanup tegen de upstream .example/TypeScript\nZo type defs komen overeen met de engines.node: \">=20\" (anders zou Node 21+-alleen API's op Node 20 controleren en crashen op runtime)\nDependabot negeert nu grote hobbels voor \n`nyc` config + `coverage` script added (matches upstream template)\nPrettier.config.mjs\nOure.github/auto-merge.yml",
|
|
12
|
+
"fr": "Nettoyage de l'audit par rapport à la norme en amont `ioBroker.example/TypeScript`:\n`@types/node` retourné de `^25.6.0` à `^20.19.24` de sorte que le type defs correspond `engines.node: \">=20\"` (autrement, les API de Node 21+ seulement contrôleraient le Node 20 et s'écraseraient au moment de l'exécution)\nDependabot ignore maintenant les bosses principales pour `@types/node`, `typescript`, `estint` de sorte que l'exécution/toolchain pinning ne peut pas dériver par fusion automatique\nscript `nyc` config + `coverage` ajouté (comparaison en amont)\n`prettier.config.mjs` fait explicite avec les redéfinitions de style projet (espaces 2-large, guillemets doubles) au lieu de compter sur la configuration manquante\nOrphan `.github/auto-merge.yml` supprimé (le workflow actif est `automerge-dependabot.yml` en utilisant `gh pr merge`, l'ancien yml n'a jamais été lu)",
|
|
13
|
+
"it": "Audit cleanup contro il `ioBroker.example/TypeScript` standard completo:\n`@types/node` rotolato indietro da `^25.6.0` a `^20.19.24` così tipo defs match `engines.node: \">=20\"` (altrimenti Node 21 +-solo API sarebbe digitare-check su Node 20 e crash a runtime)\nDependabot ora ignora urti principali per `@types/node`, `typescript`, `eslint` in modo che il runtime/toolchain pinning non può derivare via auto-merge\n`nyc` config + `coverage` script aggiunto (matches upstream template)\n`prettier.config.mjs` fatto esplicito con overrides di progetto (Spaces 2-wide, doppie citazioni) invece di affidarsi alla configurazione mancante\nOrphan `.github/auto-merge.yml` rimosso (il flusso di lavoro attivo è `automerge-dependabot.yml` utilizzando `gh pr merge`, il vecchio yml non è mai stato letto)",
|
|
14
|
+
"es": "Limpieza de auditorías contra el estándar completo del `ioBroker.example/TypeScript`:\n`@types/node` rolled back from `^25.6.0` to `^20.19.24` so type defs match `engines.node: \" prenda=20\" ' (otherwise Node 21+-only APIs would type-check on Node 20 and crash at runtime)\nDependabot hace caso omiso de los principales golpes por `@tipos/nodo`, `tiposcript`, `eslint`, por lo que el tiempo de ejecución/toolchain pinning no puede derivar a través de auto-merge\n`nyc` config + `coverage` script añadido (matches upstream template)\n`prettier.config.mjs` hecho explícito con anulas de estilo de proyecto (Spaces 2-wide, citas dobles) en lugar de confiar en los config desaparecidos\nHuérfano `.github/auto-merge.yml` eliminado (el flujo de trabajo activo es `automerge-dependabot.yml` utilizando `gh pr merge`, el yml antiguo nunca fue leído)",
|
|
15
|
+
"pl": "Oczyszczanie audytów w stosunku do pełnego standardu 'iBroker.example / TypeScript':\n'@ types / node' rolled back from '^ 25.6.0' to '^ 20.19.24' so type defs match 'engins.node: \"> = 20\"' (w przeciwnym razie tylko Node 21 + -only API would type- check on Node 20 and crash at runtime)\nDependabot ignoruje teraz główne przeszkody dla '@ types / node', 'typescript', 'eslint', więc przypinanie do łańcucha runtime / toolchain nie może dryfować poprzez automatyczne połączenie\ndodano skrypt 'nyc' config + 'coverage' (dopasowuje szablon początkowy)\n'prettie.config.mjs' jest wyraźna z przekroczeniami w stylu project- style (Spaces 2- szerokie, podwójne cytaty) zamiast polegać na brakującym konfigu\nOrphan '.github / auto- merge.yml' usunięty (aktywny przepływ pracy to 'automaterge- dependiabot.yml' przy użyciu 'gh pr merge', stary yml nigdy nie został przeczytany)",
|
|
16
|
+
"uk": "Аудиторське очищення від потоку `ioBroker.example/TypeScript`\n`@types/node` відхилений від `^25.6.0`` до `^20.19.24` так типу defs match `engines.node: \">=20\"` (іншого Node 21+-only APIs буде виданий на Node 20 і аварійний час)\nВ залежності від того, як `slint`, `slint`, `slint`, `eslint`, так що пусковий / toolchain пінінг не може drift через автоматичний мердж\n`nyc` config + `coverage` скрипт додано (змініть шаблон stream)\n`prettier.config.mjs` зробив явний з перенаряддям проекту (Косміка 2-широка, подвійні лапки) замість перекриття на відсутній конфігурації\nOrphan `.github/auto-merge.yml` вилучено (активний робочий процес `automerge-залежніabot.yml` за допомогою `gh pr злиття`, старий yml ніколи не читав)",
|
|
17
|
+
"zh-cn": "对照上游`ioBroker.example/TypeScript ' 全面标准进行审计清理:\nQQQ类型/节点 ' 从 QQ25.6.0 ' 滚回 QQ20.19.24' , 这样类型 dafs match ‘engines.node : \"QQ20'(否则节点21+唯一的API会在节点20上进行打字检查,运行时崩溃)\n依赖现在忽略了QQ类型/节点、 \" 类型标记 \" 、 \" 斜线 \" 的重大突起,因此运行时间/工具链夹针不能通过自动浮出水面\n添加“nyc”配置+“覆盖”脚本(上游模板)\n`pretier.config.mjs' 明确了项目样式的覆盖(宽2层,双引号),而不是依赖缺失的配置\n孤儿`.github/自动合并.yml ' 被删除(使用`gh pr 合并 ' 的当前工作流程为`autommerge-dependabot.yml ' ,旧yml从未读过)"
|
|
18
|
+
},
|
|
6
19
|
"0.2.17": {
|
|
7
20
|
"en": "Test setup migrated to the upstream `ioBroker.example/TypeScript` standard: tests now live next to source as `src/**/*.test.ts` and run directly via `ts-node/register`, no separate test-build. Removes `tsconfig.test.json` + `build-test/` per latest-repo review feedback.",
|
|
8
21
|
"de": "Test-Setup migriert zum `ioBroker.example/TypeScript` Standard: Tests leben jetzt neben der Quelle als `src/**/*.test.ts` und laufen direkt über `ts-node/register`, kein separater Testaufbau. Entfernt `tsconfig.test.json` + `build-test/` pro neuste Repo-Review-Feedback.",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
93
|
"pl": "API-drift guards wzmocnione w ParcelClient i StateManager (success non-boolean, deliveries non-array, error_code/error_message non-string, carrier map non-object, pola non-string). 38 nowych testów regresji (128 łącznie).",
|
|
81
94
|
"uk": "API-drift guards посилено в ParcelClient і StateManager (non-boolean success, non-array deliveries, non-string error_code/error_message, non-object carrier map, non-string delivery fields). 38 нових regression-тестів (128 загалом).",
|
|
82
95
|
"zh-cn": "强化 ParcelClient 和 StateManager 的 API-drift guards(非布尔 success、非数组 deliveries、非字符串 error_code/error_message、非对象 carrier map、非字符串 delivery 字段)。新增 38 个 regression 测试(共 128 个)。"
|
|
83
|
-
},
|
|
84
|
-
"0.2.11": {
|
|
85
|
-
"en": "Handle response stream errors. Isolate per-delivery poll failures (one broken delivery no longer blocks all others). Harden onMessage and onUnload with try/catch + callback guard. parseStatus DRY helper.",
|
|
86
|
-
"de": "Response-Stream-Errors abgefangen. Per-Delivery Poll-Failures isoliert (ein kaputtes Paket blockiert nicht mehr alle anderen). onMessage und onUnload mit try/catch + Callback-Guard gehärtet. parseStatus DRY-Helper.",
|
|
87
|
-
"ru": "Помилки response-stream обробляються. Per-delivery poll-failures ізольовано (одне зламане відправлення більше не блокує інші). onMessage та onUnload посилено через try/catch + callback-guard. parseStatus DRY-helper.",
|
|
88
|
-
"pt": "Erros de response-stream tratados. Falhas per-delivery isoladas (uma encomenda quebrada não bloqueia mais as outras). onMessage e onUnload reforçados com try/catch + callback-guard. parseStatus DRY-helper.",
|
|
89
|
-
"nl": "Response-stream fouten afgevangen. Per-delivery poll-failures geïsoleerd (één defect pakket blokkeert niet meer de anderen). onMessage en onUnload versterkt met try/catch + callback-guard. parseStatus DRY-helper.",
|
|
90
|
-
"fr": "Erreurs de response-stream traitées. Échecs per-delivery isolés (un colis cassé ne bloque plus les autres). onMessage et onUnload renforcés avec try/catch + callback-guard. parseStatus DRY-helper.",
|
|
91
|
-
"it": "Errori di response-stream gestiti. Fallimenti per-delivery isolati (un pacco rotto non blocca più gli altri). onMessage e onUnload rafforzati con try/catch + callback-guard. parseStatus DRY-helper.",
|
|
92
|
-
"es": "Errores de response-stream manejados. Fallos per-delivery aislados (un paquete roto ya no bloquea los demás). onMessage y onUnload reforzados con try/catch + callback-guard. parseStatus DRY-helper.",
|
|
93
|
-
"pl": "Błędy response-stream obsłużone. Per-delivery poll-failures odizolowane (jedna zepsuta paczka nie blokuje już pozostałych). onMessage i onUnload wzmocnione przez try/catch + callback-guard. parseStatus DRY-helper.",
|
|
94
|
-
"uk": "Помилки response-stream оброблено. Per-delivery poll-failures ізольовано (одне зламане відправлення більше не блокує інші). onMessage та onUnload посилено через try/catch + callback-guard. parseStatus DRY-helper.",
|
|
95
|
-
"zh-cn": "处理 response-stream 错误。隔离 per-delivery poll-failures(一个坏掉的包裹不再阻塞其他)。通过 try/catch + callback-guard 强化 onMessage 和 onUnload。parseStatus DRY-helper。"
|
|
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.2.
|
|
3
|
+
"version": "0.2.18",
|
|
4
4
|
"description": "ioBroker adapter for parcel.app — track packages as ioBroker states",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "krobi",
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"@iobroker/testing": "^5.2.2",
|
|
43
43
|
"@tsconfig/node20": "^20.1.9",
|
|
44
44
|
"@types/iobroker": "npm:@iobroker/types@^7.1.1",
|
|
45
|
-
"@types/node": "^
|
|
45
|
+
"@types/node": "^20.19.24",
|
|
46
|
+
"nyc": "^17.1.0",
|
|
46
47
|
"rimraf": "^6.1.3",
|
|
47
48
|
"source-map-support": "^0.5.21",
|
|
48
49
|
"ts-node": "^10.9.2",
|
|
@@ -64,11 +65,32 @@
|
|
|
64
65
|
"test:package": "mocha test/package --exit",
|
|
65
66
|
"test:integration": "mocha test/integration --exit",
|
|
66
67
|
"test": "npm run test:ts && npm run test:package",
|
|
68
|
+
"coverage": "nyc npm run test:ts",
|
|
67
69
|
"translate": "translate-adapter",
|
|
68
70
|
"lint": "eslint",
|
|
69
71
|
"lint:fix": "eslint --fix",
|
|
70
72
|
"release": "release-script"
|
|
71
73
|
},
|
|
74
|
+
"nyc": {
|
|
75
|
+
"include": [
|
|
76
|
+
"src/**/*.ts"
|
|
77
|
+
],
|
|
78
|
+
"exclude": [
|
|
79
|
+
"src/**/*.test.ts"
|
|
80
|
+
],
|
|
81
|
+
"extension": [
|
|
82
|
+
".ts"
|
|
83
|
+
],
|
|
84
|
+
"require": [
|
|
85
|
+
"ts-node/register"
|
|
86
|
+
],
|
|
87
|
+
"reporter": [
|
|
88
|
+
"text-summary",
|
|
89
|
+
"html"
|
|
90
|
+
],
|
|
91
|
+
"sourceMap": true,
|
|
92
|
+
"instrument": true
|
|
93
|
+
},
|
|
72
94
|
"bugs": {
|
|
73
95
|
"url": "https://github.com/krobipd/ioBroker.parcelapp/issues"
|
|
74
96
|
},
|