iobroker.parcelapp 0.10.1 → 0.10.2
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 +5 -6
- package/build/lib/coerce.js +5 -1
- package/build/lib/coerce.js.map +2 -2
- package/build/lib/parcel-client.js +13 -5
- package/build/lib/parcel-client.js.map +2 -2
- package/build/main.js.map +2 -2
- package/io-package.json +14 -14
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -140,7 +140,11 @@ sendTo("parcelapp.0", "addDelivery", {
|
|
|
140
140
|
Placeholder for the next version (at the beginning of the line):
|
|
141
141
|
### **WORK IN PROGRESS**
|
|
142
142
|
-->
|
|
143
|
-
### 0.10.
|
|
143
|
+
### 0.10.2 (2026-08-22)
|
|
144
|
+
|
|
145
|
+
- Changed: Internal cleanup. No user-facing changes.
|
|
146
|
+
|
|
147
|
+
### 0.10.1 (2026-07-13) — stable
|
|
144
148
|
|
|
145
149
|
- Internal refactoring. No user-facing changes.
|
|
146
150
|
|
|
@@ -165,11 +169,6 @@ sendTo("parcelapp.0", "addDelivery", {
|
|
|
165
169
|
- The delivery window is now also shown for carriers that report it only as a date/time range, not just when the API provides a Unix timestamp.
|
|
166
170
|
- When adding a delivery via script, you can now set an optional tracking language and request a push confirmation.
|
|
167
171
|
|
|
168
|
-
### 0.7.2 (2026-06-12) — stable
|
|
169
|
-
|
|
170
|
-
- Much quieter state updates: a package's last-updated timestamp now only changes when its tracking data actually changed, and device entries are no longer rewritten on every poll
|
|
171
|
-
- Adding a delivery with a malformed request now returns a clear error message instead of failing cryptically
|
|
172
|
-
|
|
173
172
|
[Older changelogs can be found there](CHANGELOG_OLD.md)
|
|
174
173
|
|
|
175
174
|
## Support
|
package/build/lib/coerce.js
CHANGED
|
@@ -50,6 +50,7 @@ function isTrueish(v) {
|
|
|
50
50
|
return false;
|
|
51
51
|
}
|
|
52
52
|
function errText(err) {
|
|
53
|
+
var _a;
|
|
53
54
|
if (err instanceof Error) {
|
|
54
55
|
return err.message;
|
|
55
56
|
}
|
|
@@ -65,8 +66,11 @@ function errText(err) {
|
|
|
65
66
|
if (typeof err === "number" || typeof err === "boolean" || typeof err === "bigint") {
|
|
66
67
|
return String(err);
|
|
67
68
|
}
|
|
69
|
+
if (typeof err === "symbol") {
|
|
70
|
+
return String(err);
|
|
71
|
+
}
|
|
68
72
|
try {
|
|
69
|
-
return JSON.stringify(err);
|
|
73
|
+
return (_a = JSON.stringify(err)) != null ? _a : Object.prototype.toString.call(err);
|
|
70
74
|
} catch {
|
|
71
75
|
return Object.prototype.toString.call(err);
|
|
72
76
|
}
|
package/build/lib/coerce.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/coerce.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Boundary coercion helpers for external API data.\n *\n * The parcel.app API is documented but field types still drift in practice\n * (rare success-flag returned as `\"true\"` string, occasional null where a\n * number is expected). These helpers guard against NaN/Infinity/non-string\n * values reaching ioBroker states.\n */\n\n// Strict decimal regex \u2014 only optional minus sign + digits + optional fractional part.\n// Rejects HEX (`0x...`), exponential (`1e3`), Infinity, NaN, leading/trailing whitespace.\n// Hassemu (E8 in v1.9.0) hardened the same coerce-helper this way; homewizard\n// adopted it in v0.7.2 (D8). Consistent with both adapters.\nconst DECIMAL_NUMBER_RE = /^-?\\d+(\\.\\d+)?$/;\n\n/**\n * Coerce to a finite number or null.\n * Accepts numbers directly; parses strict decimal strings; rejects NaN, Infinity,\n * HEX (`0x...`) and exponential notation (`1e3`).\n *\n * @param value Unknown external value\n */\nexport function coerceFiniteNumber(value: unknown): number | null {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : null;\n }\n if (typeof value === \"string\" && DECIMAL_NUMBER_RE.test(value)) {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\n/**\n * Coerce a parcel.app `success` flag. The API returns a real boolean in normal\n * operation, but the guard accepts common string/number encodings (`1`, `\"true\"`,\n * `\"1\"`) so a one-off drift doesn't break the entire poll cycle.\n *\n * @param v Value to interpret as a success flag\n */\nexport function isTrueish(v: unknown): boolean {\n if (typeof v === \"boolean\") {\n return v;\n }\n if (typeof v === \"number\") {\n return v === 1;\n }\n if (typeof v === \"string\") {\n const s = v.toLowerCase();\n return s === \"true\" || s === \"1\";\n }\n return false;\n}\n\n/**\n * Extract a log-friendly message from a thrown / rejected value. Centralizes the\n * `err instanceof Error ? err.message : String(err)` pattern that otherwise\n * gets repeated at every catch-site. Plain objects are JSON-stringified so a\n * `[object Object]` log is avoided when callers throw bag-of-fields.\n *\n * @param err Caught value of unknown shape (Error, string, undefined, ...).\n */\nexport function errText(err: unknown): string {\n if (err instanceof Error) {\n return err.message;\n }\n if (err === null) {\n return \"null\";\n }\n if (err === undefined) {\n return \"undefined\";\n }\n if (typeof err === \"string\") {\n return err;\n }\n if (typeof err === \"number\" || typeof err === \"boolean\" || typeof err === \"bigint\") {\n return String(err);\n }\n //
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,MAAM,oBAAoB;AASnB,SAAS,mBAAmB,OAA+B;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,EAC1C;AACA,MAAI,OAAO,UAAU,YAAY,kBAAkB,KAAK,KAAK,GAAG;AAC9D,UAAM,IAAI,OAAO,KAAK;AACtB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AASO,SAAS,UAAU,GAAqB;AAC7C,MAAI,OAAO,MAAM,WAAW;AAC1B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,MAAM,UAAU;AACzB,WAAO,MAAM;AAAA,EACf;AACA,MAAI,OAAO,MAAM,UAAU;AACzB,UAAM,IAAI,EAAE,YAAY;AACxB,WAAO,MAAM,UAAU,MAAM;AAAA,EAC/B;AACA,SAAO;AACT;AAUO,SAAS,QAAQ,KAAsB;
|
|
4
|
+
"sourcesContent": ["/**\n * Boundary coercion helpers for external API data.\n *\n * The parcel.app API is documented but field types still drift in practice\n * (rare success-flag returned as `\"true\"` string, occasional null where a\n * number is expected). These helpers guard against NaN/Infinity/non-string\n * values reaching ioBroker states.\n */\n\n// Strict decimal regex \u2014 only optional minus sign + digits + optional fractional part.\n// Rejects HEX (`0x...`), exponential (`1e3`), Infinity, NaN, leading/trailing whitespace.\n// Hassemu (E8 in v1.9.0) hardened the same coerce-helper this way; homewizard\n// adopted it in v0.7.2 (D8). Consistent with both adapters.\nconst DECIMAL_NUMBER_RE = /^-?\\d+(\\.\\d+)?$/;\n\n/**\n * Coerce to a finite number or null.\n * Accepts numbers directly; parses strict decimal strings; rejects NaN, Infinity,\n * HEX (`0x...`) and exponential notation (`1e3`).\n *\n * @param value Unknown external value\n */\nexport function coerceFiniteNumber(value: unknown): number | null {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : null;\n }\n if (typeof value === \"string\" && DECIMAL_NUMBER_RE.test(value)) {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\n/**\n * Coerce a parcel.app `success` flag. The API returns a real boolean in normal\n * operation, but the guard accepts common string/number encodings (`1`, `\"true\"`,\n * `\"1\"`) so a one-off drift doesn't break the entire poll cycle.\n *\n * @param v Value to interpret as a success flag\n */\nexport function isTrueish(v: unknown): boolean {\n if (typeof v === \"boolean\") {\n return v;\n }\n if (typeof v === \"number\") {\n return v === 1;\n }\n if (typeof v === \"string\") {\n const s = v.toLowerCase();\n return s === \"true\" || s === \"1\";\n }\n return false;\n}\n\n/**\n * Extract a log-friendly message from a thrown / rejected value. Centralizes the\n * `err instanceof Error ? err.message : String(err)` pattern that otherwise\n * gets repeated at every catch-site. Plain objects are JSON-stringified so a\n * `[object Object]` log is avoided when callers throw bag-of-fields.\n *\n * @param err Caught value of unknown shape (Error, string, undefined, ...).\n */\nexport function errText(err: unknown): string {\n if (err instanceof Error) {\n return err.message;\n }\n if (err === null) {\n return \"null\";\n }\n if (err === undefined) {\n return \"undefined\";\n }\n if (typeof err === \"string\") {\n return err;\n }\n if (typeof err === \"number\" || typeof err === \"boolean\" || typeof err === \"bigint\") {\n return String(err);\n }\n if (typeof err === \"symbol\") {\n // JSON.stringify(Symbol()) returns undefined (it does NOT throw), so the\n // catch below would never run and the declared `string` return would be a\n // lie. String(symbol) is the only safe conversion \u2014 `${symbol}` throws.\n return String(err);\n }\n // Plain objects would otherwise stringify to \"[object Object]\". Prefer JSON so\n // the log is at least diagnosable; circular structures fall back to the tag.\n try {\n // A function, or an object whose toJSON drops everything, also yields\n // undefined here \u2014 fall back rather than returning a non-string.\n return JSON.stringify(err) ?? Object.prototype.toString.call(err);\n } catch {\n return Object.prototype.toString.call(err);\n }\n}\n\n/**\n * v0.4.2 (X5): coerce an admin-config integer setting (number-or-string)\n * to a finite, clamped integer. Returns `defaultValue` for non-finite\n * input \u2014 guards against `setInterval(fn, NaN)` tight-loops when the\n * config field happens to come back as a string from the admin UI.\n *\n * @param raw Raw value from `this.config.<field>`.\n * @param min Inclusive lower bound.\n * @param max Inclusive upper bound.\n * @param defaultValue Fallback when raw is missing or unparseable.\n */\nexport function coerceClampedInt(raw: unknown, min: number, max: number, defaultValue: number): number {\n const n = typeof raw === \"number\" ? raw : typeof raw === \"string\" ? parseFloat(raw) : NaN;\n if (!Number.isFinite(n)) {\n return defaultValue;\n }\n return Math.max(min, Math.min(max, Math.floor(n)));\n}\n\n/**\n * Collapse control-character runs (CR / LF / TAB / NUL / VT / FF and the\n * Unicode line separators U+2028/U+2029) in an untrusted string to a single\n * space before it is interpolated into a log line \u2014 prevents log-injection\n * (a forged second log line) from external values (tracking number, carrier\n * code, raw API body, collision raw-key with its NUL separator, \u2026). Fleet\n * convention (hassemu/hueemu v1.36.0 S4); widened in v0.10.0 (I10).\n *\n * @param value Untrusted string to flatten for single-line logging.\n */\nexport function oneLine(value: string): string {\n return value.replace(/[\\r\\n\\t\\0\\v\\f\\u2028\\u2029]+/g, \" \");\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,MAAM,oBAAoB;AASnB,SAAS,mBAAmB,OAA+B;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,EAC1C;AACA,MAAI,OAAO,UAAU,YAAY,kBAAkB,KAAK,KAAK,GAAG;AAC9D,UAAM,IAAI,OAAO,KAAK;AACtB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AASO,SAAS,UAAU,GAAqB;AAC7C,MAAI,OAAO,MAAM,WAAW;AAC1B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,MAAM,UAAU;AACzB,WAAO,MAAM;AAAA,EACf;AACA,MAAI,OAAO,MAAM,UAAU;AACzB,UAAM,IAAI,EAAE,YAAY;AACxB,WAAO,MAAM,UAAU,MAAM;AAAA,EAC/B;AACA,SAAO;AACT;AAUO,SAAS,QAAQ,KAAsB;AA9D9C;AA+DE,MAAI,eAAe,OAAO;AACxB,WAAO,IAAI;AAAA,EACb;AACA,MAAI,QAAQ,MAAM;AAChB,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,QAAW;AACrB,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,UAAU;AAC3B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,aAAa,OAAO,QAAQ,UAAU;AAClF,WAAO,OAAO,GAAG;AAAA,EACnB;AACA,MAAI,OAAO,QAAQ,UAAU;AAI3B,WAAO,OAAO,GAAG;AAAA,EACnB;AAGA,MAAI;AAGF,YAAO,UAAK,UAAU,GAAG,MAAlB,YAAuB,OAAO,UAAU,SAAS,KAAK,GAAG;AAAA,EAClE,QAAQ;AACN,WAAO,OAAO,UAAU,SAAS,KAAK,GAAG;AAAA,EAC3C;AACF;AAaO,SAAS,iBAAiB,KAAc,KAAa,KAAa,cAA8B;AACrG,QAAM,IAAI,OAAO,QAAQ,WAAW,MAAM,OAAO,QAAQ,WAAW,WAAW,GAAG,IAAI;AACtF,MAAI,CAAC,OAAO,SAAS,CAAC,GAAG;AACvB,WAAO;AAAA,EACT;AACA,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC;AACnD;AAYO,SAAS,QAAQ,OAAuB;AAC7C,SAAO,MAAM,QAAQ,gCAAgC,GAAG;AAC1D;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -73,15 +73,23 @@ class ParcelClient {
|
|
|
73
73
|
log;
|
|
74
74
|
/** API base URL. Overridable so tests can run the real `request()` against a local mock server. */
|
|
75
75
|
baseUrl;
|
|
76
|
+
/** Socket idle timeout in ms — same seam idea as {@link baseUrl}, see {@link ParcelClientTimeouts}. */
|
|
77
|
+
idleTimeoutMs;
|
|
78
|
+
/** Hard per-request deadline in ms — see {@link ParcelClientTimeouts}. */
|
|
79
|
+
deadlineMs;
|
|
76
80
|
/**
|
|
77
81
|
* @param apiKey The parcel.app API key
|
|
78
82
|
* @param log Optional adapter logger for HTTPS-layer trace (v0.4.3)
|
|
79
83
|
* @param baseUrl API base URL — defaults to the production endpoint; overridden in tests
|
|
84
|
+
* @param timeouts Timeout overrides — production always uses the defaults
|
|
80
85
|
*/
|
|
81
|
-
constructor(apiKey, log, baseUrl = API_BASE) {
|
|
86
|
+
constructor(apiKey, log, baseUrl = API_BASE, timeouts = {}) {
|
|
87
|
+
var _a, _b;
|
|
82
88
|
this.apiKey = apiKey;
|
|
83
89
|
this.log = log;
|
|
84
90
|
this.baseUrl = baseUrl;
|
|
91
|
+
this.idleTimeoutMs = (_a = timeouts.idleMs) != null ? _a : REQUEST_TIMEOUT;
|
|
92
|
+
this.deadlineMs = (_b = timeouts.deadlineMs) != null ? _b : REQUEST_DEADLINE_MS;
|
|
85
93
|
}
|
|
86
94
|
/**
|
|
87
95
|
* v0.10.0 (L3): once cancelAll ran, the client is terminal — a request
|
|
@@ -259,7 +267,7 @@ class ParcelClient {
|
|
|
259
267
|
path: url.pathname + url.search,
|
|
260
268
|
method,
|
|
261
269
|
headers,
|
|
262
|
-
timeout:
|
|
270
|
+
timeout: this.idleTimeoutMs
|
|
263
271
|
};
|
|
264
272
|
const ctrl = new AbortController();
|
|
265
273
|
this.inflight.add(ctrl);
|
|
@@ -322,13 +330,13 @@ class ParcelClient {
|
|
|
322
330
|
}
|
|
323
331
|
});
|
|
324
332
|
});
|
|
325
|
-
AbortSignal.timeout(
|
|
333
|
+
AbortSignal.timeout(this.deadlineMs).addEventListener("abort", () => {
|
|
326
334
|
var _a3;
|
|
327
335
|
if (settled) {
|
|
328
336
|
return;
|
|
329
337
|
}
|
|
330
|
-
(_a3 = this.log) == null ? void 0 : _a3.debug(`HTTP deadline ${method} ${path} (${Date.now() - startedAt}ms > ${
|
|
331
|
-
req.destroy(apiError(`Request deadline exceeded (${
|
|
338
|
+
(_a3 = this.log) == null ? void 0 : _a3.debug(`HTTP deadline ${method} ${path} (${Date.now() - startedAt}ms > ${this.deadlineMs}ms)`);
|
|
339
|
+
req.destroy(apiError(`Request deadline exceeded (${this.deadlineMs / 1e3}s)`, "TIMEOUT"));
|
|
332
340
|
});
|
|
333
341
|
ctrl.signal.addEventListener("abort", () => {
|
|
334
342
|
req.destroy(apiError("Request aborted", "ABORTED"));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/parcel-client.ts"],
|
|
4
|
-
"sourcesContent": ["import * as http from \"node:http\";\nimport * as https from \"node:https\";\nimport { errText, isTrueish, oneLine } from \"./coerce\";\nimport type {\n ApiError,\n ApiErrorCode,\n ParcelApiResponse,\n ParcelDelivery,\n AddDeliveryRequest,\n AddDeliveryResponse,\n CarrierMap,\n} from \"./types\";\n\nconst API_BASE = \"https://api.parcel.app/external\";\n/** Socket IDLE timeout \u2014 fires only when the connection goes silent. */\nconst REQUEST_TIMEOUT = 15_000;\n/**\n * Hard per-request deadline. The socket idle timeout above never fires against\n * a trickle response (a byte every few seconds), which would otherwise pin\n * `isPolling` forever and silently stop the poll loop until a restart. This\n * timer caps the TOTAL request duration regardless of socket activity.\n */\nconst REQUEST_DEADLINE_MS = 60_000;\n/** Shared Retry-After clamps (used by the client parser and the adapter cooldown). */\nexport const RETRY_AFTER_MAX_SEC = 24 * 3600;\nexport const RETRY_AFTER_DEFAULT_SEC = 5 * 60;\n/** Max chars of a response body quoted into a debug log line. */\nconst BODY_SNIPPET_LEN = 200;\n\n/**\n * v0.4.3: optional logger injected by the adapter so the HTTPS client can\n * trace its own request/response lifecycle. When omitted (e.g. in tests),\n * every `this.log?.debug(...)` call is a no-op \u2014 keeps the bare-`apiKey`\n * constructor signature backward-compatible.\n */\nexport interface ParcelClientLogger {\n /** Adapter debug log. Called per request/response outcome (drift, status, parse, oversize) \u2014 low-frequency tracing. */\n debug(message: string): void;\n}\n/**\n * v0.4.2 (P9): hard cap on response body size. parcel.app deliveries lists\n * are tiny (~1 kB per package, max ~50 packages = 50 kB), so a 1 MiB cap is\n * 20\u00D7 the realistic max while still defending against a runaway response.\n */\nconst MAX_BODY_BYTES = 1 << 20; // 1 MiB\n\n/**\n * Build an {@link ApiError} carrying a typed `code` (and optional extra fields\n * such as `retryAfterSeconds`). Centralizes the `new Error(...)` + `err.code`\n * pattern; the `ApiErrorCode` union makes a typo on either side of the\n * client\u2194adapter contract a compile error.\n *\n * @param message Human-readable error message.\n * @param code Machine-readable error code used by the adapter for classification.\n * @param extra Optional additional own-properties to attach to the error.\n */\nfunction apiError(message: string, code: ApiErrorCode, extra?: Record<string, unknown>): ApiError {\n const err = new Error(message) as ApiError;\n err.code = code;\n if (extra) {\n Object.assign(err, extra);\n }\n return err;\n}\n\n/** HTTP client for the parcel.app API */\nexport class ParcelClient {\n private apiKey: string;\n private carrierCache: CarrierMap | null = null;\n /**\n * v0.7.2: in-flight fetch for the carrier list. The per-delivery updates run\n * in parallel (Promise.all) and each resolves carrier names \u2014 without this\n * mutex the first poll with N packages fired N identical concurrent fetches\n * of the static carrier-list file (and a persistently failing endpoint was\n * retried N times per poll). Same pattern as beszel's auth mutex (B1).\n */\n private carrierFetchInFlight: Promise<CarrierMap> | null = null;\n /**\n * v0.4.2 (P1): per-request AbortController. `cancelAll()` aborts every\n * pending HTTPS request \u2014 called from the adapter's `onUnload` so a slow\n * parcel.app endpoint can't keep the adapter alive past js-controller's\n * 4-second kill deadline.\n */\n private readonly inflight = new Set<AbortController>();\n /** v0.4.3: optional logger for the HTTPS-layer trace. See {@link ParcelClientLogger}. */\n private readonly log?: ParcelClientLogger;\n /** API base URL. Overridable so tests can run the real `request()` against a local mock server. */\n private readonly baseUrl: string;\n\n /**\n * @param apiKey The parcel.app API key\n * @param log Optional adapter logger for HTTPS-layer trace (v0.4.3)\n * @param baseUrl API base URL \u2014 defaults to the production endpoint; overridden in tests\n */\n constructor(apiKey: string, log?: ParcelClientLogger, baseUrl: string = API_BASE) {\n this.apiKey = apiKey;\n this.log = log;\n this.baseUrl = baseUrl;\n }\n\n /**\n * v0.10.0 (L3): once cancelAll ran, the client is terminal \u2014 a request\n * STARTED after the abort (e.g. the carrier fetch kicked off by a poll\n * batch that was already past getDeliveries at unload) must not open a\n * fresh HTTPS connection that could outlive js-controller's 4s kill\n * deadline. `request()` rejects immediately when this is set.\n */\n private cancelled = false;\n\n /**\n * v0.4.2 (P1): abort every in-flight HTTPS request and refuse new ones.\n * Idempotent.\n */\n cancelAll(): void {\n // v0.4.3 (A12): trace the shutdown anchor so the adapter log shows\n // exactly how many HTTPS calls were aborted at unload.\n this.log?.debug(`cancelAll: aborting ${this.inflight.size} inflight requests`);\n this.cancelled = true;\n for (const ctrl of this.inflight) {\n ctrl.abort();\n }\n }\n\n /**\n * Fetch deliveries from parcel.app.\n *\n * Error style: rejects with a code-bearing {@link ApiError} on every failure\n * (HTTP status, drift, transport) \u2014 callers classify via `error.code`.\n *\n * @param filterMode Filter active or recent deliveries\n */\n async getDeliveries(filterMode: \"active\" | \"recent\" = \"active\"): Promise<ParcelDelivery[]> {\n const response = await this.request<ParcelApiResponse>(\"GET\", `/deliveries/?filter_mode=${filterMode}`, true);\n\n // API-drift guard: response may be null or a non-object\n if (!response || typeof response !== \"object\") {\n // v0.4.3 (A11a): trace malformed-response drift before throwing.\n this.log?.debug(`API drift: malformed response (got ${typeof response})`);\n throw apiError(\"API error: malformed response\", \"API_ERROR\");\n }\n\n if (!isTrueish(response.success)) {\n // v0.10.0 (M6): the external error_message is flattened + capped before it\n // reaches any log sink \u2014 it bubbles into the poll error-log via the Error\n // message, and an unsanitized multi-line value would forge log lines.\n const rawMsg =\n typeof response.error_message === \"string\" ? oneLine(response.error_message).slice(0, BODY_SNIPPET_LEN) : \"\";\n // v0.4.3 (A11b): trace API-side error before throwing. An invalid key is\n // reported via HTTP 401 (handled in request()), not via a body field \u2014\n // so a `success:false` body is always a generic API_ERROR.\n this.log?.debug(`API drift: success=false, msg='${rawMsg}'`);\n throw apiError(`API error: ${rawMsg || \"UNKNOWN\"}`, \"API_ERROR\");\n }\n\n // API-drift guard. An absent OR null `deliveries` is the API's \"no\n // deliveries\" shape \u2192 [] (zero active packages is the common state; a false\n // throw there would flip the adapter to disconnected on every poll). Only a\n // PRESENT, NON-NULL, wrong-typed value (string/number/object/boolean) is\n // real drift \u2014 throw so the poll keeps the existing states stale instead of\n // reading garbage as \"zero deliveries\" and deleting every package's states.\n if (response.deliveries == null) {\n return [];\n }\n if (!Array.isArray(response.deliveries)) {\n this.log?.debug(`API drift: deliveries not an array (got ${typeof response.deliveries})`);\n throw apiError(\"API error: deliveries not an array\", \"API_ERROR\");\n }\n return response.deliveries;\n }\n\n /**\n * Add a new delivery to parcel.app.\n *\n * Error style: transport/HTTP failures reject with {@link ApiError}; a 2xx\n * body is returned RAW and never validated \u2014 `success: false` is passed\n * through unchanged because sendTo callers receive this object verbatim.\n *\n * @param delivery The delivery to add\n */\n async addDelivery(delivery: AddDeliveryRequest): Promise<AddDeliveryResponse> {\n return this.request<AddDeliveryResponse>(\"POST\", \"/add-delivery/\", true, delivery);\n }\n\n /** Get carrier names (cached after first call; concurrent callers share one fetch) */\n async getCarrierNames(): Promise<CarrierMap> {\n if (this.carrierCache) {\n return this.carrierCache;\n }\n // v0.7.2: share one in-flight fetch between the parallel per-delivery\n // updates instead of firing N identical requests on the first poll.\n if (!this.carrierFetchInFlight) {\n this.carrierFetchInFlight = this.fetchCarrierNames().finally(() => {\n this.carrierFetchInFlight = null;\n });\n }\n return this.carrierFetchInFlight;\n }\n\n /**\n * One actual carrier-list fetch. Failure \u2192 empty map, NOT cached \u2014 retried by\n * the next update batch (the mutex above only dedupes CONCURRENT callers, so\n * a poll with several 25er batches may retry once per batch; the endpoint is\n * a static, unauthenticated file without a rate limit).\n */\n private async fetchCarrierNames(): Promise<CarrierMap> {\n try {\n const raw = await this.request<unknown>(\"GET\", \"/supported_carriers.json\", false);\n // API-drift guard: must be a plain object (not null, array, or primitive)\n if (raw && typeof raw === \"object\" && !Array.isArray(raw)) {\n // v0.9.0 (C6): keep only string-valued entries instead of asserting the\n // whole object is Record<string,string>. A drifted non-string value is\n // dropped here, so the cache is honestly typed (no `as CarrierMap`).\n const clean: CarrierMap = {};\n for (const [code, name] of Object.entries(raw)) {\n if (typeof name === \"string\") {\n clean[code] = name;\n }\n }\n this.carrierCache = clean;\n // v0.4.3 (D1): trace the one-time cache fill so a successful warm-up\n // is visible in the debug log (happens once per adapter restart).\n this.log?.debug(`carriers: fetched ${Object.keys(this.carrierCache).length} entries`);\n return this.carrierCache;\n }\n // v0.4.3 (D3): non-object drift \u2014 supported_carriers.json returned\n // something that isn't an object. Empty map is returned, NOT cached.\n this.log?.debug(\n `carriers: drift (got ${Array.isArray(raw) ? \"array\" : typeof raw}, expected object), kept empty`,\n );\n return {};\n } catch (err) {\n // v0.4.3 (D2): trace the fetch-fail so the empty-map fallback isn't\n // silent. NOT cached \u2014 next poll retries; the trace then shows the\n // retry, too. Without this the user sees carrier codes instead of\n // names with no log entry explaining why.\n this.log?.debug(`carriers: fetch failed (kept empty, will retry): ${errText(err)}`);\n // Return empty map but don't cache it \u2014 allow retry next time\n return {};\n }\n }\n\n /**\n * Resolve a carrier code to a display name.\n *\n * @param carrierCode The carrier code from API\n */\n async getCarrierName(carrierCode: unknown): Promise<string> {\n // API-drift guard: non-string codes fall back to \"UNKNOWN\"\n if (typeof carrierCode !== \"string\" || carrierCode.length === 0) {\n // v0.4.3 (D4): trace non-string code drift. Helps diagnose \"all my\n // packages show UNKNOWN carrier\" reports.\n this.log?.debug(`getCarrierName: non-string code (got ${typeof carrierCode}), returning UNKNOWN`);\n return \"UNKNOWN\";\n }\n const carriers = await this.getCarrierNames();\n const mapped = carriers[carrierCode];\n return typeof mapped === \"string\" && mapped.length > 0 ? mapped : carrierCode.toUpperCase();\n }\n\n /**\n * Test if the API key is valid.\n *\n * Error style: never throws \u2014 failures are folded into the returned\n * `{ success: false, message }` result object.\n */\n async testConnection(): Promise<{ success: boolean; message: string }> {\n try {\n await this.getDeliveries(\"active\");\n return { success: true, message: \"Connection successful\" };\n } catch (err) {\n const error = err as Error & { code?: string };\n if (error.code === \"INVALID_API_KEY\") {\n return { success: false, message: \"Invalid API key\" };\n }\n return { success: false, message: error.message };\n }\n }\n\n /**\n * Execute an HTTP request against the parcel.app API.\n *\n * @param method HTTP method\n * @param path API path\n * @param authenticated Whether to send the API key\n * @param body Optional request body\n */\n private request<T>(method: string, path: string, authenticated: boolean, body?: unknown): Promise<T> {\n // v0.4.3 (A0): start timestamp for elapsed-ms in the success/timeout/error\n // log lines. One LOC, no behavior change.\n const startedAt = Date.now();\n // v0.4.3 (A1): trace request entry. ~144 calls/day at the default 10-min\n // poll interval \u2014 acceptable at debug.\n this.log?.debug(`HTTP ${method} ${path}`);\n return new Promise((resolve, reject) => {\n // v0.10.0 (L3): terminal after cancelAll \u2014 a request started AFTER the\n // shutdown abort must not open a fresh connection.\n if (this.cancelled) {\n this.log?.debug(`HTTP ${method} ${path} refused \u2014 client cancelled`);\n reject(apiError(\"Client cancelled\", \"ABORTED\"));\n return;\n }\n // v0.4.2 (E3): URL-shape validation defensive \u2014 paths are hardcoded\n // upstream but a future caller could pass garbage; surface a clear\n // error class instead of a TypeError thrown sync from the executor.\n let url: URL;\n try {\n url = new URL(`${this.baseUrl}${path}`);\n } catch {\n // v0.4.3 (A10): trace invalid-URL drift before throwing.\n this.log?.debug(`HTTP invalid URL: ${this.baseUrl}${path}`);\n reject(apiError(`Invalid URL: ${this.baseUrl}${path}`, \"INVALID_URL\"));\n return;\n }\n\n const headers: Record<string, string> = {};\n if (authenticated) {\n headers[\"api-key\"] = this.apiKey;\n }\n if (body) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n const options: https.RequestOptions = {\n hostname: url.hostname,\n port: url.port || 443,\n path: url.pathname + url.search,\n method,\n headers,\n timeout: REQUEST_TIMEOUT,\n };\n\n // v0.4.2 (P1): per-request AbortController. `cancelAll()` (called\n // from `onUnload`) aborts everything pending without waiting for\n // the configured timeout.\n const ctrl = new AbortController();\n this.inflight.add(ctrl);\n // Marks the request as finished for the deadline listener below \u2014\n // every terminal path runs cleanup().\n let settled = false;\n const cleanup = (): void => {\n settled = true;\n this.inflight.delete(ctrl);\n };\n\n // Pick transport from the URL protocol so tests can run the real\n // request() against a local http mock server; production is always https.\n const transportRequest: (\n opts: https.RequestOptions,\n callback: (res: http.IncomingMessage) => void,\n ) => http.ClientRequest = url.protocol === \"http:\" ? http.request : https.request;\n\n const req = transportRequest(options, res => {\n const chunks: Buffer[] = [];\n let bodyBytes = 0;\n let oversized = false;\n\n res.on(\"error\", err => {\n cleanup();\n reject(err);\n });\n res.on(\"data\", (chunk: Buffer) => {\n if (oversized) {\n return;\n }\n bodyBytes += chunk.length;\n // v0.4.2 (P9): drop oversized responses so a compromised or\n // misconfigured endpoint can't OOM the adapter. Reject with the\n // stable BODY_TOO_LARGE code here, then destroy WITHOUT an error so\n // req.on(\"error\") doesn't fire a second, codeless rejection (the\n // earlier `req.destroy(Error)` preempted the end-handler's code).\n if (bodyBytes > MAX_BODY_BYTES) {\n oversized = true;\n // v0.4.3 (A9): trace the oversize-drop before destroying.\n this.log?.debug(`HTTP body oversized ${path}: dropping at ${bodyBytes}B`);\n cleanup();\n reject(apiError(\"Response body too large\", \"BODY_TOO_LARGE\"));\n req.destroy();\n return;\n }\n chunks.push(chunk);\n });\n res.on(\"end\", () => {\n if (oversized) {\n return; // already cleaned up + rejected in the data handler\n }\n cleanup();\n // The MAX_BODY_BYTES cap above bounds `chunks`, so concat stays well\n // under Buffer's max length and toString won't throw here.\n const raw = Buffer.concat(chunks).toString(\"utf-8\");\n\n if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {\n const httpError = ParcelClient.mapHttpStatusError(\n res.statusCode,\n res.statusMessage,\n res.headers[\"retry-after\"],\n );\n // v0.4.3 (A3/A4): trace 4xx/5xx with code, retry-after and body-snippet.\n this.log?.debug(\n `HTTP ${method} ${path} \u2192 ${res.statusCode} ${httpError.code}` +\n `${httpError.retryAfterSeconds !== undefined ? ` retry-after=${httpError.retryAfterSeconds}s` : \"\"}` +\n ` (body=${oneLine(raw.substring(0, BODY_SNIPPET_LEN))})`,\n );\n reject(httpError);\n return;\n }\n\n try {\n const parsed = JSON.parse(raw) as T;\n // v0.4.3 (A2): trace successful response with elapsed-ms + bytes.\n this.log?.debug(`HTTP ${method} ${path} \u2192 ${res.statusCode} (${Date.now() - startedAt}ms, ${bodyBytes}B)`);\n resolve(parsed);\n } catch {\n // v0.4.3 (A8): trace JSON parse-fail with snippet (debug only).\n this.log?.debug(`HTTP JSON parse fail ${path}: ${oneLine(raw.substring(0, BODY_SNIPPET_LEN))}`);\n // v0.9.0 (S1): keep the raw body OUT of the Error message \u2014 it\n // bubbles to a poll error-log; a malformed PII-bearing body must\n // not reach error level. The snippet stays in the debug line above.\n reject(apiError(`JSON parse error (${raw.length} bytes)`, \"PARSE_ERROR\"));\n }\n });\n });\n\n // v0.10.0 (M4): arm the hard deadline via AbortSignal.timeout \u2014 an\n // unref'd platform timer (never keeps the process alive, no adapter\n // context needed). Destroying with a TIMEOUT-coded ApiError routes\n // through req.on(\"error\") below, which rejects + cleans up \u2014 a trickle\n // response (a byte every few seconds) can no longer pin the poll loop.\n AbortSignal.timeout(REQUEST_DEADLINE_MS).addEventListener(\"abort\", () => {\n if (settled) {\n return; // request finished long ago \u2014 nothing to kill, nothing to log\n }\n this.log?.debug(`HTTP deadline ${method} ${path} (${Date.now() - startedAt}ms > ${REQUEST_DEADLINE_MS}ms)`);\n req.destroy(apiError(`Request deadline exceeded (${REQUEST_DEADLINE_MS / 1000}s)`, \"TIMEOUT\"));\n });\n\n ctrl.signal.addEventListener(\"abort\", () => {\n // v0.4.3: A6 deliberately omitted \u2014 `req.destroy(Error)` propagates\n // through `req.on(\"error\")` below where A7 already logs it.\n // v0.10.0 (M1): carries the ABORTED code so the adapter routes an\n // expected shutdown-abort to debug instead of an error log line.\n req.destroy(apiError(\"Request aborted\", \"ABORTED\"));\n });\n\n req.on(\"timeout\", () => {\n req.destroy();\n cleanup();\n // v0.4.3 (A5): trace timeout with elapsed-ms.\n this.log?.debug(`HTTP timeout ${method} ${path} (${Date.now() - startedAt}ms)`);\n reject(apiError(\"Request timeout\", \"TIMEOUT\"));\n });\n\n req.on(\"error\", err => {\n cleanup();\n // v0.4.3 (A7): trace network / abort / TLS / DNS errors with elapsed.\n // Also catches the abort case (req.destroy(ApiError)) \u2014 A6 deliberately\n // not emitted to avoid double-log.\n this.log?.debug(`HTTP error ${method} ${path} (${Date.now() - startedAt}ms): ${err.message}`);\n reject(err);\n });\n\n // v0.10.0 (I8): a synchronous throw from stringify/write/end (circular\n // body, stream state) must not strand the AbortController in `inflight`\n // \u2014 cancelAll's invariant is \"inflight mirrors live requests exactly\".\n try {\n if (body) {\n req.write(JSON.stringify(body));\n }\n req.end();\n } catch (err) {\n cleanup();\n req.destroy();\n reject(apiError(`Request write failed: ${errText(err)}`, \"API_ERROR\"));\n }\n });\n }\n\n /**\n * Map a non-2xx HTTP status to its {@link ApiError}. Pure \u2014 extracted from\n * the end-handler so the 401/403/429 rules read in isolation (v0.10.0, L17).\n *\n * @param statusCode HTTP status code (non-2xx)\n * @param statusMessage HTTP status message\n * @param retryAfterHeader Raw Retry-After header value (429 only)\n */\n private static mapHttpStatusError(\n statusCode: number,\n statusMessage: string | undefined,\n retryAfterHeader: string | undefined,\n ): ApiError {\n if (statusCode === 429) {\n // v0.4.2 (P6): clamp Retry-After. Bogus values (0, negative, NaN) fall\n // back to the default; extreme values are capped.\n const retryAfter = parseInt(retryAfterHeader || \"\", 10);\n const retryAfterSeconds =\n Number.isFinite(retryAfter) && retryAfter > 0\n ? Math.min(RETRY_AFTER_MAX_SEC, retryAfter)\n : RETRY_AFTER_DEFAULT_SEC;\n return apiError(\"Rate limit exceeded\", \"RATE_LIMITED\", { retryAfterSeconds });\n }\n // v0.4.2 (P3): split 401 (invalid key) from 403 (permission / no premium).\n // Adapter treats them differently \u2014 INVALID_API_KEY says \"fix the key\",\n // FORBIDDEN says \"fix the account\".\n const code: ApiErrorCode = statusCode === 401 ? \"INVALID_API_KEY\" : statusCode === 403 ? \"FORBIDDEN\" : \"HTTP_ERROR\";\n return apiError(`HTTP ${statusCode}: ${statusMessage}`, code);\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAAsB;AACtB,YAAuB;AACvB,oBAA4C;AAW5C,MAAM,WAAW;AAEjB,MAAM,kBAAkB;AAOxB,MAAM,sBAAsB;AAErB,MAAM,sBAAsB,KAAK;AACjC,MAAM,0BAA0B,IAAI;AAE3C,MAAM,mBAAmB;
|
|
4
|
+
"sourcesContent": ["import * as http from \"node:http\";\nimport * as https from \"node:https\";\nimport { errText, isTrueish, oneLine } from \"./coerce\";\nimport type {\n ApiError,\n ApiErrorCode,\n ParcelApiResponse,\n ParcelDelivery,\n AddDeliveryRequest,\n AddDeliveryResponse,\n CarrierMap,\n} from \"./types\";\n\nconst API_BASE = \"https://api.parcel.app/external\";\n/** Socket IDLE timeout \u2014 fires only when the connection goes silent. */\nconst REQUEST_TIMEOUT = 15_000;\n/**\n * Hard per-request deadline. The socket idle timeout above never fires against\n * a trickle response (a byte every few seconds), which would otherwise pin\n * `isPolling` forever and silently stop the poll loop until a restart. This\n * timer caps the TOTAL request duration regardless of socket activity.\n */\nconst REQUEST_DEADLINE_MS = 60_000;\n/** Shared Retry-After clamps (used by the client parser and the adapter cooldown). */\nexport const RETRY_AFTER_MAX_SEC = 24 * 3600;\nexport const RETRY_AFTER_DEFAULT_SEC = 5 * 60;\n/** Max chars of a response body quoted into a debug log line. */\nconst BODY_SNIPPET_LEN = 200;\n\n/**\n * v0.4.3: optional logger injected by the adapter so the HTTPS client can\n * trace its own request/response lifecycle. When omitted (e.g. in tests),\n * every `this.log?.debug(...)` call is a no-op \u2014 keeps the bare-`apiKey`\n * constructor signature backward-compatible.\n */\nexport interface ParcelClientLogger {\n /** Adapter debug log. Called per request/response outcome (drift, status, parse, oversize) \u2014 low-frequency tracing. */\n debug(message: string): void;\n}\n\n/**\n * Timeout overrides. Production never passes these \u2014 the defaults\n * ({@link REQUEST_TIMEOUT} / {@link REQUEST_DEADLINE_MS}) apply. Same seam idea\n * as the `baseUrl` parameter: without it the two watchdogs below could only be\n * exercised by a test that waits 15 respectively 60 seconds, so they stayed\n * untested \u2014 and an untested watchdog is exactly the one that silently stops\n * working (test audit 2026-08-22, finding C14).\n */\nexport interface ParcelClientTimeouts {\n /** Socket idle timeout (ms). */\n idleMs?: number;\n /** Hard per-request deadline (ms). */\n deadlineMs?: number;\n}\n/**\n * v0.4.2 (P9): hard cap on response body size. parcel.app deliveries lists\n * are tiny (~1 kB per package, max ~50 packages = 50 kB), so a 1 MiB cap is\n * 20\u00D7 the realistic max while still defending against a runaway response.\n */\nconst MAX_BODY_BYTES = 1 << 20; // 1 MiB\n\n/**\n * Build an {@link ApiError} carrying a typed `code` (and optional extra fields\n * such as `retryAfterSeconds`). Centralizes the `new Error(...)` + `err.code`\n * pattern; the `ApiErrorCode` union makes a typo on either side of the\n * client\u2194adapter contract a compile error.\n *\n * @param message Human-readable error message.\n * @param code Machine-readable error code used by the adapter for classification.\n * @param extra Optional additional own-properties to attach to the error.\n */\nfunction apiError(message: string, code: ApiErrorCode, extra?: Record<string, unknown>): ApiError {\n const err = new Error(message) as ApiError;\n err.code = code;\n if (extra) {\n Object.assign(err, extra);\n }\n return err;\n}\n\n/** HTTP client for the parcel.app API */\nexport class ParcelClient {\n private apiKey: string;\n private carrierCache: CarrierMap | null = null;\n /**\n * v0.7.2: in-flight fetch for the carrier list. The per-delivery updates run\n * in parallel (Promise.all) and each resolves carrier names \u2014 without this\n * mutex the first poll with N packages fired N identical concurrent fetches\n * of the static carrier-list file (and a persistently failing endpoint was\n * retried N times per poll). Same pattern as beszel's auth mutex (B1).\n */\n private carrierFetchInFlight: Promise<CarrierMap> | null = null;\n /**\n * v0.4.2 (P1): per-request AbortController. `cancelAll()` aborts every\n * pending HTTPS request \u2014 called from the adapter's `onUnload` so a slow\n * parcel.app endpoint can't keep the adapter alive past js-controller's\n * 4-second kill deadline.\n */\n private readonly inflight = new Set<AbortController>();\n /** v0.4.3: optional logger for the HTTPS-layer trace. See {@link ParcelClientLogger}. */\n private readonly log?: ParcelClientLogger;\n /** API base URL. Overridable so tests can run the real `request()` against a local mock server. */\n private readonly baseUrl: string;\n /** Socket idle timeout in ms \u2014 same seam idea as {@link baseUrl}, see {@link ParcelClientTimeouts}. */\n private readonly idleTimeoutMs: number;\n /** Hard per-request deadline in ms \u2014 see {@link ParcelClientTimeouts}. */\n private readonly deadlineMs: number;\n\n /**\n * @param apiKey The parcel.app API key\n * @param log Optional adapter logger for HTTPS-layer trace (v0.4.3)\n * @param baseUrl API base URL \u2014 defaults to the production endpoint; overridden in tests\n * @param timeouts Timeout overrides \u2014 production always uses the defaults\n */\n constructor(\n apiKey: string,\n log?: ParcelClientLogger,\n baseUrl: string = API_BASE,\n timeouts: ParcelClientTimeouts = {},\n ) {\n this.apiKey = apiKey;\n this.log = log;\n this.baseUrl = baseUrl;\n this.idleTimeoutMs = timeouts.idleMs ?? REQUEST_TIMEOUT;\n this.deadlineMs = timeouts.deadlineMs ?? REQUEST_DEADLINE_MS;\n }\n\n /**\n * v0.10.0 (L3): once cancelAll ran, the client is terminal \u2014 a request\n * STARTED after the abort (e.g. the carrier fetch kicked off by a poll\n * batch that was already past getDeliveries at unload) must not open a\n * fresh HTTPS connection that could outlive js-controller's 4s kill\n * deadline. `request()` rejects immediately when this is set.\n */\n private cancelled = false;\n\n /**\n * v0.4.2 (P1): abort every in-flight HTTPS request and refuse new ones.\n * Idempotent.\n */\n cancelAll(): void {\n // v0.4.3 (A12): trace the shutdown anchor so the adapter log shows\n // exactly how many HTTPS calls were aborted at unload.\n this.log?.debug(`cancelAll: aborting ${this.inflight.size} inflight requests`);\n this.cancelled = true;\n for (const ctrl of this.inflight) {\n ctrl.abort();\n }\n }\n\n /**\n * Fetch deliveries from parcel.app.\n *\n * Error style: rejects with a code-bearing {@link ApiError} on every failure\n * (HTTP status, drift, transport) \u2014 callers classify via `error.code`.\n *\n * @param filterMode Filter active or recent deliveries\n */\n async getDeliveries(filterMode: \"active\" | \"recent\" = \"active\"): Promise<ParcelDelivery[]> {\n const response = await this.request<ParcelApiResponse>(\"GET\", `/deliveries/?filter_mode=${filterMode}`, true);\n\n // API-drift guard: response may be null or a non-object\n if (!response || typeof response !== \"object\") {\n // v0.4.3 (A11a): trace malformed-response drift before throwing.\n this.log?.debug(`API drift: malformed response (got ${typeof response})`);\n throw apiError(\"API error: malformed response\", \"API_ERROR\");\n }\n\n if (!isTrueish(response.success)) {\n // v0.10.0 (M6): the external error_message is flattened + capped before it\n // reaches any log sink \u2014 it bubbles into the poll error-log via the Error\n // message, and an unsanitized multi-line value would forge log lines.\n const rawMsg =\n typeof response.error_message === \"string\" ? oneLine(response.error_message).slice(0, BODY_SNIPPET_LEN) : \"\";\n // v0.4.3 (A11b): trace API-side error before throwing. An invalid key is\n // reported via HTTP 401 (handled in request()), not via a body field \u2014\n // so a `success:false` body is always a generic API_ERROR.\n this.log?.debug(`API drift: success=false, msg='${rawMsg}'`);\n throw apiError(`API error: ${rawMsg || \"UNKNOWN\"}`, \"API_ERROR\");\n }\n\n // API-drift guard. An absent OR null `deliveries` is the API's \"no\n // deliveries\" shape \u2192 [] (zero active packages is the common state; a false\n // throw there would flip the adapter to disconnected on every poll). Only a\n // PRESENT, NON-NULL, wrong-typed value (string/number/object/boolean) is\n // real drift \u2014 throw so the poll keeps the existing states stale instead of\n // reading garbage as \"zero deliveries\" and deleting every package's states.\n if (response.deliveries == null) {\n return [];\n }\n if (!Array.isArray(response.deliveries)) {\n this.log?.debug(`API drift: deliveries not an array (got ${typeof response.deliveries})`);\n throw apiError(\"API error: deliveries not an array\", \"API_ERROR\");\n }\n return response.deliveries;\n }\n\n /**\n * Add a new delivery to parcel.app.\n *\n * Error style: transport/HTTP failures reject with {@link ApiError}; a 2xx\n * body is returned RAW and never validated \u2014 `success: false` is passed\n * through unchanged because sendTo callers receive this object verbatim.\n *\n * @param delivery The delivery to add\n */\n async addDelivery(delivery: AddDeliveryRequest): Promise<AddDeliveryResponse> {\n return this.request<AddDeliveryResponse>(\"POST\", \"/add-delivery/\", true, delivery);\n }\n\n /** Get carrier names (cached after first call; concurrent callers share one fetch) */\n async getCarrierNames(): Promise<CarrierMap> {\n if (this.carrierCache) {\n return this.carrierCache;\n }\n // v0.7.2: share one in-flight fetch between the parallel per-delivery\n // updates instead of firing N identical requests on the first poll.\n if (!this.carrierFetchInFlight) {\n this.carrierFetchInFlight = this.fetchCarrierNames().finally(() => {\n this.carrierFetchInFlight = null;\n });\n }\n return this.carrierFetchInFlight;\n }\n\n /**\n * One actual carrier-list fetch. Failure \u2192 empty map, NOT cached \u2014 retried by\n * the next update batch (the mutex above only dedupes CONCURRENT callers, so\n * a poll with several 25er batches may retry once per batch; the endpoint is\n * a static, unauthenticated file without a rate limit).\n */\n private async fetchCarrierNames(): Promise<CarrierMap> {\n try {\n const raw = await this.request<unknown>(\"GET\", \"/supported_carriers.json\", false);\n // API-drift guard: must be a plain object (not null, array, or primitive)\n if (raw && typeof raw === \"object\" && !Array.isArray(raw)) {\n // v0.9.0 (C6): keep only string-valued entries instead of asserting the\n // whole object is Record<string,string>. A drifted non-string value is\n // dropped here, so the cache is honestly typed (no `as CarrierMap`).\n const clean: CarrierMap = {};\n for (const [code, name] of Object.entries(raw)) {\n if (typeof name === \"string\") {\n clean[code] = name;\n }\n }\n this.carrierCache = clean;\n // v0.4.3 (D1): trace the one-time cache fill so a successful warm-up\n // is visible in the debug log (happens once per adapter restart).\n this.log?.debug(`carriers: fetched ${Object.keys(this.carrierCache).length} entries`);\n return this.carrierCache;\n }\n // v0.4.3 (D3): non-object drift \u2014 supported_carriers.json returned\n // something that isn't an object. Empty map is returned, NOT cached.\n this.log?.debug(\n `carriers: drift (got ${Array.isArray(raw) ? \"array\" : typeof raw}, expected object), kept empty`,\n );\n return {};\n } catch (err) {\n // v0.4.3 (D2): trace the fetch-fail so the empty-map fallback isn't\n // silent. NOT cached \u2014 next poll retries; the trace then shows the\n // retry, too. Without this the user sees carrier codes instead of\n // names with no log entry explaining why.\n this.log?.debug(`carriers: fetch failed (kept empty, will retry): ${errText(err)}`);\n // Return empty map but don't cache it \u2014 allow retry next time\n return {};\n }\n }\n\n /**\n * Resolve a carrier code to a display name.\n *\n * @param carrierCode The carrier code from API\n */\n async getCarrierName(carrierCode: unknown): Promise<string> {\n // API-drift guard: non-string codes fall back to \"UNKNOWN\"\n if (typeof carrierCode !== \"string\" || carrierCode.length === 0) {\n // v0.4.3 (D4): trace non-string code drift. Helps diagnose \"all my\n // packages show UNKNOWN carrier\" reports.\n this.log?.debug(`getCarrierName: non-string code (got ${typeof carrierCode}), returning UNKNOWN`);\n return \"UNKNOWN\";\n }\n const carriers = await this.getCarrierNames();\n const mapped = carriers[carrierCode];\n return typeof mapped === \"string\" && mapped.length > 0 ? mapped : carrierCode.toUpperCase();\n }\n\n /**\n * Test if the API key is valid.\n *\n * Error style: never throws \u2014 failures are folded into the returned\n * `{ success: false, message }` result object.\n */\n async testConnection(): Promise<{ success: boolean; message: string }> {\n try {\n await this.getDeliveries(\"active\");\n return { success: true, message: \"Connection successful\" };\n } catch (err) {\n const error = err as Error & { code?: string };\n if (error.code === \"INVALID_API_KEY\") {\n return { success: false, message: \"Invalid API key\" };\n }\n return { success: false, message: error.message };\n }\n }\n\n /**\n * Execute an HTTP request against the parcel.app API.\n *\n * @param method HTTP method\n * @param path API path\n * @param authenticated Whether to send the API key\n * @param body Optional request body\n */\n private request<T>(method: string, path: string, authenticated: boolean, body?: unknown): Promise<T> {\n // v0.4.3 (A0): start timestamp for elapsed-ms in the success/timeout/error\n // log lines. One LOC, no behavior change.\n const startedAt = Date.now();\n // v0.4.3 (A1): trace request entry. ~144 calls/day at the default 10-min\n // poll interval \u2014 acceptable at debug.\n this.log?.debug(`HTTP ${method} ${path}`);\n return new Promise((resolve, reject) => {\n // v0.10.0 (L3): terminal after cancelAll \u2014 a request started AFTER the\n // shutdown abort must not open a fresh connection.\n if (this.cancelled) {\n this.log?.debug(`HTTP ${method} ${path} refused \u2014 client cancelled`);\n reject(apiError(\"Client cancelled\", \"ABORTED\"));\n return;\n }\n // v0.4.2 (E3): URL-shape validation defensive \u2014 paths are hardcoded\n // upstream but a future caller could pass garbage; surface a clear\n // error class instead of a TypeError thrown sync from the executor.\n let url: URL;\n try {\n url = new URL(`${this.baseUrl}${path}`);\n } catch {\n // v0.4.3 (A10): trace invalid-URL drift before throwing.\n this.log?.debug(`HTTP invalid URL: ${this.baseUrl}${path}`);\n reject(apiError(`Invalid URL: ${this.baseUrl}${path}`, \"INVALID_URL\"));\n return;\n }\n\n const headers: Record<string, string> = {};\n if (authenticated) {\n headers[\"api-key\"] = this.apiKey;\n }\n if (body) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n const options: https.RequestOptions = {\n hostname: url.hostname,\n port: url.port || 443,\n path: url.pathname + url.search,\n method,\n headers,\n timeout: this.idleTimeoutMs,\n };\n\n // v0.4.2 (P1): per-request AbortController. `cancelAll()` (called\n // from `onUnload`) aborts everything pending without waiting for\n // the configured timeout.\n const ctrl = new AbortController();\n this.inflight.add(ctrl);\n // Marks the request as finished for the deadline listener below \u2014\n // every terminal path runs cleanup().\n let settled = false;\n const cleanup = (): void => {\n settled = true;\n this.inflight.delete(ctrl);\n };\n\n // Pick transport from the URL protocol so tests can run the real\n // request() against a local http mock server; production is always https.\n const transportRequest: (\n opts: https.RequestOptions,\n callback: (res: http.IncomingMessage) => void,\n ) => http.ClientRequest = url.protocol === \"http:\" ? http.request : https.request;\n\n const req = transportRequest(options, res => {\n const chunks: Buffer[] = [];\n let bodyBytes = 0;\n let oversized = false;\n\n res.on(\"error\", err => {\n cleanup();\n reject(err);\n });\n res.on(\"data\", (chunk: Buffer) => {\n // Not covered by a test on purpose: this branch only runs for a chunk\n // that was already buffered when `req.destroy()` below fired, i.e. a\n // delivery race no test can trigger deterministically. A test that\n // hits it \"usually\" would be exactly the kind of flaky check the\n // 2026-08-22 audit removed elsewhere.\n if (oversized) {\n return;\n }\n bodyBytes += chunk.length;\n // v0.4.2 (P9): drop oversized responses so a compromised or\n // misconfigured endpoint can't OOM the adapter. Reject with the\n // stable BODY_TOO_LARGE code here, then destroy WITHOUT an error so\n // req.on(\"error\") doesn't fire a second, codeless rejection (the\n // earlier `req.destroy(Error)` preempted the end-handler's code).\n if (bodyBytes > MAX_BODY_BYTES) {\n oversized = true;\n // v0.4.3 (A9): trace the oversize-drop before destroying.\n this.log?.debug(`HTTP body oversized ${path}: dropping at ${bodyBytes}B`);\n cleanup();\n reject(apiError(\"Response body too large\", \"BODY_TOO_LARGE\"));\n req.destroy();\n return;\n }\n chunks.push(chunk);\n });\n res.on(\"end\", () => {\n if (oversized) {\n return; // already cleaned up + rejected in the data handler\n }\n cleanup();\n // The MAX_BODY_BYTES cap above bounds `chunks`, so concat stays well\n // under Buffer's max length and toString won't throw here.\n const raw = Buffer.concat(chunks).toString(\"utf-8\");\n\n if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {\n const httpError = ParcelClient.mapHttpStatusError(\n res.statusCode,\n res.statusMessage,\n res.headers[\"retry-after\"],\n );\n // v0.4.3 (A3/A4): trace 4xx/5xx with code, retry-after and body-snippet.\n this.log?.debug(\n `HTTP ${method} ${path} \u2192 ${res.statusCode} ${httpError.code}` +\n `${httpError.retryAfterSeconds !== undefined ? ` retry-after=${httpError.retryAfterSeconds}s` : \"\"}` +\n ` (body=${oneLine(raw.substring(0, BODY_SNIPPET_LEN))})`,\n );\n reject(httpError);\n return;\n }\n\n try {\n const parsed = JSON.parse(raw) as T;\n // v0.4.3 (A2): trace successful response with elapsed-ms + bytes.\n this.log?.debug(`HTTP ${method} ${path} \u2192 ${res.statusCode} (${Date.now() - startedAt}ms, ${bodyBytes}B)`);\n resolve(parsed);\n } catch {\n // v0.4.3 (A8): trace JSON parse-fail with snippet (debug only).\n this.log?.debug(`HTTP JSON parse fail ${path}: ${oneLine(raw.substring(0, BODY_SNIPPET_LEN))}`);\n // v0.9.0 (S1): keep the raw body OUT of the Error message \u2014 it\n // bubbles to a poll error-log; a malformed PII-bearing body must\n // not reach error level. The snippet stays in the debug line above.\n reject(apiError(`JSON parse error (${raw.length} bytes)`, \"PARSE_ERROR\"));\n }\n });\n });\n\n // v0.10.0 (M4): arm the hard deadline via AbortSignal.timeout \u2014 an\n // unref'd platform timer (never keeps the process alive, no adapter\n // context needed). Destroying with a TIMEOUT-coded ApiError routes\n // through req.on(\"error\") below, which rejects + cleans up \u2014 a trickle\n // response (a byte every few seconds) can no longer pin the poll loop.\n AbortSignal.timeout(this.deadlineMs).addEventListener(\"abort\", () => {\n if (settled) {\n return; // request finished long ago \u2014 nothing to kill, nothing to log\n }\n this.log?.debug(`HTTP deadline ${method} ${path} (${Date.now() - startedAt}ms > ${this.deadlineMs}ms)`);\n req.destroy(apiError(`Request deadline exceeded (${this.deadlineMs / 1000}s)`, \"TIMEOUT\"));\n });\n\n ctrl.signal.addEventListener(\"abort\", () => {\n // v0.4.3: A6 deliberately omitted \u2014 `req.destroy(Error)` propagates\n // through `req.on(\"error\")` below where A7 already logs it.\n // v0.10.0 (M1): carries the ABORTED code so the adapter routes an\n // expected shutdown-abort to debug instead of an error log line.\n req.destroy(apiError(\"Request aborted\", \"ABORTED\"));\n });\n\n req.on(\"timeout\", () => {\n req.destroy();\n cleanup();\n // v0.4.3 (A5): trace timeout with elapsed-ms.\n this.log?.debug(`HTTP timeout ${method} ${path} (${Date.now() - startedAt}ms)`);\n reject(apiError(\"Request timeout\", \"TIMEOUT\"));\n });\n\n req.on(\"error\", err => {\n cleanup();\n // v0.4.3 (A7): trace network / abort / TLS / DNS errors with elapsed.\n // Also catches the abort case (req.destroy(ApiError)) \u2014 A6 deliberately\n // not emitted to avoid double-log.\n this.log?.debug(`HTTP error ${method} ${path} (${Date.now() - startedAt}ms): ${err.message}`);\n reject(err);\n });\n\n // v0.10.0 (I8): a synchronous throw from stringify/write/end (circular\n // body, stream state) must not strand the AbortController in `inflight`\n // \u2014 cancelAll's invariant is \"inflight mirrors live requests exactly\".\n try {\n if (body) {\n req.write(JSON.stringify(body));\n }\n req.end();\n } catch (err) {\n cleanup();\n req.destroy();\n reject(apiError(`Request write failed: ${errText(err)}`, \"API_ERROR\"));\n }\n });\n }\n\n /**\n * Map a non-2xx HTTP status to its {@link ApiError}. Pure \u2014 extracted from\n * the end-handler so the 401/403/429 rules read in isolation (v0.10.0, L17).\n *\n * @param statusCode HTTP status code (non-2xx)\n * @param statusMessage HTTP status message\n * @param retryAfterHeader Raw Retry-After header value (429 only)\n */\n private static mapHttpStatusError(\n statusCode: number,\n statusMessage: string | undefined,\n retryAfterHeader: string | undefined,\n ): ApiError {\n if (statusCode === 429) {\n // v0.4.2 (P6): clamp Retry-After. Bogus values (0, negative, NaN) fall\n // back to the default; extreme values are capped.\n const retryAfter = parseInt(retryAfterHeader || \"\", 10);\n const retryAfterSeconds =\n Number.isFinite(retryAfter) && retryAfter > 0\n ? Math.min(RETRY_AFTER_MAX_SEC, retryAfter)\n : RETRY_AFTER_DEFAULT_SEC;\n return apiError(\"Rate limit exceeded\", \"RATE_LIMITED\", { retryAfterSeconds });\n }\n // v0.4.2 (P3): split 401 (invalid key) from 403 (permission / no premium).\n // Adapter treats them differently \u2014 INVALID_API_KEY says \"fix the key\",\n // FORBIDDEN says \"fix the account\".\n const code: ApiErrorCode = statusCode === 401 ? \"INVALID_API_KEY\" : statusCode === 403 ? \"FORBIDDEN\" : \"HTTP_ERROR\";\n return apiError(`HTTP ${statusCode}: ${statusMessage}`, code);\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["_a", "_b"]
|
|
7
7
|
}
|
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, isTrueish, oneLine } from \"./lib/coerce\";\nimport { ParcelClient, RETRY_AFTER_DEFAULT_SEC, RETRY_AFTER_MAX_SEC } from \"./lib/parcel-client\";\nimport { StateManager } from \"./lib/state-manager\";\nimport { DELIVERED_STATUS_CODE } from \"./lib/types\";\nimport type { AddDeliveryRequest } from \"./lib/types\";\n\nconst MIN_POLL_INTERVAL = 5;\nconst MAX_POLL_INTERVAL = 60;\nconst DEFAULT_POLL_INTERVAL = 10;\n// Minimum 60s between polls. Also the natural pacing after addDelivery: a\n// typical single add still polls immediately (the last poll is >60s ago),\n// while a burst of adds collapses to at most one extra GET per minute.\nconst MIN_POLL_GAP_MS = 60_000;\n/** v0.4.2 (M6): minimum length for an apiKey value to even be considered valid. */\nconst MIN_API_KEY_LENGTH = 10;\n// v0.9.0 (S5): cap addDelivery field lengths. A sendTo caller is local, but a\n// runaway script must not push a multi-MB POST body to parcel.app. Identifiers\n// are short; this is generous for a description.\nconst MAX_ADD_FIELD_LEN = 512;\n// v0.9.0 (S3): cap the per-poll broker fan-out. Updates run in batches of this\n// size instead of all deliveries at once, so an abnormally large API response\n// can't flood the broker with thousands of concurrent writes.\nconst UPDATE_BATCH_SIZE = 25;\n// v0.9.0 (S4): client-side throttle on addDelivery POSTs. parcel.app enforces\n// ~20 POST/day server-side; this caps a runaway/buggy script's burst so it can't\n// hammer the API. The window is generous enough never to block a real batch-add\n// (the daily server limit is the real cap).\nconst MAX_ADDS_PER_WINDOW = 20;\nconst ADD_WINDOW_MS = 60_000;\n\n/**\n * Node transport-level error codes treated as (transient) NETWORK problems:\n * DNS, connection refused/reset, unreachable \u2014 plus EPIPE/ECONNABORTED/EPROTO\n * (v0.10.0, I9), which belong to the same warn+keep-retrying class.\n */\nconst NETWORK_ERROR_CODES = new Set([\n \"ENOTFOUND\",\n \"ECONNREFUSED\",\n \"ECONNRESET\",\n \"ENETUNREACH\",\n \"EHOSTUNREACH\",\n \"EAI_AGAIN\",\n \"EPIPE\",\n \"ECONNABORTED\",\n \"EPROTO\",\n]);\n\n/**\n * v0.10.0 (L12): the seam contracts name exactly the members main uses \u2014\n * structural Picks let the orchestration tests inject plain object fakes\n * without unsafe double-casts, and the compiler documents what a fake must\n * provide.\n */\ntype ClientLike = Pick<\n ParcelClient,\n \"getDeliveries\" | \"getCarrierName\" | \"addDelivery\" | \"testConnection\" | \"cancelAll\"\n>;\ntype StateManagerLike = Pick<\n StateManager,\n \"resetPollState\" | \"packageId\" | \"parseStatus\" | \"updateDelivery\" | \"updateSummary\" | \"cleanupDeliveries\"\n>;\n\n/**\n * ioBroker adapter for parcel.app package tracking. Exported so the\n * orchestration unit tests can drive its lifecycle/poll handlers directly.\n */\nexport class ParcelappAdapter extends utils.Adapter {\n private client: ClientLike | null = null;\n private stateManager: StateManagerLike | null = null;\n /**\n * Factories for the HTTP client + state manager \u2014 default to the real\n * constructors. Test seams (fleet pattern): unit tests replace these with\n * fakes to exercise the poll orchestration (throttle/rate-limit interplay,\n * error routing, failure dedup) without real network.\n *\n * @param apiKey parcel.app API key\n */\n private makeClient: (apiKey: string) => ClientLike = apiKey =>\n new ParcelClient(apiKey, { debug: (m: string) => this.log.debug(m) });\n private makeStateManager: () => StateManagerLike = () => new StateManager(this);\n private pollTimer: ioBroker.Interval | undefined = undefined;\n private isPolling = false;\n private lastPollTime = 0;\n private rateLimitedUntil = 0;\n private lastErrorCode = \"\";\n /**\n * v0.10.0 (L2): set in onUnload. onReady checks it after its awaits so a\n * stop during the first poll can no longer arm the interval or log\n * \"started\" after the unload already ran; batch failures during shutdown\n * degrade to debug.\n */\n private unloaded = false;\n /**\n * Package ids (not raw tracking numbers) whose last updateDelivery failed.\n * Keyed like the states so the dedup survives a sanitize collision or a\n * missing tracking number; pruned each poll against the visible pkgIds.\n */\n private failedDeliveries = new Set<string>();\n /** Timestamps of recent addDelivery POSTs \u2014 the S4 throttle window. */\n private addTimestamps: number[] = [];\n /**\n * L2: true while a checkConnection test GET is in flight. A test hits the same\n * 20/hour GET budget as polling; this guards against a concurrent second test\n * (double-click / admin re-render) stacking a redundant GET. A sequential\n * re-test after the current one settles runs normally.\n */\n private testConnectionInFlight = false;\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<ClientLike>();\n\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({\n ...options,\n name: \"parcelapp\",\n });\n this.on(\"ready\", this.onReady.bind(this));\n this.on(\"unload\", this.onUnload.bind(this));\n this.on(\"message\", this.onMessage.bind(this));\n }\n\n private async onReady(): Promise<void> {\n try {\n // I18n.init resolves system.config.language itself (adapter-core reads\n // the foreign object internally; unknown languages fall back to English)\n // \u2014 the former separate getForeignObject round-trip and the local\n // resolveLanguage step are gone with the STATUS_LABELS table (L20).\n await I18n.init(join(this.adapterDir, \"admin\"), this);\n this.log.debug(`onReady: starting (autoRemoveDelivered=${this.config.autoRemoveDelivered})`);\n\n await this.setState(\"info.connection\", { val: false, ack: true });\n\n const { apiKey } = this.config;\n if (!apiKey || apiKey.trim().length < MIN_API_KEY_LENGTH) {\n this.log.error(\"No valid API key configured \u2014 please enter your parcel.app API key in the adapter settings\");\n return;\n }\n\n this.client = this.makeClient(apiKey.trim());\n this.stateManager = this.makeStateManager();\n\n try {\n await this.cleanupObsoleteStates();\n } catch (err) {\n // C8: a cleanup failure must not abort startup. Without this guard the\n // outer catch would skip arming the poll interval below, and the adapter\n // would never poll until a manual restart. Degrade to a warning.\n this.log.warn(`cleanupObsoleteStates failed (continuing): ${errText(err)}`);\n }\n\n await this.poll();\n\n // v0.10.0 (L2): a stop during the awaits above must not arm a timer or\n // log a start line after onUnload already ran.\n if (this.unloaded) {\n return;\n }\n\n const interval = ParcelappAdapter.coercePollInterval(this.config.pollInterval);\n this.log.debug(`pollInterval: raw=${JSON.stringify(this.config.pollInterval)} resolved=${interval}min`);\n const intervalMs = interval * 60 * 1000;\n this.pollTimer = this.setInterval(() => {\n void this.poll().catch(err => this.log.error(`Scheduled poll failed: ${errText(err)}`));\n }, intervalMs);\n\n this.log.info(`Parcel tracking started \u2014 polling every ${interval} minutes`);\n } catch (err: unknown) {\n this.log.error(`onReady failed: ${errText(err)}`);\n // v0.10.0 (L4): a transient startup failure (i18n files, DB hiccup) used\n // to leave a green-looking zombie \u2014 no client, no timer, no retry until\n // a manual restart. Terminate instead: js-controller restarts the\n // instance, which self-heals the transient case; its restart-loop guard\n // backstops a persistent one.\n if (!this.unloaded) {\n this.terminate(\"startup failed \u2014 requesting restart\", utils.EXIT_CODES.START_IMMEDIATELY_AFTER_STOP);\n }\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 this.unloaded = true;\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. cancelAll also marks the\n // client terminal (v0.10.0, L3) \u2014 requests started after this point\n // are refused instead of opening fresh connections.\n this.client?.cancelAll();\n // v0.4.4: also abort any short-lived test-client (from checkConnection)\n // whose HTTPS-request might still be inflight at shutdown \u2014 the prod\n // `this.client.cancelAll()` only touches the production-client.\n for (const tc of this.testClients) {\n tc.cancelAll();\n }\n this.testClients.clear();\n // v0.4.2 (M10): explicit `.catch(() => {})` on the fire-and-forget so\n // a broker-already-down doesn't leak as an unhandled rejection.\n void this.setState(\"info.connection\", { val: false, ack: true }).catch(() => {\n /* broker is shutting down \u2014 ignore */\n });\n } catch (err) {\n // v0.4.3 (G4): replace silent `// ignore` with a trace so shutdown\n // errors leave a debug breadcrumb. Broker-already-down errors here\n // are expected \u2014 debug-level keeps them out of the user log.\n try {\n this.log.debug(`onUnload error (ignored): ${errText(err)}`);\n } catch {\n /* logger already gone \u2014 nothing left to report to */\n }\n } finally {\n // v0.10.0 (I7): the callback is the contract with js-controller \u2014\n // structurally guaranteed exactly once, even if the catch-path throws.\n callback();\n }\n }\n\n private async onMessage(obj: ioBroker.Message): Promise<void> {\n try {\n // v0.4.3 (F1): entry log BEFORE the early-return \u2014 broadcast messages\n // without callback wouldn't be visible otherwise. Inside the try since\n // v0.10.0 (I6) so the handler body is gap-free top-level guarded.\n this.log.debug(\n `onMessage: command='${oneLine(String(obj?.command ?? \"\"))}' from='${obj?.from}' has-callback=${!!obj?.callback}`,\n );\n if (!obj?.command || !obj.callback) {\n return;\n }\n\n switch (obj.command) {\n case \"checkConnection\":\n await this.handleCheckConnection(obj);\n break;\n case \"addDelivery\":\n await this.handleAddDelivery(obj);\n break;\n default:\n // v0.4.3 (F6): trace unknown command before sendTo.\n this.log.debug(`onMessage: unknown command '${oneLine(String(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. The\n // reply itself is guarded too (v0.10.0, I6) \u2014 a synchronous sendTo\n // throw must not escape the message handler. checkConnection replies\n // use the admin {error} envelope (H1), scripts keep the documented\n // {success, error_message} shape.\n try {\n this.log.debug(`onMessage: '${oneLine(String(obj?.command ?? \"\"))}' failed: ${errText(err)}`);\n if (obj?.callback) {\n const reply =\n obj.command === \"checkConnection\"\n ? { error: errText(err) }\n : { success: false, error_message: errText(err) };\n this.sendTo(obj.from, obj.command, reply, obj.callback);\n }\n } catch {\n /* reply channel gone \u2014 nothing left to do */\n }\n }\n }\n\n /**\n * Admin \"Test Connection\" button (H1). The jsonConfig sendTo component reads\n * ONLY `response.error` / `response.result` \u2014 never success/message \u2014 so the\n * internal `{success, message}` result is mapped to that contract here.\n * Before this, a FAILED test showed a false-positive \"Ok\" in the admin\n * (fleet fix; beszel's message-router is the model).\n *\n * @param obj The sendTo message (validated: command + callback present)\n */\n private async handleCheckConnection(obj: ioBroker.Message): Promise<void> {\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, { error: \"API key is too short\" }, obj.callback);\n return;\n }\n // L2: a Test-Connection GET counts against the same 20/hour budget as\n // polling. Guard against a concurrent second test (double-click / admin\n // re-render) so stacked clicks can't each burn a GET (which could later trip\n // the poll's rate-limit cooldown). Set BEFORE the await so the check is\n // synchronous against a still-in-flight first test.\n if (this.testConnectionInFlight) {\n this.log.debug(\"checkConnection: a test is already running\");\n this.sendTo(obj.from, obj.command, { error: \"A connection test is already running \u2014 please wait\" }, obj.callback);\n return;\n }\n this.testConnectionInFlight = true;\n // v0.4.3: same debug-logger as the prod client so checkConnection\n // failures get the same HTTPS-layer trace (via the makeClient seam).\n const testClient = this.makeClient(key);\n // v0.4.4: register test-client so onUnload can abort its inflight\n // HTTPS-request \u2014 the adapter's `this.client.cancelAll()` only\n // touches the prod-client, not these short-lived test-clients.\n this.testClients.add(testClient);\n try {\n const result = await testClient.testConnection();\n // v0.4.3 (F3): trace checkConnection result.\n this.log.debug(`checkConnection: result=${result.success ? \"ok\" : \"fail\"} (${result.message})`);\n this.sendTo(\n obj.from,\n obj.command,\n result.success ? { result: result.message } : { error: result.message },\n obj.callback,\n );\n } finally {\n this.testClients.delete(testClient);\n this.testConnectionInFlight = false;\n }\n }\n\n /**\n * Reply an addDelivery failure to the sendTo caller. This is the documented\n * script API envelope (`{success: false, error_message}`) \u2014 unchanged for\n * backward compatibility; only the admin checkConnection uses {error}.\n *\n * @param obj The sendTo message being answered\n * @param message Human-readable failure reason\n */\n private replyAddError(obj: ioBroker.Message, message: string): void {\n this.sendTo(obj.from, obj.command, { success: false, error_message: message }, obj.callback);\n }\n\n /**\n * Script-facing addDelivery command: validate the message shape, cap field\n * lengths, throttle bursts, forward to the API and trigger a poll on\n * success. Extracted from the onMessage switch (M9) \u2014 one command, one\n * method, one change reason.\n *\n * @param obj The sendTo message (validated: command + callback present)\n */\n private async handleAddDelivery(obj: ioBroker.Message): Promise<void> {\n if (!this.client) {\n // v0.4.3 (F4): trace addDelivery-before-init.\n this.log.debug(\"addDelivery: adapter not initialized\");\n this.replyAddError(obj, \"Adapter not initialized\");\n return;\n }\n // v0.7.2: obj.message is `unknown`-shaped \u2014 a script calling\n // sendTo(\"parcelapp\", \"addDelivery\", null) used to surface as an\n // ugly TypeError through the catch instead of a clear validation\n // message. Coerce to a plain object and validate required fields.\n const raw = obj.message;\n const msg = raw !== null && typeof raw === \"object\" && !Array.isArray(raw) ? (raw as Record<string, unknown>) : {};\n if (\n typeof msg.tracking_number !== \"string\" ||\n msg.tracking_number.length === 0 ||\n typeof msg.carrier_code !== \"string\" ||\n msg.carrier_code.length === 0 ||\n typeof msg.description !== \"string\" ||\n msg.description.length === 0\n ) {\n this.log.debug(\"addDelivery: missing tracking_number/carrier_code/description in message\");\n this.replyAddError(obj, \"tracking_number, carrier_code and description are required\");\n return;\n }\n // v0.9.0 (S5): cap field lengths so a runaway local script can't push\n // a multi-MB POST body to parcel.app. Checked after the required-field\n // guard above so the two validation messages stay distinct.\n if (\n msg.tracking_number.length > MAX_ADD_FIELD_LEN ||\n msg.carrier_code.length > MAX_ADD_FIELD_LEN ||\n msg.description.length > MAX_ADD_FIELD_LEN ||\n (typeof msg.language === \"string\" && msg.language.length > MAX_ADD_FIELD_LEN)\n ) {\n this.log.debug(\"addDelivery: a field exceeds the maximum length\");\n this.replyAddError(obj, `each field must be at most ${MAX_ADD_FIELD_LEN} characters`);\n return;\n }\n // Pass the optional API fields through when the caller supplies them\n // (language: ISO 639-1 two-letter code; send_push_confirmation: push\n // notification once the delivery is added).\n const request: AddDeliveryRequest = {\n tracking_number: msg.tracking_number,\n carrier_code: msg.carrier_code,\n description: msg.description,\n };\n if (typeof msg.language === \"string\" && msg.language.length > 0) {\n request.language = msg.language;\n }\n if (typeof msg.send_push_confirmation === \"boolean\") {\n request.send_push_confirmation = msg.send_push_confirmation;\n }\n // v0.9.0 (S4): throttle addDelivery POSTs. parcel.app caps ~20/day\n // server-side; this stops a runaway/buggy script from hammering the API\n // with a burst. Record the attempt before the await so concurrent\n // callers count too.\n const nowMs = Date.now();\n this.addTimestamps = this.addTimestamps.filter(t => nowMs - t < ADD_WINDOW_MS);\n if (this.addTimestamps.length >= MAX_ADDS_PER_WINDOW) {\n this.log.warn(`addDelivery throttled: more than ${MAX_ADDS_PER_WINDOW} requests within ${ADD_WINDOW_MS / 1000}s`);\n this.replyAddError(obj, `too many addDelivery requests; max ${MAX_ADDS_PER_WINDOW} per ${ADD_WINDOW_MS / 1000}s`);\n return;\n }\n this.addTimestamps.push(nowMs);\n const addResult = await this.client.addDelivery(request);\n // v0.4.3 (F5): trace addDelivery result with the (flattened) tracking number.\n // v0.10.0 (L9): the drift-guarded isTrueish \u2014 getDeliveries hardens the same\n // API flag; a drifted `success: \"false\"` string must not read as truthy.\n const added = isTrueish(addResult.success);\n this.log.debug(`addDelivery: '${oneLine(request.tracking_number)}' result=${added ? \"ok\" : \"fail\"}`);\n this.sendTo(obj.from, obj.command, addResult, obj.callback);\n if (added) {\n // v0.10.0 (L5): plain poll, no force \u2014 a single add still polls right\n // away (the last poll is usually >60s back), but an add-burst can no\n // longer stack force-GETs past the 20/h API budget. Nothing is lost:\n // the server caches the list ~45-90 min, a fresh package rarely shows\n // tracking data immediately anyway.\n void this.poll().catch(err => this.log.error(`Poll after addDelivery failed: ${errText(err)}`));\n }\n }\n\n private async cleanupObsoleteStates(): Promise<void> {\n // One getObject per adapter start, forever \u2014 deliberately kept (I3/ARCH-24):\n // a one-shot migration marker would cost more mechanics than this read.\n // Drop the list entirely at the next major once 0.1.x installs are gone.\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 * v0.10.0 (M1): the client codes every failure it raises (TIMEOUT,\n * PARSE_ERROR, ABORTED, RATE_LIMITED, \u2026) \u2014 a present machine code always\n * wins. The message-substring sniff only remains for code-less foreign\n * errors, so an API error_message merely CONTAINING \"timeout\" can no longer\n * be misclassified.\n *\n * @param error The error to classify\n */\n private classifyError(error: Error & { code?: string }): string {\n if (error.code) {\n if (NETWORK_ERROR_CODES.has(error.code)) {\n return \"NETWORK\";\n }\n if (error.code === \"ETIMEDOUT\") {\n return \"TIMEOUT\";\n }\n return error.code;\n }\n if (error.message.includes(\"timeout\")) {\n return \"TIMEOUT\";\n }\n return \"UNKNOWN\";\n }\n\n private async poll(): Promise<void> {\n if (this.isPolling || !this.client || !this.stateManager) {\n // v0.10.0 (M4): make the re-entry/uninitialized skip visible like the\n // rate-limit/throttle skips below \u2014 this used to be the one silent spot\n // where \"the adapter does nothing\" left no trace.\n this.log.debug(\"Skipping poll \u2014 already running or not initialized\");\n return;\n }\n // v0.10.0 (L14): local bindings instead of non-null assertions in the\n // closures below \u2014 provable narrowing the compiler checks, robust against\n // a future refactor nulling the fields mid-poll.\n const client = this.client;\n const stateManager = this.stateManager;\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 (also paces the poll after a\n // successful addDelivery \u2014 see handleAddDelivery).\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 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 => stateManager.parseStatus(d) !== DELIVERED_STATUS_CODE);\n const visibleDeliveries = autoRemoveMode ? activeDeliveries : deliveries;\n\n // v0.4.2 (S3): reset the per-poll collision tracker, then compute every\n // package id in a deterministic sequential pre-pass (stable array order)\n // BEFORE the parallel updates \u2014 collision-suffixing is then deterministic\n // and packageId runs exactly once per delivery instead of twice.\n stateManager.resetPollState();\n const pkgIds = visibleDeliveries.map(d => stateManager.packageId(d));\n\n // v0.4.2 (M4): per-delivery updates run in parallel, each wrapped in\n // try/catch so one bad delivery doesn't poison the others.\n // v0.9.0 (S3): process the updates in bounded batches instead of one\n // broker fan-out for ALL deliveries at once. The keep-set is still every\n // visible pkgId (computed above), so this only caps concurrency \u2014 it never\n // drops a package. A normal poll (a handful of packages) is a single batch.\n if (visibleDeliveries.length > UPDATE_BATCH_SIZE) {\n this.log.debug(`Updating ${visibleDeliveries.length} deliveries in batches of ${UPDATE_BATCH_SIZE}`);\n }\n for (let start = 0; start < visibleDeliveries.length; start += UPDATE_BATCH_SIZE) {\n const batch = visibleDeliveries.slice(start, start + UPDATE_BATCH_SIZE);\n await Promise.all(\n batch.map(async (delivery, offset) => {\n const pkgId = pkgIds[start + offset];\n // Pre-sanitize the externally-sourced strings once for logging. The\n // fields are optional (the API can drop them), so default to \"\" before\n // oneLine flattens them onto a single log line.\n const tracking = oneLine(delivery.tracking_number ?? \"\");\n const carrier = oneLine(delivery.carrier_code ?? \"\");\n try {\n // v0.4.3 (C1): per-delivery entry. ~10 packages \u00D7 144 polls/day\n // = ~1440 debug lines/day \u2014 acceptable at debug-level. Line stays\n // short (tracking + carrier + status only, no full delivery JSON).\n // v0.10.0 (M6): status_code is typed number|string (drift), so it\n // is flattened like the other externally-sourced fields.\n this.log.debug(\n `updateDelivery: '${tracking}' carrier=${carrier} status=${oneLine(String(delivery.status_code))}`,\n );\n const carrierName = await client.getCarrierName(delivery.carrier_code);\n await stateManager.updateDelivery(delivery, carrierName, pkgId);\n this.failedDeliveries.delete(pkgId);\n } catch (err) {\n const msg = errText(err);\n if (this.failedDeliveries.has(pkgId)) {\n this.log.debug(`Failed to update '${tracking}': ${msg}`);\n } else if (this.unloaded) {\n // v0.10.0 (L2): broker teardown mid-batch is expected noise\n // during shutdown, not a per-package warning.\n this.log.debug(`Failed to update '${tracking}' during shutdown: ${msg}`);\n } else {\n this.log.warn(`Failed to update '${tracking}': ${msg}`);\n this.failedDeliveries.add(pkgId);\n }\n }\n }),\n );\n }\n\n // v0.9.0 (C1): keep-set = EVERY package the API still returns this poll\n // (pkgIds), NOT only the writes that just succeeded. A transient\n // updateDelivery failure leaves a package's states stale, but it must not\n // drop the package from cleanup \u2014 that would delete a still-present\n // package's states (and any user-set device name) at green info.connection.\n // v0.10.0 (M2): broker-side failures in cleanup/summary are NOT API\n // failures \u2014 they must neither flip info.connection to false (the GET\n // above just succeeded) nor run through the API error classification.\n try {\n await stateManager.cleanupDeliveries(pkgIds);\n // Update summary (always uses active/non-delivered)\n await stateManager.updateSummary(activeDeliveries);\n } catch (err) {\n this.log.warn(`State maintenance failed (API connection is fine, retrying next poll): ${errText(err)}`);\n }\n\n // Keep failedDeliveries bounded: drop entries for package ids no longer\n // present, so packages that vanish from the API don't linger forever.\n const seenPkgIds = new Set(pkgIds);\n for (const id of [...this.failedDeliveries]) {\n if (!seenPkgIds.has(id)) {\n this.failedDeliveries.delete(id);\n }\n }\n\n this.log.debug(`Polled ${visibleDeliveries.length} deliveries (${activeDeliveries.length} active)`);\n } catch (err) {\n await this.handlePollError(err as Error & { code?: string; retryAfterSeconds?: number });\n } finally {\n this.isPolling = false;\n }\n }\n\n /**\n * Classify + route a poll failure: log level, dedup, cooldown and the\n * info.connection=false write. Extracted from poll()'s catch (M9) so the\n * happy path reads as a plain sequence and the error policy sits next to\n * classifyError. Dispatches on the CLASSIFIED code only (L6) \u2014 one source\n * of truth for the error class.\n *\n * @param error The poll failure (usually an ApiError from the client)\n */\n private async handlePollError(error: Error & { code?: string; retryAfterSeconds?: number }): Promise<void> {\n const errorCode = this.classifyError(error);\n const isRepeat = errorCode === this.lastErrorCode;\n this.lastErrorCode = errorCode;\n\n switch (errorCode) {\n case \"ABORTED\":\n // v0.10.0 (M1): expected during shutdown \u2014 cancelAll aborts the\n // in-flight GET. A deliberate stop must not paint a red error line.\n this.log.debug(`Poll aborted: ${error.message}`);\n break;\n case \"RATE_LIMITED\": {\n // v0.4.2 (M9): clamp Retry-After into [60s, 24h] (shared constants\n // with the client parser, L7). A bogus 0/negative/fractional value\n // must neither wipe the cooldown nor set it for milliseconds.\n const rawCooldown = error.retryAfterSeconds ?? 0;\n const cooldownSec =\n Number.isFinite(rawCooldown) && rawCooldown > 0\n ? Math.min(RETRY_AFTER_MAX_SEC, Math.max(60, Math.floor(rawCooldown)))\n : RETRY_AFTER_DEFAULT_SEC;\n this.rateLimitedUntil = Date.now() + cooldownSec * 1000;\n // v0.10.0 (M3): warn once \u2014 a persistent 429 repeats at debug.\n const line = `Rate limit hit \u2014 pausing API requests for ${Math.ceil(cooldownSec / 60)} minute(s)`;\n if (isRepeat) {\n this.log.debug(line);\n } else {\n this.log.warn(line);\n }\n break;\n }\n case \"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 // v0.10.0 (M3): once at error level, repeats at debug \u2014 not 144\n // identical error lines per day for one unchanged account problem.\n const line =\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 if (isRepeat) {\n this.log.debug(line);\n } else {\n this.log.error(line);\n }\n break;\n }\n case \"INVALID_API_KEY\": {\n // v0.10.0 (M3): first occurrence at error (the user must fix the\n // config; info.connection goes red too) \u2014 repeats at debug.\n const line = \"Invalid API key \u2014 please check your parcel.app API key\";\n if (isRepeat) {\n this.log.debug(line);\n } else {\n this.log.error(line);\n }\n break;\n }\n case \"NETWORK\":\n if (isRepeat) {\n this.log.debug(`Poll failed (ongoing): ${error.message}`);\n } else {\n this.log.warn(\"Cannot reach parcel.app API \u2014 will keep retrying\");\n }\n break;\n case \"TIMEOUT\":\n if (isRepeat) {\n this.log.debug(`Poll failed (ongoing): ${error.message}`);\n } else {\n this.log.warn(\"API request timeout \u2014 will retry next cycle\");\n }\n break;\n default:\n 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 {\n this.log.error(`Poll failed: ${error.message}`);\n }\n }\n\n // C2: setStateChangedAsync avoids redundant `false` writes on sustained\n // failure. The `.catch` keeps poll() from rejecting when the broker is\n // already down, so the fire-and-forget callers never see an unhandled\n // rejection (no global process handler needed).\n await this.setStateChangedAsync(\"info.connection\", { val: false, ack: true }).catch(() => {\n /* broker shutting down \u2014 ignore */\n });\n }\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, isTrueish, oneLine } from \"./lib/coerce\";\nimport { ParcelClient, RETRY_AFTER_DEFAULT_SEC, RETRY_AFTER_MAX_SEC } from \"./lib/parcel-client\";\nimport { StateManager } from \"./lib/state-manager\";\nimport { DELIVERED_STATUS_CODE } from \"./lib/types\";\nimport type { AddDeliveryRequest } from \"./lib/types\";\n\nconst MIN_POLL_INTERVAL = 5;\nconst MAX_POLL_INTERVAL = 60;\nconst DEFAULT_POLL_INTERVAL = 10;\n// Minimum 60s between polls. Also the natural pacing after addDelivery: a\n// typical single add still polls immediately (the last poll is >60s ago),\n// while a burst of adds collapses to at most one extra GET per minute.\nconst MIN_POLL_GAP_MS = 60_000;\n/** v0.4.2 (M6): minimum length for an apiKey value to even be considered valid. */\nconst MIN_API_KEY_LENGTH = 10;\n// v0.9.0 (S5): cap addDelivery field lengths. A sendTo caller is local, but a\n// runaway script must not push a multi-MB POST body to parcel.app. Identifiers\n// are short; this is generous for a description.\nconst MAX_ADD_FIELD_LEN = 512;\n// v0.9.0 (S3): cap the per-poll broker fan-out. Updates run in batches of this\n// size instead of all deliveries at once, so an abnormally large API response\n// can't flood the broker with thousands of concurrent writes.\nconst UPDATE_BATCH_SIZE = 25;\n// v0.9.0 (S4): client-side throttle on addDelivery POSTs. parcel.app enforces\n// ~20 POST/day server-side; this caps a runaway/buggy script's burst so it can't\n// hammer the API. The window is generous enough never to block a real batch-add\n// (the daily server limit is the real cap).\nconst MAX_ADDS_PER_WINDOW = 20;\nconst ADD_WINDOW_MS = 60_000;\n\n/**\n * Node transport-level error codes treated as (transient) NETWORK problems:\n * DNS, connection refused/reset, unreachable \u2014 plus EPIPE/ECONNABORTED/EPROTO\n * (v0.10.0, I9), which belong to the same warn+keep-retrying class.\n */\nconst NETWORK_ERROR_CODES = new Set([\n \"ENOTFOUND\",\n \"ECONNREFUSED\",\n \"ECONNRESET\",\n \"ENETUNREACH\",\n \"EHOSTUNREACH\",\n \"EAI_AGAIN\",\n \"EPIPE\",\n \"ECONNABORTED\",\n \"EPROTO\",\n]);\n\n/**\n * v0.10.0 (L12): the seam contracts name exactly the members main uses \u2014\n * structural Picks let the orchestration tests inject plain object fakes\n * without unsafe double-casts, and the compiler documents what a fake must\n * provide.\n */\ntype ClientLike = Pick<\n ParcelClient,\n \"getDeliveries\" | \"getCarrierName\" | \"addDelivery\" | \"testConnection\" | \"cancelAll\"\n>;\ntype StateManagerLike = Pick<\n StateManager,\n \"resetPollState\" | \"packageId\" | \"parseStatus\" | \"updateDelivery\" | \"updateSummary\" | \"cleanupDeliveries\"\n>;\n\n/**\n * ioBroker adapter for parcel.app package tracking. Exported so the\n * orchestration unit tests can drive its lifecycle/poll handlers directly.\n */\nexport class ParcelappAdapter extends utils.Adapter {\n private client: ClientLike | null = null;\n private stateManager: StateManagerLike | null = null;\n /**\n * Factories for the HTTP client + state manager \u2014 default to the real\n * constructors. Test seams (fleet pattern): unit tests replace these with\n * fakes to exercise the poll orchestration (throttle/rate-limit interplay,\n * error routing, failure dedup) without real network.\n *\n * @param apiKey parcel.app API key\n */\n private makeClient: (apiKey: string) => ClientLike = apiKey =>\n new ParcelClient(apiKey, { debug: (m: string) => this.log.debug(m) });\n private makeStateManager: () => StateManagerLike = () => new StateManager(this);\n private pollTimer: ioBroker.Interval | undefined = undefined;\n private isPolling = false;\n private lastPollTime = 0;\n private rateLimitedUntil = 0;\n private lastErrorCode = \"\";\n /**\n * v0.10.0 (L2): set in onUnload. onReady checks it after its awaits so a\n * stop during the first poll can no longer arm the interval or log\n * \"started\" after the unload already ran; batch failures during shutdown\n * degrade to debug.\n */\n private unloaded = false;\n /**\n * Package ids (not raw tracking numbers) whose last updateDelivery failed.\n * Keyed like the states so the dedup survives a sanitize collision or a\n * missing tracking number; pruned each poll against the visible pkgIds.\n */\n private failedDeliveries = new Set<string>();\n /** Timestamps of recent addDelivery POSTs \u2014 the S4 throttle window. */\n private addTimestamps: number[] = [];\n /**\n * L2: true while a checkConnection test GET is in flight. A test hits the same\n * 20/hour GET budget as polling; this guards against a concurrent second test\n * (double-click / admin re-render) stacking a redundant GET. A sequential\n * re-test after the current one settles runs normally.\n */\n private testConnectionInFlight = false;\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<ClientLike>();\n\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({\n ...options,\n name: \"parcelapp\",\n });\n this.on(\"ready\", this.onReady.bind(this));\n this.on(\"unload\", this.onUnload.bind(this));\n this.on(\"message\", this.onMessage.bind(this));\n }\n\n private async onReady(): Promise<void> {\n try {\n // I18n.init resolves system.config.language itself (adapter-core reads\n // the foreign object internally; unknown languages fall back to English)\n // \u2014 the former separate getForeignObject round-trip and the local\n // resolveLanguage step are gone with the STATUS_LABELS table (L20).\n await I18n.init(join(this.adapterDir, \"admin\"), this);\n this.log.debug(`onReady: starting (autoRemoveDelivered=${this.config.autoRemoveDelivered})`);\n\n await this.setState(\"info.connection\", { val: false, ack: true });\n\n const { apiKey } = this.config;\n if (!apiKey || apiKey.trim().length < MIN_API_KEY_LENGTH) {\n this.log.error(\"No valid API key configured \u2014 please enter your parcel.app API key in the adapter settings\");\n return;\n }\n\n this.client = this.makeClient(apiKey.trim());\n this.stateManager = this.makeStateManager();\n\n try {\n await this.cleanupObsoleteStates();\n } catch (err) {\n // C8: a cleanup failure must not abort startup. Without this guard the\n // outer catch would skip arming the poll interval below, and the adapter\n // would never poll until a manual restart. Degrade to a warning.\n this.log.warn(`cleanupObsoleteStates failed (continuing): ${errText(err)}`);\n }\n\n await this.poll();\n\n // v0.10.0 (L2): a stop during the awaits above must not arm a timer or\n // log a start line after onUnload already ran.\n if (this.unloaded) {\n return;\n }\n\n const interval = ParcelappAdapter.coercePollInterval(this.config.pollInterval);\n this.log.debug(`pollInterval: raw=${JSON.stringify(this.config.pollInterval)} resolved=${interval}min`);\n const intervalMs = interval * 60 * 1000;\n this.pollTimer = this.setInterval(() => {\n void this.poll().catch(err => this.log.error(`Scheduled poll failed: ${errText(err)}`));\n }, intervalMs);\n\n this.log.info(`Parcel tracking started \u2014 polling every ${interval} minutes`);\n } catch (err: unknown) {\n this.log.error(`onReady failed: ${errText(err)}`);\n // v0.10.0 (L4): a transient startup failure (i18n files, DB hiccup) used\n // to leave a green-looking zombie \u2014 no client, no timer, no retry until\n // a manual restart. Terminate instead: js-controller restarts the\n // instance, which self-heals the transient case; its restart-loop guard\n // backstops a persistent one.\n if (!this.unloaded) {\n this.terminate(\"startup failed \u2014 requesting restart\", utils.EXIT_CODES.START_IMMEDIATELY_AFTER_STOP);\n }\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 this.unloaded = true;\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. cancelAll also marks the\n // client terminal (v0.10.0, L3) \u2014 requests started after this point\n // are refused instead of opening fresh connections.\n this.client?.cancelAll();\n // v0.4.4: also abort any short-lived test-client (from checkConnection)\n // whose HTTPS-request might still be inflight at shutdown \u2014 the prod\n // `this.client.cancelAll()` only touches the production-client.\n for (const tc of this.testClients) {\n tc.cancelAll();\n }\n this.testClients.clear();\n // v0.4.2 (M10): explicit `.catch(() => {})` on the fire-and-forget so\n // a broker-already-down doesn't leak as an unhandled rejection.\n void this.setState(\"info.connection\", { val: false, ack: true }).catch(() => {\n /* broker is shutting down \u2014 ignore */\n });\n } catch (err) {\n // v0.4.3 (G4): replace silent `// ignore` with a trace so shutdown\n // errors leave a debug breadcrumb. Broker-already-down errors here\n // are expected \u2014 debug-level keeps them out of the user log.\n try {\n this.log.debug(`onUnload error (ignored): ${errText(err)}`);\n } catch {\n /* logger already gone \u2014 nothing left to report to */\n }\n } finally {\n // v0.10.0 (I7): the callback is the contract with js-controller \u2014\n // structurally guaranteed exactly once, even if the catch-path throws.\n callback();\n }\n }\n\n private async onMessage(obj: ioBroker.Message): Promise<void> {\n try {\n // v0.4.3 (F1): entry log BEFORE the early-return \u2014 broadcast messages\n // without callback wouldn't be visible otherwise. Inside the try since\n // v0.10.0 (I6) so the handler body is gap-free top-level guarded.\n this.log.debug(\n `onMessage: command='${oneLine(String(obj?.command ?? \"\"))}' from='${obj?.from}' has-callback=${!!obj?.callback}`,\n );\n if (!obj?.command || !obj.callback) {\n return;\n }\n\n switch (obj.command) {\n case \"checkConnection\":\n await this.handleCheckConnection(obj);\n break;\n case \"addDelivery\":\n await this.handleAddDelivery(obj);\n break;\n default:\n // v0.4.3 (F6): trace unknown command before sendTo.\n this.log.debug(`onMessage: unknown command '${oneLine(String(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. The\n // reply itself is guarded too (v0.10.0, I6) \u2014 a synchronous sendTo\n // throw must not escape the message handler. checkConnection replies\n // use the admin {error} envelope (H1), scripts keep the documented\n // {success, error_message} shape.\n try {\n this.log.debug(`onMessage: '${oneLine(String(obj?.command ?? \"\"))}' failed: ${errText(err)}`);\n if (obj?.callback) {\n const reply =\n obj.command === \"checkConnection\"\n ? { error: errText(err) }\n : { success: false, error_message: errText(err) };\n this.sendTo(obj.from, obj.command, reply, obj.callback);\n }\n } catch {\n /* reply channel gone \u2014 nothing left to do */\n }\n }\n }\n\n /**\n * Admin \"Test Connection\" button (H1). The jsonConfig sendTo component reads\n * ONLY `response.error` / `response.result` \u2014 never success/message \u2014 so the\n * internal `{success, message}` result is mapped to that contract here.\n * Before this, a FAILED test showed a false-positive \"Ok\" in the admin\n * (fleet fix; beszel's message-router is the model).\n *\n * @param obj The sendTo message (validated: command + callback present)\n */\n private async handleCheckConnection(obj: ioBroker.Message): Promise<void> {\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, { error: \"API key is too short\" }, obj.callback);\n return;\n }\n // L2: a Test-Connection GET counts against the same 20/hour budget as\n // polling. Guard against a concurrent second test (double-click / admin\n // re-render) so stacked clicks can't each burn a GET (which could later trip\n // the poll's rate-limit cooldown). Set BEFORE the await so the check is\n // synchronous against a still-in-flight first test.\n if (this.testConnectionInFlight) {\n this.log.debug(\"checkConnection: a test is already running\");\n this.sendTo(obj.from, obj.command, { error: \"A connection test is already running \u2014 please wait\" }, obj.callback);\n return;\n }\n this.testConnectionInFlight = true;\n // v0.4.3: same debug-logger as the prod client so checkConnection\n // failures get the same HTTPS-layer trace (via the makeClient seam).\n const testClient = this.makeClient(key);\n // v0.4.4: register test-client so onUnload can abort its inflight\n // HTTPS-request \u2014 the adapter's `this.client.cancelAll()` only\n // touches the prod-client, not these short-lived test-clients.\n this.testClients.add(testClient);\n try {\n const result = await testClient.testConnection();\n // v0.4.3 (F3): trace checkConnection result.\n this.log.debug(`checkConnection: result=${result.success ? \"ok\" : \"fail\"} (${result.message})`);\n this.sendTo(\n obj.from,\n obj.command,\n result.success ? { result: result.message } : { error: result.message },\n obj.callback,\n );\n } finally {\n this.testClients.delete(testClient);\n this.testConnectionInFlight = false;\n }\n }\n\n /**\n * Reply an addDelivery failure to the sendTo caller. This is the documented\n * script API envelope (`{success: false, error_message}`) \u2014 unchanged for\n * backward compatibility; only the admin checkConnection uses {error}.\n *\n * @param obj The sendTo message being answered\n * @param message Human-readable failure reason\n */\n private replyAddError(obj: ioBroker.Message, message: string): void {\n this.sendTo(obj.from, obj.command, { success: false, error_message: message }, obj.callback);\n }\n\n /**\n * Script-facing addDelivery command: validate the message shape, cap field\n * lengths, throttle bursts, forward to the API and trigger a poll on\n * success. Extracted from the onMessage switch (M9) \u2014 one command, one\n * method, one change reason.\n *\n * @param obj The sendTo message (validated: command + callback present)\n */\n private async handleAddDelivery(obj: ioBroker.Message): Promise<void> {\n if (!this.client) {\n // v0.4.3 (F4): trace addDelivery-before-init.\n this.log.debug(\"addDelivery: adapter not initialized\");\n this.replyAddError(obj, \"Adapter not initialized\");\n return;\n }\n // v0.7.2: obj.message is `unknown`-shaped \u2014 a script calling\n // sendTo(\"parcelapp\", \"addDelivery\", null) used to surface as an\n // ugly TypeError through the catch instead of a clear validation\n // message. Coerce to a plain object and validate required fields.\n const raw = obj.message;\n const msg = raw !== null && typeof raw === \"object\" && !Array.isArray(raw) ? (raw as Record<string, unknown>) : {};\n if (\n typeof msg.tracking_number !== \"string\" ||\n msg.tracking_number.length === 0 ||\n typeof msg.carrier_code !== \"string\" ||\n msg.carrier_code.length === 0 ||\n typeof msg.description !== \"string\" ||\n msg.description.length === 0\n ) {\n this.log.debug(\"addDelivery: missing tracking_number/carrier_code/description in message\");\n this.replyAddError(obj, \"tracking_number, carrier_code and description are required\");\n return;\n }\n // v0.9.0 (S5): cap field lengths so a runaway local script can't push\n // a multi-MB POST body to parcel.app. Checked after the required-field\n // guard above so the two validation messages stay distinct.\n if (\n msg.tracking_number.length > MAX_ADD_FIELD_LEN ||\n msg.carrier_code.length > MAX_ADD_FIELD_LEN ||\n msg.description.length > MAX_ADD_FIELD_LEN ||\n (typeof msg.language === \"string\" && msg.language.length > MAX_ADD_FIELD_LEN)\n ) {\n this.log.debug(\"addDelivery: a field exceeds the maximum length\");\n this.replyAddError(obj, `each field must be at most ${MAX_ADD_FIELD_LEN} characters`);\n return;\n }\n // Pass the optional API fields through when the caller supplies them\n // (language: ISO 639-1 two-letter code; send_push_confirmation: push\n // notification once the delivery is added).\n const request: AddDeliveryRequest = {\n tracking_number: msg.tracking_number,\n carrier_code: msg.carrier_code,\n description: msg.description,\n };\n if (typeof msg.language === \"string\" && msg.language.length > 0) {\n request.language = msg.language;\n }\n if (typeof msg.send_push_confirmation === \"boolean\") {\n request.send_push_confirmation = msg.send_push_confirmation;\n }\n // v0.9.0 (S4): throttle addDelivery POSTs. parcel.app caps ~20/day\n // server-side; this stops a runaway/buggy script from hammering the API\n // with a burst. Record the attempt before the await so concurrent\n // callers count too.\n const nowMs = Date.now();\n this.addTimestamps = this.addTimestamps.filter(t => nowMs - t < ADD_WINDOW_MS);\n if (this.addTimestamps.length >= MAX_ADDS_PER_WINDOW) {\n this.log.warn(`addDelivery throttled: more than ${MAX_ADDS_PER_WINDOW} requests within ${ADD_WINDOW_MS / 1000}s`);\n this.replyAddError(obj, `too many addDelivery requests; max ${MAX_ADDS_PER_WINDOW} per ${ADD_WINDOW_MS / 1000}s`);\n return;\n }\n this.addTimestamps.push(nowMs);\n const addResult = await this.client.addDelivery(request);\n // v0.4.3 (F5): trace addDelivery result with the (flattened) tracking number.\n // v0.10.0 (L9): the drift-guarded isTrueish \u2014 getDeliveries hardens the same\n // API flag; a drifted `success: \"false\"` string must not read as truthy.\n const added = isTrueish(addResult.success);\n this.log.debug(`addDelivery: '${oneLine(request.tracking_number)}' result=${added ? \"ok\" : \"fail\"}`);\n this.sendTo(obj.from, obj.command, addResult, obj.callback);\n if (added) {\n // v0.10.0 (L5): plain poll, no force \u2014 a single add still polls right\n // away (the last poll is usually >60s back), but an add-burst can no\n // longer stack force-GETs past the 20/h API budget. Nothing is lost:\n // the server caches the list ~45-90 min, a fresh package rarely shows\n // tracking data immediately anyway.\n void this.poll().catch(err => this.log.error(`Poll after addDelivery failed: ${errText(err)}`));\n }\n }\n\n private async cleanupObsoleteStates(): Promise<void> {\n // One getObject per adapter start, forever \u2014 deliberately kept (I3/ARCH-24):\n // a one-shot migration marker would cost more mechanics than this read.\n // Drop the list entirely at the next major once 0.1.x installs are gone.\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 * v0.10.0 (M1): the client codes every failure it raises (TIMEOUT,\n * PARSE_ERROR, ABORTED, RATE_LIMITED, \u2026) \u2014 a present machine code always\n * wins. The message-substring sniff only remains for code-less foreign\n * errors, so an API error_message merely CONTAINING \"timeout\" can no longer\n * be misclassified.\n *\n * @param error The error to classify\n */\n private classifyError(error: Error & { code?: string }): string {\n if (error.code) {\n if (NETWORK_ERROR_CODES.has(error.code)) {\n return \"NETWORK\";\n }\n if (error.code === \"ETIMEDOUT\") {\n return \"TIMEOUT\";\n }\n return error.code;\n }\n if (error.message.includes(\"timeout\")) {\n return \"TIMEOUT\";\n }\n return \"UNKNOWN\";\n }\n\n private async poll(): Promise<void> {\n if (this.isPolling || !this.client || !this.stateManager) {\n // v0.10.0 (M4): make the re-entry/uninitialized skip visible like the\n // rate-limit/throttle skips below \u2014 this used to be the one silent spot\n // where \"the adapter does nothing\" left no trace.\n this.log.debug(\"Skipping poll \u2014 already running or not initialized\");\n return;\n }\n // v0.10.0 (L14): local bindings instead of non-null assertions in the\n // closures below \u2014 provable narrowing the compiler checks, robust against\n // a future refactor nulling the fields mid-poll.\n const client = this.client;\n const stateManager = this.stateManager;\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 (also paces the poll after a\n // successful addDelivery \u2014 see handleAddDelivery).\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 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 => stateManager.parseStatus(d) !== DELIVERED_STATUS_CODE);\n const visibleDeliveries = autoRemoveMode ? activeDeliveries : deliveries;\n\n // v0.4.2 (S3): reset the per-poll collision tracker, then compute every\n // package id in a deterministic sequential pre-pass (stable array order)\n // BEFORE the parallel updates \u2014 collision-suffixing is then deterministic\n // and packageId runs exactly once per delivery instead of twice.\n stateManager.resetPollState();\n const pkgIds = visibleDeliveries.map(d => stateManager.packageId(d));\n\n // v0.4.2 (M4): per-delivery updates run in parallel, each wrapped in\n // try/catch so one bad delivery doesn't poison the others.\n // v0.9.0 (S3): process the updates in bounded batches instead of one\n // broker fan-out for ALL deliveries at once. The keep-set is still every\n // visible pkgId (computed above), so this only caps concurrency \u2014 it never\n // drops a package. A normal poll (a handful of packages) is a single batch.\n if (visibleDeliveries.length > UPDATE_BATCH_SIZE) {\n this.log.debug(`Updating ${visibleDeliveries.length} deliveries in batches of ${UPDATE_BATCH_SIZE}`);\n }\n for (let start = 0; start < visibleDeliveries.length; start += UPDATE_BATCH_SIZE) {\n const batch = visibleDeliveries.slice(start, start + UPDATE_BATCH_SIZE);\n await Promise.all(\n batch.map(async (delivery, offset) => {\n const pkgId = pkgIds[start + offset];\n // Pre-sanitize the externally-sourced strings once for logging. The\n // fields are optional (the API can drop them), so default to \"\" before\n // oneLine flattens them onto a single log line.\n const tracking = oneLine(delivery.tracking_number ?? \"\");\n const carrier = oneLine(delivery.carrier_code ?? \"\");\n try {\n // v0.4.3 (C1): per-delivery entry. ~10 packages \u00D7 144 polls/day\n // = ~1440 debug lines/day \u2014 acceptable at debug-level. Line stays\n // short (tracking + carrier + status only, no full delivery JSON).\n // v0.10.0 (M6): status_code is typed number|string (drift), so it\n // is flattened like the other externally-sourced fields.\n this.log.debug(\n `updateDelivery: '${tracking}' carrier=${carrier} status=${oneLine(String(delivery.status_code))}`,\n );\n const carrierName = await client.getCarrierName(delivery.carrier_code);\n await stateManager.updateDelivery(delivery, carrierName, pkgId);\n this.failedDeliveries.delete(pkgId);\n } catch (err) {\n const msg = errText(err);\n if (this.failedDeliveries.has(pkgId)) {\n this.log.debug(`Failed to update '${tracking}': ${msg}`);\n } else if (this.unloaded) {\n // v0.10.0 (L2): broker teardown mid-batch is expected noise\n // during shutdown, not a per-package warning.\n this.log.debug(`Failed to update '${tracking}' during shutdown: ${msg}`);\n } else {\n this.log.warn(`Failed to update '${tracking}': ${msg}`);\n this.failedDeliveries.add(pkgId);\n }\n }\n }),\n );\n }\n\n // v0.9.0 (C1): keep-set = EVERY package the API still returns this poll\n // (pkgIds), NOT only the writes that just succeeded. A transient\n // updateDelivery failure leaves a package's states stale, but it must not\n // drop the package from cleanup \u2014 that would delete a still-present\n // package's states (and any user-set device name) at green info.connection.\n // v0.10.0 (M2): broker-side failures in cleanup/summary are NOT API\n // failures \u2014 they must neither flip info.connection to false (the GET\n // above just succeeded) nor run through the API error classification.\n try {\n await stateManager.cleanupDeliveries(pkgIds);\n // Update summary (always uses active/non-delivered)\n await stateManager.updateSummary(activeDeliveries);\n } catch (err) {\n this.log.warn(`State maintenance failed (API connection is fine, retrying next poll): ${errText(err)}`);\n }\n\n // Keep failedDeliveries bounded: drop entries for package ids no longer\n // present, so packages that vanish from the API don't linger forever.\n const seenPkgIds = new Set(pkgIds);\n for (const id of [...this.failedDeliveries]) {\n if (!seenPkgIds.has(id)) {\n this.failedDeliveries.delete(id);\n }\n }\n\n this.log.debug(`Polled ${visibleDeliveries.length} deliveries (${activeDeliveries.length} active)`);\n } catch (err) {\n await this.handlePollError(err as Error & { code?: string; retryAfterSeconds?: number });\n } finally {\n this.isPolling = false;\n }\n }\n\n /**\n * Classify + route a poll failure: log level, dedup, cooldown and the\n * info.connection=false write. Extracted from poll()'s catch (M9) so the\n * happy path reads as a plain sequence and the error policy sits next to\n * classifyError. Dispatches on the CLASSIFIED code only (L6) \u2014 one source\n * of truth for the error class.\n *\n * @param error The poll failure (usually an ApiError from the client)\n */\n private async handlePollError(error: Error & { code?: string; retryAfterSeconds?: number }): Promise<void> {\n const errorCode = this.classifyError(error);\n const isRepeat = errorCode === this.lastErrorCode;\n this.lastErrorCode = errorCode;\n\n switch (errorCode) {\n case \"ABORTED\":\n // v0.10.0 (M1): expected during shutdown \u2014 cancelAll aborts the\n // in-flight GET. A deliberate stop must not paint a red error line.\n this.log.debug(`Poll aborted: ${error.message}`);\n break;\n case \"RATE_LIMITED\": {\n // v0.4.2 (M9): clamp Retry-After into [60s, 24h] (shared constants\n // with the client parser, L7). A bogus 0/negative/fractional value\n // must neither wipe the cooldown nor set it for milliseconds.\n const rawCooldown = error.retryAfterSeconds ?? 0;\n const cooldownSec =\n Number.isFinite(rawCooldown) && rawCooldown > 0\n ? Math.min(RETRY_AFTER_MAX_SEC, Math.max(60, Math.floor(rawCooldown)))\n : RETRY_AFTER_DEFAULT_SEC;\n this.rateLimitedUntil = Date.now() + cooldownSec * 1000;\n // v0.10.0 (M3): warn once \u2014 a persistent 429 repeats at debug.\n const line = `Rate limit hit \u2014 pausing API requests for ${Math.ceil(cooldownSec / 60)} minute(s)`;\n if (isRepeat) {\n this.log.debug(line);\n } else {\n this.log.warn(line);\n }\n break;\n }\n case \"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 // v0.10.0 (M3): once at error level, repeats at debug \u2014 not 144\n // identical error lines per day for one unchanged account problem.\n const line =\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 if (isRepeat) {\n this.log.debug(line);\n } else {\n this.log.error(line);\n }\n break;\n }\n case \"INVALID_API_KEY\": {\n // v0.10.0 (M3): first occurrence at error (the user must fix the\n // config; info.connection goes red too) \u2014 repeats at debug.\n const line = \"Invalid API key \u2014 please check your parcel.app API key\";\n if (isRepeat) {\n this.log.debug(line);\n } else {\n this.log.error(line);\n }\n break;\n }\n case \"NETWORK\":\n if (isRepeat) {\n this.log.debug(`Poll failed (ongoing): ${error.message}`);\n } else {\n this.log.warn(\"Cannot reach parcel.app API \u2014 will keep retrying\");\n }\n break;\n case \"TIMEOUT\":\n if (isRepeat) {\n this.log.debug(`Poll failed (ongoing): ${error.message}`);\n } else {\n this.log.warn(\"API request timeout \u2014 will retry next cycle\");\n }\n break;\n default:\n 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 {\n this.log.error(`Poll failed: ${error.message}`);\n }\n }\n\n // C2: setStateChangedAsync avoids redundant `false` writes on sustained\n // failure. The `.catch` keeps poll() from rejecting when the broker is\n // already down, so the fire-and-forget callers never see an unhandled\n // rejection (no global process handler needed).\n await this.setStateChangedAsync(\"info.connection\", { val: false, ack: true }).catch(() => {\n /* broker shutting down \u2014 ignore */\n });\n }\n}\n\n// Process entry point. Not reachable from the unit tests (they import the class\n// instead of running the file as a process), but NOT untested: `npm run\n// test:integration` boots the adapter through exactly this branch inside a real\n// js-controller, which is what catches module-scope startup crashes.\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.10.
|
|
4
|
+
"version": "0.10.2",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.10.2": {
|
|
7
|
+
"en": "Internal cleanup. No user-facing changes.",
|
|
8
|
+
"de": "Interne Aufräumarbeiten. Keine Änderungen für Nutzer.",
|
|
9
|
+
"ru": "Внутренняя очистка. Изменений для пользователей нет.",
|
|
10
|
+
"pt": "Limpeza interna. Sem alterações para o utilizador.",
|
|
11
|
+
"nl": "Interne opschoning. Geen wijzigingen voor gebruikers.",
|
|
12
|
+
"fr": "Nettoyage interne. Aucun changement pour l'utilisateur.",
|
|
13
|
+
"it": "Pulizia interna. Nessuna modifica per gli utenti.",
|
|
14
|
+
"es": "Limpieza interna. Sin cambios para el usuario.",
|
|
15
|
+
"pl": "Porządki wewnętrzne. Bez zmian dla użytkownika.",
|
|
16
|
+
"uk": "Внутрішнє прибирання. Без змін для користувачів.",
|
|
17
|
+
"zh-cn": "内部清理。用户无可见变化。"
|
|
18
|
+
},
|
|
6
19
|
"0.10.1": {
|
|
7
20
|
"en": "Internal refactoring. No user-facing changes.",
|
|
8
21
|
"de": "Interne Überarbeitung. Keine für Nutzer sichtbaren Änderungen.",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
93
|
"pl": "Naprawiono przypadek brzegowy strefy czasowej w szacowanym czasie dostawy: gdy API podaje tylko datę, szacunek mógł różnić się o dzień w strefach na zachód od UTC — teraz stabilny.",
|
|
81
94
|
"uk": "Виправлено граничний випадок часового поясу в оцінках доставки: коли API повертає лише дату, оцінка могла відрізнятися на день у зонах на захід від UTC — тепер стабільно.",
|
|
82
95
|
"zh-cn": "修复了配送预计时间的时区边界问题:当 API 仅提供日期时,在 UTC 以西时区的预计可能相差一天,现在已稳定。"
|
|
83
|
-
},
|
|
84
|
-
"0.7.0": {
|
|
85
|
-
"en": "Added optional Sentry error reporting: crashes are sent to the developer so issues get fixed faster. Active only with ioBroker diagnostics enabled; anonymous.",
|
|
86
|
-
"de": "Optionale Fehlermeldung über Sentry hinzugefügt: Abstürze werden an den Entwickler gesendet, damit Probleme schneller behoben werden. Nur aktiv bei eingeschalteter ioBroker-Diagnose; anonym.",
|
|
87
|
-
"ru": "Добавлена необязательная отправка ошибок через Sentry: сбои отправляются разработчику, чтобы быстрее их устранять. Работает только при включённой диагностике ioBroker; анонимно.",
|
|
88
|
-
"pt": "Adicionado relatório de erros opcional via Sentry: as falhas são enviadas ao programador para corrigir problemas mais rápido. Ativo apenas com o diagnóstico do ioBroker ligado; anónimo.",
|
|
89
|
-
"nl": "Optionele foutrapportage via Sentry toegevoegd: crashes worden naar de ontwikkelaar gestuurd om problemen sneller op te lossen. Alleen actief met ioBroker-diagnostiek ingeschakeld; anoniem.",
|
|
90
|
-
"fr": "Rapport d'erreurs optionnel via Sentry ajouté : les plantages sont envoyés au développeur pour corriger plus vite. Actif seulement si le diagnostic ioBroker est activé ; anonyme.",
|
|
91
|
-
"it": "Aggiunta segnalazione errori opzionale via Sentry: gli arresti vengono inviati allo sviluppatore per risolvere prima i problemi. Attivo solo con la diagnostica ioBroker attiva; anonimo.",
|
|
92
|
-
"es": "Añadido informe de errores opcional mediante Sentry: los fallos se envían al desarrollador para solucionar problemas más rápido. Activo solo con el diagnóstico de ioBroker activado; anónimo.",
|
|
93
|
-
"pl": "Dodano opcjonalne raportowanie błędów przez Sentry: awarie są wysyłane do dewelopera, aby szybciej rozwiązywać problemy. Aktywne tylko przy włączonej diagnostyce ioBroker; anonimowo.",
|
|
94
|
-
"uk": "Додано необов'язкову відправку помилок через Sentry: збої надсилаються розробнику, щоб швидше їх виправляти. Працює лише з увімкненою діагностикою ioBroker; анонімно.",
|
|
95
|
-
"zh-cn": "新增可选的 Sentry 错误上报:崩溃信息会发送给开发者以更快修复问题。仅在启用 ioBroker 诊断时生效;匿名。"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"plugins": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.parcelapp",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "ioBroker adapter for the parcel.app API",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "krobi",
|
|
@@ -30,22 +30,22 @@
|
|
|
30
30
|
"node": ">=22"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@iobroker/adapter-core": "^3.4.
|
|
33
|
+
"@iobroker/adapter-core": "^3.4.3"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@alcalzone/release-script": "^5.2.1",
|
|
37
37
|
"@alcalzone/release-script-plugin-iobroker": "^5.2.0",
|
|
38
|
-
"@alcalzone/release-script-plugin-license": "^5.2.
|
|
38
|
+
"@alcalzone/release-script-plugin-license": "^5.2.2",
|
|
39
39
|
"@iobroker/adapter-dev": "^1.5.0",
|
|
40
40
|
"@iobroker/eslint-config": "^2.3.4",
|
|
41
41
|
"@iobroker/testing": "^5.3.0",
|
|
42
42
|
"@tsconfig/node22": "^22.0.5",
|
|
43
43
|
"@types/iobroker": "npm:@iobroker/types@^7.2.2",
|
|
44
44
|
"@types/node": "^22.19.17",
|
|
45
|
-
"@vitest/coverage-v8": "^4.1.
|
|
45
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
46
46
|
"rimraf": "^6.1.3",
|
|
47
47
|
"typescript": "~6.0.3",
|
|
48
|
-
"vitest": "^4.1.
|
|
48
|
+
"vitest": "^4.1.11"
|
|
49
49
|
},
|
|
50
50
|
"files": [
|
|
51
51
|
"admin",
|