iobroker.parcelapp 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/main.js CHANGED
@@ -23,13 +23,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  ));
24
24
  var utils = __toESM(require("@iobroker/adapter-core"));
25
25
  var import_coerce = require("./lib/coerce");
26
- var import_i18n_logs = require("./lib/i18n-logs");
27
26
  var import_parcel_client = require("./lib/parcel-client");
28
27
  var import_state_manager = require("./lib/state-manager");
29
28
  const MIN_POLL_INTERVAL = 5;
30
29
  const MAX_POLL_INTERVAL = 60;
31
30
  const DEFAULT_POLL_INTERVAL = 10;
32
31
  const MIN_POLL_GAP_MS = 6e4;
32
+ const MIN_API_KEY_LENGTH = 10;
33
33
  class ParcelappAdapter extends utils.Adapter {
34
34
  client = null;
35
35
  stateManager = null;
@@ -50,25 +50,27 @@ class ParcelappAdapter extends utils.Adapter {
50
50
  name: "parcelapp"
51
51
  });
52
52
  this.on("ready", () => {
53
- this.onReady().catch((err) => this.log.error((0, import_i18n_logs.tLog)(this.systemLang, "onReadyFailed", { error: (0, import_coerce.errText)(err) })));
53
+ this.onReady().catch((err) => this.log.error(`onReady failed: ${(0, import_coerce.errText)(err)}`));
54
54
  });
55
55
  this.on("unload", this.onUnload.bind(this));
56
56
  this.on("message", (obj) => {
57
- this.onMessage(obj).catch(
58
- (err) => this.log.error((0, import_i18n_logs.tLog)(this.systemLang, "onMessageFailed", { error: (0, import_coerce.errText)(err) }))
59
- );
57
+ this.onMessage(obj).catch((err) => this.log.error(`onMessage failed: ${(0, import_coerce.errText)(err)}`));
60
58
  });
61
59
  this.unhandledRejectionHandler = (reason) => {
62
- this.log.error((0, import_i18n_logs.tLog)(this.systemLang, "unhandledRejection", { error: (0, import_coerce.errText)(reason) }));
60
+ var _a;
61
+ this.log.error(`Unhandled rejection: ${(0, import_coerce.errText)(reason)}`);
62
+ (_a = this.terminate) == null ? void 0 : _a.call(this, 11);
63
63
  };
64
64
  this.uncaughtExceptionHandler = (err) => {
65
- this.log.error((0, import_i18n_logs.tLog)(this.systemLang, "uncaughtException", { error: (0, import_coerce.errText)(err) }));
65
+ var _a;
66
+ this.log.error(`Uncaught exception: ${(0, import_coerce.errText)(err)}`);
67
+ (_a = this.terminate) == null ? void 0 : _a.call(this, 11);
66
68
  };
67
69
  process.on("unhandledRejection", this.unhandledRejectionHandler);
68
70
  process.on("uncaughtException", this.uncaughtExceptionHandler);
69
71
  }
