iobroker.parcelapp 0.2.2 → 0.2.4

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.
@@ -1,27 +1,52 @@
1
1
  "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.STATUS_LABELS_EN = exports.STATUS_LABELS_DE = void 0;
4
- /** Delivery status codes (0-8) in German */
5
- exports.STATUS_LABELS_DE = {
6
- 0: "Zugestellt",
7
- 1: "Eingefroren",
8
- 2: "Unterwegs",
9
- 3: "Abholung erwartet",
10
- 4: "In Zustellung",
11
- 5: "Nicht gefunden",
12
- 6: "Zustellversuch gescheitert",
13
- 7: "Ausnahme",
14
- 8: "Registriert",
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 });
15
9
  };
16
- /** Delivery status codes (0-8) in English */
17
- exports.STATUS_LABELS_EN = {
18
- 0: "Delivered",
19
- 1: "Frozen",
20
- 2: "In Transit",
21
- 3: "Awaiting Pickup",
22
- 4: "Out for Delivery",
23
- 5: "Not Found",
24
- 6: "Delivery Attempt Failed",
25
- 7: "Exception",
26
- 8: "Info Received",
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;
27
17
  };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var types_exports = {};
20
+ __export(types_exports, {
21
+ STATUS_LABELS_DE: () => STATUS_LABELS_DE,
22
+ STATUS_LABELS_EN: () => STATUS_LABELS_EN
23
+ });
24
+ module.exports = __toCommonJS(types_exports);
25
+ const STATUS_LABELS_DE = {
26
+ 0: "Zugestellt",
27
+ 1: "Eingefroren",
28
+ 2: "Unterwegs",
29
+ 3: "Abholung erwartet",
30
+ 4: "In Zustellung",
31
+ 5: "Nicht gefunden",
32
+ 6: "Zustellversuch gescheitert",
33
+ 7: "Ausnahme",
34
+ 8: "Registriert"
35
+ };
36
+ const STATUS_LABELS_EN = {
37
+ 0: "Delivered",
38
+ 1: "Frozen",
39
+ 2: "In Transit",
40
+ 3: "Awaiting Pickup",
41
+ 4: "Out for Delivery",
42
+ 5: "Not Found",
43
+ 6: "Delivery Attempt Failed",
44
+ 7: "Exception",
45
+ 8: "Info Received"
46
+ };
47
+ // Annotate the CommonJS export names for ESM import in node:
48
+ 0 && (module.exports = {
49
+ STATUS_LABELS_DE,
50
+ STATUS_LABELS_EN
51
+ });
52
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/lib/types.ts"],
4
+ "sourcesContent": ["/** API response from parcel.app deliveries endpoint */\nexport interface ParcelApiResponse {\n /** Whether the request was successful */\n success: boolean;\n /** Error message if request failed */\n error_message?: string;\n /** Error code if request failed */\n error_code?: string;\n /** List of deliveries */\n deliveries?: ParcelDelivery[];\n}\n\n/** Single delivery from the parcel.app API */\nexport interface ParcelDelivery {\n /** Carrier identifier */\n carrier_code: string;\n /** User-defined description */\n description: string;\n /** Status code (0-8 as string) */\n status_code: string;\n /** Tracking number */\n tracking_number: string;\n /** Extra info (postal code, email) */\n extra_information?: string;\n /** Expected delivery date */\n date_expected?: string;\n /** Expected delivery date end */\n date_expected_end?: string;\n /** Expected delivery Unix timestamp */\n timestamp_expected?: number;\n /** Expected delivery end Unix timestamp */\n timestamp_expected_end?: number;\n /** Tracking events (newest first) */\n events?: ParcelEvent[];\n}\n\n/** Single tracking event */\nexport interface ParcelEvent {\n /** Event description */\n event: string;\n /** Event date string */\n date: string;\n /** Event location */\n location?: string;\n /** Additional details */\n additional?: string;\n}\n\n/** Request body for adding a delivery */\nexport interface AddDeliveryRequest {\n /** Tracking number to add */\n tracking_number: string;\n /** Carrier code */\n carrier_code: string;\n /** User description */\n description: string;\n /** Tracking language */\n language?: string;\n /** Send push confirmation */\n send_push_confirmation?: boolean;\n}\n\n/** Add delivery API response */\nexport interface AddDeliveryResponse {\n /** Whether the delivery was added */\n success: boolean;\n /** Error message if failed */\n error_message?: string;\n}\n\n/** Carrier names mapping (carrier_code \u2192 display name) */\nexport type CarrierMap = Record<string, string>;\n\n/** Delivery status codes (0-8) in German */\nexport const STATUS_LABELS_DE: Record<number, string> = {\n 0: \"Zugestellt\",\n 1: \"Eingefroren\",\n 2: \"Unterwegs\",\n 3: \"Abholung erwartet\",\n 4: \"In Zustellung\",\n 5: \"Nicht gefunden\",\n 6: \"Zustellversuch gescheitert\",\n 7: \"Ausnahme\",\n 8: \"Registriert\",\n};\n\n/** Delivery status codes (0-8) in English */\nexport const STATUS_LABELS_EN: Record<number, string> = {\n 0: \"Delivered\",\n 1: \"Frozen\",\n 2: \"In Transit\",\n 3: \"Awaiting Pickup\",\n 4: \"Out for Delivery\",\n 5: \"Not Found\",\n 6: \"Delivery Attempt Failed\",\n 7: \"Exception\",\n 8: \"Info Received\",\n};\n\n// Augment the ioBroker global namespace\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace ioBroker {\n interface AdapterConfig {\n /** parcel.app API key */\n apiKey: string;\n /** Polling interval in minutes */\n pollInterval: number;\n /** Language for status labels */\n language: \"de\" | \"en\";\n /** Automatically remove delivered packages from states */\n autoRemoveDelivered: boolean;\n }\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0EO,MAAM,mBAA2C;AAAA,EACtD,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;AAGO,MAAM,mBAA2C;AAAA,EACtD,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;",
6
+ "names": []
7
+ }
package/build/main.js CHANGED
@@ -1,259 +1,253 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- const utils = __importStar(require("@iobroker/adapter-core"));
37
- const parcel_client_1 = require("./lib/parcel-client");
38
- const state_manager_1 = require("./lib/state-manager");
39
- require("./lib/types");
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+ var utils = __toESM(require("@iobroker/adapter-core"));
25
+ var import_parcel_client = require("./lib/parcel-client");
26
+ var import_state_manager = require("./lib/state-manager");
27
+ var import_types = require("./lib/types");
40
28
  const MIN_POLL_INTERVAL = 5;
