iobroker.parcelapp 0.3.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -12
- package/build/lib/coerce.js +99 -0
- package/build/lib/coerce.js.map +7 -0
- package/build/lib/i18n-states.js +219 -0
- package/build/lib/i18n-states.js.map +7 -0
- package/build/lib/parcel-client.js +2 -14
- package/build/lib/parcel-client.js.map +2 -2
- package/build/lib/state-manager.js +69 -32
- package/build/lib/state-manager.js.map +2 -2
- package/build/main.js +17 -15
- package/build/main.js.map +2 -2
- package/io-package.json +39 -39
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# ioBroker.parcelapp
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/iobroker.parcelapp)
|
|
4
|
-

|
|
5
5
|

|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://www.npmjs.com/package/iobroker.parcelapp)
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
<img src="https://raw.githubusercontent.com/krobipd/ioBroker.parcelapp/main/admin/parcelapp.svg" width="100" />
|
|
13
13
|
|
|
14
|
-
ioBroker adapter
|
|
14
|
+
ioBroker adapter for the [parcel.app](https://parcelapp.net) API. Supports all carriers that parcel.app tracks.
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
@@ -21,19 +21,18 @@ ioBroker adapter that connects to the [parcel.app](https://parcelapp.net) API an
|
|
|
21
21
|
- **Per-package ioBroker states** — carrier, status, tracking number, delivery window, last event, last location
|
|
22
22
|
- **Summary states** — active count, today count, combined delivery window
|
|
23
23
|
- **Delivery time estimates** — today, tomorrow, in X days with combined time window
|
|
24
|
-
- **
|
|
24
|
+
- **Configurable poll interval** (5–60 minutes)
|
|
25
25
|
- **Configurable cleanup** — auto-remove delivered packages or keep them until deleted in parcel.app
|
|
26
26
|
- **Add deliveries** via sendTo message from scripts or other adapters
|
|
27
27
|
- **Admin UI** with connection test and polling settings
|
|
28
|
-
- **Status labels follow the ioBroker system language** — all 11 supported languages (de, en, ru, pt, nl, fr, it, es, pl, uk, zh-cn), English fallback for unknown codes
|
|
29
28
|
|
|
30
29
|
---
|
|
31
30
|
|
|
32
31
|
## Requirements
|
|
33
32
|
|
|
34
|
-
- **Node.js >=
|
|
33
|
+
- **Node.js >= 22**
|
|
35
34
|
- **ioBroker js-controller >= 7.0.7**
|
|
36
|
-
- **ioBroker Admin >= 7.
|
|
35
|
+
- **ioBroker Admin >= 7.8.23**
|
|
37
36
|
- **parcel.app Premium subscription** — required for API access
|
|
38
37
|
|
|
39
38
|
---
|
|
@@ -119,6 +118,19 @@ The delivery is added to your parcel.app account and immediately appears in ioBr
|
|
|
119
118
|
---
|
|
120
119
|
|
|
121
120
|
## Changelog
|
|
121
|
+
<!--
|
|
122
|
+
Placeholder for the next version (at the beginning of the line):
|
|
123
|
+
### **WORK IN PROGRESS**
|
|
124
|
+
-->
|
|
125
|
+
### 0.4.1 (2026-05-09)
|
|
126
|
+
|
|
127
|
+
- Adapter log messages are now English only, in line with the ioBroker community standard. Localized state names (11 languages) are unchanged.
|
|
128
|
+
|
|
129
|
+
### 0.4.0 (2026-05-06)
|
|
130
|
+
|
|
131
|
+
- State names now follow your ioBroker system language (11 languages).
|
|
132
|
+
- Minimum requirements: Node.js 22 and ioBroker Admin 7.8.23.
|
|
133
|
+
|
|
122
134
|
### 0.3.2 (2026-05-01)
|
|
123
135
|
- Documentation: rewrote release notes for v0.2.14–v0.3.1 in user-friendly style across all languages.
|
|
124
136
|
|
|
@@ -128,12 +140,6 @@ The delivery is added to your parcel.app account and immediately appears in ioBr
|
|
|
128
140
|
### 0.3.0 (2026-04-30)
|
|
129
141
|
- Internal cleanup. No user-facing changes.
|
|
130
142
|
|
|
131
|
-
### 0.2.18 (2026-04-28)
|
|
132
|
-
- Internal cleanup. No user-facing changes.
|
|
133
|
-
|
|
134
|
-
### 0.2.17 (2026-04-28)
|
|
135
|
-
- Internal cleanup. No user-facing changes.
|
|
136
|
-
|
|
137
143
|
Older entries are in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
|
|
138
144
|
|
|
139
145
|
## Support
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var coerce_exports = {};
|
|
20
|
+
__export(coerce_exports, {
|
|
21
|
+
coerceBoolean: () => coerceBoolean,
|
|
22
|
+
coerceFiniteNumber: () => coerceFiniteNumber,
|
|
23
|
+
coerceString: () => coerceString,
|
|
24
|
+
errText: () => errText,
|
|
25
|
+
isPlainObject: () => isPlainObject,
|
|
26
|
+
isTrueish: () => isTrueish
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(coerce_exports);
|
|
29
|
+
const DECIMAL_NUMBER_RE = /^-?\d+(\.\d+)?$/;
|
|
30
|
+
function coerceFiniteNumber(value) {
|
|
31
|
+
if (typeof value === "number") {
|
|
32
|
+
return Number.isFinite(value) ? value : null;
|
|
33
|
+
}
|
|
34
|
+
if (typeof value === "string" && DECIMAL_NUMBER_RE.test(value)) {
|
|
35
|
+
const n = Number(value);
|
|
36
|
+
return Number.isFinite(n) ? n : null;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
function coerceString(value) {
|
|
41
|
+
if (typeof value === "string" && value.length > 0) {
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
function coerceBoolean(value) {
|
|
47
|
+
if (typeof value === "boolean") {
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
function isPlainObject(value) {
|
|
53
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
54
|
+
}
|
|
55
|
+
function isTrueish(v) {
|
|
56
|
+
if (typeof v === "boolean") {
|
|
57
|
+
return v;
|
|
58
|
+
}
|
|
59
|
+
if (typeof v === "number") {
|
|
60
|
+
return v === 1;
|
|
61
|
+
}
|
|
62
|
+
if (typeof v === "string") {
|
|
63
|
+
const s = v.toLowerCase();
|
|
64
|
+
return s === "true" || s === "1";
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
function errText(err) {
|
|
69
|
+
if (err instanceof Error) {
|
|
70
|
+
return err.message;
|
|
71
|
+
}
|
|
72
|
+
if (err === null) {
|
|
73
|
+
return "null";
|
|
74
|
+
}
|
|
75
|
+
if (err === void 0) {
|
|
76
|
+
return "undefined";
|
|
77
|
+
}
|
|
78
|
+
if (typeof err === "string") {
|
|
79
|
+
return err;
|
|
80
|
+
}
|
|
81
|
+
if (typeof err === "number" || typeof err === "boolean" || typeof err === "bigint") {
|
|
82
|
+
return String(err);
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
return JSON.stringify(err);
|
|
86
|
+
} catch {
|
|
87
|
+
return Object.prototype.toString.call(err);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
91
|
+
0 && (module.exports = {
|
|
92
|
+
coerceBoolean,
|
|
93
|
+
coerceFiniteNumber,
|
|
94
|
+
coerceString,
|
|
95
|
+
errText,
|
|
96
|
+
isPlainObject,
|
|
97
|
+
isTrueish
|
|
98
|
+
});
|
|
99
|
+
//# sourceMappingURL=coerce.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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 to a non-empty string, or null.\n *\n * @param value Unknown external value\n */\nexport function coerceString(value: unknown): string | null {\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n return null;\n}\n\n/**\n * Coerce to a boolean (only `true`/`false` accepted \u2014 no truthy/falsy JS rules).\n *\n * @param value Unknown external value\n */\nexport function coerceBoolean(value: unknown): boolean | null {\n if (typeof value === \"boolean\") {\n return value;\n }\n return null;\n}\n\n/**\n * Guard for plain objects (not arrays, not null).\n *\n * @param value Unknown external value\n */\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Coerce a parcel.app `success` flag. The API returns a real boolean in normal\n * operation, but the guard accepts common string/number encodings (`1`, `\"true\"`,\n * `\"1\"`) so a one-off drift doesn't break the entire poll cycle.\n *\n * @param v Value to interpret as a success flag\n */\nexport function isTrueish(v: unknown): boolean {\n if (typeof v === \"boolean\") {\n return v;\n }\n if (typeof v === \"number\") {\n return v === 1;\n }\n if (typeof v === \"string\") {\n const s = v.toLowerCase();\n return s === \"true\" || s === \"1\";\n }\n return false;\n}\n\n/**\n * Extract a log-friendly message from a thrown / rejected value. Centralizes the\n * `err instanceof Error ? err.message : String(err)` pattern that otherwise\n * gets repeated at every catch-site. Plain objects are JSON-stringified so a\n * `[object Object]` log is avoided when callers throw bag-of-fields.\n *\n * @param err Caught value of unknown shape (Error, string, undefined, ...).\n */\nexport function errText(err: unknown): string {\n if (err instanceof Error) {\n return err.message;\n }\n if (err === null) {\n return \"null\";\n }\n if (err === undefined) {\n return \"undefined\";\n }\n if (typeof err === \"string\") {\n return err;\n }\n if (typeof err === \"number\" || typeof err === \"boolean\" || typeof err === \"bigint\") {\n return String(err);\n }\n // Plain objects + symbols would otherwise stringify to \"[object Object]\" / fail.\n // Prefer JSON for the common case so the log is at least diagnosable.\n try {\n return JSON.stringify(err);\n } catch {\n return Object.prototype.toString.call(err);\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;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;AAOO,SAAS,aAAa,OAA+B;AAC1D,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,cAAc,OAAgC;AAC5D,MAAI,OAAO,UAAU,WAAW;AAC9B,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,cAAc,OAAkD;AAC9E,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AASO,SAAS,UAAU,GAAqB;AAC7C,MAAI,OAAO,MAAM,WAAW;AAC1B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,MAAM,UAAU;AACzB,WAAO,MAAM;AAAA,EACf;AACA,MAAI,OAAO,MAAM,UAAU;AACzB,UAAM,IAAI,EAAE,YAAY;AACxB,WAAO,MAAM,UAAU,MAAM;AAAA,EAC/B;AACA,SAAO;AACT;AAUO,SAAS,QAAQ,KAAsB;AAC5C,MAAI,eAAe,OAAO;AACxB,WAAO,IAAI;AAAA,EACb;AACA,MAAI,QAAQ,MAAM;AAChB,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,QAAW;AACrB,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,UAAU;AAC3B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,aAAa,OAAO,QAAQ,UAAU;AAClF,WAAO,OAAO,GAAG;AAAA,EACnB;AAGA,MAAI;AACF,WAAO,KAAK,UAAU,GAAG;AAAA,EAC3B,QAAQ;AACN,WAAO,OAAO,UAAU,SAAS,KAAK,GAAG;AAAA,EAC3C;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var i18n_states_exports = {};
|
|
20
|
+
__export(i18n_states_exports, {
|
|
21
|
+
STATE_NAMES: () => STATE_NAMES,
|
|
22
|
+
tName: () => tName
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(i18n_states_exports);
|
|
25
|
+
const STATE_NAMES = {
|
|
26
|
+
// ──────── Per-delivery states ────────
|
|
27
|
+
carrier: {
|
|
28
|
+
en: "Carrier",
|
|
29
|
+
de: "Versanddienst",
|
|
30
|
+
ru: "\u041F\u0435\u0440\u0435\u0432\u043E\u0437\u0447\u0438\u043A",
|
|
31
|
+
pt: "Transportadora",
|
|
32
|
+
nl: "Vervoerder",
|
|
33
|
+
fr: "Transporteur",
|
|
34
|
+
it: "Corriere",
|
|
35
|
+
es: "Transportista",
|
|
36
|
+
pl: "Przewo\u017Anik",
|
|
37
|
+
uk: "\u041F\u0435\u0440\u0435\u0432\u0456\u0437\u043D\u0438\u043A",
|
|
38
|
+
"zh-cn": "\u627F\u8FD0\u5546"
|
|
39
|
+
},
|
|
40
|
+
status: {
|
|
41
|
+
en: "Status",
|
|
42
|
+
de: "Status",
|
|
43
|
+
ru: "\u0421\u0442\u0430\u0442\u0443\u0441",
|
|
44
|
+
pt: "Estado",
|
|
45
|
+
nl: "Status",
|
|
46
|
+
fr: "\xC9tat",
|
|
47
|
+
it: "Stato",
|
|
48
|
+
es: "Estado",
|
|
49
|
+
pl: "Status",
|
|
50
|
+
uk: "\u0421\u0442\u0430\u0442\u0443\u0441",
|
|
51
|
+
"zh-cn": "\u72B6\u6001"
|
|
52
|
+
},
|
|
53
|
+
statusCode: {
|
|
54
|
+
en: "Status Code",
|
|
55
|
+
de: "Status-Code",
|
|
56
|
+
ru: "\u041A\u043E\u0434 \u0441\u0442\u0430\u0442\u0443\u0441\u0430",
|
|
57
|
+
pt: "C\xF3digo de estado",
|
|
58
|
+
nl: "Statuscode",
|
|
59
|
+
fr: "Code d'\xE9tat",
|
|
60
|
+
it: "Codice di stato",
|
|
61
|
+
es: "C\xF3digo de estado",
|
|
62
|
+
pl: "Kod statusu",
|
|
63
|
+
uk: "\u041A\u043E\u0434 \u0441\u0442\u0430\u0442\u0443\u0441\u0443",
|
|
64
|
+
"zh-cn": "\u72B6\u6001\u4EE3\u7801"
|
|
65
|
+
},
|
|
66
|
+
description: {
|
|
67
|
+
en: "Description",
|
|
68
|
+
de: "Beschreibung",
|
|
69
|
+
ru: "\u041E\u043F\u0438\u0441\u0430\u043D\u0438\u0435",
|
|
70
|
+
pt: "Descri\xE7\xE3o",
|
|
71
|
+
nl: "Beschrijving",
|
|
72
|
+
fr: "Description",
|
|
73
|
+
it: "Descrizione",
|
|
74
|
+
es: "Descripci\xF3n",
|
|
75
|
+
pl: "Opis",
|
|
76
|
+
uk: "\u041E\u043F\u0438\u0441",
|
|
77
|
+
"zh-cn": "\u63CF\u8FF0"
|
|
78
|
+
},
|
|
79
|
+
trackingNumber: {
|
|
80
|
+
en: "Tracking Number",
|
|
81
|
+
de: "Sendungsnummer",
|
|
82
|
+
ru: "\u0422\u0440\u0435\u043A-\u043D\u043E\u043C\u0435\u0440",
|
|
83
|
+
pt: "N\xFAmero de rastreio",
|
|
84
|
+
nl: "Trackingnummer",
|
|
85
|
+
fr: "Num\xE9ro de suivi",
|
|
86
|
+
it: "Numero di tracciamento",
|
|
87
|
+
es: "N\xFAmero de seguimiento",
|
|
88
|
+
pl: "Numer \u015Bledzenia",
|
|
89
|
+
uk: "\u041D\u043E\u043C\u0435\u0440 \u0432\u0456\u0434\u0441\u0442\u0435\u0436\u0435\u043D\u043D\u044F",
|
|
90
|
+
"zh-cn": "\u8FFD\u8E2A\u53F7"
|
|
91
|
+
},
|
|
92
|
+
extraInfo: {
|
|
93
|
+
en: "Extra Information",
|
|
94
|
+
de: "Zusatz-Information",
|
|
95
|
+
ru: "\u0414\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u0430\u044F \u0438\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0438\u044F",
|
|
96
|
+
pt: "Informa\xE7\xE3o adicional",
|
|
97
|
+
nl: "Extra informatie",
|
|
98
|
+
fr: "Informations suppl\xE9mentaires",
|
|
99
|
+
it: "Informazioni aggiuntive",
|
|
100
|
+
es: "Informaci\xF3n adicional",
|
|
101
|
+
pl: "Dodatkowe informacje",
|
|
102
|
+
uk: "\u0414\u043E\u0434\u0430\u0442\u043A\u043E\u0432\u0430 \u0456\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0456\u044F",
|
|
103
|
+
"zh-cn": "\u9644\u52A0\u4FE1\u606F"
|
|
104
|
+
},
|
|
105
|
+
deliveryWindow: {
|
|
106
|
+
en: "Delivery Window",
|
|
107
|
+
de: "Zustellfenster",
|
|
108
|
+
ru: "\u041E\u043A\u043D\u043E \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0438",
|
|
109
|
+
pt: "Janela de entrega",
|
|
110
|
+
nl: "Bezorgvenster",
|
|
111
|
+
fr: "Cr\xE9neau de livraison",
|
|
112
|
+
it: "Finestra di consegna",
|
|
113
|
+
es: "Ventana de entrega",
|
|
114
|
+
pl: "Okno dostawy",
|
|
115
|
+
uk: "\u0412\u0456\u043A\u043D\u043E \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0438",
|
|
116
|
+
"zh-cn": "\u6D3E\u9001\u65F6\u6BB5"
|
|
117
|
+
},
|
|
118
|
+
deliveryEstimate: {
|
|
119
|
+
en: "Delivery Estimate",
|
|
120
|
+
de: "Voraussichtliche Zustellung",
|
|
121
|
+
ru: "\u041E\u0436\u0438\u0434\u0430\u0435\u043C\u0430\u044F \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0430",
|
|
122
|
+
pt: "Previs\xE3o de entrega",
|
|
123
|
+
nl: "Verwachte bezorging",
|
|
124
|
+
fr: "Livraison estim\xE9e",
|
|
125
|
+
it: "Consegna prevista",
|
|
126
|
+
es: "Entrega estimada",
|
|
127
|
+
pl: "Szacowana dostawa",
|
|
128
|
+
uk: "\u041E\u0447\u0456\u043A\u0443\u0432\u0430\u043D\u0430 \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0430",
|
|
129
|
+
"zh-cn": "\u9884\u8BA1\u9001\u8FBE"
|
|
130
|
+
},
|
|
131
|
+
lastEvent: {
|
|
132
|
+
en: "Last Event",
|
|
133
|
+
de: "Letztes Ereignis",
|
|
134
|
+
ru: "\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0435 \u0441\u043E\u0431\u044B\u0442\u0438\u0435",
|
|
135
|
+
pt: "\xDAltimo evento",
|
|
136
|
+
nl: "Laatste gebeurtenis",
|
|
137
|
+
fr: "Dernier \xE9v\xE9nement",
|
|
138
|
+
it: "Ultimo evento",
|
|
139
|
+
es: "\xDAltimo evento",
|
|
140
|
+
pl: "Ostatnie zdarzenie",
|
|
141
|
+
uk: "\u041E\u0441\u0442\u0430\u043D\u043D\u044F \u043F\u043E\u0434\u0456\u044F",
|
|
142
|
+
"zh-cn": "\u6700\u8FD1\u4E8B\u4EF6"
|
|
143
|
+
},
|
|
144
|
+
lastLocation: {
|
|
145
|
+
en: "Last Location",
|
|
146
|
+
de: "Letzter Standort",
|
|
147
|
+
ru: "\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0435 \u043C\u0435\u0441\u0442\u043E\u043F\u043E\u043B\u043E\u0436\u0435\u043D\u0438\u0435",
|
|
148
|
+
pt: "\xDAltima localiza\xE7\xE3o",
|
|
149
|
+
nl: "Laatste locatie",
|
|
150
|
+
fr: "Dernier emplacement",
|
|
151
|
+
it: "Ultima posizione",
|
|
152
|
+
es: "\xDAltima ubicaci\xF3n",
|
|
153
|
+
pl: "Ostatnia lokalizacja",
|
|
154
|
+
uk: "\u041E\u0441\u0442\u0430\u043D\u043D\u0454 \u043C\u0456\u0441\u0446\u0435\u0437\u043D\u0430\u0445\u043E\u0434\u0436\u0435\u043D\u043D\u044F",
|
|
155
|
+
"zh-cn": "\u6700\u8FD1\u4F4D\u7F6E"
|
|
156
|
+
},
|
|
157
|
+
lastUpdated: {
|
|
158
|
+
en: "Last Updated",
|
|
159
|
+
de: "Zuletzt aktualisiert",
|
|
160
|
+
ru: "\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0435 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435",
|
|
161
|
+
pt: "\xDAltima atualiza\xE7\xE3o",
|
|
162
|
+
nl: "Laatst bijgewerkt",
|
|
163
|
+
fr: "Derni\xE8re mise \xE0 jour",
|
|
164
|
+
it: "Ultimo aggiornamento",
|
|
165
|
+
es: "\xDAltima actualizaci\xF3n",
|
|
166
|
+
pl: "Ostatnia aktualizacja",
|
|
167
|
+
uk: "\u041E\u0441\u0442\u0430\u043D\u043D\u0454 \u043E\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044F",
|
|
168
|
+
"zh-cn": "\u6700\u540E\u66F4\u65B0"
|
|
169
|
+
},
|
|
170
|
+
// ──────── Summary states ────────
|
|
171
|
+
activeCount: {
|
|
172
|
+
en: "Active Deliveries",
|
|
173
|
+
de: "Aktive Sendungen",
|
|
174
|
+
ru: "\u0410\u043A\u0442\u0438\u0432\u043D\u044B\u0435 \u043F\u043E\u0441\u044B\u043B\u043A\u0438",
|
|
175
|
+
pt: "Entregas ativas",
|
|
176
|
+
nl: "Actieve zendingen",
|
|
177
|
+
fr: "Livraisons actives",
|
|
178
|
+
it: "Spedizioni attive",
|
|
179
|
+
es: "Env\xEDos activos",
|
|
180
|
+
pl: "Aktywne przesy\u0142ki",
|
|
181
|
+
uk: "\u0410\u043A\u0442\u0438\u0432\u043D\u0456 \u0432\u0456\u0434\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043D\u044F",
|
|
182
|
+
"zh-cn": "\u6D3B\u52A8\u4E2D\u7684\u5305\u88F9"
|
|
183
|
+
},
|
|
184
|
+
todayCount: {
|
|
185
|
+
en: "Deliveries Today",
|
|
186
|
+
de: "Sendungen heute",
|
|
187
|
+
ru: "\u0414\u043E\u0441\u0442\u0430\u0432\u043A\u0438 \u0441\u0435\u0433\u043E\u0434\u043D\u044F",
|
|
188
|
+
pt: "Entregas hoje",
|
|
189
|
+
nl: "Zendingen vandaag",
|
|
190
|
+
fr: "Livraisons aujourd'hui",
|
|
191
|
+
it: "Spedizioni di oggi",
|
|
192
|
+
es: "Entregas de hoy",
|
|
193
|
+
pl: "Dostawy dzisiaj",
|
|
194
|
+
uk: "\u0414\u043E\u0441\u0442\u0430\u0432\u043A\u0438 \u0441\u044C\u043E\u0433\u043E\u0434\u043D\u0456",
|
|
195
|
+
"zh-cn": "\u4ECA\u65E5\u9001\u8FBE"
|
|
196
|
+
},
|
|
197
|
+
summaryDeliveryWindow: {
|
|
198
|
+
en: "Combined Delivery Window",
|
|
199
|
+
de: "Kombiniertes Zustellfenster",
|
|
200
|
+
ru: "\u041E\u0431\u044A\u0435\u0434\u0438\u043D\u0451\u043D\u043D\u043E\u0435 \u043E\u043A\u043D\u043E \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0438",
|
|
201
|
+
pt: "Janela de entrega combinada",
|
|
202
|
+
nl: "Gecombineerd bezorgvenster",
|
|
203
|
+
fr: "Cr\xE9neau de livraison combin\xE9",
|
|
204
|
+
it: "Finestra di consegna combinata",
|
|
205
|
+
es: "Ventana de entrega combinada",
|
|
206
|
+
pl: "\u0141\u0105czne okno dostawy",
|
|
207
|
+
uk: "\u041E\u0431'\u0454\u0434\u043D\u0430\u043D\u0435 \u0432\u0456\u043A\u043D\u043E \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0438",
|
|
208
|
+
"zh-cn": "\u5408\u5E76\u6D3E\u9001\u65F6\u6BB5"
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
function tName(key) {
|
|
212
|
+
return STATE_NAMES[key];
|
|
213
|
+
}
|
|
214
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
215
|
+
0 && (module.exports = {
|
|
216
|
+
STATE_NAMES,
|
|
217
|
+
tName
|
|
218
|
+
});
|
|
219
|
+
//# sourceMappingURL=i18n-states.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/i18n-states.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Localized state names for parcel deliveries in 11 ioBroker system languages.\n *\n * ioBroker accepts plain strings or `{ en, de, ... }` translation objects for\n * `common.name`. Admin, vis and the Object-Browser pick the user's language\n * automatically \u2014 we just hand them the object.\n *\n * Adapter logs (`this.log.*`) stay English by ioBroker convention so that\n * user bug reports remain readable for maintainers regardless of the user's\n * system language.\n */\n\ntype Lang = \"en\" | \"de\" | \"ru\" | \"pt\" | \"nl\" | \"fr\" | \"it\" | \"es\" | \"pl\" | \"uk\" | \"zh-cn\";\n\n/** Translation object as ioBroker expects it. */\nexport type StateName = Record<Lang, string>;\n\n/** State / channel display names (`common.name`). */\nexport const STATE_NAMES: Record<string, StateName> = {\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Per-delivery states \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n carrier: {\n en: \"Carrier\",\n de: \"Versanddienst\",\n ru: \"\u041F\u0435\u0440\u0435\u0432\u043E\u0437\u0447\u0438\u043A\",\n pt: \"Transportadora\",\n nl: \"Vervoerder\",\n fr: \"Transporteur\",\n it: \"Corriere\",\n es: \"Transportista\",\n pl: \"Przewo\u017Anik\",\n uk: \"\u041F\u0435\u0440\u0435\u0432\u0456\u0437\u043D\u0438\u043A\",\n \"zh-cn\": \"\u627F\u8FD0\u5546\",\n },\n status: {\n en: \"Status\",\n de: \"Status\",\n ru: \"\u0421\u0442\u0430\u0442\u0443\u0441\",\n pt: \"Estado\",\n nl: \"Status\",\n fr: \"\u00C9tat\",\n it: \"Stato\",\n es: \"Estado\",\n pl: \"Status\",\n uk: \"\u0421\u0442\u0430\u0442\u0443\u0441\",\n \"zh-cn\": \"\u72B6\u6001\",\n },\n statusCode: {\n en: \"Status Code\",\n de: \"Status-Code\",\n ru: \"\u041A\u043E\u0434 \u0441\u0442\u0430\u0442\u0443\u0441\u0430\",\n pt: \"C\u00F3digo de estado\",\n nl: \"Statuscode\",\n fr: \"Code d'\u00E9tat\",\n it: \"Codice di stato\",\n es: \"C\u00F3digo de estado\",\n pl: \"Kod statusu\",\n uk: \"\u041A\u043E\u0434 \u0441\u0442\u0430\u0442\u0443\u0441\u0443\",\n \"zh-cn\": \"\u72B6\u6001\u4EE3\u7801\",\n },\n description: {\n en: \"Description\",\n de: \"Beschreibung\",\n ru: \"\u041E\u043F\u0438\u0441\u0430\u043D\u0438\u0435\",\n pt: \"Descri\u00E7\u00E3o\",\n nl: \"Beschrijving\",\n fr: \"Description\",\n it: \"Descrizione\",\n es: \"Descripci\u00F3n\",\n pl: \"Opis\",\n uk: \"\u041E\u043F\u0438\u0441\",\n \"zh-cn\": \"\u63CF\u8FF0\",\n },\n trackingNumber: {\n en: \"Tracking Number\",\n de: \"Sendungsnummer\",\n ru: \"\u0422\u0440\u0435\u043A-\u043D\u043E\u043C\u0435\u0440\",\n pt: \"N\u00FAmero de rastreio\",\n nl: \"Trackingnummer\",\n fr: \"Num\u00E9ro de suivi\",\n it: \"Numero di tracciamento\",\n es: \"N\u00FAmero de seguimiento\",\n pl: \"Numer \u015Bledzenia\",\n uk: \"\u041D\u043E\u043C\u0435\u0440 \u0432\u0456\u0434\u0441\u0442\u0435\u0436\u0435\u043D\u043D\u044F\",\n \"zh-cn\": \"\u8FFD\u8E2A\u53F7\",\n },\n extraInfo: {\n en: \"Extra Information\",\n de: \"Zusatz-Information\",\n ru: \"\u0414\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u0430\u044F \u0438\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0438\u044F\",\n pt: \"Informa\u00E7\u00E3o adicional\",\n nl: \"Extra informatie\",\n fr: \"Informations suppl\u00E9mentaires\",\n it: \"Informazioni aggiuntive\",\n es: \"Informaci\u00F3n adicional\",\n pl: \"Dodatkowe informacje\",\n uk: \"\u0414\u043E\u0434\u0430\u0442\u043A\u043E\u0432\u0430 \u0456\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0456\u044F\",\n \"zh-cn\": \"\u9644\u52A0\u4FE1\u606F\",\n },\n deliveryWindow: {\n en: \"Delivery Window\",\n de: \"Zustellfenster\",\n ru: \"\u041E\u043A\u043D\u043E \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0438\",\n pt: \"Janela de entrega\",\n nl: \"Bezorgvenster\",\n fr: \"Cr\u00E9neau de livraison\",\n it: \"Finestra di consegna\",\n es: \"Ventana de entrega\",\n pl: \"Okno dostawy\",\n uk: \"\u0412\u0456\u043A\u043D\u043E \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0438\",\n \"zh-cn\": \"\u6D3E\u9001\u65F6\u6BB5\",\n },\n deliveryEstimate: {\n en: \"Delivery Estimate\",\n de: \"Voraussichtliche Zustellung\",\n ru: \"\u041E\u0436\u0438\u0434\u0430\u0435\u043C\u0430\u044F \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0430\",\n pt: \"Previs\u00E3o de entrega\",\n nl: \"Verwachte bezorging\",\n fr: \"Livraison estim\u00E9e\",\n it: \"Consegna prevista\",\n es: \"Entrega estimada\",\n pl: \"Szacowana dostawa\",\n uk: \"\u041E\u0447\u0456\u043A\u0443\u0432\u0430\u043D\u0430 \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0430\",\n \"zh-cn\": \"\u9884\u8BA1\u9001\u8FBE\",\n },\n lastEvent: {\n en: \"Last Event\",\n de: \"Letztes Ereignis\",\n ru: \"\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0435 \u0441\u043E\u0431\u044B\u0442\u0438\u0435\",\n pt: \"\u00DAltimo evento\",\n nl: \"Laatste gebeurtenis\",\n fr: \"Dernier \u00E9v\u00E9nement\",\n it: \"Ultimo evento\",\n es: \"\u00DAltimo evento\",\n pl: \"Ostatnie zdarzenie\",\n uk: \"\u041E\u0441\u0442\u0430\u043D\u043D\u044F \u043F\u043E\u0434\u0456\u044F\",\n \"zh-cn\": \"\u6700\u8FD1\u4E8B\u4EF6\",\n },\n lastLocation: {\n en: \"Last Location\",\n de: \"Letzter Standort\",\n ru: \"\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0435 \u043C\u0435\u0441\u0442\u043E\u043F\u043E\u043B\u043E\u0436\u0435\u043D\u0438\u0435\",\n pt: \"\u00DAltima localiza\u00E7\u00E3o\",\n nl: \"Laatste locatie\",\n fr: \"Dernier emplacement\",\n it: \"Ultima posizione\",\n es: \"\u00DAltima ubicaci\u00F3n\",\n pl: \"Ostatnia lokalizacja\",\n uk: \"\u041E\u0441\u0442\u0430\u043D\u043D\u0454 \u043C\u0456\u0441\u0446\u0435\u0437\u043D\u0430\u0445\u043E\u0434\u0436\u0435\u043D\u043D\u044F\",\n \"zh-cn\": \"\u6700\u8FD1\u4F4D\u7F6E\",\n },\n lastUpdated: {\n en: \"Last Updated\",\n de: \"Zuletzt aktualisiert\",\n ru: \"\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0435 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435\",\n pt: \"\u00DAltima atualiza\u00E7\u00E3o\",\n nl: \"Laatst bijgewerkt\",\n fr: \"Derni\u00E8re mise \u00E0 jour\",\n it: \"Ultimo aggiornamento\",\n es: \"\u00DAltima actualizaci\u00F3n\",\n pl: \"Ostatnia aktualizacja\",\n uk: \"\u041E\u0441\u0442\u0430\u043D\u043D\u0454 \u043E\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044F\",\n \"zh-cn\": \"\u6700\u540E\u66F4\u65B0\",\n },\n\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Summary states \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n activeCount: {\n en: \"Active Deliveries\",\n de: \"Aktive Sendungen\",\n ru: \"\u0410\u043A\u0442\u0438\u0432\u043D\u044B\u0435 \u043F\u043E\u0441\u044B\u043B\u043A\u0438\",\n pt: \"Entregas ativas\",\n nl: \"Actieve zendingen\",\n fr: \"Livraisons actives\",\n it: \"Spedizioni attive\",\n es: \"Env\u00EDos activos\",\n pl: \"Aktywne przesy\u0142ki\",\n uk: \"\u0410\u043A\u0442\u0438\u0432\u043D\u0456 \u0432\u0456\u0434\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043D\u044F\",\n \"zh-cn\": \"\u6D3B\u52A8\u4E2D\u7684\u5305\u88F9\",\n },\n todayCount: {\n en: \"Deliveries Today\",\n de: \"Sendungen heute\",\n ru: \"\u0414\u043E\u0441\u0442\u0430\u0432\u043A\u0438 \u0441\u0435\u0433\u043E\u0434\u043D\u044F\",\n pt: \"Entregas hoje\",\n nl: \"Zendingen vandaag\",\n fr: \"Livraisons aujourd'hui\",\n it: \"Spedizioni di oggi\",\n es: \"Entregas de hoy\",\n pl: \"Dostawy dzisiaj\",\n uk: \"\u0414\u043E\u0441\u0442\u0430\u0432\u043A\u0438 \u0441\u044C\u043E\u0433\u043E\u0434\u043D\u0456\",\n \"zh-cn\": \"\u4ECA\u65E5\u9001\u8FBE\",\n },\n summaryDeliveryWindow: {\n en: \"Combined Delivery Window\",\n de: \"Kombiniertes Zustellfenster\",\n ru: \"\u041E\u0431\u044A\u0435\u0434\u0438\u043D\u0451\u043D\u043D\u043E\u0435 \u043E\u043A\u043D\u043E \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0438\",\n pt: \"Janela de entrega combinada\",\n nl: \"Gecombineerd bezorgvenster\",\n fr: \"Cr\u00E9neau de livraison combin\u00E9\",\n it: \"Finestra di consegna combinata\",\n es: \"Ventana de entrega combinada\",\n pl: \"\u0141\u0105czne okno dostawy\",\n uk: \"\u041E\u0431'\u0454\u0434\u043D\u0430\u043D\u0435 \u0432\u0456\u043A\u043D\u043E \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0438\",\n \"zh-cn\": \"\u5408\u5E76\u6D3E\u9001\u65F6\u6BB5\",\n },\n};\n\n/**\n * Translation object for a state name. Pass into `common.name`; ioBroker\n * Admin/vis/Object-Browser localizes automatically.\n *\n * @param key Translation key in {@link STATE_NAMES}.\n */\nexport function tName(key: keyof typeof STATE_NAMES): StateName {\n return STATE_NAMES[key];\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -32,21 +32,9 @@ __export(parcel_client_exports, {
|
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(parcel_client_exports);
|
|
34
34
|
var https = __toESM(require("node:https"));
|
|
35
|
+
var import_coerce = require("./coerce");
|
|
35
36
|
const API_BASE = "https://api.parcel.app/external";
|
|
36
37
|
const REQUEST_TIMEOUT = 15e3;
|
|
37
|
-
function isTrueish(v) {
|
|
38
|
-
if (typeof v === "boolean") {
|
|
39
|
-
return v;
|
|
40
|
-
}
|
|
41
|
-
if (typeof v === "number") {
|
|
42
|
-
return v === 1;
|
|
43
|
-
}
|
|
44
|
-
if (typeof v === "string") {
|
|
45
|
-
const s = v.toLowerCase();
|
|
46
|
-
return s === "true" || s === "1";
|
|
47
|
-
}
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
38
|
class ParcelClient {
|
|
51
39
|
apiKey;
|
|
52
40
|
carrierCache = null;
|
|
@@ -66,7 +54,7 @@ class ParcelClient {
|
|
|
66
54
|
err.code = "API_ERROR";
|
|
67
55
|
throw err;
|
|
68
56
|
}
|
|
69
|
-
if (!isTrueish(response.success)) {
|
|
57
|
+
if (!(0, import_coerce.isTrueish)(response.success)) {
|
|
70
58
|
const rawCode = typeof response.error_code === "string" ? response.error_code : "";
|
|
71
59
|
const rawMsg = typeof response.error_message === "string" ? response.error_message : "";
|
|
72
60
|
const code = rawCode || rawMsg || "UNKNOWN";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/parcel-client.ts"],
|
|
4
|
-
"sourcesContent": ["import * as https from \"node:https\";\nimport type { ParcelApiResponse, ParcelDelivery, AddDeliveryRequest, AddDeliveryResponse, CarrierMap } from \"./types\";\n\nconst API_BASE = \"https://api.parcel.app/external\";\nconst REQUEST_TIMEOUT = 15_000;\n\n
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAAuB;
|
|
4
|
+
"sourcesContent": ["import * as https from \"node:https\";\nimport { isTrueish } from \"./coerce\";\nimport type { ParcelApiResponse, ParcelDelivery, AddDeliveryRequest, AddDeliveryResponse, CarrierMap } from \"./types\";\n\nconst API_BASE = \"https://api.parcel.app/external\";\nconst REQUEST_TIMEOUT = 15_000;\n\n/** HTTP client for the parcel.app API */\nexport class ParcelClient {\n private apiKey: string;\n private carrierCache: CarrierMap | null = null;\n\n /** @param apiKey The parcel.app API key */\n constructor(apiKey: string) {\n this.apiKey = apiKey;\n }\n\n /**\n * Fetch deliveries from parcel.app.\n *\n * @param filterMode Filter active or recent deliveries\n */\n async getDeliveries(filterMode: \"active\" | \"recent\" = \"active\"): Promise<ParcelDelivery[]> {\n const response = await this.request<ParcelApiResponse>(\"GET\", `/deliveries/?filter_mode=${filterMode}`, true);\n\n // API-drift guard: response may be null or a non-object\n if (!response || typeof response !== \"object\") {\n const err = new Error(\"API error: malformed response\") as Error & {\n code: string;\n };\n err.code = \"API_ERROR\";\n throw err;\n }\n\n if (!isTrueish(response.success)) {\n const rawCode = typeof response.error_code === \"string\" ? response.error_code : \"\";\n const rawMsg = typeof response.error_message === \"string\" ? response.error_message : \"\";\n const code = rawCode || rawMsg || \"UNKNOWN\";\n const err = new Error(`API error: ${rawMsg || code}`) as Error & {\n code: string;\n };\n err.code = rawCode === \"INVALID_API_KEY\" ? \"INVALID_API_KEY\" : \"API_ERROR\";\n throw err;\n }\n\n // API-drift guard: deliveries must be an array\n return Array.isArray(response.deliveries) ? response.deliveries : [];\n }\n\n /**\n * Add a new delivery to parcel.app.\n *\n * @param delivery The delivery to add\n */\n async addDelivery(delivery: AddDeliveryRequest): Promise<AddDeliveryResponse> {\n return this.request<AddDeliveryResponse>(\"POST\", \"/add-delivery/\", true, delivery);\n }\n\n /** Get carrier names (cached after first call) */\n async getCarrierNames(): Promise<CarrierMap> {\n if (this.carrierCache) {\n return this.carrierCache;\n }\n\n try {\n const raw = await this.request<unknown>(\"GET\", \"/supported_carriers.json\", false);\n // API-drift guard: must be a plain object (not null, array, or primitive)\n if (raw && typeof raw === \"object\" && !Array.isArray(raw)) {\n this.carrierCache = raw as CarrierMap;\n } else {\n return {};\n }\n } catch {\n // Return empty map but don't cache it \u2014 allow retry next time\n return {};\n }\n\n return this.carrierCache;\n }\n\n /**\n * Resolve a carrier code to a display name.\n *\n * @param carrierCode The carrier code from API\n */\n async getCarrierName(carrierCode: unknown): Promise<string> {\n // API-drift guard: non-string codes fall back to \"UNKNOWN\"\n if (typeof carrierCode !== \"string\" || carrierCode.length === 0) {\n return \"UNKNOWN\";\n }\n const carriers = await this.getCarrierNames();\n const mapped = carriers[carrierCode];\n return typeof mapped === \"string\" && mapped.length > 0 ? mapped : carrierCode.toUpperCase();\n }\n\n /** Test if the API key is valid */\n async testConnection(): Promise<{ success: boolean; message: string }> {\n try {\n await this.getDeliveries(\"active\");\n return { success: true, message: \"Connection successful\" };\n } catch (err) {\n const error = err as Error & { code?: string };\n if (error.code === \"INVALID_API_KEY\") {\n return { success: false, message: \"Invalid API key\" };\n }\n return { success: false, message: error.message };\n }\n }\n\n /**\n * Execute an HTTP request against the parcel.app API.\n *\n * @param method HTTP method\n * @param path API path\n * @param authenticated Whether to send the API key\n * @param body Optional request body\n */\n private request<T>(method: string, path: string, authenticated: boolean, body?: unknown): Promise<T> {\n return new Promise((resolve, reject) => {\n const url = new URL(`${API_BASE}${path}`);\n\n const headers: Record<string, string> = {};\n if (authenticated) {\n headers[\"api-key\"] = this.apiKey;\n }\n if (body) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n const options: https.RequestOptions = {\n hostname: url.hostname,\n port: url.port || 443,\n path: url.pathname + url.search,\n method,\n headers,\n timeout: REQUEST_TIMEOUT,\n };\n\n const req = https.request(options, res => {\n const chunks: Buffer[] = [];\n\n res.on(\"error\", err => reject(err));\n res.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n res.on(\"end\", () => {\n const raw = Buffer.concat(chunks).toString(\"utf-8\");\n\n if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {\n if (res.statusCode === 429) {\n const retryAfter = parseInt(res.headers[\"retry-after\"] || \"\", 10);\n const err = new Error(\"Rate limit exceeded\") as Error & {\n code: string;\n retryAfterSeconds: number;\n };\n err.code = \"RATE_LIMITED\";\n // Use Retry-After header or default to 5 minutes\n err.retryAfterSeconds = retryAfter > 0 ? retryAfter : 5 * 60;\n reject(err);\n return;\n }\n const err = new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`) as Error & { code: string };\n err.code = res.statusCode === 401 || res.statusCode === 403 ? \"INVALID_API_KEY\" : \"HTTP_ERROR\";\n reject(err);\n return;\n }\n\n try {\n resolve(JSON.parse(raw) as T);\n } catch {\n reject(new Error(`JSON parse error: ${raw.substring(0, 200)}`));\n }\n });\n });\n\n req.on(\"timeout\", () => {\n req.destroy();\n reject(new Error(\"Request timeout\"));\n });\n\n req.on(\"error\", err => reject(err));\n\n if (body) {\n req.write(JSON.stringify(body));\n }\n req.end();\n });\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["err"]
|
|
7
7
|
}
|
|
@@ -22,17 +22,12 @@ __export(state_manager_exports, {
|
|
|
22
22
|
resolveLanguage: () => resolveLanguage
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(state_manager_exports);
|
|
25
|
+
var import_coerce = require("./coerce");
|
|
26
|
+
var import_i18n_states = require("./i18n-states");
|
|
25
27
|
var import_types = require("./types");
|
|
26
28
|
const TRACKABLE_STATUSES = /* @__PURE__ */ new Set([2, 4, 8]);
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
return v;
|
|
30
|
-
}
|
|
31
|
-
if (typeof v === "string" && v.length > 0) {
|
|
32
|
-
const n = parseFloat(v);
|
|
33
|
-
return Number.isFinite(n) ? n : null;
|
|
34
|
-
}
|
|
35
|
-
return null;
|
|
29
|
+
function asName(name) {
|
|
30
|
+
return name;
|
|
36
31
|
}
|
|
37
32
|
const ESTIMATE_LABELS = {
|
|
38
33
|
de: {
|
|
@@ -111,6 +106,14 @@ function resolveLanguage(language) {
|
|
|
111
106
|
class StateManager {
|
|
112
107
|
adapter;
|
|
113
108
|
language;
|
|
109
|
+
/**
|
|
110
|
+
* Cache of state IDs that have already passed `setObjectNotExistsAsync`.
|
|
111
|
+
* Skips repeat DB lookups on the hot path — each poll touches ~11 states
|
|
112
|
+
* per delivery, and most deliveries see no schema change between polls.
|
|
113
|
+
* On `cleanupDeliveries`, IDs of removed packages are dropped so a re-add
|
|
114
|
+
* triggers a fresh creation.
|
|
115
|
+
*/
|
|
116
|
+
createdIds = /* @__PURE__ */ new Set();
|
|
114
117
|
/**
|
|
115
118
|
* @param adapter The ioBroker adapter instance
|
|
116
119
|
* @param language Language code from system.config.language (falls back to English)
|
|
@@ -183,35 +186,53 @@ class StateManager {
|
|
|
183
186
|
const labels = import_types.STATUS_LABELS[this.language];
|
|
184
187
|
const statusText = labels[statusCode] || `Unknown (${statusCode})`;
|
|
185
188
|
await Promise.all([
|
|
186
|
-
this.createAndSet(`${devicePath}.carrier`, "
|
|
187
|
-
this.createAndSet(`${devicePath}.status`, "
|
|
188
|
-
this.createAndSet(`${devicePath}.statusCode`,
|
|
189
|
-
this.createAndSet(`${devicePath}.description`, "
|
|
190
|
-
this.createAndSet(
|
|
191
|
-
|
|
189
|
+
this.createAndSet(`${devicePath}.carrier`, asName((0, import_i18n_states.tName)("carrier")), "string", "text", carrierName),
|
|
190
|
+
this.createAndSet(`${devicePath}.status`, asName((0, import_i18n_states.tName)("status")), "string", "text", statusText),
|
|
191
|
+
this.createAndSet(`${devicePath}.statusCode`, asName((0, import_i18n_states.tName)("statusCode")), "number", "value", statusCode),
|
|
192
|
+
this.createAndSet(`${devicePath}.description`, asName((0, import_i18n_states.tName)("description")), "string", "text", description),
|
|
193
|
+
this.createAndSet(
|
|
194
|
+
`${devicePath}.trackingNumber`,
|
|
195
|
+
asName((0, import_i18n_states.tName)("trackingNumber")),
|
|
196
|
+
"string",
|
|
197
|
+
"text",
|
|
198
|
+
trackingNumber
|
|
199
|
+
),
|
|
200
|
+
this.createAndSet(`${devicePath}.extraInfo`, asName((0, import_i18n_states.tName)("extraInfo")), "string", "text", extraInfo),
|
|
192
201
|
this.createAndSet(
|
|
193
202
|
`${devicePath}.deliveryWindow`,
|
|
194
|
-
|
|
203
|
+
asName((0, import_i18n_states.tName)("deliveryWindow")),
|
|
195
204
|
"string",
|
|
196
205
|
"text",
|
|
197
206
|
this.calculateDeliveryWindow(delivery, statusCode)
|
|
198
207
|
),
|
|
199
208
|
this.createAndSet(
|
|
200
209
|
`${devicePath}.deliveryEstimate`,
|
|
201
|
-
|
|
210
|
+
asName((0, import_i18n_states.tName)("deliveryEstimate")),
|
|
202
211
|
"string",
|
|
203
212
|
"text",
|
|
204
213
|
this.calculateDeliveryEstimate(delivery, statusCode)
|
|
205
214
|
),
|
|
206
|
-
this.createAndSet(
|
|
215
|
+
this.createAndSet(
|
|
216
|
+
`${devicePath}.lastEvent`,
|
|
217
|
+
asName((0, import_i18n_states.tName)("lastEvent")),
|
|
218
|
+
"string",
|
|
219
|
+
"text",
|
|
220
|
+
this.formatLastEvent(delivery)
|
|
221
|
+
),
|
|
207
222
|
this.createAndSet(
|
|
208
223
|
`${devicePath}.lastLocation`,
|
|
209
|
-
|
|
224
|
+
asName((0, import_i18n_states.tName)("lastLocation")),
|
|
210
225
|
"string",
|
|
211
226
|
"text",
|
|
212
227
|
this.extractLastLocation(delivery)
|
|
213
228
|
),
|
|
214
|
-
this.createAndSet(
|
|
229
|
+
this.createAndSet(
|
|
230
|
+
`${devicePath}.lastUpdated`,
|
|
231
|
+
asName((0, import_i18n_states.tName)("lastUpdated")),
|
|
232
|
+
"string",
|
|
233
|
+
"date",
|
|
234
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
235
|
+
)
|
|
215
236
|
]);
|
|
216
237
|
}
|
|
217
238
|
/**
|
|
@@ -223,11 +244,17 @@ class StateManager {
|
|
|
223
244
|
async updateSummary(activeDeliveries) {
|
|
224
245
|
const todayDeliveries = activeDeliveries.filter((d) => this.isToday(d, this.parseStatus(d)));
|
|
225
246
|
await Promise.all([
|
|
226
|
-
this.createAndSet(
|
|
227
|
-
|
|
247
|
+
this.createAndSet(
|
|
248
|
+
"summary.activeCount",
|
|
249
|
+
asName((0, import_i18n_states.tName)("activeCount")),
|
|
250
|
+
"number",
|
|
251
|
+
"value",
|
|
252
|
+
activeDeliveries.length
|
|
253
|
+
),
|
|
254
|
+
this.createAndSet("summary.todayCount", asName((0, import_i18n_states.tName)("todayCount")), "number", "value", todayDeliveries.length),
|
|
228
255
|
this.createAndSet(
|
|
229
256
|
"summary.deliveryWindow",
|
|
230
|
-
|
|
257
|
+
asName((0, import_i18n_states.tName)("summaryDeliveryWindow")),
|
|
231
258
|
"string",
|
|
232
259
|
"text",
|
|
233
260
|
this.calculateCombinedWindow(todayDeliveries)
|
|
@@ -250,6 +277,11 @@ class StateManager {
|
|
|
250
277
|
if (relativeId.startsWith("deliveries.") && !activeSet.has(relativeId)) {
|
|
251
278
|
await this.adapter.delObjectAsync(relativeId, { recursive: true });
|
|
252
279
|
this.adapter.log.debug(`Removed stale delivery: ${relativeId}`);
|
|
280
|
+
for (const id of this.createdIds) {
|
|
281
|
+
if (id === relativeId || id.startsWith(`${relativeId}.`)) {
|
|
282
|
+
this.createdIds.delete(id);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
253
285
|
}
|
|
254
286
|
}
|
|
255
287
|
}
|
|
@@ -264,7 +296,7 @@ class StateManager {
|
|
|
264
296
|
return "";
|
|
265
297
|
}
|
|
266
298
|
const formatTime = (timestamp) => {
|
|
267
|
-
const ts =
|
|
299
|
+
const ts = (0, import_coerce.coerceFiniteNumber)(timestamp);
|
|
268
300
|
if (ts === null || ts <= 0) {
|
|
269
301
|
return null;
|
|
270
302
|
}
|
|
@@ -293,7 +325,7 @@ class StateManager {
|
|
|
293
325
|
return null;
|
|
294
326
|
}
|
|
295
327
|
let expectedDate = null;
|
|
296
|
-
const ts =
|
|
328
|
+
const ts = (0, import_coerce.coerceFiniteNumber)(delivery.timestamp_expected);
|
|
297
329
|
if (ts !== null && ts > 0) {
|
|
298
330
|
expectedDate = new Date(ts * 1e3);
|
|
299
331
|
} else if (typeof delivery.date_expected === "string" && delivery.date_expected.length > 0) {
|
|
@@ -404,20 +436,25 @@ class StateManager {
|
|
|
404
436
|
return `${times[0].start} - ${times[times.length - 1].end}`;
|
|
405
437
|
}
|
|
406
438
|
/**
|
|
407
|
-
* Create/extend a read-only state and set its value.
|
|
439
|
+
* Create/extend a read-only state and set its value. Skips the
|
|
440
|
+
* `setObjectNotExistsAsync` round-trip once the ID is in the cache —
|
|
441
|
+
* states are static after first creation; only the value changes per poll.
|
|
408
442
|
*
|
|
409
443
|
* @param id State ID relative to adapter namespace
|
|
410
|
-
* @param name Display name
|
|
444
|
+
* @param name Display name (translation object or plain string)
|
|
411
445
|
* @param type Value type
|
|
412
446
|
* @param role ioBroker role
|
|
413
447
|
* @param val Value to set
|
|
414
448
|
*/
|
|
415
449
|
async createAndSet(id, name, type, role, val) {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
450
|
+
if (!this.createdIds.has(id)) {
|
|
451
|
+
await this.adapter.setObjectNotExistsAsync(id, {
|
|
452
|
+
type: "state",
|
|
453
|
+
common: { name, type, role, read: true, write: false },
|
|
454
|
+
native: {}
|
|
455
|
+
});
|
|
456
|
+
this.createdIds.add(id);
|
|
457
|
+
}
|
|
421
458
|
await this.adapter.setStateAsync(id, { val, ack: true });
|
|
422
459
|
}
|
|
423
460
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/state-manager.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AdapterInstance } from \"@iobroker/adapter-core\";\nimport type { ParcelDelivery } from \"./types\";\nimport { STATUS_LABELS, SUPPORTED_LANGUAGES, FALLBACK_LANGUAGE } from \"./types\";\n\n/** Status codes that have expected delivery date/time */\nconst TRACKABLE_STATUSES = new Set([2, 4, 8]);\n\n/**\n * Coerce a value to a finite number. Accepts numbers and numeric strings.\n * Returns null for anything else \u2014 used to guard against API drift.\n *\n * @param v Value to coerce\n */\nfunction coerceNumber(v: unknown): number | null {\n if (typeof v === \"number\" && Number.isFinite(v)) {\n return v;\n }\n if (typeof v === \"string\" && v.length > 0) {\n const n = parseFloat(v);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\n/** Delivery-estimate labels keyed by language code. Keys must match STATUS_LABELS. */\nconst ESTIMATE_LABELS: Record<string, Record<string, string>> = {\n de: {\n overdue: \"\u00FCberf\u00E4llig\",\n today: \"heute\",\n tomorrow: \"morgen\",\n days: \"in %d Tagen\",\n },\n en: {\n overdue: \"overdue\",\n today: \"today\",\n tomorrow: \"tomorrow\",\n days: \"in %d days\",\n },\n ru: {\n overdue: \"\u043F\u0440\u043E\u0441\u0440\u043E\u0447\u0435\u043D\u043E\",\n today: \"\u0441\u0435\u0433\u043E\u0434\u043D\u044F\",\n tomorrow: \"\u0437\u0430\u0432\u0442\u0440\u0430\",\n days: \"\u0447\u0435\u0440\u0435\u0437 %d \u0434\u043D.\",\n },\n pt: {\n overdue: \"atrasado\",\n today: \"hoje\",\n tomorrow: \"amanh\u00E3\",\n days: \"em %d dias\",\n },\n nl: {\n overdue: \"te laat\",\n today: \"vandaag\",\n tomorrow: \"morgen\",\n days: \"over %d dagen\",\n },\n fr: {\n overdue: \"en retard\",\n today: \"aujourd'hui\",\n tomorrow: \"demain\",\n days: \"dans %d jours\",\n },\n it: {\n overdue: \"in ritardo\",\n today: \"oggi\",\n tomorrow: \"domani\",\n days: \"tra %d giorni\",\n },\n es: {\n overdue: \"atrasado\",\n today: \"hoy\",\n tomorrow: \"ma\u00F1ana\",\n days: \"en %d d\u00EDas\",\n },\n pl: {\n overdue: \"zaleg\u0142e\",\n today: \"dzisiaj\",\n tomorrow: \"jutro\",\n days: \"za %d dni\",\n },\n uk: {\n overdue: \"\u043F\u0440\u043E\u0441\u0442\u0440\u043E\u0447\u0435\u043D\u043E\",\n today: \"\u0441\u044C\u043E\u0433\u043E\u0434\u043D\u0456\",\n tomorrow: \"\u0437\u0430\u0432\u0442\u0440\u0430\",\n days: \"\u0447\u0435\u0440\u0435\u0437 %d \u0434\u043D.\",\n },\n \"zh-cn\": {\n overdue: \"\u5DF2\u903E\u671F\",\n today: \"\u4ECA\u5929\",\n tomorrow: \"\u660E\u5929\",\n days: \"%d \u5929\u540E\",\n },\n};\n\n/**\n * Resolve a language code to one that has labels. Falls back to English\n * when the system language is not one of the supported ioBroker languages.\n *\n * @param language Raw language code (e.g. from system.config.language)\n */\nexport function resolveLanguage(language: unknown): string {\n if (typeof language === \"string\" && SUPPORTED_LANGUAGES.includes(language)) {\n return language;\n }\n return FALLBACK_LANGUAGE;\n}\n\n/** Manages ioBroker states for parcel deliveries */\nexport class StateManager {\n private adapter: AdapterInstance;\n private language: string;\n\n /**\n * @param adapter The ioBroker adapter instance\n * @param language Language code from system.config.language (falls back to English)\n */\n constructor(adapter: AdapterInstance, language: string) {\n this.adapter = adapter;\n this.language = resolveLanguage(language);\n }\n\n /**\n * Sanitize a string for use as ioBroker object ID (see adapter.FORBIDDEN_CHARS).\n * API-drift guard: returns \"unknown\" for non-string input.\n *\n * @param name Raw value to sanitize (any type)\n */\n sanitize(name: unknown): string {\n if (typeof name !== \"string\") {\n return \"unknown\";\n }\n return (\n name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\")\n .slice(0, 50) || \"unknown\"\n );\n }\n\n /**\n * Parse the status code from a delivery. API documents `status_code` as\n * a numeric string, but we accept numbers too and fall back to 0 for drift.\n *\n * @param delivery The delivery to parse\n */\n parseStatus(delivery: ParcelDelivery): number {\n const raw = delivery.status_code as unknown;\n if (typeof raw === \"number\" && Number.isFinite(raw)) {\n return Math.trunc(raw);\n }\n if (typeof raw === \"string\") {\n const n = parseInt(raw, 10);\n return Number.isFinite(n) ? n : 0;\n }\n return 0;\n }\n\n /**\n * Build a unique package ID from a delivery.\n *\n * @param delivery The delivery to build an ID for\n */\n packageId(delivery: ParcelDelivery): string {\n let id = this.sanitize(delivery.tracking_number);\n // API-drift guard: only string values extend the id\n if (typeof delivery.extra_information === \"string\" && delivery.extra_information.length > 0) {\n id += `_${this.sanitize(delivery.extra_information)}`;\n }\n return id;\n }\n\n /**\n * Update or create all states for a delivery.\n *\n * @param delivery The delivery data from API\n * @param carrierName Resolved carrier display name\n */\n async updateDelivery(delivery: ParcelDelivery, carrierName: string): Promise<void> {\n const pkgId = this.packageId(delivery);\n const devicePath = `deliveries.${pkgId}`;\n\n const description = typeof delivery.description === \"string\" ? delivery.description : \"\";\n const trackingNumber = typeof delivery.tracking_number === \"string\" ? delivery.tracking_number : \"\";\n const extraInfo = typeof delivery.extra_information === \"string\" ? delivery.extra_information : \"\";\n\n await this.adapter.extendObjectAsync(devicePath, {\n type: \"device\",\n common: {\n name: description || `Package ${trackingNumber || pkgId}`,\n },\n native: {},\n });\n\n const statusCode = this.parseStatus(delivery);\n const labels = STATUS_LABELS[this.language];\n const statusText = labels[statusCode] || `Unknown (${statusCode})`;\n\n await Promise.all([\n this.createAndSet(`${devicePath}.carrier`, \"Carrier\", \"string\", \"text\", carrierName),\n this.createAndSet(`${devicePath}.status`, \"Status\", \"string\", \"text\", statusText),\n this.createAndSet(`${devicePath}.statusCode`, \"Status Code\", \"number\", \"value\", statusCode),\n this.createAndSet(`${devicePath}.description`, \"Description\", \"string\", \"text\", description),\n this.createAndSet(`${devicePath}.trackingNumber`, \"Tracking Number\", \"string\", \"text\", trackingNumber),\n this.createAndSet(`${devicePath}.extraInfo`, \"Extra Information\", \"string\", \"text\", extraInfo),\n this.createAndSet(\n `${devicePath}.deliveryWindow`,\n \"Delivery Window\",\n \"string\",\n \"text\",\n this.calculateDeliveryWindow(delivery, statusCode),\n ),\n this.createAndSet(\n `${devicePath}.deliveryEstimate`,\n \"Delivery Estimate\",\n \"string\",\n \"text\",\n this.calculateDeliveryEstimate(delivery, statusCode),\n ),\n this.createAndSet(`${devicePath}.lastEvent`, \"Last Event\", \"string\", \"text\", this.formatLastEvent(delivery)),\n this.createAndSet(\n `${devicePath}.lastLocation`,\n \"Last Location\",\n \"string\",\n \"text\",\n this.extractLastLocation(delivery),\n ),\n this.createAndSet(`${devicePath}.lastUpdated`, \"Last Updated\", \"string\", \"date\", new Date().toISOString()),\n ]);\n }\n\n /**\n * Update summary states. Expects already-filtered active deliveries.\n * The `summary` channel itself is declared via io-package.json instanceObjects.\n *\n * @param activeDeliveries Only active (non-delivered) deliveries\n */\n async updateSummary(activeDeliveries: ParcelDelivery[]): Promise<void> {\n const todayDeliveries = activeDeliveries.filter(d => this.isToday(d, this.parseStatus(d)));\n\n await Promise.all([\n this.createAndSet(\"summary.activeCount\", \"Active Deliveries\", \"number\", \"value\", activeDeliveries.length),\n this.createAndSet(\"summary.todayCount\", \"Deliveries Today\", \"number\", \"value\", todayDeliveries.length),\n this.createAndSet(\n \"summary.deliveryWindow\",\n \"Combined Delivery Window\",\n \"string\",\n \"text\",\n this.calculateCombinedWindow(todayDeliveries),\n ),\n ]);\n }\n\n /**\n * Remove deliveries that are no longer active.\n *\n * @param activeIds List of currently active package IDs\n */\n async cleanupDeliveries(activeIds: string[]): Promise<void> {\n const activeSet = new Set(activeIds.map(id => `deliveries.${id}`));\n\n const objects = await this.adapter.getObjectViewAsync(\"system\", \"device\", {\n startkey: `${this.adapter.namespace}.deliveries.`,\n endkey: `${this.adapter.namespace}.deliveries.\u9999`,\n });\n\n for (const row of objects.rows) {\n const relativeId = row.id.replace(`${this.adapter.namespace}.`, \"\");\n if (relativeId.startsWith(\"deliveries.\") && !activeSet.has(relativeId)) {\n await this.adapter.delObjectAsync(relativeId, { recursive: true });\n this.adapter.log.debug(`Removed stale delivery: ${relativeId}`);\n }\n }\n }\n\n /**\n * Calculate delivery time window \u2014 only from Unix timestamps.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private calculateDeliveryWindow(delivery: ParcelDelivery, statusCode: number): string {\n if (!TRACKABLE_STATUSES.has(statusCode)) {\n return \"\";\n }\n\n const formatTime = (timestamp: unknown): string | null => {\n const ts = coerceNumber(timestamp);\n if (ts === null || ts <= 0) {\n return null;\n }\n const d = new Date(ts * 1000);\n if (Number.isNaN(d.getTime())) {\n return null;\n }\n return `${d.getHours().toString().padStart(2, \"0\")}:${d.getMinutes().toString().padStart(2, \"0\")}`;\n };\n\n const start = formatTime(delivery.timestamp_expected);\n const end = formatTime(delivery.timestamp_expected_end);\n\n if (!start) {\n return \"\";\n }\n return end ? `${start} - ${end}` : start;\n }\n\n /**\n * Days from today to the expected delivery date. Returns null when the\n * delivery has no usable expected date or is in a non-trackable status.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private computeDiffDays(delivery: ParcelDelivery, statusCode: number): number | null {\n if (!TRACKABLE_STATUSES.has(statusCode)) {\n return null;\n }\n\n let expectedDate: Date | null = null;\n const ts = coerceNumber(delivery.timestamp_expected);\n if (ts !== null && ts > 0) {\n expectedDate = new Date(ts * 1000);\n } else if (typeof delivery.date_expected === \"string\" && delivery.date_expected.length > 0) {\n expectedDate = new Date(delivery.date_expected);\n }\n\n if (!expectedDate || isNaN(expectedDate.getTime())) {\n return null;\n }\n\n const now = new Date();\n const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n const expectedStart = new Date(expectedDate.getFullYear(), expectedDate.getMonth(), expectedDate.getDate());\n return Math.round((expectedStart.getTime() - todayStart.getTime()) / (1000 * 60 * 60 * 24));\n }\n\n /**\n * Calculate human-readable delivery estimate.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private calculateDeliveryEstimate(delivery: ParcelDelivery, statusCode: number): string {\n const diffDays = this.computeDiffDays(delivery, statusCode);\n if (diffDays === null) {\n return \"\";\n }\n const l = ESTIMATE_LABELS[this.language];\n if (diffDays < 0) {\n return l.overdue;\n }\n if (diffDays === 0) {\n return l.today;\n }\n if (diffDays === 1) {\n return l.tomorrow;\n }\n return l.days.replace(\"%d\", String(diffDays));\n }\n\n /**\n * Whether the delivery is expected today. Language-agnostic, used by the\n * summary filter so `todayCount` works across all languages.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private isToday(delivery: ParcelDelivery, statusCode: number): boolean {\n return this.computeDiffDays(delivery, statusCode) === 0;\n }\n\n /**\n * Format the latest tracking event.\n *\n * @param delivery The delivery data\n */\n private formatLastEvent(delivery: ParcelDelivery): string {\n if (!Array.isArray(delivery.events) || delivery.events.length === 0) {\n return \"\";\n }\n const latest = delivery.events[0];\n if (!latest || typeof latest !== \"object\") {\n return \"\";\n }\n const parts: string[] = [];\n if (typeof latest.event === \"string\" && latest.event.length > 0) {\n parts.push(latest.event);\n }\n if (typeof latest.date === \"string\" && latest.date.length > 0) {\n parts.push(latest.date);\n }\n return parts.join(\" - \");\n }\n\n /**\n * Extract location from latest event.\n *\n * @param delivery The delivery data\n */\n private extractLastLocation(delivery: ParcelDelivery): string {\n if (!Array.isArray(delivery.events) || delivery.events.length === 0) {\n return \"\";\n }\n const latest = delivery.events[0];\n if (!latest || typeof latest !== \"object\") {\n return \"\";\n }\n return typeof latest.location === \"string\" ? latest.location : \"\";\n }\n\n /**\n * Calculate combined delivery window for today's packages.\n *\n * @param todayDeliveries Deliveries expected today\n */\n private calculateCombinedWindow(todayDeliveries: ParcelDelivery[]): string {\n const windows = todayDeliveries\n .map(d => this.calculateDeliveryWindow(d, this.parseStatus(d)))\n .filter(w => w.length > 0);\n\n if (windows.length === 0) {\n return \"\";\n }\n if (windows.length === 1) {\n return windows[0];\n }\n\n const times: {\n /** Window start */ start: string;\n /** Window end */ end: string;\n }[] = [];\n for (const w of windows) {\n const match = w.match(/(\\d{2}:\\d{2})(?:\\s*-\\s*(\\d{2}:\\d{2}))?/);\n if (match) {\n times.push({ start: match[1], end: match[2] || match[1] });\n }\n }\n\n if (times.length === 0) {\n return \"\";\n }\n\n times.sort((a, b) => a.start.localeCompare(b.start));\n return `${times[0].start} - ${times[times.length - 1].end}`;\n }\n\n /**\n * Create/extend a read-only state and set its value.\n *\n * @param id State ID relative to adapter namespace\n * @param name Display name\n * @param type Value type\n * @param role ioBroker role\n * @param val Value to set\n */\n private async createAndSet(\n id: string,\n name: string,\n type: ioBroker.CommonType,\n role: string,\n val: ioBroker.StateValue,\n ): Promise<void> {\n await this.adapter.setObjectNotExistsAsync(id, {\n type: \"state\",\n common: { name, type, role, read: true, write: false },\n native: {},\n });\n await this.adapter.setStateAsync(id, { val, ack: true });\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
4
|
+
"sourcesContent": ["import type { AdapterInstance } from \"@iobroker/adapter-core\";\nimport { coerceFiniteNumber } from \"./coerce\";\nimport type { StateName } from \"./i18n-states\";\nimport { tName } from \"./i18n-states\";\nimport type { ParcelDelivery } from \"./types\";\nimport { STATUS_LABELS, SUPPORTED_LANGUAGES, FALLBACK_LANGUAGE } from \"./types\";\n\n/** Status codes that have expected delivery date/time */\nconst TRACKABLE_STATUSES = new Set([2, 4, 8]);\n\n/**\n * Translation-object cast helper \u2014 ioBroker's `common.name` accepts `StringOrTranslated`.\n *\n * @param name Translation object from {@link STATE_NAMES}.\n */\nfunction asName(name: StateName): ioBroker.StringOrTranslated {\n return name;\n}\n\n/** Delivery-estimate labels keyed by language code. Keys must match STATUS_LABELS. */\nconst ESTIMATE_LABELS: Record<string, Record<string, string>> = {\n de: {\n overdue: \"\u00FCberf\u00E4llig\",\n today: \"heute\",\n tomorrow: \"morgen\",\n days: \"in %d Tagen\",\n },\n en: {\n overdue: \"overdue\",\n today: \"today\",\n tomorrow: \"tomorrow\",\n days: \"in %d days\",\n },\n ru: {\n overdue: \"\u043F\u0440\u043E\u0441\u0440\u043E\u0447\u0435\u043D\u043E\",\n today: \"\u0441\u0435\u0433\u043E\u0434\u043D\u044F\",\n tomorrow: \"\u0437\u0430\u0432\u0442\u0440\u0430\",\n days: \"\u0447\u0435\u0440\u0435\u0437 %d \u0434\u043D.\",\n },\n pt: {\n overdue: \"atrasado\",\n today: \"hoje\",\n tomorrow: \"amanh\u00E3\",\n days: \"em %d dias\",\n },\n nl: {\n overdue: \"te laat\",\n today: \"vandaag\",\n tomorrow: \"morgen\",\n days: \"over %d dagen\",\n },\n fr: {\n overdue: \"en retard\",\n today: \"aujourd'hui\",\n tomorrow: \"demain\",\n days: \"dans %d jours\",\n },\n it: {\n overdue: \"in ritardo\",\n today: \"oggi\",\n tomorrow: \"domani\",\n days: \"tra %d giorni\",\n },\n es: {\n overdue: \"atrasado\",\n today: \"hoy\",\n tomorrow: \"ma\u00F1ana\",\n days: \"en %d d\u00EDas\",\n },\n pl: {\n overdue: \"zaleg\u0142e\",\n today: \"dzisiaj\",\n tomorrow: \"jutro\",\n days: \"za %d dni\",\n },\n uk: {\n overdue: \"\u043F\u0440\u043E\u0441\u0442\u0440\u043E\u0447\u0435\u043D\u043E\",\n today: \"\u0441\u044C\u043E\u0433\u043E\u0434\u043D\u0456\",\n tomorrow: \"\u0437\u0430\u0432\u0442\u0440\u0430\",\n days: \"\u0447\u0435\u0440\u0435\u0437 %d \u0434\u043D.\",\n },\n \"zh-cn\": {\n overdue: \"\u5DF2\u903E\u671F\",\n today: \"\u4ECA\u5929\",\n tomorrow: \"\u660E\u5929\",\n days: \"%d \u5929\u540E\",\n },\n};\n\n/**\n * Resolve a language code to one that has labels. Falls back to English\n * when the system language is not one of the supported ioBroker languages.\n *\n * @param language Raw language code (e.g. from system.config.language)\n */\nexport function resolveLanguage(language: unknown): string {\n if (typeof language === \"string\" && SUPPORTED_LANGUAGES.includes(language)) {\n return language;\n }\n return FALLBACK_LANGUAGE;\n}\n\n/** Manages ioBroker states for parcel deliveries */\nexport class StateManager {\n private adapter: AdapterInstance;\n private language: string;\n /**\n * Cache of state IDs that have already passed `setObjectNotExistsAsync`.\n * Skips repeat DB lookups on the hot path \u2014 each poll touches ~11 states\n * per delivery, and most deliveries see no schema change between polls.\n * On `cleanupDeliveries`, IDs of removed packages are dropped so a re-add\n * triggers a fresh creation.\n */\n private readonly createdIds = new Set<string>();\n\n /**\n * @param adapter The ioBroker adapter instance\n * @param language Language code from system.config.language (falls back to English)\n */\n constructor(adapter: AdapterInstance, language: string) {\n this.adapter = adapter;\n this.language = resolveLanguage(language);\n }\n\n /**\n * Sanitize a string for use as ioBroker object ID (see adapter.FORBIDDEN_CHARS).\n * API-drift guard: returns \"unknown\" for non-string input.\n *\n * @param name Raw value to sanitize (any type)\n */\n sanitize(name: unknown): string {\n if (typeof name !== \"string\") {\n return \"unknown\";\n }\n return (\n name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\")\n .slice(0, 50) || \"unknown\"\n );\n }\n\n /**\n * Parse the status code from a delivery. API documents `status_code` as\n * a numeric string, but we accept numbers too and fall back to 0 for drift.\n *\n * @param delivery The delivery to parse\n */\n parseStatus(delivery: ParcelDelivery): number {\n const raw = delivery.status_code as unknown;\n if (typeof raw === \"number\" && Number.isFinite(raw)) {\n return Math.trunc(raw);\n }\n if (typeof raw === \"string\") {\n const n = parseInt(raw, 10);\n return Number.isFinite(n) ? n : 0;\n }\n return 0;\n }\n\n /**\n * Build a unique package ID from a delivery.\n *\n * @param delivery The delivery to build an ID for\n */\n packageId(delivery: ParcelDelivery): string {\n let id = this.sanitize(delivery.tracking_number);\n // API-drift guard: only string values extend the id\n if (typeof delivery.extra_information === \"string\" && delivery.extra_information.length > 0) {\n id += `_${this.sanitize(delivery.extra_information)}`;\n }\n return id;\n }\n\n /**\n * Update or create all states for a delivery.\n *\n * @param delivery The delivery data from API\n * @param carrierName Resolved carrier display name\n */\n async updateDelivery(delivery: ParcelDelivery, carrierName: string): Promise<void> {\n const pkgId = this.packageId(delivery);\n const devicePath = `deliveries.${pkgId}`;\n\n const description = typeof delivery.description === \"string\" ? delivery.description : \"\";\n const trackingNumber = typeof delivery.tracking_number === \"string\" ? delivery.tracking_number : \"\";\n const extraInfo = typeof delivery.extra_information === \"string\" ? delivery.extra_information : \"\";\n\n await this.adapter.extendObjectAsync(devicePath, {\n type: \"device\",\n common: {\n name: description || `Package ${trackingNumber || pkgId}`,\n },\n native: {},\n });\n\n const statusCode = this.parseStatus(delivery);\n const labels = STATUS_LABELS[this.language];\n const statusText = labels[statusCode] || `Unknown (${statusCode})`;\n\n await Promise.all([\n this.createAndSet(`${devicePath}.carrier`, asName(tName(\"carrier\")), \"string\", \"text\", carrierName),\n this.createAndSet(`${devicePath}.status`, asName(tName(\"status\")), \"string\", \"text\", statusText),\n this.createAndSet(`${devicePath}.statusCode`, asName(tName(\"statusCode\")), \"number\", \"value\", statusCode),\n this.createAndSet(`${devicePath}.description`, asName(tName(\"description\")), \"string\", \"text\", description),\n this.createAndSet(\n `${devicePath}.trackingNumber`,\n asName(tName(\"trackingNumber\")),\n \"string\",\n \"text\",\n trackingNumber,\n ),\n this.createAndSet(`${devicePath}.extraInfo`, asName(tName(\"extraInfo\")), \"string\", \"text\", extraInfo),\n this.createAndSet(\n `${devicePath}.deliveryWindow`,\n asName(tName(\"deliveryWindow\")),\n \"string\",\n \"text\",\n this.calculateDeliveryWindow(delivery, statusCode),\n ),\n this.createAndSet(\n `${devicePath}.deliveryEstimate`,\n asName(tName(\"deliveryEstimate\")),\n \"string\",\n \"text\",\n this.calculateDeliveryEstimate(delivery, statusCode),\n ),\n this.createAndSet(\n `${devicePath}.lastEvent`,\n asName(tName(\"lastEvent\")),\n \"string\",\n \"text\",\n this.formatLastEvent(delivery),\n ),\n this.createAndSet(\n `${devicePath}.lastLocation`,\n asName(tName(\"lastLocation\")),\n \"string\",\n \"text\",\n this.extractLastLocation(delivery),\n ),\n this.createAndSet(\n `${devicePath}.lastUpdated`,\n asName(tName(\"lastUpdated\")),\n \"string\",\n \"date\",\n new Date().toISOString(),\n ),\n ]);\n }\n\n /**\n * Update summary states. Expects already-filtered active deliveries.\n * The `summary` channel itself is declared via io-package.json instanceObjects.\n *\n * @param activeDeliveries Only active (non-delivered) deliveries\n */\n async updateSummary(activeDeliveries: ParcelDelivery[]): Promise<void> {\n const todayDeliveries = activeDeliveries.filter(d => this.isToday(d, this.parseStatus(d)));\n\n await Promise.all([\n this.createAndSet(\n \"summary.activeCount\",\n asName(tName(\"activeCount\")),\n \"number\",\n \"value\",\n activeDeliveries.length,\n ),\n this.createAndSet(\"summary.todayCount\", asName(tName(\"todayCount\")), \"number\", \"value\", todayDeliveries.length),\n this.createAndSet(\n \"summary.deliveryWindow\",\n asName(tName(\"summaryDeliveryWindow\")),\n \"string\",\n \"text\",\n this.calculateCombinedWindow(todayDeliveries),\n ),\n ]);\n }\n\n /**\n * Remove deliveries that are no longer active.\n *\n * @param activeIds List of currently active package IDs\n */\n async cleanupDeliveries(activeIds: string[]): Promise<void> {\n const activeSet = new Set(activeIds.map(id => `deliveries.${id}`));\n\n const objects = await this.adapter.getObjectViewAsync(\"system\", \"device\", {\n startkey: `${this.adapter.namespace}.deliveries.`,\n endkey: `${this.adapter.namespace}.deliveries.\u9999`,\n });\n\n for (const row of objects.rows) {\n const relativeId = row.id.replace(`${this.adapter.namespace}.`, \"\");\n if (relativeId.startsWith(\"deliveries.\") && !activeSet.has(relativeId)) {\n await this.adapter.delObjectAsync(relativeId, { recursive: true });\n this.adapter.log.debug(`Removed stale delivery: ${relativeId}`);\n // Drop cached child IDs \u2014 re-pairing the same delivery must re-create\n // its states from scratch.\n for (const id of this.createdIds) {\n if (id === relativeId || id.startsWith(`${relativeId}.`)) {\n this.createdIds.delete(id);\n }\n }\n }\n }\n }\n\n /**\n * Calculate delivery time window \u2014 only from Unix timestamps.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private calculateDeliveryWindow(delivery: ParcelDelivery, statusCode: number): string {\n if (!TRACKABLE_STATUSES.has(statusCode)) {\n return \"\";\n }\n\n const formatTime = (timestamp: unknown): string | null => {\n const ts = coerceFiniteNumber(timestamp);\n if (ts === null || ts <= 0) {\n return null;\n }\n const d = new Date(ts * 1000);\n if (Number.isNaN(d.getTime())) {\n return null;\n }\n return `${d.getHours().toString().padStart(2, \"0\")}:${d.getMinutes().toString().padStart(2, \"0\")}`;\n };\n\n const start = formatTime(delivery.timestamp_expected);\n const end = formatTime(delivery.timestamp_expected_end);\n\n if (!start) {\n return \"\";\n }\n return end ? `${start} - ${end}` : start;\n }\n\n /**\n * Days from today to the expected delivery date. Returns null when the\n * delivery has no usable expected date or is in a non-trackable status.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private computeDiffDays(delivery: ParcelDelivery, statusCode: number): number | null {\n if (!TRACKABLE_STATUSES.has(statusCode)) {\n return null;\n }\n\n let expectedDate: Date | null = null;\n const ts = coerceFiniteNumber(delivery.timestamp_expected);\n if (ts !== null && ts > 0) {\n expectedDate = new Date(ts * 1000);\n } else if (typeof delivery.date_expected === \"string\" && delivery.date_expected.length > 0) {\n expectedDate = new Date(delivery.date_expected);\n }\n\n if (!expectedDate || isNaN(expectedDate.getTime())) {\n return null;\n }\n\n const now = new Date();\n const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n const expectedStart = new Date(expectedDate.getFullYear(), expectedDate.getMonth(), expectedDate.getDate());\n return Math.round((expectedStart.getTime() - todayStart.getTime()) / (1000 * 60 * 60 * 24));\n }\n\n /**\n * Calculate human-readable delivery estimate.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private calculateDeliveryEstimate(delivery: ParcelDelivery, statusCode: number): string {\n const diffDays = this.computeDiffDays(delivery, statusCode);\n if (diffDays === null) {\n return \"\";\n }\n const l = ESTIMATE_LABELS[this.language];\n if (diffDays < 0) {\n return l.overdue;\n }\n if (diffDays === 0) {\n return l.today;\n }\n if (diffDays === 1) {\n return l.tomorrow;\n }\n return l.days.replace(\"%d\", String(diffDays));\n }\n\n /**\n * Whether the delivery is expected today. Language-agnostic, used by the\n * summary filter so `todayCount` works across all languages.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private isToday(delivery: ParcelDelivery, statusCode: number): boolean {\n return this.computeDiffDays(delivery, statusCode) === 0;\n }\n\n /**\n * Format the latest tracking event.\n *\n * @param delivery The delivery data\n */\n private formatLastEvent(delivery: ParcelDelivery): string {\n if (!Array.isArray(delivery.events) || delivery.events.length === 0) {\n return \"\";\n }\n const latest = delivery.events[0];\n if (!latest || typeof latest !== \"object\") {\n return \"\";\n }\n const parts: string[] = [];\n if (typeof latest.event === \"string\" && latest.event.length > 0) {\n parts.push(latest.event);\n }\n if (typeof latest.date === \"string\" && latest.date.length > 0) {\n parts.push(latest.date);\n }\n return parts.join(\" - \");\n }\n\n /**\n * Extract location from latest event.\n *\n * @param delivery The delivery data\n */\n private extractLastLocation(delivery: ParcelDelivery): string {\n if (!Array.isArray(delivery.events) || delivery.events.length === 0) {\n return \"\";\n }\n const latest = delivery.events[0];\n if (!latest || typeof latest !== \"object\") {\n return \"\";\n }\n return typeof latest.location === \"string\" ? latest.location : \"\";\n }\n\n /**\n * Calculate combined delivery window for today's packages.\n *\n * @param todayDeliveries Deliveries expected today\n */\n private calculateCombinedWindow(todayDeliveries: ParcelDelivery[]): string {\n const windows = todayDeliveries\n .map(d => this.calculateDeliveryWindow(d, this.parseStatus(d)))\n .filter(w => w.length > 0);\n\n if (windows.length === 0) {\n return \"\";\n }\n if (windows.length === 1) {\n return windows[0];\n }\n\n const times: {\n /** Window start */ start: string;\n /** Window end */ end: string;\n }[] = [];\n for (const w of windows) {\n const match = w.match(/(\\d{2}:\\d{2})(?:\\s*-\\s*(\\d{2}:\\d{2}))?/);\n if (match) {\n times.push({ start: match[1], end: match[2] || match[1] });\n }\n }\n\n if (times.length === 0) {\n return \"\";\n }\n\n times.sort((a, b) => a.start.localeCompare(b.start));\n return `${times[0].start} - ${times[times.length - 1].end}`;\n }\n\n /**\n * Create/extend a read-only state and set its value. Skips the\n * `setObjectNotExistsAsync` round-trip once the ID is in the cache \u2014\n * states are static after first creation; only the value changes per poll.\n *\n * @param id State ID relative to adapter namespace\n * @param name Display name (translation object or plain string)\n * @param type Value type\n * @param role ioBroker role\n * @param val Value to set\n */\n private async createAndSet(\n id: string,\n name: ioBroker.StringOrTranslated,\n type: ioBroker.CommonType,\n role: string,\n val: ioBroker.StateValue,\n ): Promise<void> {\n if (!this.createdIds.has(id)) {\n await this.adapter.setObjectNotExistsAsync(id, {\n type: \"state\",\n common: { name, type, role, read: true, write: false },\n native: {},\n });\n this.createdIds.add(id);\n }\n await this.adapter.setStateAsync(id, { val, ack: true });\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/build/main.js
CHANGED
|
@@ -22,15 +22,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
22
|
mod
|
|
23
23
|
));
|
|
24
24
|
var utils = __toESM(require("@iobroker/adapter-core"));
|
|
25
|
+
var import_coerce = require("./lib/coerce");
|
|
25
26
|
var import_parcel_client = require("./lib/parcel-client");
|
|
26
27
|
var import_state_manager = require("./lib/state-manager");
|
|
27
28
|
const MIN_POLL_INTERVAL = 5;
|
|
28
29
|
const MAX_POLL_INTERVAL = 60;
|
|
29
30
|
const DEFAULT_POLL_INTERVAL = 10;
|
|
30
31
|
const MIN_POLL_GAP_MS = 6e4;
|
|
31
|
-
function errText(err) {
|
|
32
|
-
return err instanceof Error ? err.message : String(err);
|
|
33
|
-
}
|
|
34
32
|
class ParcelappAdapter extends utils.Adapter {
|
|
35
33
|
client = null;
|
|
36
34
|
stateManager = null;
|
|
@@ -42,6 +40,8 @@ class ParcelappAdapter extends utils.Adapter {
|
|
|
42
40
|
failedDeliveries = /* @__PURE__ */ new Set();
|
|
43
41
|
unhandledRejectionHandler = null;
|
|
44
42
|
uncaughtExceptionHandler = null;
|
|
43
|
+
/** ioBroker system language — read once in `onReady` from `system.config`. EN fallback. */
|
|
44
|
+
systemLang = "en";
|
|
45
45
|
/** @param options Adapter options */
|
|
46
46
|
constructor(options = {}) {
|
|
47
47
|
super({
|
|
@@ -49,31 +49,34 @@ class ParcelappAdapter extends utils.Adapter {
|
|
|
49
49
|
name: "parcelapp"
|
|
50
50
|
});
|
|
51
51
|
this.on("ready", () => {
|
|
52
|
-
this.onReady().catch((err) => this.log.error(`onReady failed: ${errText(err)}`));
|
|
52
|
+
this.onReady().catch((err) => this.log.error(`onReady failed: ${(0, import_coerce.errText)(err)}`));
|
|
53
53
|
});
|
|
54
54
|
this.on("unload", this.onUnload.bind(this));
|
|
55
55
|
this.on("message", (obj) => {
|
|
56
|
-
this.onMessage(obj).catch((err) => this.log.error(`onMessage failed: ${errText(err)}`));
|
|
56
|
+
this.onMessage(obj).catch((err) => this.log.error(`onMessage failed: ${(0, import_coerce.errText)(err)}`));
|
|
57
57
|
});
|
|
58
58
|
this.unhandledRejectionHandler = (reason) => {
|
|
59
|
-
this.log.error(`Unhandled rejection: ${errText(reason)}`);
|
|
59
|
+
this.log.error(`Unhandled rejection: ${(0, import_coerce.errText)(reason)}`);
|
|
60
60
|
};
|
|
61
61
|
this.uncaughtExceptionHandler = (err) => {
|
|
62
|
-
this.log.error(`Uncaught exception: ${errText(err)}`);
|
|
62
|
+
this.log.error(`Uncaught exception: ${(0, import_coerce.errText)(err)}`);
|
|
63
63
|
};
|
|
64
64
|
process.on("unhandledRejection", this.unhandledRejectionHandler);
|
|
65
65
|
process.on("uncaughtException", this.uncaughtExceptionHandler);
|
|
66
66
|
}
|
|
67
67
|
async onReady() {
|
|
68
68
|
var _a, _b, _c;
|
|
69
|
+
const sysConfig = await this.getForeignObjectAsync("system.config");
|
|
70
|
+
const language = (_b = (_a = sysConfig == null ? void 0 : sysConfig.common) == null ? void 0 : _a.language) != null ? _b : "";
|
|
71
|
+
if (typeof language === "string" && language.length > 0) {
|
|
72
|
+
this.systemLang = language;
|
|
73
|
+
}
|
|
69
74
|
await this.setStateAsync("info.connection", { val: false, ack: true });
|
|
70
75
|
const { apiKey } = this.config;
|
|
71
76
|
if (!apiKey || apiKey.trim().length < 10) {
|
|
72
77
|
this.log.error("No valid API key configured \u2014 please enter your parcel.app API key in the adapter settings");
|
|
73
78
|
return;
|
|
74
79
|
}
|
|
75
|
-
const sysConfig = await this.getForeignObjectAsync("system.config");
|
|
76
|
-
const language = (_b = (_a = sysConfig == null ? void 0 : sysConfig.common) == null ? void 0 : _a.language) != null ? _b : "";
|
|
77
80
|
this.client = new import_parcel_client.ParcelClient(apiKey.trim());
|
|
78
81
|
this.stateManager = new import_state_manager.StateManager(this, language);
|
|
79
82
|
await this.cleanupObsoleteStates();
|
|
@@ -146,8 +149,7 @@ class ParcelappAdapter extends utils.Adapter {
|
|
|
146
149
|
this.sendTo(obj.from, obj.command, { error: "Unknown command" }, obj.callback);
|
|
147
150
|
}
|
|
148
151
|
} catch (err) {
|
|
149
|
-
|
|
150
|
-
this.sendTo(obj.from, obj.command, { success: false, error_message: msg }, obj.callback);
|
|
152
|
+
this.sendTo(obj.from, obj.command, { success: false, error_message: (0, import_coerce.errText)(err) }, obj.callback);
|
|
151
153
|
}
|
|
152
154
|
}
|
|
153
155
|
async cleanupObsoleteStates() {
|
|
@@ -218,11 +220,11 @@ class ParcelappAdapter extends utils.Adapter {
|
|
|
218
220
|
activeIds.push(this.stateManager.packageId(delivery));
|
|
219
221
|
this.failedDeliveries.delete(delivery.tracking_number);
|
|
220
222
|
} catch (err) {
|
|
221
|
-
const msg =
|
|
223
|
+
const msg = (0, import_coerce.errText)(err);
|
|
222
224
|
if (this.failedDeliveries.has(delivery.tracking_number)) {
|
|
223
225
|
this.log.debug(`Failed to update "${delivery.tracking_number}": ${msg}`);
|
|
224
226
|
} else {
|
|
225
|
-
this.log.warn(`Failed to update
|
|
227
|
+
this.log.warn(`Failed to update '${delivery.tracking_number}': ${msg}`);
|
|
226
228
|
this.failedDeliveries.add(delivery.tracking_number);
|
|
227
229
|
}
|
|
228
230
|
}
|
|
@@ -244,9 +246,9 @@ class ParcelappAdapter extends utils.Adapter {
|
|
|
244
246
|
} else if (isRepeat) {
|
|
245
247
|
this.log.debug(`Poll failed (ongoing): ${error.message}`);
|
|
246
248
|
} else if (errorCode === "NETWORK") {
|
|
247
|
-
this.log.warn(
|
|
249
|
+
this.log.warn("Cannot reach parcel.app API \u2014 will keep retrying");
|
|
248
250
|
} else if (errorCode === "TIMEOUT") {
|
|
249
|
-
this.log.warn(
|
|
251
|
+
this.log.warn("API request timeout \u2014 will retry next cycle");
|
|
250
252
|
} else {
|
|
251
253
|
this.log.error(`Poll failed: ${error.message}`);
|
|
252
254
|
}
|
package/build/main.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/main.ts"],
|
|
4
|
-
"sourcesContent": ["import * as utils from \"@iobroker/adapter-core\";\nimport { ParcelClient } from \"./lib/parcel-client\";\nimport { StateManager } from \"./lib/state-manager\";\n\nconst MIN_POLL_INTERVAL = 5;\nconst MAX_POLL_INTERVAL = 60;\nconst DEFAULT_POLL_INTERVAL = 10;\nconst MIN_POLL_GAP_MS = 60_000; // Minimum 60s between polls\n\n/**\n * Extract a log-friendly message from an unknown error value.\n *\n * @param err Value caught in a promise rejection (may or may not be an Error)\n */\nfunction errText(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/** ioBroker adapter for parcel.app package tracking */\nclass ParcelappAdapter extends utils.Adapter {\n private client: ParcelClient | null = null;\n private stateManager: StateManager | null = null;\n private pollTimer: ioBroker.Interval | undefined = undefined;\n private isPolling = false;\n private lastPollTime = 0;\n private rateLimitedUntil = 0;\n private lastErrorCode = \"\";\n private failedDeliveries = new Set<string>();\n private unhandledRejectionHandler: ((reason: unknown) => void) | null = null;\n private uncaughtExceptionHandler: ((err: Error) => void) | null = null;\n\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({\n ...options,\n name: \"parcelapp\",\n });\n // Wrap async handlers with .catch() so a rejection can never become an\n // unhandled promise rejection (which would SIGKILL the adapter and trap\n // js-controller in a restart loop without any stack trace).\n this.on(\"ready\", () => {\n this.onReady().catch(err => this.log.error(`onReady failed: ${errText(err)}`));\n });\n this.on(\"unload\", this.onUnload.bind(this));\n this.on(\"message\", obj => {\n this.onMessage(obj).catch(err => this.log.error(`onMessage failed: ${errText(err)}`));\n });\n // Last-line-of-defence against unhandled rejections / sync throws from\n // fire-and-forget paths (e.g. `void this.poll()`). The per-handler\n // .catch() wrappers cover the documented async paths; this catches\n // anything that slips past during refactors.\n this.unhandledRejectionHandler = (reason: unknown) => {\n this.log.error(`Unhandled rejection: ${errText(reason)}`);\n };\n this.uncaughtExceptionHandler = (err: Error) => {\n this.log.error(`Uncaught exception: ${errText(err)}`);\n };\n process.on(\"unhandledRejection\", this.unhandledRejectionHandler);\n process.on(\"uncaughtException\", this.uncaughtExceptionHandler);\n }\n\n private async onReady(): Promise<void> {\n await this.setStateAsync(\"info.connection\", { val: false, ack: true });\n\n // Validate config\n const { apiKey } = this.config;\n if (!apiKey || apiKey.trim().length < 10) {\n this.log.error(\"No valid API key configured \u2014 please enter your parcel.app API key in the adapter settings\");\n return;\n }\n\n // Pick the label language from the ioBroker system configuration.\n // StateManager falls back to English if the language is unsupported.\n const sysConfig = await this.getForeignObjectAsync(\"system.config\");\n const language = (sysConfig?.common as { language?: string } | undefined)?.language ?? \"\";\n\n // Initialize\n this.client = new ParcelClient(apiKey.trim());\n this.stateManager = new StateManager(this, language);\n\n // Cleanup obsolete states\n await this.cleanupObsoleteStates();\n\n // Initial poll\n await this.poll();\n\n // Set up recurring poll\n const interval = Math.max(\n MIN_POLL_INTERVAL,\n Math.min(MAX_POLL_INTERVAL, this.config.pollInterval ?? DEFAULT_POLL_INTERVAL),\n );\n const intervalMs = interval * 60 * 1000;\n this.pollTimer = this.setInterval(() => void this.poll(), intervalMs);\n\n this.log.info(`Parcel tracking started \u2014 polling every ${interval} minutes`);\n }\n\n private onUnload(callback: () => void): void {\n try {\n if (this.pollTimer) {\n this.clearInterval(this.pollTimer);\n this.pollTimer = undefined;\n }\n if (this.unhandledRejectionHandler) {\n process.off(\"unhandledRejection\", this.unhandledRejectionHandler);\n this.unhandledRejectionHandler = null;\n }\n if (this.uncaughtExceptionHandler) {\n process.off(\"uncaughtException\", this.uncaughtExceptionHandler);\n this.uncaughtExceptionHandler = null;\n }\n void this.setState(\"info.connection\", { val: false, ack: true });\n } catch {\n // ignore\n }\n callback();\n }\n\n private async onMessage(obj: ioBroker.Message): Promise<void> {\n if (!obj?.command || !obj.callback) {\n return;\n }\n\n try {\n switch (obj.command) {\n case \"checkConnection\": {\n const msg = obj.message as { apiKey?: string };\n const key = msg?.apiKey?.trim() || \"\";\n if (!key || key.length < 10) {\n this.sendTo(obj.from, obj.command, { success: false, message: \"API key is too short\" }, obj.callback);\n return;\n }\n const testClient = new ParcelClient(key);\n const result = await testClient.testConnection();\n this.sendTo(obj.from, obj.command, result, obj.callback);\n break;\n }\n case \"addDelivery\": {\n if (!this.client) {\n this.sendTo(\n obj.from,\n obj.command,\n { success: false, error_message: \"Adapter not initialized\" },\n obj.callback,\n );\n return;\n }\n const request = obj.message as {\n tracking_number: string;\n carrier_code: string;\n description: string;\n };\n const addResult = await this.client.addDelivery(request);\n this.sendTo(obj.from, obj.command, addResult, obj.callback);\n if (addResult.success) {\n void this.poll();\n }\n break;\n }\n default:\n this.sendTo(obj.from, obj.command, { error: \"Unknown command\" }, obj.callback);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n this.sendTo(obj.from, obj.command, { success: false, error_message: msg }, obj.callback);\n }\n }\n\n private async cleanupObsoleteStates(): Promise<void> {\n const obsoleteStates = [\n \"summary.json\", // removed in 0.2.0\n ];\n for (const stateId of obsoleteStates) {\n const obj = await this.getObjectAsync(stateId);\n if (obj) {\n await this.delObjectAsync(stateId);\n this.log.debug(`Removed obsolete state: ${stateId}`);\n }\n }\n }\n\n /**\n * Classify an error for deduplication and log-level decisions.\n *\n * @param error The error to classify\n */\n private classifyError(error: Error & { code?: string }): string {\n if (error.code === \"RATE_LIMITED\") {\n return \"RATE_LIMITED\";\n }\n if (error.code === \"INVALID_API_KEY\") {\n return \"INVALID_API_KEY\";\n }\n // Network errors: DNS, connection refused, no internet\n if (\n error.code === \"ENOTFOUND\" ||\n error.code === \"ECONNREFUSED\" ||\n error.code === \"ECONNRESET\" ||\n error.code === \"ENETUNREACH\" ||\n error.code === \"EHOSTUNREACH\" ||\n error.code === \"EAI_AGAIN\"\n ) {\n return \"NETWORK\";\n }\n if (error.message.includes(\"timeout\") || error.code === \"ETIMEDOUT\") {\n return \"TIMEOUT\";\n }\n return error.code || \"UNKNOWN\";\n }\n\n private async poll(): Promise<void> {\n if (this.isPolling || !this.client || !this.stateManager) {\n return;\n }\n\n const now = Date.now();\n\n // Skip if rate limited\n if (now < this.rateLimitedUntil) {\n const waitMin = Math.ceil((this.rateLimitedUntil - now) / 60_000);\n this.log.debug(`Skipping poll \u2014 rate limited for ${waitMin} more minute(s)`);\n return;\n }\n\n // Throttle: minimum gap between polls\n if (now - this.lastPollTime < MIN_POLL_GAP_MS) {\n this.log.debug(\"Skipping poll \u2014 too soon after last poll\");\n return;\n }\n\n this.isPolling = true;\n this.lastPollTime = now;\n try {\n // When keeping delivered packages, use \"recent\" to get them from API\n const autoRemove = this.config.autoRemoveDelivered !== false;\n const deliveries = await this.client.getDeliveries(autoRemove ? \"active\" : \"recent\");\n\n // Reset error state on success\n this.rateLimitedUntil = 0;\n if (this.lastErrorCode) {\n this.log.info(\"Connection restored\");\n this.lastErrorCode = \"\";\n }\n await this.setStateAsync(\"info.connection\", { val: true, ack: true });\n\n // Split into active (non-delivered) and visible (what gets states)\n const activeDeliveries = deliveries.filter(d => this.stateManager!.parseStatus(d) !== 0);\n const visibleDeliveries = autoRemove ? activeDeliveries : deliveries;\n\n // Update each delivery (isolated: one failure must not block others)\n const activeIds: string[] = [];\n for (const delivery of visibleDeliveries) {\n try {\n const carrierName = await this.client.getCarrierName(delivery.carrier_code);\n await this.stateManager.updateDelivery(delivery, carrierName);\n activeIds.push(this.stateManager.packageId(delivery));\n this.failedDeliveries.delete(delivery.tracking_number);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (this.failedDeliveries.has(delivery.tracking_number)) {\n this.log.debug(`Failed to update \"${delivery.tracking_number}\": ${msg}`);\n } else {\n this.log.warn(`Failed to update \"${delivery.tracking_number}\": ${msg}`);\n this.failedDeliveries.add(delivery.tracking_number);\n }\n }\n }\n\n // Cleanup stale deliveries\n await this.stateManager.cleanupDeliveries(activeIds);\n\n // Update summary (always uses active/non-delivered)\n await this.stateManager.updateSummary(activeDeliveries);\n\n this.log.debug(`Polled ${visibleDeliveries.length} deliveries (${activeDeliveries.length} active)`);\n } catch (err) {\n const error = err as Error & {\n code?: string;\n retryAfterSeconds?: number;\n };\n\n // Classify the error\n const errorCode = this.classifyError(error);\n const isRepeat = errorCode === this.lastErrorCode;\n this.lastErrorCode = errorCode;\n\n if (error.code === \"RATE_LIMITED\") {\n const cooldownSec = error.retryAfterSeconds || 5 * 60;\n this.rateLimitedUntil = Date.now() + cooldownSec * 1000;\n this.log.warn(`Rate limit hit \u2014 pausing API requests for ${Math.ceil(cooldownSec / 60)} minute(s)`);\n } else if (error.code === \"INVALID_API_KEY\") {\n // Always log \u2014 user must fix config\n this.log.error(\"Invalid API key \u2014 please check your parcel.app API key\");\n } else if (isRepeat) {\n // Same error as last time \u2014 don't spam the log\n this.log.debug(`Poll failed (ongoing): ${error.message}`);\n } else if (errorCode === \"NETWORK\") {\n this.log.warn(`Cannot reach parcel.app API \u2014 will keep retrying`);\n } else if (errorCode === \"TIMEOUT\") {\n this.log.warn(`API request timeout \u2014 will retry next cycle`);\n } else {\n this.log.error(`Poll failed: ${error.message}`);\n }\n\n await this.setStateAsync(\"info.connection\", { val: false, ack: true });\n } finally {\n this.isPolling = false;\n }\n }\n}\n\nif (require.main !== module) {\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new ParcelappAdapter(options);\n} else {\n (() => new ParcelappAdapter())();\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;AAAA,YAAuB;AACvB,2BAA6B;AAC7B,2BAA6B;AAE7B,MAAM,oBAAoB;AAC1B,MAAM,oBAAoB;AAC1B,MAAM,wBAAwB;AAC9B,MAAM,kBAAkB;
|
|
4
|
+
"sourcesContent": ["import * as utils from \"@iobroker/adapter-core\";\nimport { errText } from \"./lib/coerce\";\nimport { ParcelClient } from \"./lib/parcel-client\";\nimport { StateManager } from \"./lib/state-manager\";\n\nconst MIN_POLL_INTERVAL = 5;\nconst MAX_POLL_INTERVAL = 60;\nconst DEFAULT_POLL_INTERVAL = 10;\nconst MIN_POLL_GAP_MS = 60_000; // Minimum 60s between polls\n\n/** ioBroker adapter for parcel.app package tracking */\nclass ParcelappAdapter extends utils.Adapter {\n private client: ParcelClient | null = null;\n private stateManager: StateManager | null = null;\n private pollTimer: ioBroker.Interval | undefined = undefined;\n private isPolling = false;\n private lastPollTime = 0;\n private rateLimitedUntil = 0;\n private lastErrorCode = \"\";\n private failedDeliveries = new Set<string>();\n private unhandledRejectionHandler: ((reason: unknown) => void) | null = null;\n private uncaughtExceptionHandler: ((err: Error) => void) | null = null;\n /** ioBroker system language \u2014 read once in `onReady` from `system.config`. EN fallback. */\n private systemLang: string = \"en\";\n\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({\n ...options,\n name: \"parcelapp\",\n });\n // Wrap async handlers with .catch() so a rejection can never become an\n // unhandled promise rejection (which would SIGKILL the adapter and trap\n // js-controller in a restart loop without any stack trace).\n this.on(\"ready\", () => {\n this.onReady().catch(err => this.log.error(`onReady failed: ${errText(err)}`));\n });\n this.on(\"unload\", this.onUnload.bind(this));\n this.on(\"message\", obj => {\n this.onMessage(obj).catch(err => this.log.error(`onMessage failed: ${errText(err)}`));\n });\n // Last-line-of-defence against unhandled rejections / sync throws from\n // fire-and-forget paths (e.g. `void this.poll()`). The per-handler\n // .catch() wrappers cover the documented async paths; this catches\n // anything that slips past during refactors.\n this.unhandledRejectionHandler = (reason: unknown) => {\n this.log.error(`Unhandled rejection: ${errText(reason)}`);\n };\n this.uncaughtExceptionHandler = (err: Error) => {\n this.log.error(`Uncaught exception: ${errText(err)}`);\n };\n process.on(\"unhandledRejection\", this.unhandledRejectionHandler);\n process.on(\"uncaughtException\", this.uncaughtExceptionHandler);\n }\n\n private async onReady(): Promise<void> {\n // Pick the system language up-front so all user-facing logs go out in the\n // user's language. StateManager also gets it for state-name localization.\n const sysConfig = await this.getForeignObjectAsync(\"system.config\");\n const language = (sysConfig?.common as { language?: string } | undefined)?.language ?? \"\";\n if (typeof language === \"string\" && language.length > 0) {\n this.systemLang = language;\n }\n\n await this.setStateAsync(\"info.connection\", { val: false, ack: true });\n\n // Validate config\n const { apiKey } = this.config;\n if (!apiKey || apiKey.trim().length < 10) {\n this.log.error(\"No valid API key configured \u2014 please enter your parcel.app API key in the adapter settings\");\n return;\n }\n\n // Initialize\n this.client = new ParcelClient(apiKey.trim());\n this.stateManager = new StateManager(this, language);\n\n // Cleanup obsolete states\n await this.cleanupObsoleteStates();\n\n // Initial poll\n await this.poll();\n\n // Set up recurring poll\n const interval = Math.max(\n MIN_POLL_INTERVAL,\n Math.min(MAX_POLL_INTERVAL, this.config.pollInterval ?? DEFAULT_POLL_INTERVAL),\n );\n const intervalMs = interval * 60 * 1000;\n this.pollTimer = this.setInterval(() => void this.poll(), intervalMs);\n\n this.log.info(`Parcel tracking started \u2014 polling every ${interval} minutes`);\n }\n\n private onUnload(callback: () => void): void {\n try {\n if (this.pollTimer) {\n this.clearInterval(this.pollTimer);\n this.pollTimer = undefined;\n }\n if (this.unhandledRejectionHandler) {\n process.off(\"unhandledRejection\", this.unhandledRejectionHandler);\n this.unhandledRejectionHandler = null;\n }\n if (this.uncaughtExceptionHandler) {\n process.off(\"uncaughtException\", this.uncaughtExceptionHandler);\n this.uncaughtExceptionHandler = null;\n }\n void this.setState(\"info.connection\", { val: false, ack: true });\n } catch {\n // ignore\n }\n callback();\n }\n\n private async onMessage(obj: ioBroker.Message): Promise<void> {\n if (!obj?.command || !obj.callback) {\n return;\n }\n\n try {\n switch (obj.command) {\n case \"checkConnection\": {\n const msg = obj.message as { apiKey?: string };\n const key = msg?.apiKey?.trim() || \"\";\n if (!key || key.length < 10) {\n this.sendTo(obj.from, obj.command, { success: false, message: \"API key is too short\" }, obj.callback);\n return;\n }\n const testClient = new ParcelClient(key);\n const result = await testClient.testConnection();\n this.sendTo(obj.from, obj.command, result, obj.callback);\n break;\n }\n case \"addDelivery\": {\n if (!this.client) {\n this.sendTo(\n obj.from,\n obj.command,\n { success: false, error_message: \"Adapter not initialized\" },\n obj.callback,\n );\n return;\n }\n const request = obj.message as {\n tracking_number: string;\n carrier_code: string;\n description: string;\n };\n const addResult = await this.client.addDelivery(request);\n this.sendTo(obj.from, obj.command, addResult, obj.callback);\n if (addResult.success) {\n void this.poll();\n }\n break;\n }\n default:\n this.sendTo(obj.from, obj.command, { error: \"Unknown command\" }, obj.callback);\n }\n } catch (err) {\n this.sendTo(obj.from, obj.command, { success: false, error_message: errText(err) }, obj.callback);\n }\n }\n\n private async cleanupObsoleteStates(): Promise<void> {\n const obsoleteStates = [\n \"summary.json\", // removed in 0.2.0\n ];\n for (const stateId of obsoleteStates) {\n const obj = await this.getObjectAsync(stateId);\n if (obj) {\n await this.delObjectAsync(stateId);\n this.log.debug(`Removed obsolete state: ${stateId}`);\n }\n }\n }\n\n /**\n * Classify an error for deduplication and log-level decisions.\n *\n * @param error The error to classify\n */\n private classifyError(error: Error & { code?: string }): string {\n if (error.code === \"RATE_LIMITED\") {\n return \"RATE_LIMITED\";\n }\n if (error.code === \"INVALID_API_KEY\") {\n return \"INVALID_API_KEY\";\n }\n // Network errors: DNS, connection refused, no internet\n if (\n error.code === \"ENOTFOUND\" ||\n error.code === \"ECONNREFUSED\" ||\n error.code === \"ECONNRESET\" ||\n error.code === \"ENETUNREACH\" ||\n error.code === \"EHOSTUNREACH\" ||\n error.code === \"EAI_AGAIN\"\n ) {\n return \"NETWORK\";\n }\n if (error.message.includes(\"timeout\") || error.code === \"ETIMEDOUT\") {\n return \"TIMEOUT\";\n }\n return error.code || \"UNKNOWN\";\n }\n\n private async poll(): Promise<void> {\n if (this.isPolling || !this.client || !this.stateManager) {\n return;\n }\n\n const now = Date.now();\n\n // Skip if rate limited\n if (now < this.rateLimitedUntil) {\n const waitMin = Math.ceil((this.rateLimitedUntil - now) / 60_000);\n this.log.debug(`Skipping poll \u2014 rate limited for ${waitMin} more minute(s)`);\n return;\n }\n\n // Throttle: minimum gap between polls\n if (now - this.lastPollTime < MIN_POLL_GAP_MS) {\n this.log.debug(\"Skipping poll \u2014 too soon after last poll\");\n return;\n }\n\n this.isPolling = true;\n this.lastPollTime = now;\n try {\n // When keeping delivered packages, use \"recent\" to get them from API\n const autoRemove = this.config.autoRemoveDelivered !== false;\n const deliveries = await this.client.getDeliveries(autoRemove ? \"active\" : \"recent\");\n\n // Reset error state on success\n this.rateLimitedUntil = 0;\n if (this.lastErrorCode) {\n this.log.info(\"Connection restored\");\n this.lastErrorCode = \"\";\n }\n await this.setStateAsync(\"info.connection\", { val: true, ack: true });\n\n // Split into active (non-delivered) and visible (what gets states)\n const activeDeliveries = deliveries.filter(d => this.stateManager!.parseStatus(d) !== 0);\n const visibleDeliveries = autoRemove ? activeDeliveries : deliveries;\n\n // Update each delivery (isolated: one failure must not block others)\n const activeIds: string[] = [];\n for (const delivery of visibleDeliveries) {\n try {\n const carrierName = await this.client.getCarrierName(delivery.carrier_code);\n await this.stateManager.updateDelivery(delivery, carrierName);\n activeIds.push(this.stateManager.packageId(delivery));\n this.failedDeliveries.delete(delivery.tracking_number);\n } catch (err) {\n const msg = errText(err);\n if (this.failedDeliveries.has(delivery.tracking_number)) {\n this.log.debug(`Failed to update \"${delivery.tracking_number}\": ${msg}`);\n } else {\n this.log.warn(`Failed to update '${delivery.tracking_number}': ${msg}`);\n this.failedDeliveries.add(delivery.tracking_number);\n }\n }\n }\n\n // Cleanup stale deliveries\n await this.stateManager.cleanupDeliveries(activeIds);\n\n // Update summary (always uses active/non-delivered)\n await this.stateManager.updateSummary(activeDeliveries);\n\n this.log.debug(`Polled ${visibleDeliveries.length} deliveries (${activeDeliveries.length} active)`);\n } catch (err) {\n const error = err as Error & {\n code?: string;\n retryAfterSeconds?: number;\n };\n\n // Classify the error\n const errorCode = this.classifyError(error);\n const isRepeat = errorCode === this.lastErrorCode;\n this.lastErrorCode = errorCode;\n\n if (error.code === \"RATE_LIMITED\") {\n const cooldownSec = error.retryAfterSeconds || 5 * 60;\n this.rateLimitedUntil = Date.now() + cooldownSec * 1000;\n this.log.warn(`Rate limit hit \u2014 pausing API requests for ${Math.ceil(cooldownSec / 60)} minute(s)`);\n } else if (error.code === \"INVALID_API_KEY\") {\n // Always log \u2014 user must fix config\n this.log.error(\"Invalid API key \u2014 please check your parcel.app API key\");\n } else if (isRepeat) {\n // Same error as last time \u2014 don't spam the log\n this.log.debug(`Poll failed (ongoing): ${error.message}`);\n } else if (errorCode === \"NETWORK\") {\n this.log.warn(\"Cannot reach parcel.app API \u2014 will keep retrying\");\n } else if (errorCode === \"TIMEOUT\") {\n this.log.warn(\"API request timeout \u2014 will retry next cycle\");\n } else {\n this.log.error(`Poll failed: ${error.message}`);\n }\n\n await this.setStateAsync(\"info.connection\", { val: false, ack: true });\n } finally {\n this.isPolling = false;\n }\n }\n}\n\nif (require.main !== module) {\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new ParcelappAdapter(options);\n} else {\n (() => new ParcelappAdapter())();\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "parcelapp",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.1",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.4.1": {
|
|
7
|
+
"en": "Adapter log messages are now English only, in line with the ioBroker community standard. Localized state names (11 languages) remain unchanged.",
|
|
8
|
+
"de": "Adapter-Logs sind jetzt nur noch auf Englisch, gemäß ioBroker-Community-Standard. Lokalisierte Datenpunkt-Namen (11 Sprachen) bleiben erhalten.",
|
|
9
|
+
"ru": "Сообщения журнала теперь только на английском, согласно стандарту сообщества ioBroker. Локализованные имена состояний (11 языков) сохраняются.",
|
|
10
|
+
"pt": "As mensagens de log do adaptador agora são apenas em inglês, conforme o padrão da comunidade ioBroker. Os nomes de estados localizados (11 idiomas) permanecem inalterados.",
|
|
11
|
+
"nl": "Adapter-logberichten zijn nu alleen Engels, conform de ioBroker-communitystandaard. Gelokaliseerde statusnamen (11 talen) blijven ongewijzigd.",
|
|
12
|
+
"fr": "Les messages de log de l'adaptateur sont désormais uniquement en anglais, conformément au standard de la communauté ioBroker. Les noms d'états localisés (11 langues) restent inchangés.",
|
|
13
|
+
"it": "I messaggi di log dell'adattatore sono ora solo in inglese, secondo lo standard della community ioBroker. I nomi degli stati localizzati (11 lingue) rimangono invariati.",
|
|
14
|
+
"es": "Los mensajes de registro del adaptador ahora son solo en inglés, conforme al estándar de la comunidad ioBroker. Los nombres de estados localizados (11 idiomas) permanecen sin cambios.",
|
|
15
|
+
"pl": "Komunikaty dziennika adaptera są teraz wyłącznie po angielsku, zgodnie ze standardem społeczności ioBroker. Zlokalizowane nazwy stanów (11 języków) pozostają bez zmian.",
|
|
16
|
+
"uk": "Повідомлення журналу адаптера тепер лише англійською, відповідно до стандарту спільноти ioBroker. Локалізовані назви станів (11 мов) залишаються без змін.",
|
|
17
|
+
"zh-cn": "适配器日志消息现在仅为英文,符合 ioBroker 社区标准。本地化的数据点名称(11 种语言)保持不变。"
|
|
18
|
+
},
|
|
19
|
+
"0.4.0": {
|
|
20
|
+
"en": "State names now follow your ioBroker system language across 11 languages. User-visible info/warn/error logs are localized too. Min Node 22, Admin 7.8.23.",
|
|
21
|
+
"de": "Datenpunkt-Namen folgen jetzt der ioBroker-Systemsprache in 11 Sprachen. User-sichtbare info/warn/error-Logs ebenfalls lokalisiert. Min Node 22, Admin 7.8.23.",
|
|
22
|
+
"ru": "Имена датчиков теперь следуют системному языку ioBroker на 11 языках. Пользовательские info/warn/error логи также локализованы. Мин. Node 22, Admin 7.8.23.",
|
|
23
|
+
"pt": "Nomes dos datapoints seguem agora o idioma do sistema ioBroker em 11 idiomas. Logs info/warn/error visíveis ao utilizador também localizados. Mín. Node 22, Admin 7.8.23.",
|
|
24
|
+
"nl": "Datapointnamen volgen nu je ioBroker-systeemtaal in 11 talen. Voor de gebruiker zichtbare info/warn/error-logs ook gelokaliseerd. Min. Node 22, Admin 7.8.23.",
|
|
25
|
+
"fr": "Les noms des datapoints suivent désormais la langue système ioBroker en 11 langues. Logs info/warn/error visibles localisés. Min Node 22, Admin 7.8.23.",
|
|
26
|
+
"it": "I nomi dei datapoint seguono ora la lingua di sistema ioBroker in 11 lingue. Log info/warn/error visibili agli utenti localizzati. Min Node 22, Admin 7.8.23.",
|
|
27
|
+
"es": "Los nombres de datapoints siguen ahora el idioma del sistema ioBroker en 11 idiomas. Logs info/warn/error visibles al usuario localizados. Mín. Node 22, Admin 7.8.23.",
|
|
28
|
+
"pl": "Nazwy datapointów podążają teraz za językiem systemu ioBroker w 11 językach. Widoczne dla użytkownika logi info/warn/error również zlokalizowane. Min Node 22, Admin 7.8.23.",
|
|
29
|
+
"uk": "Імена датапоінтів тепер слідують системній мові ioBroker 11 мовами. Користувачу видимі info/warn/error логи також локалізовані. Мін. Node 22, Admin 7.8.23.",
|
|
30
|
+
"zh-cn": "数据点名称现在遵循 ioBroker 系统语言(11 种语言)。用户可见的 info/warn/error 日志也已本地化。最低 Node 22、Admin 7.8.23。"
|
|
31
|
+
},
|
|
6
32
|
"0.3.2": {
|
|
7
33
|
"en": "Documentation: rewrote release notes in user-friendly style across all languages.",
|
|
8
34
|
"de": "Dokumentation: Release-Notes in alle Sprachen User-orientiert neu geschrieben.",
|
|
@@ -67,32 +93,6 @@
|
|
|
67
93
|
"pl": "Wewnętrzne porządki. Brak zmian widocznych dla użytkownika.",
|
|
68
94
|
"uk": "Внутрішнє прибирання. Без помітних для користувача змін.",
|
|
69
95
|
"zh-cn": "内部清理。对用户无可见变化。"
|
|
70
|
-
},
|
|
71
|
-
"0.2.16": {
|
|
72
|
-
"en": "Min js-controller restored to >=6.0.11 (was incorrectly bumped to >=7.0.23 in 0.2.15).",
|
|
73
|
-
"de": "Min js-controller zurück auf >=6.0.11 (war in 0.2.15 fälschlich auf >=7.0.23 gesetzt).",
|
|
74
|
-
"ru": "Мин. js-controller восстановлен на >=6.0.11 (в 0.2.15 ошибочно был >=7.0.23).",
|
|
75
|
-
"pt": "Mín. js-controller restaurado para >=6.0.11 (em 0.2.15 estava erroneamente em >=7.0.23).",
|
|
76
|
-
"nl": "Min. js-controller hersteld op >=6.0.11 (was in 0.2.15 ten onrechte >=7.0.23).",
|
|
77
|
-
"fr": "Min js-controller rétabli à >=6.0.11 (était par erreur à >=7.0.23 en 0.2.15).",
|
|
78
|
-
"it": "Min js-controller ripristinato a >=6.0.11 (in 0.2.15 era erroneamente >=7.0.23).",
|
|
79
|
-
"es": "Mín. js-controller restaurado a >=6.0.11 (en 0.2.15 estaba incorrectamente en >=7.0.23).",
|
|
80
|
-
"pl": "Min. js-controller przywrócony do >=6.0.11 (w 0.2.15 błędnie ustawiony na >=7.0.23).",
|
|
81
|
-
"uk": "Мін. js-controller повернено на >=6.0.11 (у 0.2.15 помилково було >=7.0.23).",
|
|
82
|
-
"zh-cn": "最低 js-controller 恢复为 >=6.0.11(0.2.15 中错误地设置为 >=7.0.23)。"
|
|
83
|
-
},
|
|
84
|
-
"0.2.15": {
|
|
85
|
-
"en": "Crash defense: process-level error handlers.",
|
|
86
|
-
"de": "Crash-Schutz: process-level Error-Handler.",
|
|
87
|
-
"ru": "Защита от сбоев: process-level обработчики ошибок.",
|
|
88
|
-
"pt": "Proteção contra falhas: handlers de erro process-level.",
|
|
89
|
-
"nl": "Crashbescherming: process-level error-handlers.",
|
|
90
|
-
"fr": "Défense contre les crashs : handlers d'erreur process-level.",
|
|
91
|
-
"it": "Difesa anti-crash: handler di errore process-level.",
|
|
92
|
-
"es": "Defensa contra fallos: handlers de error process-level.",
|
|
93
|
-
"pl": "Ochrona przed crashem: handlery błędów process-level.",
|
|
94
|
-
"uk": "Захист від збоїв: process-level обробники помилок.",
|
|
95
|
-
"zh-cn": "崩溃防护:process-level 错误处理器。"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|
|
@@ -109,17 +109,17 @@
|
|
|
109
109
|
"zh-cn": "包裹追踪"
|
|
110
110
|
},
|
|
111
111
|
"desc": {
|
|
112
|
-
"en": "ioBroker adapter for parcel.app
|
|
113
|
-
"de": "ioBroker-Adapter für parcel.app
|
|
114
|
-
"ru": "
|
|
115
|
-
"pt": "Adaptador ioBroker para parcel.app
|
|
116
|
-
"nl": "ioBroker-adapter voor parcel.app
|
|
117
|
-
"fr": "Adaptateur ioBroker pour parcel.app
|
|
118
|
-
"it": "Adattatore ioBroker per parcel.app
|
|
119
|
-
"es": "Adaptador ioBroker para parcel.app
|
|
120
|
-
"pl": "Adapter ioBroker dla parcel.app
|
|
121
|
-
"uk": "Адаптер ioBroker для parcel.app
|
|
122
|
-
"zh-cn": "parcel.app 的 ioBroker
|
|
112
|
+
"en": "ioBroker adapter for the parcel.app API. Supports all carriers that parcel.app tracks.",
|
|
113
|
+
"de": "ioBroker-Adapter für die parcel.app-API. Unterstützt alle Carrier, die parcel.app verfolgt.",
|
|
114
|
+
"ru": "ioBroker-адаптер для API parcel.app. Поддерживает всех перевозчиков, которых отслеживает parcel.app.",
|
|
115
|
+
"pt": "Adaptador ioBroker para a API parcel.app. Suporta todas as transportadoras que parcel.app rastreia.",
|
|
116
|
+
"nl": "ioBroker-adapter voor de parcel.app-API. Ondersteunt alle vervoerders die parcel.app volgt.",
|
|
117
|
+
"fr": "Adaptateur ioBroker pour l'API parcel.app. Prend en charge tous les transporteurs suivis par parcel.app.",
|
|
118
|
+
"it": "Adattatore ioBroker per l'API parcel.app. Supporta tutti i corrieri tracciati da parcel.app.",
|
|
119
|
+
"es": "Adaptador ioBroker para la API de parcel.app. Compatible con todos los transportistas que parcel.app rastrea.",
|
|
120
|
+
"pl": "Adapter ioBroker dla API parcel.app. Obsługuje wszystkich przewoźników śledzonych przez parcel.app.",
|
|
121
|
+
"uk": "Адаптер ioBroker для API parcel.app. Підтримує всіх перевізників, яких відстежує parcel.app.",
|
|
122
|
+
"zh-cn": "用于 parcel.app API 的 ioBroker 适配器。支持 parcel.app 跟踪的所有承运商。"
|
|
123
123
|
},
|
|
124
124
|
"authors": [
|
|
125
125
|
"krobi <krobi@power-dreams.com>"
|
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
],
|
|
163
163
|
"globalDependencies": [
|
|
164
164
|
{
|
|
165
|
-
"admin": ">=7.
|
|
165
|
+
"admin": ">=7.8.23"
|
|
166
166
|
}
|
|
167
167
|
]
|
|
168
168
|
},
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.parcelapp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "ioBroker adapter for parcel.app
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "ioBroker adapter for the parcel.app API",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "krobi",
|
|
7
7
|
"email": "krobi@power-dreams.com"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"url": "https://github.com/krobipd/ioBroker.parcelapp"
|
|
28
28
|
},
|
|
29
29
|
"engines": {
|
|
30
|
-
"node": ">=
|
|
30
|
+
"node": ">=22",
|
|
31
31
|
"npm": ">=10"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
"@iobroker/adapter-dev": "^1.5.0",
|
|
41
41
|
"@iobroker/eslint-config": "^2.2.0",
|
|
42
42
|
"@iobroker/testing": "^5.2.2",
|
|
43
|
-
"@tsconfig/
|
|
43
|
+
"@tsconfig/node22": "^22.0.5",
|
|
44
44
|
"@types/iobroker": "npm:@iobroker/types@^7.1.1",
|
|
45
|
-
"@types/node": "^
|
|
46
|
-
"nyc": "^
|
|
45
|
+
"@types/node": "^22.19.17",
|
|
46
|
+
"nyc": "^18.0.0",
|
|
47
47
|
"rimraf": "^6.1.3",
|
|
48
48
|
"source-map-support": "^0.5.21",
|
|
49
49
|
"ts-node": "^10.9.2",
|