70
72
  async onReady() {
71
- var _a, _b, _c;
73
+ var _a, _b;
72
74
  const sysConfig = await this.getForeignObjectAsync("system.config");
73
75
  const language = (_b = (_a = sysConfig == null ? void 0 : sysConfig.common) == null ? void 0 : _a.language) != null ? _b : "";
74
76
  if (typeof language === "string" && language.length > 0) {
@@ -76,28 +78,35 @@ class ParcelappAdapter extends utils.Adapter {
76
78
  }
77
79
  await this.setStateAsync("info.connection", { val: false, ack: true });
78
80
  const { apiKey } = this.config;
79
- if (!apiKey || apiKey.trim().length < 10) {
80
- this.log.error((0, import_i18n_logs.tLog)(this.systemLang, "noApiKey"));
81
+ if (!apiKey || apiKey.trim().length < MIN_API_KEY_LENGTH) {
82
+ this.log.error("No valid API key configured \u2014 please enter your parcel.app API key in the adapter settings");
81
83
  return;
82
84
  }
83
85
  this.client = new import_parcel_client.ParcelClient(apiKey.trim());
84
86
  this.stateManager = new import_state_manager.StateManager(this, language);
85
87
  await this.cleanupObsoleteStates();
86
88
  await this.poll();
87
- const interval = Math.max(
88
- MIN_POLL_INTERVAL,
89
- Math.min(MAX_POLL_INTERVAL, (_c = this.config.pollInterval) != null ? _c : DEFAULT_POLL_INTERVAL)
90
- );
89
+ const interval = ParcelappAdapter.coercePollInterval(this.config.pollInterval);
91
90
  const intervalMs = interval * 60 * 1e3;
92
91
  this.pollTimer = this.setInterval(() => void this.poll(), intervalMs);
93
- this.log.info((0, import_i18n_logs.tLog)(this.systemLang, "trackingStarted", { minutes: interval }));
92
+ this.log.info(`Parcel tracking started \u2014 polling every ${interval} minutes`);
93
+ }
94
+ /**
95
+ * v0.4.2 (M5+X5): delegate to the shared `coerceClampedInt` helper.
96
+ *
97
+ * @param raw Raw `pollInterval` from admin config (number or numeric string).
98
+ */
99
+ static coercePollInterval(raw) {
100
+ return (0, import_coerce.coerceClampedInt)(raw, MIN_POLL_INTERVAL, MAX_POLL_INTERVAL, DEFAULT_POLL_INTERVAL);
94
101
  }
95
102
  onUnload(callback) {
103
+ var _a;
96
104
  try {
97
105
  if (this.pollTimer) {
98
106
  this.clearInterval(this.pollTimer);
99
107
  this.pollTimer = void 0;
100
108
  }
109
+ (_a = this.client) == null ? void 0 : _a.cancelAll();
101
110
  if (this.unhandledRejectionHandler) {
102
111
  process.off("unhandledRejection", this.unhandledRejectionHandler);
103
112
  this.unhandledRejectionHandler = null;
@@ -106,7 +115,8 @@ class ParcelappAdapter extends utils.Adapter {
106
115
  process.off("uncaughtException", this.uncaughtExceptionHandler);
107
116
  this.uncaughtExceptionHandler = null;
108
117
  }
109
- void this.setState("info.connection", { val: false, ack: true });
118
+ void this.setState("info.connection", { val: false, ack: true }).catch(() => {
119
+ });
110
120
  } catch {
111
121
  }
112
122
  callback();
@@ -121,7 +131,7 @@ class ParcelappAdapter extends utils.Adapter {
121
131
  case "checkConnection": {
122
132
  const msg = obj.message;
123
133
  const key = ((_a = msg == null ? void 0 : msg.apiKey) == null ? void 0 : _a.trim()) || "";
124
- if (!key || key.length < 10) {
134
+ if (!key || key.length < MIN_API_KEY_LENGTH) {
125
135
  this.sendTo(obj.from, obj.command, { success: false, message: "API key is too short" }, obj.callback);
126
136
  return;
127
137
  }
@@ -180,6 +190,9 @@ class ParcelappAdapter extends utils.Adapter {
180
190
  if (error.code === "INVALID_API_KEY") {
181
191
  return "INVALID_API_KEY";
182
192
  }
193
+ if (error.code === "FORBIDDEN") {
194
+ return "FORBIDDEN";
195
+ }
183
196
  if (error.code === "ENOTFOUND" || error.code === "ECONNREFUSED" || error.code === "ECONNRESET" || error.code === "ENETUNREACH" || error.code === "EHOSTUNREACH" || error.code === "EAI_AGAIN") {
184
197
  return "NETWORK";
185
198
  }
@@ -189,6 +202,7 @@ class ParcelappAdapter extends utils.Adapter {
189
202
  return error.code || "UNKNOWN";
190
203
  }
191
204
  async poll() {
205
+ var _a;
192
206
  if (this.isPolling || !this.client || !this.stateManager) {
193
207
  return;
194
208
  }
@@ -209,29 +223,33 @@ class ParcelappAdapter extends utils.Adapter {
209
223
  const deliveries = await this.client.getDeliveries(autoRemove ? "active" : "recent");
210
224
  this.rateLimitedUntil = 0;
211
225
  if (this.lastErrorCode) {
212
- this.log.info((0, import_i18n_logs.tLog)(this.systemLang, "connectionRestored"));
226
+ this.log.info("Connection restored");
213
227
  this.lastErrorCode = "";
214
228
  }
215
229
  await this.setStateAsync("info.connection", { val: true, ack: true });
216
230
  const activeDeliveries = deliveries.filter((d) => this.stateManager.parseStatus(d) !== 0);
217
231
  const visibleDeliveries = autoRemove ? activeDeliveries : deliveries;
218
- const activeIds = [];
219
- for (const delivery of visibleDeliveries) {
220
- try {
221
- const carrierName = await this.client.getCarrierName(delivery.carrier_code);
222
- await this.stateManager.updateDelivery(delivery, carrierName);
223
- activeIds.push(this.stateManager.packageId(delivery));
224
- this.failedDeliveries.delete(delivery.tracking_number);
225
- } catch (err) {
226
- const msg = (0, import_coerce.errText)(err);
227
- if (this.failedDeliveries.has(delivery.tracking_number)) {
228
- this.log.debug(`Failed to update "${delivery.tracking_number}": ${msg}`);
229
- } else {
230
- this.log.warn((0, import_i18n_logs.tLog)(this.systemLang, "updateFailed", { tracking: delivery.tracking_number, error: msg }));
231
- this.failedDeliveries.add(delivery.tracking_number);
232
+ this.stateManager.resetPollState();
233
+ const idResults = await Promise.all(
234
+ visibleDeliveries.map(async (delivery) => {
235
+ try {
236
+ const carrierName = await this.client.getCarrierName(delivery.carrier_code);
237
+ await this.stateManager.updateDelivery(delivery, carrierName);
238
+ this.failedDeliveries.delete(delivery.tracking_number);
239
+ return this.stateManager.packageId(delivery);
240
+ } catch (err) {
241
+ const msg = (0, import_coerce.errText)(err);
242
+ if (this.failedDeliveries.has(delivery.tracking_number)) {
243
+ this.log.debug(`Failed to update "${delivery.tracking_number}": ${msg}`);
244
+ } else {
245
+ this.log.warn(`Failed to update '${delivery.tracking_number}': ${msg}`);
246
+ this.failedDeliveries.add(delivery.tracking_number);
247
+ }
248
+ return null;
232
249
  }
233
- }
234
- }
250
+ })
251
+ );
252
+ const activeIds = idResults.filter((id) => id !== null);
235
253
  await this.stateManager.cleanupDeliveries(activeIds);
236
254
  await this.stateManager.updateSummary(activeDeliveries);
237
255
  this.log.debug(`Polled ${visibleDeliveries.length} deliveries (${activeDeliveries.length} active)`);
@@ -241,19 +259,24 @@ class ParcelappAdapter extends utils.Adapter {
241
259
  const isRepeat = errorCode === this.lastErrorCode;
242
260
  this.lastErrorCode = errorCode;
243
261
  if (error.code === "RATE_LIMITED") {
244
- const cooldownSec = error.retryAfterSeconds || 5 * 60;
262
+ const rawCooldown = (_a = error.retryAfterSeconds) != null ? _a : 0;
263
+ const cooldownSec = Number.isFinite(rawCooldown) && rawCooldown > 0 ? Math.min(24 * 3600, Math.max(60, Math.floor(rawCooldown))) : 5 * 60;
245
264
  this.rateLimitedUntil = Date.now() + cooldownSec * 1e3;
246
- this.log.warn((0, import_i18n_logs.tLog)(this.systemLang, "rateLimitHit", { minutes: Math.ceil(cooldownSec / 60) }));
265
+ this.log.warn(`Rate limit hit \u2014 pausing API requests for ${Math.ceil(cooldownSec / 60)} minute(s)`);
266
+ } else if (error.code === "FORBIDDEN") {
267
+ this.log.error(
268
+ "parcel.app returned 403 Forbidden \u2014 your account may not have an active Premium subscription, or the API key was revoked. Check your account on parcelapp.net."
269
+ );
247
270
  } else if (error.code === "INVALID_API_KEY") {
248
- this.log.error((0, import_i18n_logs.tLog)(this.systemLang, "invalidApiKey"));
271
+ this.log.error("Invalid API key \u2014 please check your parcel.app API key");
249
272
  } else if (isRepeat) {
250
273
  this.log.debug(`Poll failed (ongoing): ${error.message}`);
251
274
  } else if (errorCode === "NETWORK") {
252
- this.log.warn((0, import_i18n_logs.tLog)(this.systemLang, "cannotReach"));
275
+ this.log.warn("Cannot reach parcel.app API \u2014 will keep retrying");
253
276
  } else if (errorCode === "TIMEOUT") {
254
- this.log.warn((0, import_i18n_logs.tLog)(this.systemLang, "apiTimeout"));
277
+ this.log.warn("API request timeout \u2014 will retry next cycle");
255
278
  } else {
256
- this.log.error((0, import_i18n_logs.tLog)(this.systemLang, "pollFailed", { error: error.message }));
279
+ this.log.error(`Poll failed: ${error.message}`);
257
280
  }
258
281
  await this.setStateAsync("info.connection", { val: false, ack: true });
259
282
  } finally {
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 { errText } from \"./lib/coerce\";\nimport { tLog } from \"./lib/i18n-logs\";\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(tLog(this.systemLang, \"onReadyFailed\", { error: errText(err) })));\n });\n this.on(\"unload\", this.onUnload.bind(this));\n this.on(\"message\", obj => {\n this.onMessage(obj).catch(err =>\n this.log.error(tLog(this.systemLang, \"onMessageFailed\", { error: errText(err) })),\n );\n });\n // Last-line-of-defence against unhandled rejections / sync throws from\n // fire-and-forget paths (e.g. `void this.poll()`). The per-handler\n // .catch() wrappers cover the documented async paths; this catches\n // anything that slips past during refactors.\n this.unhandledRejectionHandler = (reason: unknown) => {\n this.log.error(tLog(this.systemLang, \"unhandledRejection\", { error: errText(reason) }));\n };\n this.uncaughtExceptionHandler = (err: Error) => {\n this.log.error(tLog(this.systemLang, \"uncaughtException\", { error: 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(tLog(this.systemLang, \"noApiKey\"));\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(tLog(this.systemLang, \"trackingStarted\", { minutes: interval }));\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(tLog(this.systemLang, \"connectionRestored\"));\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(tLog(this.systemLang, \"updateFailed\", { tracking: delivery.tracking_number, error: 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(tLog(this.systemLang, \"rateLimitHit\", { minutes: Math.ceil(cooldownSec / 60) }));\n } else if (error.code === \"INVALID_API_KEY\") {\n // Always log \u2014 user must fix config\n this.log.error(tLog(this.systemLang, \"invalidApiKey\"));\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(tLog(this.systemLang, \"cannotReach\"));\n } else if (errorCode === \"TIMEOUT\") {\n this.log.warn(tLog(this.systemLang, \"apiTimeout\"));\n } else {\n this.log.error(tLog(this.systemLang, \"pollFailed\", { error: 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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",
4
+ "sourcesContent": ["import * as utils from \"@iobroker/adapter-core\";\nimport { coerceClampedInt, 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/** v0.4.2 (M6): minimum length for an apiKey value to even be considered valid. */\nconst MIN_API_KEY_LENGTH = 10;\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 // v0.4.2 (M1): log + terminate(11) instead of leaving the process alive\n // in an undefined state. The per-handler wrappers cover expected paths;\n // anything reaching here is by definition unexpected.\n this.unhandledRejectionHandler = (reason: unknown) => {\n this.log.error(`Unhandled rejection: ${errText(reason)}`);\n this.terminate?.(11);\n };\n this.uncaughtExceptionHandler = (err: Error) => {\n this.log.error(`Uncaught exception: ${errText(err)}`);\n this.terminate?.(11);\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 < MIN_API_KEY_LENGTH) {\n this.log.error(\"No valid API key configured \u2014 please enter your parcel.app API key in the adapter settings\");\n return;\n }\n\n // 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 // v0.4.2 (M5): coerce explicitly. Admin can store `pollInterval` as a\n // string; `Math.min(60, \"10\")` happens to coerce, but `Math.max(5,\n // undefined)` returns NaN, and `setInterval(fn, NaN)` becomes\n // `setInterval(fn, 0)` \u2014 a tight loop that hammers the API.\n const interval = ParcelappAdapter.coercePollInterval(this.config.pollInterval);\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 /**\n * v0.4.2 (M5+X5): delegate to the shared `coerceClampedInt` helper.\n *\n * @param raw Raw `pollInterval` from admin config (number or numeric string).\n */\n private static coercePollInterval(raw: unknown): number {\n return coerceClampedInt(raw, MIN_POLL_INTERVAL, MAX_POLL_INTERVAL, DEFAULT_POLL_INTERVAL);\n }\n\n private onUnload(callback: () => void): void {\n try {\n if (this.pollTimer) {\n this.clearInterval(this.pollTimer);\n this.pollTimer = undefined;\n }\n // v0.4.2 (M11+P1): cancel every in-flight HTTPS request so a slow\n // parcel.app endpoint doesn't keep the adapter alive past\n // js-controller's 4-second kill deadline.\n this.client?.cancelAll();\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 // v0.4.2 (M10): explicit `.catch(() => {})` on the fire-and-forget so\n // a broker-already-down doesn't leak as an unhandled rejection.\n void this.setState(\"info.connection\", { val: false, ack: true }).catch(() => {\n /* broker is shutting down \u2014 ignore */\n });\n } catch {\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 < MIN_API_KEY_LENGTH) {\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 // v0.4.2 (P3): 403 is a permission issue, distinct from invalid api-key.\n if (error.code === \"FORBIDDEN\") {\n return \"FORBIDDEN\";\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 // v0.4.2 (S3): reset per-poll collision tracker so the bare-id wins\n // for the first occurrence in this poll (deterministic, back-compat).\n this.stateManager.resetPollState();\n\n // v0.4.2 (M4): per-delivery updates run in parallel, each wrapped in\n // try/catch so one bad delivery doesn't poison the others.\n const idResults = await Promise.all(\n visibleDeliveries.map(async delivery => {\n try {\n const carrierName = await this.client!.getCarrierName(delivery.carrier_code);\n await this.stateManager!.updateDelivery(delivery, carrierName);\n this.failedDeliveries.delete(delivery.tracking_number);\n return this.stateManager!.packageId(delivery);\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 return null;\n }\n }),\n );\n const activeIds = idResults.filter((id): id is string => id !== null);\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 // v0.4.2 (M9): clamp Retry-After value into [60s, 24h]. A bogus 0,\n // negative, or fractional value used to either wipe the cooldown\n // (set rateLimitedUntil to past) or set it for fractions of a\n // second \u2014 neither is the intended behavior.\n const rawCooldown = error.retryAfterSeconds ?? 0;\n const cooldownSec =\n Number.isFinite(rawCooldown) && rawCooldown > 0\n ? Math.min(24 * 3600, Math.max(60, Math.floor(rawCooldown)))\n : 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 === \"FORBIDDEN\") {\n // v0.4.2 (P3): 403 is a permission issue (e.g. Premium subscription\n // expired). Reauth wouldn't help \u2014 surface a clear hint.\n this.log.error(\n \"parcel.app returned 403 Forbidden \u2014 your account may not have an active Premium subscription, or the API key was revoked. Check your account on parcelapp.net.\",\n );\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.0",
4
+ "version": "0.4.2",
5
5
  "news": {
6
+ "0.4.2": {
7
+ "en": "Adapter shuts down cleanly when parcel.app is slow, 403 Forbidden gives a 'check Premium' hint instead of reauth-loop, two trackings with the same sanitized id no longer overwrite.",
8
+ "de": "Adapter beendet sauber wenn parcel.app langsam ist, 403-Fehler liefert 'Premium prüfen'-Hinweis statt Reauth-Loop, und zwei Trackings mit identischer Sanitize-ID überschreiben sich nicht mehr.",
9
+ "ru": "Адаптер корректно завершается при медленной parcel.app, 403 даёт подсказку 'проверьте Premium' вместо цикла переавторизации, два трекинга с одинаковым ID больше не перезаписываются.",
10
+ "pt": "O adaptador encerra de forma limpa quando o parcel.app está lento, 403 mostra dica 'verificar Premium' em vez de re-autenticar em loop, dois rastreios com o mesmo ID já não se sobrescrevem.",
11
+ "nl": "Adapter sluit netjes af als parcel.app traag is, 403 geeft 'Premium controleren'-hint in plaats van reauth-loop, en twee trackings met dezelfde id overschrijven elkaar niet meer.",
12
+ "fr": "L'adaptateur s'arrête proprement quand parcel.app est lent, un 403 donne 'vérifier Premium' au lieu de boucler la réauth, et deux suivis avec le même id ne s'écrasent plus.",
13
+ "it": "L'adapter si arresta correttamente quando parcel.app è lento, un 403 mostra 'controlla Premium' invece di un loop di reauth, due tracciamenti con lo stesso id non si sovrascrivono più.",
14
+ "es": "El adaptador se cierra limpiamente cuando parcel.app va lento, 403 muestra 'verificar Premium' en vez de bucle de reauth, y dos seguimientos con el mismo id ya no se sobrescriben.",
15
+ "pl": "Adapter zamyka się czysto, gdy parcel.app jest powolny, 403 daje wskazówkę 'sprawdź Premium' zamiast pętli reauth, dwa śledzenia z tym samym id już się nie nadpisują.",
16
+ "uk": "Адаптер коректно вимикається при повільному parcel.app, 403 дає підказку 'перевірте Premium' замість циклу входу, два трекінги з однаковим id більше не перезаписуються.",
17
+ "zh-cn": "当 parcel.app 响应缓慢时,适配器现在能干净退出(终止进行中的请求)。403 错误会提示检查 Premium 订阅,而不是反复重新认证。两个清洗后 ID 相同的快递不再互相覆盖。"
18
+ },
19
+ "0.4.1": {
20
+ "en": "Adapter log messages are now English only, in line with the ioBroker community standard. Localized state names (11 languages) remain unchanged.",
21
+ "de": "Adapter-Logs sind jetzt nur noch auf Englisch, gemäß ioBroker-Community-Standard. Lokalisierte Datenpunkt-Namen (11 Sprachen) bleiben erhalten.",
22
+ "ru": "Сообщения журнала теперь только на английском, согласно стандарту сообщества ioBroker. Локализованные имена состояний (11 языков) сохраняются.",
23
+ "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.",
24
+ "nl": "Adapter-logberichten zijn nu alleen Engels, conform de ioBroker-communitystandaard. Gelokaliseerde statusnamen (11 talen) blijven ongewijzigd.",
25
+ "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.",
26
+ "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.",
27
+ "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.",
28
+ "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.",
29
+ "uk": "Повідомлення журналу адаптера тепер лише англійською, відповідно до стандарту спільноти ioBroker. Локалізовані назви станів (11 мов) залишаються без змін.",
30
+ "zh-cn": "适配器日志消息现在仅为英文,符合 ioBroker 社区标准。本地化的数据点名称(11 种语言)保持不变。"
31
+ },
6
32
  "0.4.0": {
7
33
  "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.",
8
34
  "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.",
@@ -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.17": {
72
- "en": "Internal cleanup. No user-facing changes.",
73
- "de": "Interne Bereinigung. Keine User-sichtbaren Änderungen.",
74
- "ru": "Внутренняя очистка. Без видимых изменений для пользователя.",
75
- "pt": "Limpeza interna. Sem alterações visíveis para o usuário.",
76
- "nl": "Interne opschoning. Geen wijzigingen voor de gebruiker.",
77
- "fr": "Nettoyage interne. Aucune modification visible pour l'utilisateur.",
78
- "it": "Pulizia interna. Nessuna modifica visibile all'utente.",
79
- "es": "Limpieza interna. Sin cambios visibles para el usuario.",
80
- "pl": "Wewnętrzne porządki. Brak zmian widocznych dla użytkownika.",
81
- "uk": "Внутрішнє прибирання. Без помітних для користувача змін.",
82
- "zh-cn": "内部清理。对用户无可见变化。"
83
- },
84
- "0.2.16": {
85
- "en": "Min js-controller restored to >=6.0.11 (was incorrectly bumped to >=7.0.23 in 0.2.15).",
86
- "de": "Min js-controller zurück auf >=6.0.11 (war in 0.2.15 fälschlich auf >=7.0.23 gesetzt).",
87
- "ru": "Мин. js-controller восстановлен на >=6.0.11 (в 0.2.15 ошибочно был >=7.0.23).",
88
- "pt": "Mín. js-controller restaurado para >=6.0.11 (em 0.2.15 estava erroneamente em >=7.0.23).",
89
- "nl": "Min. js-controller hersteld op >=6.0.11 (was in 0.2.15 ten onrechte >=7.0.23).",
90
- "fr": "Min js-controller rétabli à >=6.0.11 (était par erreur à >=7.0.23 en 0.2.15).",
91
- "it": "Min js-controller ripristinato a >=6.0.11 (in 0.2.15 era erroneamente >=7.0.23).",
92
- "es": "Mín. js-controller restaurado a >=6.0.11 (en 0.2.15 estaba incorrectamente en >=7.0.23).",
93
- "pl": "Min. js-controller przywrócony do >=6.0.11 (w 0.2.15 błędnie ustawiony na >=7.0.23).",
94
- "uk": "Мін. js-controller повернено на >=6.0.11 (у 0.2.15 помилково було >=7.0.23).",
95
- "zh-cn": "最低 js-controller 恢复为 >=6.0.11(0.2.15 中错误地设置为 >=7.0.23)。"
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.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "ioBroker adapter for the parcel.app API",
5
5
  "author": {
6
6
  "name": "krobi",
@@ -43,7 +43,7 @@
43
43
  "@tsconfig/node22": "^22.0.5",
44
44
  "@types/iobroker": "npm:@iobroker/types@^7.1.1",
45
45
  "@types/node": "^22.19.17",
46
- "nyc": "^17.1.0",
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",