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 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** of delivered packages — auto-remove or keep as "Delivered"
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;CAqEhB"}
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
- if (error.code === "INVALID_API_KEY") {
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 (error.message.includes("timeout")) {
177
- this.log.error(`API request timeout: ${error.message}`);
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.0",
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": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.parcelapp",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "ioBroker adapter for parcel.app — track packages from 300+ carriers",
5
5
  "author": {
6
6
  "name": "krobi",