iobroker.parcelapp 0.2.0 → 0.2.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 +5 -5
- package/build/lib/parcel-client.d.ts.map +1 -1
- package/build/lib/parcel-client.js +9 -0
- package/build/main.js +68 -3
- package/io-package.json +14 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ ioBroker adapter that connects to the [parcel.app](https://parcelapp.net) API an
|
|
|
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
|
- **Automatic polling** with configurable interval (5–60 minutes)
|
|
25
|
-
- **Configurable cleanup**
|
|
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, polling settings, and status language selection
|
|
28
28
|
- **Multilingual status labels** (German/English)
|
|
@@ -44,7 +44,7 @@ ioBroker adapter that connects to the [parcel.app](https://parcelapp.net) API an
|
|
|
44
44
|
|--------|-------------|---------|
|
|
45
45
|
| **API Key** | Your parcel.app API key (get it at [web.parcelapp.net](https://web.parcelapp.net)) | — |
|
|
46
46
|
| **Poll Interval** | How often to fetch updates (minutes) | 10 |
|
|
47
|
-
| **Auto-remove delivered** | Remove delivered packages from states automatically | Yes |
|
|
47
|
+
| **Auto-remove delivered** | Remove delivered packages from states automatically. When disabled, they stay until deleted in parcel.app. | Yes |
|
|
48
48
|
| **Status Language** | Language for status labels (German/English) | German |
|
|
49
49
|
|
|
50
50
|
---
|
|
@@ -94,6 +94,9 @@ parcelapp.0.
|
|
|
94
94
|
|
|
95
95
|
## Changelog
|
|
96
96
|
|
|
97
|
+
### 0.2.1 (2026-03-25)
|
|
98
|
+
- Robust error handling: rate limit detection, connection error deduplication, poll throttling
|
|
99
|
+
|
|
97
100
|
### 0.2.0 (2026-03-25)
|
|
98
101
|
- Added option to keep delivered packages in states
|
|
99
102
|
- Simplified admin UI to single page
|
|
@@ -114,9 +117,6 @@ parcelapp.0.
|
|
|
114
117
|
### 0.1.1 (2026-03-23)
|
|
115
118
|
- Redesigned adapter logo
|
|
116
119
|
|
|
117
|
-
### 0.1.0 (2026-03-23)
|
|
118
|
-
- Initial release
|
|
119
|
-
|
|
120
120
|
Older changelog: [CHANGELOG.md](CHANGELOG.md)
|
|
121
121
|
|
|
122
122
|
---
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parcel-client.d.ts","sourceRoot":"","sources":["../../src/lib/parcel-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,UAAU,EACX,MAAM,SAAS,CAAC;AAKjB,yCAAyC;AACzC,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAA2B;IAE/C,2CAA2C;gBAC/B,MAAM,EAAE,MAAM;IAI1B;;;;OAIG;IACG,aAAa,CACjB,UAAU,GAAE,QAAQ,GAAG,QAAmB,GACzC,OAAO,CAAC,cAAc,EAAE,CAAC;IAqB5B;;;;OAIG;IACG,WAAW,CACf,QAAQ,EAAE,kBAAkB,GAC3B,OAAO,CAAC,mBAAmB,CAAC;IAS/B,kDAAkD;IAC5C,eAAe,IAAI,OAAO,CAAC,UAAU,CAAC;IAkB5C;;;;OAIG;IACG,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAK1D,mCAAmC;IAC7B,cAAc,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAatE;;;;;;;OAOG;IACH,OAAO,CAAC,OAAO;
|
|
1
|
+
{"version":3,"file":"parcel-client.d.ts","sourceRoot":"","sources":["../../src/lib/parcel-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,UAAU,EACX,MAAM,SAAS,CAAC;AAKjB,yCAAyC;AACzC,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAA2B;IAE/C,2CAA2C;gBAC/B,MAAM,EAAE,MAAM;IAI1B;;;;OAIG;IACG,aAAa,CACjB,UAAU,GAAE,QAAQ,GAAG,QAAmB,GACzC,OAAO,CAAC,cAAc,EAAE,CAAC;IAqB5B;;;;OAIG;IACG,WAAW,CACf,QAAQ,EAAE,kBAAkB,GAC3B,OAAO,CAAC,mBAAmB,CAAC;IAS/B,kDAAkD;IAC5C,eAAe,IAAI,OAAO,CAAC,UAAU,CAAC;IAkB5C;;;;OAIG;IACG,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAK1D,mCAAmC;IAC7B,cAAc,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAatE;;;;;;;OAOG;IACH,OAAO,CAAC,OAAO;CAiFhB"}
|
|
@@ -137,6 +137,15 @@ class ParcelClient {
|
|
|
137
137
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
138
138
|
if (res.statusCode &&
|
|
139
139
|
(res.statusCode < 200 || res.statusCode >= 300)) {
|
|
140
|
+
if (res.statusCode === 429) {
|
|
141
|
+
const retryAfter = parseInt(res.headers["retry-after"] || "", 10);
|
|
142
|
+
const err = new Error("Rate limit exceeded");
|
|
143
|
+
err.code = "RATE_LIMITED";
|
|
144
|
+
// Use Retry-After header or default to 5 minutes
|
|
145
|
+
err.retryAfterSeconds = retryAfter > 0 ? retryAfter : 5 * 60;
|
|
146
|
+
reject(err);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
140
149
|
const err = new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`);
|
|
141
150
|
err.code =
|
|
142
151
|
res.statusCode === 401 || res.statusCode === 403
|
package/build/main.js
CHANGED
|
@@ -40,12 +40,16 @@ require("./lib/types");
|
|
|
40
40
|
const MIN_POLL_INTERVAL = 5;
|
|
41
41
|
const MAX_POLL_INTERVAL = 60;
|
|
42
42
|
const DEFAULT_POLL_INTERVAL = 10;
|
|
43
|
+
const MIN_POLL_GAP_MS = 60_000; // Minimum 60s between polls
|
|
43
44
|
/** ioBroker adapter for parcel.app package tracking */
|
|
44
45
|
class ParcelappAdapter extends utils.Adapter {
|
|
45
46
|
client = null;
|
|
46
47
|
stateManager = null;
|
|
47
48
|
pollTimer = null;
|
|
48
49
|
isPolling = false;
|
|
50
|
+
lastPollTime = 0;
|
|
51
|
+
rateLimitedUntil = 0;
|
|
52
|
+
lastErrorCode = "";
|
|
49
53
|
/** @param options Adapter options */
|
|
50
54
|
constructor(options = {}) {
|
|
51
55
|
super({
|
|
@@ -138,15 +142,59 @@ class ParcelappAdapter extends utils.Adapter {
|
|
|
138
142
|
}
|
|
139
143
|
}
|
|
140
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Classify an error for deduplication and log-level decisions.
|
|
147
|
+
*
|
|
148
|
+
* @param error The error to classify
|
|
149
|
+
*/
|
|
150
|
+
classifyError(error) {
|
|
151
|
+
if (error.code === "RATE_LIMITED") {
|
|
152
|
+
return "RATE_LIMITED";
|
|
153
|
+
}
|
|
154
|
+
if (error.code === "INVALID_API_KEY") {
|
|
155
|
+
return "INVALID_API_KEY";
|
|
156
|
+
}
|
|
157
|
+
// Network errors: DNS, connection refused, no internet
|
|
158
|
+
if (error.code === "ENOTFOUND" ||
|
|
159
|
+
error.code === "ECONNREFUSED" ||
|
|
160
|
+
error.code === "ECONNRESET" ||
|
|
161
|
+
error.code === "ENETUNREACH" ||
|
|
162
|
+
error.code === "EAI_AGAIN") {
|
|
163
|
+
return "NETWORK";
|
|
164
|
+
}
|
|
165
|
+
if (error.message.includes("timeout") || error.code === "ETIMEDOUT") {
|
|
166
|
+
return "TIMEOUT";
|
|
167
|
+
}
|
|
168
|
+
return error.code || "UNKNOWN";
|
|
169
|
+
}
|
|
141
170
|
async poll() {
|
|
142
171
|
if (this.isPolling || !this.client || !this.stateManager) {
|
|
143
172
|
return;
|
|
144
173
|
}
|
|
174
|
+
const now = Date.now();
|
|
175
|
+
// Skip if rate limited
|
|
176
|
+
if (now < this.rateLimitedUntil) {
|
|
177
|
+
const waitMin = Math.ceil((this.rateLimitedUntil - now) / 60_000);
|
|
178
|
+
this.log.debug(`Skipping poll — rate limited for ${waitMin} more minute(s)`);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
// Throttle: minimum gap between polls
|
|
182
|
+
if (now - this.lastPollTime < MIN_POLL_GAP_MS) {
|
|
183
|
+
this.log.debug("Skipping poll — too soon after last poll");
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
145
186
|
this.isPolling = true;
|
|
187
|
+
this.lastPollTime = now;
|
|
146
188
|
try {
|
|
147
189
|
// When keeping delivered packages, use "recent" to get them from API
|
|
148
190
|
const autoRemove = this.config.autoRemoveDelivered !== false;
|
|
149
191
|
const deliveries = await this.client.getDeliveries(autoRemove ? "active" : "recent");
|
|
192
|
+
// Reset error state on success
|
|
193
|
+
this.rateLimitedUntil = 0;
|
|
194
|
+
if (this.lastErrorCode) {
|
|
195
|
+
this.log.info("Connection restored");
|
|
196
|
+
this.lastErrorCode = "";
|
|
197
|
+
}
|
|
150
198
|
await this.setStateAsync("info.connection", { val: true, ack: true });
|
|
151
199
|
// Filter deliveries based on auto-remove setting
|
|
152
200
|
const visibleDeliveries = autoRemove
|
|
@@ -170,11 +218,28 @@ class ParcelappAdapter extends utils.Adapter {
|
|
|
170
218
|
}
|
|
171
219
|
catch (err) {
|
|
172
220
|
const error = err;
|
|
173
|
-
|
|
221
|
+
// Classify the error
|
|
222
|
+
const errorCode = this.classifyError(error);
|
|
223
|
+
const isRepeat = errorCode === this.lastErrorCode;
|
|
224
|
+
this.lastErrorCode = errorCode;
|
|
225
|
+
if (error.code === "RATE_LIMITED") {
|
|
226
|
+
const cooldownSec = error.retryAfterSeconds || 5 * 60;
|
|
227
|
+
this.rateLimitedUntil = Date.now() + cooldownSec * 1000;
|
|
228
|
+
this.log.warn(`Rate limit hit — pausing API requests for ${Math.ceil(cooldownSec / 60)} minute(s)`);
|
|
229
|
+
}
|
|
230
|
+
else if (error.code === "INVALID_API_KEY") {
|
|
231
|
+
// Always log — user must fix config
|
|
174
232
|
this.log.error("Invalid API key — please check your parcel.app API key");
|
|
175
233
|
}
|
|
176
|
-
else if (
|
|
177
|
-
|
|
234
|
+
else if (isRepeat) {
|
|
235
|
+
// Same error as last time — don't spam the log
|
|
236
|
+
this.log.debug(`Poll failed (ongoing): ${error.message}`);
|
|
237
|
+
}
|
|
238
|
+
else if (errorCode === "NETWORK") {
|
|
239
|
+
this.log.warn(`Cannot reach parcel.app API — will keep retrying`);
|
|
240
|
+
}
|
|
241
|
+
else if (errorCode === "TIMEOUT") {
|
|
242
|
+
this.log.warn(`API request timeout — will retry next cycle`);
|
|
178
243
|
}
|
|
179
244
|
else {
|
|
180
245
|
this.log.error(`Poll failed: ${error.message}`);
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "parcelapp",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.2.1": {
|
|
7
|
+
"en": "Robust error handling: API rate limit detection, connection error deduplication, poll throttling",
|
|
8
|
+
"de": "Robuste Fehlerbehandlung: Erkennung von API-Ratenlimits, Deduplizierung von Verbindungsfehlern, Polling-Drosselung",
|
|
9
|
+
"ru": "Надежная обработка ошибок: обнаружение ограничения скорости API, дедупликация ошибок соединения, регулирование опроса.",
|
|
10
|
+
"pt": "Tratamento robusto de erros: detecção de limite de taxa de API, desduplicação de erros de conexão, otimização de pesquisa",
|
|
11
|
+
"nl": "Robuuste foutafhandeling: detectie van API-snelheidslimieten, deduplicatie van verbindingsfouten, poll-throttling",
|
|
12
|
+
"fr": "Gestion robuste des erreurs : détection de limite de débit API, déduplication des erreurs de connexion, limitation des interrogations",
|
|
13
|
+
"it": "Gestione efficace degli errori: rilevamento del limite di velocità dell'API, deduplicazione degli errori di connessione, limitazione del poll",
|
|
14
|
+
"es": "Manejo sólido de errores: detección de límite de tasa de API, deduplicación de errores de conexión, limitación de encuestas",
|
|
15
|
+
"pl": "Solidna obsługa błędów: wykrywanie limitów szybkości interfejsu API, deduplikacja błędów połączenia, ograniczanie odpytywania",
|
|
16
|
+
"uk": "Надійна обробка помилок: виявлення обмеження швидкості API, дедуплікація помилок підключення, регулювання опитування",
|
|
17
|
+
"zh-cn": "强大的错误处理:API 速率限制检测、连接错误重复数据删除、轮询限制"
|
|
18
|
+
},
|
|
6
19
|
"0.2.0": {
|
|
7
20
|
"en": "Added option to keep delivered packages, simplified admin UI, removed summary.json state",
|
|
8
21
|
"de": "Option zum Beibehalten gelieferter Pakete hinzugefügt, Admin-Benutzeroberfläche vereinfacht, Status „summary.json“ entfernt",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
93
|
"pl": "Przeprojektowano logo adaptera, naprawiono problemy repochecker",
|
|
81
94
|
"uk": "Оновлено логотип адаптера, виправлено проблеми repochecker",
|
|
82
95
|
"zh-cn": "重新设计适配器图标,修复 repochecker 问题"
|
|
83
|
-
},
|
|
84
|
-
"0.1.0": {
|
|
85
|
-
"en": "Initial release — track packages from 300+ carriers via parcel.app",
|
|
86
|
-
"de": "Erstveröffentlichung — Pakete von 300+ Versandunternehmen über parcel.app verfolgen",
|
|
87
|
-
"ru": "Первый выпуск — отслеживание посылок от 300+ перевозчиков через parcel.app",
|
|
88
|
-
"pt": "Lançamento inicial — rastreie pacotes de mais de 300 transportadoras via parcel.app",
|
|
89
|
-
"nl": "Eerste release — volg pakketten van 300+ vervoerders via parcel.app",
|
|
90
|
-
"fr": "Version initiale — suivez vos colis de plus de 300 transporteurs via parcel.app",
|
|
91
|
-
"it": "Prima versione — traccia pacchi da più di 300 corrieri tramite parcel.app",
|
|
92
|
-
"es": "Versión inicial — rastrea paquetes de más de 300 transportistas a través de parcel.app",
|
|
93
|
-
"pl": "Pierwsze wydanie — śledź przesyłki od 300+ przewoźników przez parcel.app",
|
|
94
|
-
"uk": "Перший випуск — відстеження посилок від 300+ перевізників через parcel.app",
|
|
95
|
-
"zh-cn": "初始版本 — 通过 parcel.app 追踪 300+ 快递公司的包裹"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|