41
29
  const MAX_POLL_INTERVAL = 60;
42
30
  const DEFAULT_POLL_INTERVAL = 10;
43
- const MIN_POLL_GAP_MS = 60_000; // Minimum 60s between polls
44
- /** ioBroker adapter for parcel.app package tracking */
31
+ const MIN_POLL_GAP_MS = 6e4;
45
32
  class ParcelappAdapter extends utils.Adapter {
46
- client = null;
47
- stateManager = null;
48
- pollTimer = undefined;
49
- isPolling = false;
50
- lastPollTime = 0;
51
- rateLimitedUntil = 0;
52
- lastErrorCode = "";
53
- /** @param options Adapter options */
54
- constructor(options = {}) {
55
- super({
56
- ...options,
57
- name: "parcelapp",
58
- });
59
- this.on("ready", this.onReady.bind(this));
60
- this.on("unload", this.onUnload.bind(this));
61
- this.on("message", this.onMessage.bind(this));
33
+ client = null;
34
+ stateManager = null;
35
+ pollTimer = void 0;
36
+ isPolling = false;
37
+ lastPollTime = 0;
38
+ rateLimitedUntil = 0;
39
+ lastErrorCode = "";
40
+ /** @param options Adapter options */
41
+ constructor(options = {}) {
42
+ super({
43
+ ...options,
44
+ name: "parcelapp"
45
+ });
46
+ this.on("ready", this.onReady.bind(this));
47
+ this.on("unload", this.onUnload.bind(this));
48
+ this.on("message", this.onMessage.bind(this));
49
+ }
50
+ async onReady() {
51
+ var _a;
52
+ await this.setStateAsync("info.connection", { val: false, ack: true });
53
+ const { apiKey } = this.config;
54
+ if (!apiKey || apiKey.trim().length < 10) {
55
+ this.log.error(
56
+ "No valid API key configured \u2014 please enter your parcel.app API key in the adapter settings"
57
+ );
58
+ return;
62
59
  }
63
- async onReady() {
64
- await this.setStateAsync("info.connection", { val: false, ack: true });
65
- // Validate config
66
- const { apiKey } = this.config;
67
- if (!apiKey || apiKey.trim().length < 10) {
68
- this.log.error("No valid API key configured — please enter your parcel.app API key in the adapter settings");
69
- return;
70
- }
71
- // Initialize
72
- this.client = new parcel_client_1.ParcelClient(apiKey.trim());
73
- this.stateManager = new state_manager_1.StateManager(this);
74
- // Cleanup obsolete states
75
- await this.cleanupObsoleteStates();
76
- // Initial poll
77
- await this.poll();
78
- // Set up recurring poll
79
- const interval = Math.max(MIN_POLL_INTERVAL, Math.min(MAX_POLL_INTERVAL, this.config.pollInterval ?? DEFAULT_POLL_INTERVAL));
80
- const intervalMs = interval * 60 * 1000;
81
- this.pollTimer = this.setInterval(() => void this.poll(), intervalMs);
82
- this.log.info(`Parcel tracking started — polling every ${interval} minutes`);
60
+ this.client = new import_parcel_client.ParcelClient(apiKey.trim());
61
+ this.stateManager = new import_state_manager.StateManager(this);
62
+ await this.cleanupObsoleteStates();
63
+ await this.poll();
64
+ const interval = Math.max(
65
+ MIN_POLL_INTERVAL,
66
+ Math.min(
67
+ MAX_POLL_INTERVAL,
68
+ (_a = this.config.pollInterval) != null ? _a : DEFAULT_POLL_INTERVAL
69
+ )
70
+ );
71
+ const intervalMs = interval * 60 * 1e3;
72
+ this.pollTimer = this.setInterval(() => void this.poll(), intervalMs);
73
+ this.log.info(
74
+ `Parcel tracking started \u2014 polling every ${interval} minutes`
75
+ );
76
+ }
77
+ onUnload(callback) {
78
+ if (this.pollTimer) {
79
+ this.clearInterval(this.pollTimer);
80
+ this.pollTimer = void 0;
83
81
  }
84
- onUnload(callback) {
85
- if (this.pollTimer) {
86
- this.clearInterval(this.pollTimer);
87
- this.pollTimer = undefined;
88
- }
89
- void this.setState("info.connection", { val: false, ack: true });
90
- callback();
82
+ void this.setState("info.connection", { val: false, ack: true });
83
+ callback();
84
+ }
85
+ async onMessage(obj) {
86
+ var _a;
87
+ if (!(obj == null ? void 0 : obj.command)) {
88
+ return;
91
89
  }
92
- async onMessage(obj) {
93
- if (!obj?.command) {
94
- return;
90
+ switch (obj.command) {
91
+ case "checkConnection": {
92
+ const msg = obj.message;
93
+ const key = ((_a = msg == null ? void 0 : msg.apiKey) == null ? void 0 : _a.trim()) || "";
94
+ if (!key || key.length < 10) {
95
+ this.sendTo(
96
+ obj.from,
97
+ obj.command,
98
+ {
99
+ success: false,
100
+ message: "API key is too short"
101
+ },
102
+ obj.callback
103
+ );
104
+ return;
95
105
  }
96
- switch (obj.command) {
97
- case "checkConnection": {
98
- const msg = obj.message;
99
- const key = msg?.apiKey?.trim() || "";
100
- if (!key || key.length < 10) {
101
- this.sendTo(obj.from, obj.command, {
102
- success: false,
103
- message: "API key is too short",
104
- }, obj.callback);
105
- return;
106
- }
107
- const testClient = new parcel_client_1.ParcelClient(key);
108
- const result = await testClient.testConnection();
109
- this.sendTo(obj.from, obj.command, result, obj.callback);
110
- break;
111
- }
112
- case "addDelivery": {
113
- if (!this.client) {
114
- this.sendTo(obj.from, obj.command, {
115
- success: false,
116
- error_message: "Adapter not initialized",
117
- }, obj.callback);
118
- return;
119
- }
120
- const request = obj.message;
121
- const addResult = await this.client.addDelivery(request);
122
- this.sendTo(obj.from, obj.command, addResult, obj.callback);
123
- if (addResult.success) {
124
- // Trigger immediate poll to pick up the new delivery
125
- void this.poll();
126
- }
127
- break;
128
- }
129
- default:
130
- this.sendTo(obj.from, obj.command, { error: "Unknown command" }, obj.callback);
106
+ const testClient = new import_parcel_client.ParcelClient(key);
107
+ const result = await testClient.testConnection();
108
+ this.sendTo(obj.from, obj.command, result, obj.callback);
109
+ break;
110
+ }
111
+ case "addDelivery": {
112
+ if (!this.client) {
113
+ this.sendTo(
114
+ obj.from,
115
+ obj.command,
116
+ {
117
+ success: false,
118
+ error_message: "Adapter not initialized"
119
+ },
120
+ obj.callback
121
+ );
122
+ return;
131
123
  }
132
- }
133
- async cleanupObsoleteStates() {
134
- const obsoleteStates = [
135
- "summary.json", // removed in 0.2.0
136
- ];
137
- for (const stateId of obsoleteStates) {
138
- const obj = await this.getObjectAsync(stateId);
139
- if (obj) {
140
- await this.delObjectAsync(stateId);
141
- this.log.debug(`Removed obsolete state: ${stateId}`);
142
- }
124
+ const request = obj.message;
125
+ const addResult = await this.client.addDelivery(request);
126
+ this.sendTo(obj.from, obj.command, addResult, obj.callback);
127
+ if (addResult.success) {
128
+ void this.poll();
143
129
  }
130
+ break;
131
+ }
132
+ default:
133
+ this.sendTo(
134
+ obj.from,
135
+ obj.command,
136
+ { error: "Unknown command" },
137
+ obj.callback
138
+ );
144
139
  }
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";
140
+ }
141
+ async cleanupObsoleteStates() {
142
+ const obsoleteStates = [
143
+ "summary.json"
144
+ // removed in 0.2.0
145
+ ];
146
+ for (const stateId of obsoleteStates) {
147
+ const obj = await this.getObjectAsync(stateId);
148
+ if (obj) {
149
+ await this.delObjectAsync(stateId);
150
+ this.log.debug(`Removed obsolete state: ${stateId}`);
151
+ }
169
152
  }
170
- async poll() {
171
- if (this.isPolling || !this.client || !this.stateManager) {
172
- return;
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
- }
186
- this.isPolling = true;
187
- this.lastPollTime = now;
188
- try {
189
- // When keeping delivered packages, use "recent" to get them from API
190
- const autoRemove = this.config.autoRemoveDelivered !== false;
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
- }
198
- await this.setStateAsync("info.connection", { val: true, ack: true });
199
- // Filter deliveries based on auto-remove setting
200
- const visibleDeliveries = autoRemove
201
- ? deliveries.filter((d) => parseInt(d.status_code, 10) !== 0)
202
- : deliveries;
203
- // Update each delivery
204
- const activeIds = [];
205
- for (const delivery of visibleDeliveries) {
206
- const carrierName = await this.client.getCarrierName(delivery.carrier_code);
207
- await this.stateManager.updateDelivery(delivery, carrierName);
208
- activeIds.push(this.stateManager.packageId(delivery));
209
- }
210
- // Cleanup stale deliveries
211
- await this.stateManager.cleanupDeliveries(activeIds);
212
- // Update summary
213
- const summaryDeliveries = autoRemove
214
- ? visibleDeliveries
215
- : deliveries.filter((d) => parseInt(d.status_code, 10) !== 0);
216
- await this.stateManager.updateSummary(summaryDeliveries);
217
- this.log.debug(`Polled ${visibleDeliveries.length} deliveries (${summaryDeliveries.length} active)`);
218
- }
219
- catch (err) {
220
- const error = err;
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
232
- this.log.error("Invalid API key — please check your parcel.app API key");
233
- }
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`);
243
- }
244
- else {
245
- this.log.error(`Poll failed: ${error.message}`);
246
- }
247
- await this.setStateAsync("info.connection", { val: false, ack: true });
248
- }
249
- finally {
250
- this.isPolling = false;
251
- }
153
+ }
154
+ /**
155
+ * Classify an error for deduplication and log-level decisions.
156
+ *
157
+ * @param error The error to classify
158
+ */
159
+ classifyError(error) {
160
+ if (error.code === "RATE_LIMITED") {
161
+ return "RATE_LIMITED";
162
+ }
163
+ if (error.code === "INVALID_API_KEY") {
164
+ return "INVALID_API_KEY";
165
+ }
166
+ if (error.code === "ENOTFOUND" || error.code === "ECONNREFUSED" || error.code === "ECONNRESET" || error.code === "ENETUNREACH" || error.code === "EAI_AGAIN") {
167
+ return "NETWORK";
168
+ }
169
+ if (error.message.includes("timeout") || error.code === "ETIMEDOUT") {
170
+ return "TIMEOUT";
252
171
  }
172
+ return error.code || "UNKNOWN";
173
+ }
174
+ async poll() {
175
+ if (this.isPolling || !this.client || !this.stateManager) {
176
+ return;
177
+ }
178
+ const now = Date.now();
179
+ if (now < this.rateLimitedUntil) {
180
+ const waitMin = Math.ceil((this.rateLimitedUntil - now) / 6e4);
181
+ this.log.debug(
182
+ `Skipping poll \u2014 rate limited for ${waitMin} more minute(s)`
183
+ );
184
+ return;
185
+ }
186
+ if (now - this.lastPollTime < MIN_POLL_GAP_MS) {
187
+ this.log.debug("Skipping poll \u2014 too soon after last poll");
188
+ return;
189
+ }
190
+ this.isPolling = true;
191
+ this.lastPollTime = now;
192
+ try {
193
+ const autoRemove = this.config.autoRemoveDelivered !== false;
194
+ const deliveries = await this.client.getDeliveries(
195
+ autoRemove ? "active" : "recent"
196
+ );
197
+ this.rateLimitedUntil = 0;
198
+ if (this.lastErrorCode) {
199
+ this.log.info("Connection restored");
200
+ this.lastErrorCode = "";
201
+ }
202
+ await this.setStateAsync("info.connection", { val: true, ack: true });
203
+ const visibleDeliveries = autoRemove ? deliveries.filter((d) => parseInt(d.status_code, 10) !== 0) : deliveries;
204
+ const activeIds = [];
205
+ for (const delivery of visibleDeliveries) {
206
+ const carrierName = await this.client.getCarrierName(
207
+ delivery.carrier_code
208
+ );
209
+ await this.stateManager.updateDelivery(delivery, carrierName);
210
+ activeIds.push(this.stateManager.packageId(delivery));
211
+ }
212
+ await this.stateManager.cleanupDeliveries(activeIds);
213
+ const summaryDeliveries = autoRemove ? visibleDeliveries : deliveries.filter((d) => parseInt(d.status_code, 10) !== 0);
214
+ await this.stateManager.updateSummary(summaryDeliveries);
215
+ this.log.debug(
216
+ `Polled ${visibleDeliveries.length} deliveries (${summaryDeliveries.length} active)`
217
+ );
218
+ } catch (err) {
219
+ const error = err;
220
+ const errorCode = this.classifyError(error);
221
+ const isRepeat = errorCode === this.lastErrorCode;
222
+ this.lastErrorCode = errorCode;
223
+ if (error.code === "RATE_LIMITED") {
224
+ const cooldownSec = error.retryAfterSeconds || 5 * 60;
225
+ this.rateLimitedUntil = Date.now() + cooldownSec * 1e3;
226
+ this.log.warn(
227
+ `Rate limit hit \u2014 pausing API requests for ${Math.ceil(cooldownSec / 60)} minute(s)`
228
+ );
229
+ } else if (error.code === "INVALID_API_KEY") {
230
+ this.log.error(
231
+ "Invalid API key \u2014 please check your parcel.app API key"
232
+ );
233
+ } else if (isRepeat) {
234
+ this.log.debug(`Poll failed (ongoing): ${error.message}`);
235
+ } else if (errorCode === "NETWORK") {
236
+ this.log.warn(`Cannot reach parcel.app API \u2014 will keep retrying`);
237
+ } else if (errorCode === "TIMEOUT") {
238
+ this.log.warn(`API request timeout \u2014 will retry next cycle`);
239
+ } else {
240
+ this.log.error(`Poll failed: ${error.message}`);
241
+ }
242
+ await this.setStateAsync("info.connection", { val: false, ack: true });
243
+ } finally {
244
+ this.isPolling = false;
245
+ }
246
+ }
253
247
  }
254
248
  if (require.main !== module) {
255
- module.exports = (options) => new ParcelappAdapter(options);
256
- }
257
- else {
258
- (() => new ParcelappAdapter())();
249
+ module.exports = (options) => new ParcelappAdapter(options);
250
+ } else {
251
+ (() => new ParcelappAdapter())();
259
252
  }
253
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 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\";\nimport \"./lib/types\";\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\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({\n ...options,\n name: \"parcelapp\",\n });\n this.on(\"ready\", this.onReady.bind(this));\n this.on(\"unload\", this.onUnload.bind(this));\n this.on(\"message\", this.onMessage.bind(this));\n }\n\n private async onReady(): Promise<void> {\n 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(\n \"No valid API key configured \u2014 please enter your parcel.app API key in the adapter settings\",\n );\n return;\n }\n\n // Initialize\n this.client = new ParcelClient(apiKey.trim());\n this.stateManager = new StateManager(this);\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(\n MAX_POLL_INTERVAL,\n this.config.pollInterval ?? DEFAULT_POLL_INTERVAL,\n ),\n );\n const intervalMs = interval * 60 * 1000;\n this.pollTimer = this.setInterval(() => void this.poll(), intervalMs);\n\n this.log.info(\n `Parcel tracking started \u2014 polling every ${interval} minutes`,\n );\n }\n\n private onUnload(callback: () => void): void {\n if (this.pollTimer) {\n this.clearInterval(this.pollTimer);\n this.pollTimer = undefined;\n }\n void this.setState(\"info.connection\", { val: false, ack: true });\n callback();\n }\n\n private async onMessage(obj: ioBroker.Message): Promise<void> {\n if (!obj?.command) {\n return;\n }\n\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(\n obj.from,\n obj.command,\n {\n success: false,\n message: \"API key is too short\",\n },\n obj.callback,\n );\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 {\n success: false,\n error_message: \"Adapter not initialized\",\n },\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 // Trigger immediate poll to pick up the new delivery\n void this.poll();\n }\n break;\n }\n default:\n this.sendTo(\n obj.from,\n obj.command,\n { error: \"Unknown command\" },\n obj.callback,\n );\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 === \"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(\n `Skipping poll \u2014 rate limited for ${waitMin} more minute(s)`,\n );\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(\n autoRemove ? \"active\" : \"recent\",\n );\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 // Filter deliveries based on auto-remove setting\n const visibleDeliveries = autoRemove\n ? deliveries.filter((d) => parseInt(d.status_code, 10) !== 0)\n : deliveries;\n\n // Update each delivery\n const activeIds: string[] = [];\n for (const delivery of visibleDeliveries) {\n const carrierName = await this.client.getCarrierName(\n delivery.carrier_code,\n );\n await this.stateManager.updateDelivery(delivery, carrierName);\n activeIds.push(this.stateManager.packageId(delivery));\n }\n\n // Cleanup stale deliveries\n await this.stateManager.cleanupDeliveries(activeIds);\n\n // Update summary\n const summaryDeliveries = autoRemove\n ? visibleDeliveries\n : deliveries.filter((d) => parseInt(d.status_code, 10) !== 0);\n await this.stateManager.updateSummary(summaryDeliveries);\n\n this.log.debug(\n `Polled ${visibleDeliveries.length} deliveries (${summaryDeliveries.length} active)`,\n );\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(\n `Rate limit hit \u2014 pausing API requests for ${Math.ceil(cooldownSec / 60)} minute(s)`,\n );\n } else if (error.code === \"INVALID_API_KEY\") {\n // Always log \u2014 user must fix config\n this.log.error(\n \"Invalid API key \u2014 please check your parcel.app API key\",\n );\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) =>\n new ParcelappAdapter(options);\n} else {\n (() => new ParcelappAdapter())();\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }