iobroker.parcelapp 0.10.3 โ 0.11.0
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 +30 -29
- package/admin/i18n/de.json +11 -1
- package/admin/i18n/en.json +11 -1
- package/admin/i18n/es.json +11 -1
- package/admin/i18n/fr.json +11 -1
- package/admin/i18n/it.json +11 -1
- package/admin/i18n/nl.json +11 -1
- package/admin/i18n/pl.json +11 -1
- package/admin/i18n/pt.json +11 -1
- package/admin/i18n/ru.json +11 -1
- package/admin/i18n/uk.json +11 -1
- package/admin/i18n/zh-cn.json +11 -1
- package/build/lib/coerce.js +16 -1
- package/build/lib/coerce.js.map +2 -2
- package/build/lib/parcel-client.js +21 -4
- package/build/lib/parcel-client.js.map +2 -2
- package/build/lib/state-manager.js +67 -25
- package/build/lib/state-manager.js.map +2 -2
- package/build/main.js +109 -19
- package/build/main.js.map +2 -2
- package/io-package.json +54 -29
- package/package.json +9 -6
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
ioBroker adapter for the [parcel.app](https://parcelapp.net) API. Supports all carriers that parcel.app tracks.
|
|
10
10
|
|
|
11
|
+
๐ **Full user documentation:** [English](docs/en/README.md) ยท [Deutsch](docs/de/README.md) โ setup step by step, every datapoint explained, scripting and FAQ.
|
|
12
|
+
|
|
11
13
|
---
|
|
12
14
|
|
|
13
15
|
## Features
|
|
@@ -35,18 +37,18 @@ For details and how to disable it, see the [Sentry plugin documentation](https:/
|
|
|
35
37
|
|
|
36
38
|
- **Node.js >= 22**
|
|
37
39
|
- **ioBroker js-controller >= 7.2.2**
|
|
38
|
-
- **ioBroker Admin >=
|
|
40
|
+
- **ioBroker Admin >= 8.0.11**
|
|
39
41
|
- **parcel.app Premium subscription** โ required for API access
|
|
40
42
|
|
|
41
43
|
---
|
|
42
44
|
|
|
43
45
|
## Configuration
|
|
44
46
|
|
|
45
|
-
| Option | Description
|
|
46
|
-
| ------------------------- |
|
|
47
|
-
| **API Key** | Your parcel.app API key (get it at [web.parcelapp.net](https://web.parcelapp.net))
|
|
47
|
+
| Option | Description | Default |
|
|
48
|
+
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
|
49
|
+
| **API Key** | Your parcel.app API key (get it at [web.parcelapp.net](https://web.parcelapp.net)) | โ |
|
|
48
50
|
| **Poll Interval** | How often to fetch updates (minutes). parcel.app serves the list from a ~45โ90 min server cache, so shorter intervals mostly reduce the delay until a refresh is noticed. | 10 |
|
|
49
|
-
| **Auto-remove delivered** | Remove delivered packages from states automatically. When disabled, they stay until deleted in parcel.app.
|
|
51
|
+
| **Auto-remove delivered** | Remove delivered packages from states automatically. When disabled, they stay until deleted in parcel.app. | Yes |
|
|
50
52
|
|
|
51
53
|
Status labels (`Delivered`, `In Transit`, โฆ) and delivery estimates (`today`, `tomorrow`, `in X days`) are rendered in the ioBroker system language.
|
|
52
54
|
|
|
@@ -78,13 +80,13 @@ parcelapp.0.
|
|
|
78
80
|
|
|
79
81
|
**Status codes** (`statusCode` โ the primary datapoint for automations):
|
|
80
82
|
|
|
81
|
-
| Code | Meaning
|
|
82
|
-
| ---- |
|
|
83
|
-
| 0 | Delivered
|
|
84
|
-
| 1 | Frozen
|
|
85
|
-
| 2 | In Transit
|
|
86
|
-
| 3 | Awaiting Pickup
|
|
87
|
-
| 4 | Out for Delivery | -1
|
|
83
|
+
| Code | Meaning | Code | Meaning |
|
|
84
|
+
| ---- | ---------------- | ---- | ------------------------------------------------------ |
|
|
85
|
+
| 0 | Delivered | 5 | Not Found |
|
|
86
|
+
| 1 | Frozen | 6 | Delivery Attempt Failed |
|
|
87
|
+
| 2 | In Transit | 7 | Exception |
|
|
88
|
+
| 3 | Awaiting Pickup | 8 | Info Received |
|
|
89
|
+
| 4 | Out for Delivery | -1 | Unknown (unexpected API value โ package stays visible) |
|
|
88
90
|
|
|
89
91
|
---
|
|
90
92
|
|
|
@@ -140,7 +142,22 @@ sendTo("parcelapp.0", "addDelivery", {
|
|
|
140
142
|
Placeholder for the next version (at the beginning of the line):
|
|
141
143
|
### **WORK IN PROGRESS**
|
|
142
144
|
-->
|
|
143
|
-
### 0.
|
|
145
|
+
### 0.11.0 (2026-09-04)
|
|
146
|
+
|
|
147
|
+
- Fixed: Since version 0.10.3 the Test Connection button gave no response at all, and packages added from a script never showed up โ both work again.
|
|
148
|
+
- Fixed: On installations that already existed, the summary datapoints and the connection state kept their old English names โ an update now reaches every datapoint.
|
|
149
|
+
- New: Datapoints whose name alone does not explain them now carry a short description in the object tree, in all eleven languages.
|
|
150
|
+
- New: Detailed user documentation in English and German, shown in the ioBroker documentation portal.
|
|
151
|
+
- Fixed: Two settings from much older versions were still listed in the instance configuration although nothing used them any more.
|
|
152
|
+
|
|
153
|
+
### 0.10.4 (2026-09-02)
|
|
154
|
+
|
|
155
|
+
- Fixed: A malformed reply from parcel.app (empty body or a broken delivery entry) no longer aborts the poll with a cryptic internal message โ it is reported as an API problem and retried next poll.
|
|
156
|
+
- Fixed: A brief ioBroker database hiccup while marking the connection as online was mistaken for a parcel.app failure and switched the connection indicator to red.
|
|
157
|
+
- Fixed: Scripts that call checkConnection with a non-text API key now receive the regular "API key is too short" reply instead of an internal failure.
|
|
158
|
+
- Improved: Control characters in texts coming from parcel.app (carrier names, status notes) are now stripped completely before they reach the states.
|
|
159
|
+
|
|
160
|
+
### 0.10.3 (2026-08-27) โ stable
|
|
144
161
|
|
|
145
162
|
- Fixed: Stopping or restarting the adapter was cut short โ the stopped instance kept claiming a live connection to parcel.app instead of showing as disconnected.
|
|
146
163
|
|
|
@@ -152,22 +169,6 @@ sendTo("parcelapp.0", "addDelivery", {
|
|
|
152
169
|
|
|
153
170
|
- Internal refactoring. No user-facing changes.
|
|
154
171
|
|
|
155
|
-
### 0.10.0 (2026-07-08)
|
|
156
|
-
|
|
157
|
-
- Fixed: the admin "Test Connection" button now reports real failures โ before, it always showed "Ok" even with a wrong API key.
|
|
158
|
-
- Fixed: a package's last-updated timestamp no longer jumps to the restart time after an adapter restart โ it only changes when tracking data actually changed.
|
|
159
|
-
- Fixed: a stalled API response can no longer freeze polling until a manual restart โ every request now has a hard 60-second deadline.
|
|
160
|
-
- Fixed: a failed adapter start now triggers an automatic restart instead of leaving the adapter idle until restarted by hand.
|
|
161
|
-
- Changed: recurring errors such as a wrong API key are logged once instead of every poll cycle, and stopping the adapter no longer leaves a red error line in the log.
|
|
162
|
-
- Changed: short ioBroker database hiccups no longer flip the connection indicator โ it now reflects only the parcel.app connection.
|
|
163
|
-
- Changed: the fallback package name ("Package โฆ") is localized like all other texts, and the adapter is listed under a fitting admin category (misc-data).
|
|
164
|
-
- Changed: the automatic poll after adding a delivery now respects the one-minute pacing, so bulk-adds can no longer exhaust the hourly API budget.
|
|
165
|
-
|
|
166
|
-
### 0.9.0 (2026-06-23) โ stable
|
|
167
|
-
|
|
168
|
-
- Fixed: tracked packages could disappear from the object tree after a temporary update error or an unexpected API response โ a package is now kept until parcel.app actually stops returning it.
|
|
169
|
-
- Changed: multi-day delivery windows now show the date on each side (e.g. `12-06 14:30 - 12-08 18:30`) instead of looking same-day; out-of-range or reversed dates no longer produce a misleading window.
|
|
170
|
-
|
|
171
172
|
[Older changelogs can be found there](CHANGELOG_OLD.md)
|
|
172
173
|
|
|
173
174
|
## Support
|
package/admin/i18n/de.json
CHANGED
|
@@ -44,5 +44,15 @@
|
|
|
44
44
|
"status_6": "Zustellversuch gescheitert",
|
|
45
45
|
"status_7": "Ausnahme",
|
|
46
46
|
"status_8": "Registriert",
|
|
47
|
-
"packageName": "Paket %s"
|
|
47
|
+
"packageName": "Paket %s",
|
|
48
|
+
"descInfoConnection": "Wahr, solange der Adapter die parcel.app-API erreicht.",
|
|
49
|
+
"descStatusCode": "Numerischer Status von parcel.app (0-8); -1 bedeutet, dass der Wert nicht gedeutet werden konnte.",
|
|
50
|
+
"descExtraInfo": "Zusatzangabe, die der Zusteller benรถtigt, etwa Postleitzahl oder E-Mail-Adresse.",
|
|
51
|
+
"descDeliveryWindow": "Erwartetes Zustellfenster; ein รผber mehrere Tage reichendes Fenster trรคgt auf beiden Seiten das Datum.",
|
|
52
|
+
"descDeliveryEstimate": "Erwartete Zustellung in Worten - heute, morgen oder in einigen Tagen.",
|
|
53
|
+
"descLastEvent": "Jรผngste Sendungsmeldung des Zustellers mit Datum.",
|
|
54
|
+
"descLastUpdated": "Zeitpunkt der letzten รnderung der Sendungsdaten - nicht der letzte Abruf.",
|
|
55
|
+
"descActiveCount": "Anzahl der Pakete, die noch nicht zugestellt sind.",
|
|
56
|
+
"descTodayCount": "Anzahl der Pakete, die heute erwartet werden.",
|
|
57
|
+
"descSummaryDeliveryWindow": "Gemeinsames Fenster aller heute erwarteten Pakete - frรผhester Beginn bis spรคtestes Ende."
|
|
48
58
|
}
|
package/admin/i18n/en.json
CHANGED
|
@@ -44,5 +44,15 @@
|
|
|
44
44
|
"status_6": "Delivery Attempt Failed",
|
|
45
45
|
"status_7": "Exception",
|
|
46
46
|
"status_8": "Info Received",
|
|
47
|
-
"packageName": "Package %s"
|
|
47
|
+
"packageName": "Package %s",
|
|
48
|
+
"descInfoConnection": "True while the adapter can reach the parcel.app API.",
|
|
49
|
+
"descStatusCode": "Numeric status from parcel.app (0-8); -1 means the value could not be interpreted.",
|
|
50
|
+
"descExtraInfo": "Additional detail the carrier needs, such as a postal code or e-mail address.",
|
|
51
|
+
"descDeliveryWindow": "Expected delivery time window; a window spanning several days carries the date on both sides.",
|
|
52
|
+
"descDeliveryEstimate": "Expected delivery in words - today, tomorrow or in a number of days.",
|
|
53
|
+
"descLastEvent": "Most recent tracking event reported by the carrier, with its date.",
|
|
54
|
+
"descLastUpdated": "When the tracking data last changed - not when the adapter last polled.",
|
|
55
|
+
"descActiveCount": "Number of packages that have not been delivered yet.",
|
|
56
|
+
"descTodayCount": "Number of packages expected to arrive today.",
|
|
57
|
+
"descSummaryDeliveryWindow": "Combined window of all packages expected today - earliest start to latest end."
|
|
48
58
|
}
|
package/admin/i18n/es.json
CHANGED
|
@@ -44,5 +44,15 @@
|
|
|
44
44
|
"status_6": "Intento de entrega fallido",
|
|
45
45
|
"status_7": "Excepciรณn",
|
|
46
46
|
"status_8": "Registrado",
|
|
47
|
-
"packageName": "Paquete %s"
|
|
47
|
+
"packageName": "Paquete %s",
|
|
48
|
+
"descInfoConnection": "Verdadero mientras el adaptador pueda acceder a la API de parcel.app.",
|
|
49
|
+
"descStatusCode": "Estado numรฉrico de parcel.app (0-8); -1 significa que el valor no se pudo interpretar.",
|
|
50
|
+
"descExtraInfo": "Dato adicional que necesita el transportista, por ejemplo un cรณdigo postal o una direcciรณn de correo.",
|
|
51
|
+
"descDeliveryWindow": "Ventana de entrega prevista; una ventana de varios dรญas lleva la fecha a ambos lados.",
|
|
52
|
+
"descDeliveryEstimate": "Entrega prevista en palabras - hoy, maรฑana o dentro de unos dรญas.",
|
|
53
|
+
"descLastEvent": "Evento de seguimiento mรกs reciente comunicado por el transportista, con su fecha.",
|
|
54
|
+
"descLastUpdated": "Momento del รบltimo cambio de los datos de seguimiento - no de la รบltima consulta.",
|
|
55
|
+
"descActiveCount": "Nรบmero de paquetes que aรบn no se han entregado.",
|
|
56
|
+
"descTodayCount": "Nรบmero de paquetes que se esperan hoy.",
|
|
57
|
+
"descSummaryDeliveryWindow": "Ventana combinada de todos los paquetes esperados hoy - del inicio mรกs temprano al final mรกs tardรญo."
|
|
48
58
|
}
|
package/admin/i18n/fr.json
CHANGED
|
@@ -44,5 +44,15 @@
|
|
|
44
44
|
"status_6": "รchec de la livraison",
|
|
45
45
|
"status_7": "Exception",
|
|
46
46
|
"status_8": "Enregistrรฉ",
|
|
47
|
-
"packageName": "Colis %s"
|
|
47
|
+
"packageName": "Colis %s",
|
|
48
|
+
"descInfoConnection": "Vrai tant que l'adaptateur parvient ร joindre l'API parcel.app.",
|
|
49
|
+
"descStatusCode": "Statut numรฉrique de parcel.app (0-8) ; -1 signifie que la valeur n'a pas pu รชtre interprรฉtรฉe.",
|
|
50
|
+
"descExtraInfo": "Information complรฉmentaire exigรฉe par le transporteur, par exemple un code postal ou une adresse e-mail.",
|
|
51
|
+
"descDeliveryWindow": "Crรฉneau de livraison prรฉvu ; un crรฉneau sur plusieurs jours porte la date de chaque cรดtรฉ.",
|
|
52
|
+
"descDeliveryEstimate": "Livraison prรฉvue en toutes lettres - aujourd'hui, demain ou dans quelques jours.",
|
|
53
|
+
"descLastEvent": "Dernier รฉvรฉnement de suivi communiquรฉ par le transporteur, avec sa date.",
|
|
54
|
+
"descLastUpdated": "Moment du dernier changement des donnรฉes de suivi - pas de la derniรจre interrogation.",
|
|
55
|
+
"descActiveCount": "Nombre de colis qui ne sont pas encore livrรฉs.",
|
|
56
|
+
"descTodayCount": "Nombre de colis attendus aujourd'hui.",
|
|
57
|
+
"descSummaryDeliveryWindow": "Crรฉneau combinรฉ de tous les colis attendus aujourd'hui - du dรฉbut le plus tรดt ร la fin la plus tardive."
|
|
48
58
|
}
|
package/admin/i18n/it.json
CHANGED
|
@@ -44,5 +44,15 @@
|
|
|
44
44
|
"status_6": "Consegna fallita",
|
|
45
45
|
"status_7": "Eccezione",
|
|
46
46
|
"status_8": "Registrato",
|
|
47
|
-
"packageName": "Pacco %s"
|
|
47
|
+
"packageName": "Pacco %s",
|
|
48
|
+
"descInfoConnection": "Vero finchรฉ l'adattatore riesce a raggiungere l'API di parcel.app.",
|
|
49
|
+
"descStatusCode": "Stato numerico di parcel.app (0-8); -1 significa che il valore non รจ stato interpretabile.",
|
|
50
|
+
"descExtraInfo": "Dato aggiuntivo richiesto dal corriere, ad esempio un codice postale o un indirizzo e-mail.",
|
|
51
|
+
"descDeliveryWindow": "Finestra di consegna prevista; una finestra su piรน giorni riporta la data su entrambi i lati.",
|
|
52
|
+
"descDeliveryEstimate": "Consegna prevista a parole - oggi, domani o fra alcuni giorni.",
|
|
53
|
+
"descLastEvent": "Evento di tracciamento piรน recente comunicato dal corriere, con la data.",
|
|
54
|
+
"descLastUpdated": "Momento dell'ultima variazione dei dati di tracciamento - non dell'ultima interrogazione.",
|
|
55
|
+
"descActiveCount": "Numero di pacchi non ancora consegnati.",
|
|
56
|
+
"descTodayCount": "Numero di pacchi attesi oggi.",
|
|
57
|
+
"descSummaryDeliveryWindow": "Finestra combinata di tutti i pacchi attesi oggi - dall'inizio piรน precoce alla fine piรน tarda."
|
|
48
58
|
}
|
package/admin/i18n/nl.json
CHANGED
|
@@ -44,5 +44,15 @@
|
|
|
44
44
|
"status_6": "Bezorgpoging mislukt",
|
|
45
45
|
"status_7": "Uitzondering",
|
|
46
46
|
"status_8": "Geregistreerd",
|
|
47
|
-
"packageName": "Pakket %s"
|
|
47
|
+
"packageName": "Pakket %s",
|
|
48
|
+
"descInfoConnection": "Waar zolang de adapter de parcel.app-API kan bereiken.",
|
|
49
|
+
"descStatusCode": "Numerieke status van parcel.app (0-8); -1 betekent dat de waarde niet kon worden geduid.",
|
|
50
|
+
"descExtraInfo": "Aanvullend gegeven dat de vervoerder nodig heeft, zoals postcode of e-mailadres.",
|
|
51
|
+
"descDeliveryWindow": "Verwacht bezorgvenster; een venster over meerdere dagen toont aan beide kanten de datum.",
|
|
52
|
+
"descDeliveryEstimate": "Verwachte bezorging in woorden - vandaag, morgen of over een aantal dagen.",
|
|
53
|
+
"descLastEvent": "Meest recente zendingsmelding van de vervoerder, met datum.",
|
|
54
|
+
"descLastUpdated": "Moment waarop de zendinggegevens voor het laatst zijn gewijzigd - niet de laatste opvraging.",
|
|
55
|
+
"descActiveCount": "Aantal pakketten dat nog niet is bezorgd.",
|
|
56
|
+
"descTodayCount": "Aantal pakketten dat vandaag wordt verwacht.",
|
|
57
|
+
"descSummaryDeliveryWindow": "Gecombineerd venster van alle vandaag verwachte pakketten - vroegste start tot laatste einde."
|
|
48
58
|
}
|
package/admin/i18n/pl.json
CHANGED
|
@@ -44,5 +44,15 @@
|
|
|
44
44
|
"status_6": "Nieudana prรณba dorฤczenia",
|
|
45
45
|
"status_7": "Wyjฤ
tek",
|
|
46
46
|
"status_8": "Zarejestrowane",
|
|
47
|
-
"packageName": "Paczka %s"
|
|
47
|
+
"packageName": "Paczka %s",
|
|
48
|
+
"descInfoConnection": "Prawda, dopรณki adapter moลผe poลฤ
czyฤ siฤ z API parcel.app.",
|
|
49
|
+
"descStatusCode": "Status liczbowy z parcel.app (0-8); -1 oznacza, ลผe wartoลci nie daลo siฤ zinterpretowaฤ.",
|
|
50
|
+
"descExtraInfo": "Dodatkowa informacja wymagana przez przewoลบnika, np. kod pocztowy lub adres e-mail.",
|
|
51
|
+
"descDeliveryWindow": "Przewidywane okno dostawy; okno obejmujฤ
ce kilka dni zawiera datฤ po obu stronach.",
|
|
52
|
+
"descDeliveryEstimate": "Przewidywana dostawa sลownie - dziล, jutro lub za kilka dni.",
|
|
53
|
+
"descLastEvent": "Najnowsze zdarzenie ลledzenia zgลoszone przez przewoลบnika, wraz z datฤ
.",
|
|
54
|
+
"descLastUpdated": "Moment ostatniej zmiany danych ลledzenia - nie ostatniego odpytania.",
|
|
55
|
+
"descActiveCount": "Liczba przesyลek, ktรณre nie zostaลy jeszcze dorฤczone.",
|
|
56
|
+
"descTodayCount": "Liczba przesyลek oczekiwanych dzisiaj.",
|
|
57
|
+
"descSummaryDeliveryWindow": "Wspรณlne okno wszystkich przesyลek oczekiwanych dzisiaj - od najwczeลniejszego poczฤ
tku do najpรณลบniejszego koลca."
|
|
48
58
|
}
|
package/admin/i18n/pt.json
CHANGED
|
@@ -44,5 +44,15 @@
|
|
|
44
44
|
"status_6": "Tentativa de entrega falhou",
|
|
45
45
|
"status_7": "Exceรงรฃo",
|
|
46
46
|
"status_8": "Registado",
|
|
47
|
-
"packageName": "Encomenda %s"
|
|
47
|
+
"packageName": "Encomenda %s",
|
|
48
|
+
"descInfoConnection": "Verdadeiro enquanto o adaptador conseguir alcanรงar a API do parcel.app.",
|
|
49
|
+
"descStatusCode": "Estado numรฉrico do parcel.app (0-8); -1 significa que o valor nรฃo pรดde ser interpretado.",
|
|
50
|
+
"descExtraInfo": "Detalhe adicional exigido pela transportadora, como cรณdigo postal ou endereรงo de e-mail.",
|
|
51
|
+
"descDeliveryWindow": "Janela de entrega prevista; uma janela que abrange vรกrios dias mostra a data de ambos os lados.",
|
|
52
|
+
"descDeliveryEstimate": "Entrega prevista por extenso - hoje, amanhรฃ ou dentro de alguns dias.",
|
|
53
|
+
"descLastEvent": "Evento de rastreio mais recente comunicado pela transportadora, com a data.",
|
|
54
|
+
"descLastUpdated": "Momento da รบltima alteraรงรฃo dos dados de rastreio - nรฃo da รบltima consulta.",
|
|
55
|
+
"descActiveCount": "Nรบmero de encomendas que ainda nรฃo foram entregues.",
|
|
56
|
+
"descTodayCount": "Nรบmero de encomendas previstas para hoje.",
|
|
57
|
+
"descSummaryDeliveryWindow": "Janela combinada de todas as encomendas previstas para hoje - do inรญcio mais cedo ao fim mais tarde."
|
|
48
58
|
}
|
package/admin/i18n/ru.json
CHANGED
|
@@ -44,5 +44,15 @@
|
|
|
44
44
|
"status_6": "ะะตัะดะฐัะฝะฐั ะดะพััะฐะฒะบะฐ",
|
|
45
45
|
"status_7": "ะัะบะปััะตะฝะธะต",
|
|
46
46
|
"status_8": "ะะฐัะตะณะธัััะธัะพะฒะฐะฝะพ",
|
|
47
|
-
"packageName": "ะะพััะปะบะฐ %s"
|
|
47
|
+
"packageName": "ะะพััะปะบะฐ %s",
|
|
48
|
+
"descInfoConnection": "ะััะธะฝะฐ, ะฟะพะบะฐ ะฐะดะฐะฟัะตั ะผะพะถะตั ัะฒัะทะฐัััั ั API parcel.app.",
|
|
49
|
+
"descStatusCode": "ะงะธัะปะพะฒะพะน ััะฐััั ะพั parcel.app (0-8); -1 ะพะทะฝะฐัะฐะตั, ััะพ ะทะฝะฐัะตะฝะธะต ะฝะต ัะดะฐะปะพัั ัะฐัะฟะพะทะฝะฐัั.",
|
|
50
|
+
"descExtraInfo": "ะะพะฟะพะปะฝะธัะตะปัะฝัะต ะดะฐะฝะฝัะต, ะฝัะถะฝัะต ะฟะตัะตะฒะพะทัะธะบั, ะฝะฐะฟัะธะผะตั ะฟะพััะพะฒัะน ะธะฝะดะตะบั ะธะปะธ ะฐะดัะตั ัะปะตะบััะพะฝะฝะพะน ะฟะพััั.",
|
|
51
|
+
"descDeliveryWindow": "ะะถะธะดะฐะตะผะพะต ะฒัะตะผะตะฝะฝะพะต ะพะบะฝะพ ะดะพััะฐะฒะบะธ; ะพะบะฝะพ, ะพั
ะฒะฐััะฒะฐััะตะต ะฝะตัะบะพะปัะบะพ ะดะฝะตะน, ัะพะดะตัะถะธั ะดะฐัั ั ะพะฑะตะธั
ััะพัะพะฝ.",
|
|
52
|
+
"descDeliveryEstimate": "ะะถะธะดะฐะตะผะฐั ะดะพััะฐะฒะบะฐ ัะปะพะฒะฐะผะธ - ัะตะณะพะดะฝั, ะทะฐะฒััะฐ ะธะปะธ ัะตัะตะท ะฝะตัะบะพะปัะบะพ ะดะฝะตะน.",
|
|
53
|
+
"descLastEvent": "ะะพัะปะตะดะฝะตะต ัะพะฑััะธะต ะพััะปะตะถะธะฒะฐะฝะธั ะพั ะฟะตัะตะฒะพะทัะธะบะฐ ั ะดะฐัะพะน.",
|
|
54
|
+
"descLastUpdated": "ะะพะผะตะฝั ะฟะพัะปะตะดะฝะตะณะพ ะธะทะผะตะฝะตะฝะธั ะดะฐะฝะฝัั
ะพััะปะตะถะธะฒะฐะฝะธั - ะฝะต ะฒัะตะผั ะฟะพัะปะตะดะฝะตะณะพ ะพะฟัะพัะฐ.",
|
|
55
|
+
"descActiveCount": "ะะพะปะธัะตััะฒะพ ะฟะพััะปะพะบ, ะบะพัะพััะต ะตัั ะฝะต ะดะพััะฐะฒะปะตะฝั.",
|
|
56
|
+
"descTodayCount": "ะะพะปะธัะตััะฒะพ ะฟะพััะปะพะบ, ะพะถะธะดะฐะตะผัั
ัะตะณะพะดะฝั.",
|
|
57
|
+
"descSummaryDeliveryWindow": "ะะฑัะตะต ะพะบะฝะพ ะฒัะตั
ะพะถะธะดะฐะตะผัั
ัะตะณะพะดะฝั ะฟะพััะปะพะบ - ะพั ัะฐะผะพะณะพ ัะฐะฝะฝะตะณะพ ะฝะฐัะฐะปะฐ ะดะพ ัะฐะผะพะณะพ ะฟะพะทะดะฝะตะณะพ ะบะพะฝัะฐ."
|
|
48
58
|
}
|
package/admin/i18n/uk.json
CHANGED
|
@@ -44,5 +44,15 @@
|
|
|
44
44
|
"status_6": "ะะตะฒะดะฐะปะฐ ัะฟัะพะฑะฐ ะดะพััะฐะฒะบะธ",
|
|
45
45
|
"status_7": "ะะธะฝััะพะบ",
|
|
46
46
|
"status_8": "ะะฐัะตััััะพะฒะฐะฝะพ",
|
|
47
|
-
"packageName": "ะะพัะธะปะบะฐ %s"
|
|
47
|
+
"packageName": "ะะพัะธะปะบะฐ %s",
|
|
48
|
+
"descInfoConnection": "ะััะธะฝะฐ, ะดะพะบะธ ะฐะดะฐะฟัะตั ะผะพะถะต ะดัััะฐัะธัั ะดะพ API parcel.app.",
|
|
49
|
+
"descStatusCode": "ะงะธัะปะพะฒะธะน ััะฐััั ะฒัะด parcel.app (0-8); -1 ะพะทะฝะฐัะฐั, ัะพ ะทะฝะฐัะตะฝะฝั ะฝะต ะฒะดะฐะปะพัั ัะพะทะฟัะทะฝะฐัะธ.",
|
|
50
|
+
"descExtraInfo": "ะะพะดะฐัะบะพะฒั ะดะฐะฝั, ะฟะพัััะฑะฝั ะฟะตัะตะฒัะทะฝะธะบะพะฒั, ะฝะฐะฟัะธะบะปะฐะด ะฟะพััะพะฒะธะน ัะฝะดะตะบั ะฐะฑะพ ะฐะดัะตัะฐ ะตะปะตะบััะพะฝะฝะพั ะฟะพััะธ.",
|
|
51
|
+
"descDeliveryWindow": "ะััะบัะฒะฐะฝะต ะฒัะบะฝะพ ะดะพััะฐะฒะบะธ; ะฒัะบะฝะพ, ัะพ ะพั
ะพะฟะปัั ะบัะปัะบะฐ ะดะฝัะฒ, ะผัััะธัั ะดะฐัั ะท ะพะฑะพั
ะฑะพะบัะฒ.",
|
|
52
|
+
"descDeliveryEstimate": "ะััะบัะฒะฐะฝะฐ ะดะพััะฐะฒะบะฐ ัะปะพะฒะฐะผะธ - ััะพะณะพะดะฝั, ะทะฐะฒััะฐ ะฐะฑะพ ะทะฐ ะบัะปัะบะฐ ะดะฝัะฒ.",
|
|
53
|
+
"descLastEvent": "ะะฐะนะฝะพะฒััะฐ ะฟะพะดัั ะฒัะดััะตะถะตะฝะฝั ะฒัะด ะฟะตัะตะฒัะทะฝะธะบะฐ, ัะฐะทะพะผ ัะท ะดะฐัะพั.",
|
|
54
|
+
"descLastUpdated": "ะะพะผะตะฝั ะพััะฐะฝะฝัะพั ะทะผัะฝะธ ะดะฐะฝะธั
ะฒัะดััะตะถะตะฝะฝั - ะฝะต ะพััะฐะฝะฝัะพะณะพ ะพะฟะธััะฒะฐะฝะฝั.",
|
|
55
|
+
"descActiveCount": "ะัะปัะบัััั ะฟะพัะธะปะพะบ, ัะบั ัะต ะฝะต ะดะพััะฐะฒะปะตะฝะพ.",
|
|
56
|
+
"descTodayCount": "ะัะปัะบัััั ะฟะพัะธะปะพะบ, ัะพ ะพััะบัััััั ััะพะณะพะดะฝั.",
|
|
57
|
+
"descSummaryDeliveryWindow": "ะกะฟัะปัะฝะต ะฒัะบะฝะพ ะฒััั
ะพััะบัะฒะฐะฝะธั
ััะพะณะพะดะฝั ะฟะพัะธะปะพะบ - ะฒัะด ะฝะฐะนัะฐะฝััะพะณะพ ะฟะพัะฐัะบั ะดะพ ะฝะฐะนะฟัะทะฝััะพะณะพ ะบัะฝัั."
|
|
48
58
|
}
|
package/admin/i18n/zh-cn.json
CHANGED
|
@@ -44,5 +44,15 @@
|
|
|
44
44
|
"status_6": "ๆดพ้ๅคฑ่ดฅ",
|
|
45
45
|
"status_7": "ๅผๅธธ",
|
|
46
46
|
"status_8": "ๅทฒ็ป่ฎฐ",
|
|
47
|
-
"packageName": "ๅ
่ฃน %s"
|
|
47
|
+
"packageName": "ๅ
่ฃน %s",
|
|
48
|
+
"descInfoConnection": "้้
ๅจ่ฝๅค่ฟๆฅ parcel.app API ๆถไธบ็ใ",
|
|
49
|
+
"descStatusCode": "ๆฅ่ช parcel.app ็ๆฐๅญ็ถๆ๏ผ0-8๏ผ๏ผ-1 ่กจ็คบ่ฏฅๅผๆ ๆณ่ฏๅซใ",
|
|
50
|
+
"descExtraInfo": "ๆฟ่ฟๅๆ้็้ๅ ไฟกๆฏ๏ผไพๅฆ้ฎๆฟ็ผ็ ๆ็ตๅญ้ฎไปถๅฐๅใ",
|
|
51
|
+
"descDeliveryWindow": "้ข่ฎกๆดพ้ๆถ้ดๆฎต๏ผ่ทจ่ถๅคๅคฉ็ๆถ้ดๆฎตไผๅจไธคไพงๆพ็คบๆฅๆใ",
|
|
52
|
+
"descDeliveryEstimate": "ไปฅๆๅญ่กจ็คบ็้ข่ฎกๆดพ้ๆถ้ดโโไปๅคฉใๆๅคฉๆ่ฅๅนฒๅคฉๅใ",
|
|
53
|
+
"descLastEvent": "ๆฟ่ฟๅๆฅๅ็ๆๆฐ่ท่ธชไบไปถๅๅ
ถๆฅๆใ",
|
|
54
|
+
"descLastUpdated": "่ท่ธชๆฐๆฎๆๅไธๆฌกๅ็ๅๅ็ๆถ้ดโโไธๆฏๆๅไธๆฌก่ฝฎ่ฏข็ๆถ้ดใ",
|
|
55
|
+
"descActiveCount": "ๅฐๆชๆดพ้็ๅ
่ฃนๆฐ้ใ",
|
|
56
|
+
"descTodayCount": "้ข่ฎกไปๅคฉ้่พพ็ๅ
่ฃนๆฐ้ใ",
|
|
57
|
+
"descSummaryDeliveryWindow": "ไปๅคฉ้ข่ฎก้่พพ็ๆๆๅ
่ฃน็ๅๅนถๆถ้ดๆฎตโโๆๆฉๅผๅงๅฐๆๆ็ปๆใ"
|
|
48
58
|
}
|
package/build/lib/coerce.js
CHANGED
|
@@ -83,7 +83,22 @@ function coerceClampedInt(raw, min, max, defaultValue) {
|
|
|
83
83
|
return Math.max(min, Math.min(max, Math.floor(n)));
|
|
84
84
|
}
|
|
85
85
|
function oneLine(value) {
|
|
86
|
-
|
|
86
|
+
var _a;
|
|
87
|
+
let out = "";
|
|
88
|
+
let inRun = false;
|
|
89
|
+
for (const ch of value) {
|
|
90
|
+
const code = (_a = ch.codePointAt(0)) != null ? _a : 0;
|
|
91
|
+
if (code < 32 || code === 127 || code === 8232 || code === 8233) {
|
|
92
|
+
if (!inRun) {
|
|
93
|
+
out += " ";
|
|
94
|
+
inRun = true;
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
out += ch;
|
|
98
|
+
inRun = false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
87
102
|
}
|
|
88
103
|
// Annotate the CommonJS export names for ESM import in node:
|
|
89
104
|
0 && (module.exports = {
|
package/build/lib/coerce.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/coerce.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Boundary coercion helpers for external API data.\n *\n * The parcel.app API is documented but field types still drift in practice\n * (rare success-flag returned as `\"true\"` string, occasional null where a\n * number is expected). These helpers guard against NaN/Infinity/non-string\n * values reaching ioBroker states.\n */\n\n// Strict decimal regex \u2014 only optional minus sign + digits + optional fractional part.\n// Rejects HEX (`0x...`), exponential (`1e3`), Infinity, NaN, leading/trailing whitespace.\n// Hassemu (E8 in v1.9.0) hardened the same coerce-helper this way; homewizard\n// adopted it in v0.7.2 (D8). Consistent with both adapters.\nconst DECIMAL_NUMBER_RE = /^-?\\d+(\\.\\d+)?$/;\n\n/**\n * Coerce to a finite number or null.\n * Accepts numbers directly; parses strict decimal strings; rejects NaN, Infinity,\n * HEX (`0x...`) and exponential notation (`1e3`).\n *\n * @param value Unknown external value\n */\nexport function coerceFiniteNumber(value: unknown): number | null {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : null;\n }\n if (typeof value === \"string\" && DECIMAL_NUMBER_RE.test(value)) {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\n/**\n * Coerce a parcel.app `success` flag. The API returns a real boolean in normal\n * operation, but the guard accepts common string/number encodings (`1`, `\"true\"`,\n * `\"1\"`) so a one-off drift doesn't break the entire poll cycle.\n *\n * @param v Value to interpret as a success flag\n */\nexport function isTrueish(v: unknown): boolean {\n if (typeof v === \"boolean\") {\n return v;\n }\n if (typeof v === \"number\") {\n return v === 1;\n }\n if (typeof v === \"string\") {\n const s = v.toLowerCase();\n return s === \"true\" || s === \"1\";\n }\n return false;\n}\n\n/**\n * Extract a log-friendly message from a thrown / rejected value. Centralizes the\n * `err instanceof Error ? err.message : String(err)` pattern that otherwise\n * gets repeated at every catch-site. Plain objects are JSON-stringified so a\n * `[object Object]` log is avoided when callers throw bag-of-fields.\n *\n * @param err Caught value of unknown shape (Error, string, undefined, ...).\n */\nexport function errText(err: unknown): string {\n if (err instanceof Error) {\n return err.message;\n }\n if (err === null) {\n return \"null\";\n }\n if (err === undefined) {\n return \"undefined\";\n }\n if (typeof err === \"string\") {\n return err;\n }\n if (typeof err === \"number\" || typeof err === \"boolean\" || typeof err === \"bigint\") {\n return String(err);\n }\n if (typeof err === \"symbol\") {\n // JSON.stringify(Symbol()) returns undefined (it does NOT throw), so the\n // catch below would never run and the declared `string` return would be a\n // lie. String(symbol) is the only safe conversion \u2014 `${symbol}` throws.\n return String(err);\n }\n // Plain objects would otherwise stringify to \"[object Object]\". Prefer JSON so\n // the log is at least diagnosable; circular structures fall back to the tag.\n try {\n // A function, or an object whose toJSON drops everything, also yields\n // undefined here \u2014 fall back rather than returning a non-string.\n return JSON.stringify(err) ?? Object.prototype.toString.call(err);\n } catch {\n return Object.prototype.toString.call(err);\n }\n}\n\n/**\n * v0.4.2 (X5): coerce an admin-config integer setting (number-or-string)\n * to a finite, clamped integer. Returns `defaultValue` for non-finite\n * input \u2014 guards against `setInterval(fn, NaN)` tight-loops when the\n * config field happens to come back as a string from the admin UI.\n *\n * @param raw Raw value from `this.config.<field>`.\n * @param min Inclusive lower bound.\n * @param max Inclusive upper bound.\n * @param defaultValue Fallback when raw is missing or unparseable.\n */\nexport function coerceClampedInt(raw: unknown, min: number, max: number, defaultValue: number): number {\n const n = typeof raw === \"number\" ? raw : typeof raw === \"string\" ? parseFloat(raw) : NaN;\n if (!Number.isFinite(n)) {\n return defaultValue;\n }\n return Math.max(min, Math.min(max, Math.floor(n)));\n}\n\n/**\n * Collapse control-character runs
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,MAAM,oBAAoB;AASnB,SAAS,mBAAmB,OAA+B;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,EAC1C;AACA,MAAI,OAAO,UAAU,YAAY,kBAAkB,KAAK,KAAK,GAAG;AAC9D,UAAM,IAAI,OAAO,KAAK;AACtB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AASO,SAAS,UAAU,GAAqB;AAC7C,MAAI,OAAO,MAAM,WAAW;AAC1B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,MAAM,UAAU;AACzB,WAAO,MAAM;AAAA,EACf;AACA,MAAI,OAAO,MAAM,UAAU;AACzB,UAAM,IAAI,EAAE,YAAY;AACxB,WAAO,MAAM,UAAU,MAAM;AAAA,EAC/B;AACA,SAAO;AACT;AAUO,SAAS,QAAQ,KAAsB;AA9D9C;AA+DE,MAAI,eAAe,OAAO;AACxB,WAAO,IAAI;AAAA,EACb;AACA,MAAI,QAAQ,MAAM;AAChB,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,QAAW;AACrB,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,UAAU;AAC3B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,aAAa,OAAO,QAAQ,UAAU;AAClF,WAAO,OAAO,GAAG;AAAA,EACnB;AACA,MAAI,OAAO,QAAQ,UAAU;AAI3B,WAAO,OAAO,GAAG;AAAA,EACnB;AAGA,MAAI;AAGF,YAAO,UAAK,UAAU,GAAG,MAAlB,YAAuB,OAAO,UAAU,SAAS,KAAK,GAAG;AAAA,EAClE,QAAQ;AACN,WAAO,OAAO,UAAU,SAAS,KAAK,GAAG;AAAA,EAC3C;AACF;AAaO,SAAS,iBAAiB,KAAc,KAAa,KAAa,cAA8B;AACrG,QAAM,IAAI,OAAO,QAAQ,WAAW,MAAM,OAAO,QAAQ,WAAW,WAAW,GAAG,IAAI;AACtF,MAAI,CAAC,OAAO,SAAS,CAAC,GAAG;AACvB,WAAO;AAAA,EACT;AACA,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC;AACnD;
|
|
4
|
+
"sourcesContent": ["/**\n * Boundary coercion helpers for external API data.\n *\n * The parcel.app API is documented but field types still drift in practice\n * (rare success-flag returned as `\"true\"` string, occasional null where a\n * number is expected). These helpers guard against NaN/Infinity/non-string\n * values reaching ioBroker states.\n */\n\n// Strict decimal regex \u2014 only optional minus sign + digits + optional fractional part.\n// Rejects HEX (`0x...`), exponential (`1e3`), Infinity, NaN, leading/trailing whitespace.\n// Hassemu (E8 in v1.9.0) hardened the same coerce-helper this way; homewizard\n// adopted it in v0.7.2 (D8). Consistent with both adapters.\nconst DECIMAL_NUMBER_RE = /^-?\\d+(\\.\\d+)?$/;\n\n/**\n * Coerce to a finite number or null.\n * Accepts numbers directly; parses strict decimal strings; rejects NaN, Infinity,\n * HEX (`0x...`) and exponential notation (`1e3`).\n *\n * @param value Unknown external value\n */\nexport function coerceFiniteNumber(value: unknown): number | null {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : null;\n }\n if (typeof value === \"string\" && DECIMAL_NUMBER_RE.test(value)) {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\n/**\n * Coerce a parcel.app `success` flag. The API returns a real boolean in normal\n * operation, but the guard accepts common string/number encodings (`1`, `\"true\"`,\n * `\"1\"`) so a one-off drift doesn't break the entire poll cycle.\n *\n * @param v Value to interpret as a success flag\n */\nexport function isTrueish(v: unknown): boolean {\n if (typeof v === \"boolean\") {\n return v;\n }\n if (typeof v === \"number\") {\n return v === 1;\n }\n if (typeof v === \"string\") {\n const s = v.toLowerCase();\n return s === \"true\" || s === \"1\";\n }\n return false;\n}\n\n/**\n * Extract a log-friendly message from a thrown / rejected value. Centralizes the\n * `err instanceof Error ? err.message : String(err)` pattern that otherwise\n * gets repeated at every catch-site. Plain objects are JSON-stringified so a\n * `[object Object]` log is avoided when callers throw bag-of-fields.\n *\n * @param err Caught value of unknown shape (Error, string, undefined, ...).\n */\nexport function errText(err: unknown): string {\n if (err instanceof Error) {\n return err.message;\n }\n if (err === null) {\n return \"null\";\n }\n if (err === undefined) {\n return \"undefined\";\n }\n if (typeof err === \"string\") {\n return err;\n }\n if (typeof err === \"number\" || typeof err === \"boolean\" || typeof err === \"bigint\") {\n return String(err);\n }\n if (typeof err === \"symbol\") {\n // JSON.stringify(Symbol()) returns undefined (it does NOT throw), so the\n // catch below would never run and the declared `string` return would be a\n // lie. String(symbol) is the only safe conversion \u2014 `${symbol}` throws.\n return String(err);\n }\n // Plain objects would otherwise stringify to \"[object Object]\". Prefer JSON so\n // the log is at least diagnosable; circular structures fall back to the tag.\n try {\n // A function, or an object whose toJSON drops everything, also yields\n // undefined here \u2014 fall back rather than returning a non-string.\n return JSON.stringify(err) ?? Object.prototype.toString.call(err);\n } catch {\n return Object.prototype.toString.call(err);\n }\n}\n\n/**\n * v0.4.2 (X5): coerce an admin-config integer setting (number-or-string)\n * to a finite, clamped integer. Returns `defaultValue` for non-finite\n * input \u2014 guards against `setInterval(fn, NaN)` tight-loops when the\n * config field happens to come back as a string from the admin UI.\n *\n * @param raw Raw value from `this.config.<field>`.\n * @param min Inclusive lower bound.\n * @param max Inclusive upper bound.\n * @param defaultValue Fallback when raw is missing or unparseable.\n */\nexport function coerceClampedInt(raw: unknown, min: number, max: number, defaultValue: number): number {\n const n = typeof raw === \"number\" ? raw : typeof raw === \"string\" ? parseFloat(raw) : NaN;\n if (!Number.isFinite(n)) {\n return defaultValue;\n }\n return Math.max(min, Math.min(max, Math.floor(n)));\n}\n\n/**\n * Collapse control-character runs in an untrusted string to a single space\n * before it is interpolated into a log line \u2014 prevents log-injection (a forged\n * second log line) and smuggled terminal escapes from external values\n * (tracking number, carrier code, raw API body, collision raw-key with its NUL\n * separator, \u2026). Covers the whole C0 range (CR/LF/TAB/NUL/VT/FF, ESC, \u2026), DEL\n * and the Unicode line separators U+2028/U+2029. Fleet convention (hassemu /\n * hueemu); widened from the line-break set in v0.10.0 (I10) to the full range\n * in 0.10.4 \u2014 as a character loop, because a regex literal with control\n * characters is rejected by the lint (no-control-regex).\n *\n * @param value Untrusted string to flatten for single-line logging.\n */\nexport function oneLine(value: string): string {\n let out = \"\";\n let inRun = false;\n for (const ch of value) {\n const code = ch.codePointAt(0) ?? 0;\n if (code < 0x20 || code === 0x7f || code === 0x2028 || code === 0x2029) {\n if (!inRun) {\n out += \" \";\n inRun = true;\n }\n } else {\n out += ch;\n inRun = false;\n }\n }\n return out;\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,MAAM,oBAAoB;AASnB,SAAS,mBAAmB,OAA+B;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,EAC1C;AACA,MAAI,OAAO,UAAU,YAAY,kBAAkB,KAAK,KAAK,GAAG;AAC9D,UAAM,IAAI,OAAO,KAAK;AACtB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AASO,SAAS,UAAU,GAAqB;AAC7C,MAAI,OAAO,MAAM,WAAW;AAC1B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,MAAM,UAAU;AACzB,WAAO,MAAM;AAAA,EACf;AACA,MAAI,OAAO,MAAM,UAAU;AACzB,UAAM,IAAI,EAAE,YAAY;AACxB,WAAO,MAAM,UAAU,MAAM;AAAA,EAC/B;AACA,SAAO;AACT;AAUO,SAAS,QAAQ,KAAsB;AA9D9C;AA+DE,MAAI,eAAe,OAAO;AACxB,WAAO,IAAI;AAAA,EACb;AACA,MAAI,QAAQ,MAAM;AAChB,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,QAAW;AACrB,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,UAAU;AAC3B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,aAAa,OAAO,QAAQ,UAAU;AAClF,WAAO,OAAO,GAAG;AAAA,EACnB;AACA,MAAI,OAAO,QAAQ,UAAU;AAI3B,WAAO,OAAO,GAAG;AAAA,EACnB;AAGA,MAAI;AAGF,YAAO,UAAK,UAAU,GAAG,MAAlB,YAAuB,OAAO,UAAU,SAAS,KAAK,GAAG;AAAA,EAClE,QAAQ;AACN,WAAO,OAAO,UAAU,SAAS,KAAK,GAAG;AAAA,EAC3C;AACF;AAaO,SAAS,iBAAiB,KAAc,KAAa,KAAa,cAA8B;AACrG,QAAM,IAAI,OAAO,QAAQ,WAAW,MAAM,OAAO,QAAQ,WAAW,WAAW,GAAG,IAAI;AACtF,MAAI,CAAC,OAAO,SAAS,CAAC,GAAG;AACvB,WAAO;AAAA,EACT;AACA,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC;AACnD;AAeO,SAAS,QAAQ,OAAuB;AA/H/C;AAgIE,MAAI,MAAM;AACV,MAAI,QAAQ;AACZ,aAAW,MAAM,OAAO;AACtB,UAAM,QAAO,QAAG,YAAY,CAAC,MAAhB,YAAqB;AAClC,QAAI,OAAO,MAAQ,SAAS,OAAQ,SAAS,QAAU,SAAS,MAAQ;AACtE,UAAI,CAAC,OAAO;AACV,eAAO;AACP,gBAAQ;AAAA,MACV;AAAA,IACF,OAAO;AACL,aAAO;AACP,cAAQ;AAAA,IACV;AAAA,EACF;AACA,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -120,7 +120,7 @@ class ParcelClient {
|
|
|
120
120
|
* @param filterMode Filter active or recent deliveries
|
|
121
121
|
*/
|
|
122
122
|
async getDeliveries(filterMode = "active") {
|
|
123
|
-
var _a, _b, _c;
|
|
123
|
+
var _a, _b, _c, _d;
|
|
124
124
|
const response = await this.request("GET", `/deliveries/?filter_mode=${filterMode}`, true);
|
|
125
125
|
if (!response || typeof response !== "object") {
|
|
126
126
|
(_a = this.log) == null ? void 0 : _a.debug(`API drift: malformed response (got ${typeof response})`);
|
|
@@ -138,19 +138,36 @@ class ParcelClient {
|
|
|
138
138
|
(_c = this.log) == null ? void 0 : _c.debug(`API drift: deliveries not an array (got ${typeof response.deliveries})`);
|
|
139
139
|
throw apiError("API error: deliveries not an array", "API_ERROR");
|
|
140
140
|
}
|
|
141
|
+
const entries = response.deliveries;
|
|
142
|
+
const bad = entries.findIndex((d) => d === null || typeof d !== "object" || Array.isArray(d));
|
|
143
|
+
if (bad !== -1) {
|
|
144
|
+
(_d = this.log) == null ? void 0 : _d.debug(
|
|
145
|
+
`API drift: deliveries[${bad}] is not an object (got ${Array.isArray(entries[bad]) ? "array" : typeof entries[bad]})`
|
|
146
|
+
);
|
|
147
|
+
throw apiError("API error: malformed delivery entry", "API_ERROR");
|
|
148
|
+
}
|
|
141
149
|
return response.deliveries;
|
|
142
150
|
}
|
|
143
151
|
/**
|
|
144
152
|
* Add a new delivery to parcel.app.
|
|
145
153
|
*
|
|
146
154
|
* Error style: transport/HTTP failures reject with {@link ApiError}; a 2xx
|
|
147
|
-
* body is returned
|
|
148
|
-
*
|
|
155
|
+
* body is returned as-is once it is a plain object (its fields are not
|
|
156
|
+
* validated) โ `success: false` is passed through unchanged because sendTo
|
|
157
|
+
* callers receive this object verbatim.
|
|
149
158
|
*
|
|
150
159
|
* @param delivery The delivery to add
|
|
151
160
|
*/
|
|
152
161
|
async addDelivery(delivery) {
|
|
153
|
-
|
|
162
|
+
var _a;
|
|
163
|
+
const response = await this.request("POST", "/add-delivery/", true, delivery);
|
|
164
|
+
if (!response || typeof response !== "object" || Array.isArray(response)) {
|
|
165
|
+
(_a = this.log) == null ? void 0 : _a.debug(
|
|
166
|
+
`API drift: malformed add-delivery response (got ${Array.isArray(response) ? "array" : typeof response})`
|
|
167
|
+
);
|
|
168
|
+
throw apiError("API error: malformed response", "API_ERROR");
|
|
169
|
+
}
|
|
170
|
+
return response;
|
|
154
171
|
}
|
|
155
172
|
/** Get carrier names (cached after first call; concurrent callers share one fetch) */
|
|
156
173
|
async getCarrierNames() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/parcel-client.ts"],
|
|
4
|
-
"sourcesContent": ["import * as http from \"node:http\";\nimport * as https from \"node:https\";\nimport { errText, isTrueish, oneLine } from \"./coerce\";\nimport type {\n ApiError,\n ApiErrorCode,\n ParcelApiResponse,\n ParcelDelivery,\n AddDeliveryRequest,\n AddDeliveryResponse,\n CarrierMap,\n} from \"./types\";\n\nconst API_BASE = \"https://api.parcel.app/external\";\n/** Socket IDLE timeout \u2014 fires only when the connection goes silent. */\nconst REQUEST_TIMEOUT = 15_000;\n/**\n * Hard per-request deadline. The socket idle timeout above never fires against\n * a trickle response (a byte every few seconds), which would otherwise pin\n * `isPolling` forever and silently stop the poll loop until a restart. This\n * timer caps the TOTAL request duration regardless of socket activity.\n */\nconst REQUEST_DEADLINE_MS = 60_000;\n/** Shared Retry-After clamps (used by the client parser and the adapter cooldown). */\nexport const RETRY_AFTER_MAX_SEC = 24 * 3600;\nexport const RETRY_AFTER_DEFAULT_SEC = 5 * 60;\n/** Max chars of a response body quoted into a debug log line. */\nconst BODY_SNIPPET_LEN = 200;\n\n/**\n * v0.4.3: optional logger injected by the adapter so the HTTPS client can\n * trace its own request/response lifecycle. When omitted (e.g. in tests),\n * every `this.log?.debug(...)` call is a no-op \u2014 keeps the bare-`apiKey`\n * constructor signature backward-compatible.\n */\nexport interface ParcelClientLogger {\n /** Adapter debug log. Called per request/response outcome (drift, status, parse, oversize) \u2014 low-frequency tracing. */\n debug(message: string): void;\n}\n\n/**\n * Timeout overrides. Production never passes these \u2014 the defaults\n * ({@link REQUEST_TIMEOUT} / {@link REQUEST_DEADLINE_MS}) apply. Same seam idea\n * as the `baseUrl` parameter: without it the two watchdogs below could only be\n * exercised by a test that waits 15 respectively 60 seconds, so they stayed\n * untested \u2014 and an untested watchdog is exactly the one that silently stops\n * working (test audit 2026-08-22, finding C14).\n */\nexport interface ParcelClientTimeouts {\n /** Socket idle timeout (ms). */\n idleMs?: number;\n /** Hard per-request deadline (ms). */\n deadlineMs?: number;\n}\n/**\n * v0.4.2 (P9): hard cap on response body size. parcel.app deliveries lists\n * are tiny (~1 kB per package, max ~50 packages = 50 kB), so a 1 MiB cap is\n * 20\u00D7 the realistic max while still defending against a runaway response.\n */\nconst MAX_BODY_BYTES = 1 << 20; // 1 MiB\n\n/**\n * Build an {@link ApiError} carrying a typed `code` (and optional extra fields\n * such as `retryAfterSeconds`). Centralizes the `new Error(...)` + `err.code`\n * pattern; the `ApiErrorCode` union makes a typo on either side of the\n * client\u2194adapter contract a compile error.\n *\n * @param message Human-readable error message.\n * @param code Machine-readable error code used by the adapter for classification.\n * @param extra Optional additional own-properties to attach to the error.\n */\nfunction apiError(message: string, code: ApiErrorCode, extra?: Record<string, unknown>): ApiError {\n const err = new Error(message) as ApiError;\n err.code = code;\n if (extra) {\n Object.assign(err, extra);\n }\n return err;\n}\n\n/** HTTP client for the parcel.app API */\nexport class ParcelClient {\n private apiKey: string;\n private carrierCache: CarrierMap | null = null;\n /**\n * v0.7.2: in-flight fetch for the carrier list. The per-delivery updates run\n * in parallel (Promise.all) and each resolves carrier names \u2014 without this\n * mutex the first poll with N packages fired N identical concurrent fetches\n * of the static carrier-list file (and a persistently failing endpoint was\n * retried N times per poll). Same pattern as beszel's auth mutex (B1).\n */\n private carrierFetchInFlight: Promise<CarrierMap> | null = null;\n /**\n * v0.4.2 (P1): per-request AbortController. `cancelAll()` aborts every\n * pending HTTPS request \u2014 called from the adapter's `onUnload` so a slow\n * parcel.app endpoint can't keep the adapter alive past js-controller's\n * 4-second kill deadline.\n */\n private readonly inflight = new Set<AbortController>();\n /** v0.4.3: optional logger for the HTTPS-layer trace. See {@link ParcelClientLogger}. */\n private readonly log?: ParcelClientLogger;\n /** API base URL. Overridable so tests can run the real `request()` against a local mock server. */\n private readonly baseUrl: string;\n /** Socket idle timeout in ms \u2014 same seam idea as {@link baseUrl}, see {@link ParcelClientTimeouts}. */\n private readonly idleTimeoutMs: number;\n /** Hard per-request deadline in ms \u2014 see {@link ParcelClientTimeouts}. */\n private readonly deadlineMs: number;\n\n /**\n * @param apiKey The parcel.app API key\n * @param log Optional adapter logger for HTTPS-layer trace (v0.4.3)\n * @param baseUrl API base URL \u2014 defaults to the production endpoint; overridden in tests\n * @param timeouts Timeout overrides \u2014 production always uses the defaults\n */\n constructor(\n apiKey: string,\n log?: ParcelClientLogger,\n baseUrl: string = API_BASE,\n timeouts: ParcelClientTimeouts = {},\n ) {\n this.apiKey = apiKey;\n this.log = log;\n this.baseUrl = baseUrl;\n this.idleTimeoutMs = timeouts.idleMs ?? REQUEST_TIMEOUT;\n this.deadlineMs = timeouts.deadlineMs ?? REQUEST_DEADLINE_MS;\n }\n\n /**\n * v0.10.0 (L3): once cancelAll ran, the client is terminal \u2014 a request\n * STARTED after the abort (e.g. the carrier fetch kicked off by a poll\n * batch that was already past getDeliveries at unload) must not open a\n * fresh HTTPS connection that could outlive js-controller's 4s kill\n * deadline. `request()` rejects immediately when this is set.\n */\n private cancelled = false;\n\n /**\n * v0.4.2 (P1): abort every in-flight HTTPS request and refuse new ones.\n * Idempotent.\n */\n cancelAll(): void {\n // v0.4.3 (A12): trace the shutdown anchor so the adapter log shows\n // exactly how many HTTPS calls were aborted at unload.\n this.log?.debug(`cancelAll: aborting ${this.inflight.size} inflight requests`);\n this.cancelled = true;\n for (const ctrl of this.inflight) {\n ctrl.abort();\n }\n }\n\n /**\n * Fetch deliveries from parcel.app.\n *\n * Error style: rejects with a code-bearing {@link ApiError} on every failure\n * (HTTP status, drift, transport) \u2014 callers classify via `error.code`.\n *\n * @param filterMode Filter active or recent deliveries\n */\n async getDeliveries(filterMode: \"active\" | \"recent\" = \"active\"): Promise<ParcelDelivery[]> {\n const response = await this.request<ParcelApiResponse>(\"GET\", `/deliveries/?filter_mode=${filterMode}`, true);\n\n // API-drift guard: response may be null or a non-object\n if (!response || typeof response !== \"object\") {\n // v0.4.3 (A11a): trace malformed-response drift before throwing.\n this.log?.debug(`API drift: malformed response (got ${typeof response})`);\n throw apiError(\"API error: malformed response\", \"API_ERROR\");\n }\n\n if (!isTrueish(response.success)) {\n // v0.10.0 (M6): the external error_message is flattened + capped before it\n // reaches any log sink \u2014 it bubbles into the poll error-log via the Error\n // message, and an unsanitized multi-line value would forge log lines.\n const rawMsg =\n typeof response.error_message === \"string\" ? oneLine(response.error_message).slice(0, BODY_SNIPPET_LEN) : \"\";\n // v0.4.3 (A11b): trace API-side error before throwing. An invalid key is\n // reported via HTTP 401 (handled in request()), not via a body field \u2014\n // so a `success:false` body is always a generic API_ERROR.\n this.log?.debug(`API drift: success=false, msg='${rawMsg}'`);\n throw apiError(`API error: ${rawMsg || \"UNKNOWN\"}`, \"API_ERROR\");\n }\n\n // API-drift guard. An absent OR null `deliveries` is the API's \"no\n // deliveries\" shape \u2192 [] (zero active packages is the common state; a false\n // throw there would flip the adapter to disconnected on every poll). Only a\n // PRESENT, NON-NULL, wrong-typed value (string/number/object/boolean) is\n // real drift \u2014 throw so the poll keeps the existing states stale instead of\n // reading garbage as \"zero deliveries\" and deleting every package's states.\n if (response.deliveries == null) {\n return [];\n }\n if (!Array.isArray(response.deliveries)) {\n this.log?.debug(`API drift: deliveries not an array (got ${typeof response.deliveries})`);\n throw apiError(\"API error: deliveries not an array\", \"API_ERROR\");\n }\n return response.deliveries;\n }\n\n /**\n * Add a new delivery to parcel.app.\n *\n * Error style: transport/HTTP failures reject with {@link ApiError}; a 2xx\n * body is returned RAW and never validated \u2014 `success: false` is passed\n * through unchanged because sendTo callers receive this object verbatim.\n *\n * @param delivery The delivery to add\n */\n async addDelivery(delivery: AddDeliveryRequest): Promise<AddDeliveryResponse> {\n return this.request<AddDeliveryResponse>(\"POST\", \"/add-delivery/\", true, delivery);\n }\n\n /** Get carrier names (cached after first call; concurrent callers share one fetch) */\n async getCarrierNames(): Promise<CarrierMap> {\n if (this.carrierCache) {\n return this.carrierCache;\n }\n // v0.7.2: share one in-flight fetch between the parallel per-delivery\n // updates instead of firing N identical requests on the first poll.\n if (!this.carrierFetchInFlight) {\n this.carrierFetchInFlight = this.fetchCarrierNames().finally(() => {\n this.carrierFetchInFlight = null;\n });\n }\n return this.carrierFetchInFlight;\n }\n\n /**\n * One actual carrier-list fetch. Failure \u2192 empty map, NOT cached \u2014 retried by\n * the next update batch (the mutex above only dedupes CONCURRENT callers, so\n * a poll with several 25er batches may retry once per batch; the endpoint is\n * a static, unauthenticated file without a rate limit).\n */\n private async fetchCarrierNames(): Promise<CarrierMap> {\n try {\n const raw = await this.request<unknown>(\"GET\", \"/supported_carriers.json\", false);\n // API-drift guard: must be a plain object (not null, array, or primitive)\n if (raw && typeof raw === \"object\" && !Array.isArray(raw)) {\n // v0.9.0 (C6): keep only string-valued entries instead of asserting the\n // whole object is Record<string,string>. A drifted non-string value is\n // dropped here, so the cache is honestly typed (no `as CarrierMap`).\n const clean: CarrierMap = {};\n for (const [code, name] of Object.entries(raw)) {\n if (typeof name === \"string\") {\n clean[code] = name;\n }\n }\n this.carrierCache = clean;\n // v0.4.3 (D1): trace the one-time cache fill so a successful warm-up\n // is visible in the debug log (happens once per adapter restart).\n this.log?.debug(`carriers: fetched ${Object.keys(this.carrierCache).length} entries`);\n return this.carrierCache;\n }\n // v0.4.3 (D3): non-object drift \u2014 supported_carriers.json returned\n // something that isn't an object. Empty map is returned, NOT cached.\n this.log?.debug(\n `carriers: drift (got ${Array.isArray(raw) ? \"array\" : typeof raw}, expected object), kept empty`,\n );\n return {};\n } catch (err) {\n // v0.4.3 (D2): trace the fetch-fail so the empty-map fallback isn't\n // silent. NOT cached \u2014 next poll retries; the trace then shows the\n // retry, too. Without this the user sees carrier codes instead of\n // names with no log entry explaining why.\n this.log?.debug(`carriers: fetch failed (kept empty, will retry): ${errText(err)}`);\n // Return empty map but don't cache it \u2014 allow retry next time\n return {};\n }\n }\n\n /**\n * Resolve a carrier code to a display name.\n *\n * @param carrierCode The carrier code from API\n */\n async getCarrierName(carrierCode: unknown): Promise<string> {\n // API-drift guard: non-string codes fall back to \"UNKNOWN\"\n if (typeof carrierCode !== \"string\" || carrierCode.length === 0) {\n // v0.4.3 (D4): trace non-string code drift. Helps diagnose \"all my\n // packages show UNKNOWN carrier\" reports.\n this.log?.debug(`getCarrierName: non-string code (got ${typeof carrierCode}), returning UNKNOWN`);\n return \"UNKNOWN\";\n }\n const carriers = await this.getCarrierNames();\n const mapped = carriers[carrierCode];\n return typeof mapped === \"string\" && mapped.length > 0 ? mapped : carrierCode.toUpperCase();\n }\n\n /**\n * Test if the API key is valid.\n *\n * Error style: never throws \u2014 failures are folded into the returned\n * `{ success: false, message }` result object.\n */\n async testConnection(): Promise<{ success: boolean; message: string }> {\n try {\n await this.getDeliveries(\"active\");\n return { success: true, message: \"Connection successful\" };\n } catch (err) {\n const error = err as Error & { code?: string };\n if (error.code === \"INVALID_API_KEY\") {\n return { success: false, message: \"Invalid API key\" };\n }\n return { success: false, message: error.message };\n }\n }\n\n /**\n * Execute an HTTP request against the parcel.app API.\n *\n * @param method HTTP method\n * @param path API path\n * @param authenticated Whether to send the API key\n * @param body Optional request body\n */\n private request<T>(method: string, path: string, authenticated: boolean, body?: unknown): Promise<T> {\n // v0.4.3 (A0): start timestamp for elapsed-ms in the success/timeout/error\n // log lines. One LOC, no behavior change.\n const startedAt = Date.now();\n // v0.4.3 (A1): trace request entry. ~144 calls/day at the default 10-min\n // poll interval \u2014 acceptable at debug.\n this.log?.debug(`HTTP ${method} ${path}`);\n return new Promise((resolve, reject) => {\n // v0.10.0 (L3): terminal after cancelAll \u2014 a request started AFTER the\n // shutdown abort must not open a fresh connection.\n if (this.cancelled) {\n this.log?.debug(`HTTP ${method} ${path} refused \u2014 client cancelled`);\n reject(apiError(\"Client cancelled\", \"ABORTED\"));\n return;\n }\n // v0.4.2 (E3): URL-shape validation defensive \u2014 paths are hardcoded\n // upstream but a future caller could pass garbage; surface a clear\n // error class instead of a TypeError thrown sync from the executor.\n let url: URL;\n try {\n url = new URL(`${this.baseUrl}${path}`);\n } catch {\n // v0.4.3 (A10): trace invalid-URL drift before throwing.\n this.log?.debug(`HTTP invalid URL: ${this.baseUrl}${path}`);\n reject(apiError(`Invalid URL: ${this.baseUrl}${path}`, \"INVALID_URL\"));\n return;\n }\n\n const headers: Record<string, string> = {};\n if (authenticated) {\n headers[\"api-key\"] = this.apiKey;\n }\n if (body) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n const options: https.RequestOptions = {\n hostname: url.hostname,\n port: url.port || 443,\n path: url.pathname + url.search,\n method,\n headers,\n timeout: this.idleTimeoutMs,\n };\n\n // v0.4.2 (P1): per-request AbortController. `cancelAll()` (called\n // from `onUnload`) aborts everything pending without waiting for\n // the configured timeout.\n const ctrl = new AbortController();\n this.inflight.add(ctrl);\n // Marks the request as finished for the deadline listener below \u2014\n // every terminal path runs cleanup().\n let settled = false;\n const cleanup = (): void => {\n settled = true;\n this.inflight.delete(ctrl);\n };\n\n // Pick transport from the URL protocol so tests can run the real\n // request() against a local http mock server; production is always https.\n const transportRequest: (\n opts: https.RequestOptions,\n callback: (res: http.IncomingMessage) => void,\n ) => http.ClientRequest = url.protocol === \"http:\" ? http.request : https.request;\n\n const req = transportRequest(options, res => {\n const chunks: Buffer[] = [];\n let bodyBytes = 0;\n let oversized = false;\n\n res.on(\"error\", err => {\n cleanup();\n reject(err);\n });\n res.on(\"data\", (chunk: Buffer) => {\n // Not covered by a test on purpose: this branch only runs for a chunk\n // that was already buffered when `req.destroy()` below fired, i.e. a\n // delivery race no test can trigger deterministically. A test that\n // hits it \"usually\" would be exactly the kind of flaky check the\n // 2026-08-22 audit removed elsewhere.\n if (oversized) {\n return;\n }\n bodyBytes += chunk.length;\n // v0.4.2 (P9): drop oversized responses so a compromised or\n // misconfigured endpoint can't OOM the adapter. Reject with the\n // stable BODY_TOO_LARGE code here, then destroy WITHOUT an error so\n // req.on(\"error\") doesn't fire a second, codeless rejection (the\n // earlier `req.destroy(Error)` preempted the end-handler's code).\n if (bodyBytes > MAX_BODY_BYTES) {\n oversized = true;\n // v0.4.3 (A9): trace the oversize-drop before destroying.\n this.log?.debug(`HTTP body oversized ${path}: dropping at ${bodyBytes}B`);\n cleanup();\n reject(apiError(\"Response body too large\", \"BODY_TOO_LARGE\"));\n req.destroy();\n return;\n }\n chunks.push(chunk);\n });\n res.on(\"end\", () => {\n if (oversized) {\n return; // already cleaned up + rejected in the data handler\n }\n cleanup();\n // The MAX_BODY_BYTES cap above bounds `chunks`, so concat stays well\n // under Buffer's max length and toString won't throw here.\n const raw = Buffer.concat(chunks).toString(\"utf-8\");\n\n if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {\n const httpError = ParcelClient.mapHttpStatusError(\n res.statusCode,\n res.statusMessage,\n res.headers[\"retry-after\"],\n );\n // v0.4.3 (A3/A4): trace 4xx/5xx with code, retry-after and body-snippet.\n this.log?.debug(\n `HTTP ${method} ${path} \u2192 ${res.statusCode} ${httpError.code}` +\n `${httpError.retryAfterSeconds !== undefined ? ` retry-after=${httpError.retryAfterSeconds}s` : \"\"}` +\n ` (body=${oneLine(raw.substring(0, BODY_SNIPPET_LEN))})`,\n );\n reject(httpError);\n return;\n }\n\n try {\n const parsed = JSON.parse(raw) as T;\n // v0.4.3 (A2): trace successful response with elapsed-ms + bytes.\n this.log?.debug(`HTTP ${method} ${path} \u2192 ${res.statusCode} (${Date.now() - startedAt}ms, ${bodyBytes}B)`);\n resolve(parsed);\n } catch {\n // v0.4.3 (A8): trace JSON parse-fail with snippet (debug only).\n this.log?.debug(`HTTP JSON parse fail ${path}: ${oneLine(raw.substring(0, BODY_SNIPPET_LEN))}`);\n // v0.9.0 (S1): keep the raw body OUT of the Error message \u2014 it\n // bubbles to a poll error-log; a malformed PII-bearing body must\n // not reach error level. The snippet stays in the debug line above.\n reject(apiError(`JSON parse error (${raw.length} bytes)`, \"PARSE_ERROR\"));\n }\n });\n });\n\n // v0.10.0 (M4): arm the hard deadline via AbortSignal.timeout \u2014 an\n // unref'd platform timer (never keeps the process alive, no adapter\n // context needed). Destroying with a TIMEOUT-coded ApiError routes\n // through req.on(\"error\") below, which rejects + cleans up \u2014 a trickle\n // response (a byte every few seconds) can no longer pin the poll loop.\n AbortSignal.timeout(this.deadlineMs).addEventListener(\"abort\", () => {\n if (settled) {\n return; // request finished long ago \u2014 nothing to kill, nothing to log\n }\n this.log?.debug(`HTTP deadline ${method} ${path} (${Date.now() - startedAt}ms > ${this.deadlineMs}ms)`);\n req.destroy(apiError(`Request deadline exceeded (${this.deadlineMs / 1000}s)`, \"TIMEOUT\"));\n });\n\n ctrl.signal.addEventListener(\"abort\", () => {\n // v0.4.3: A6 deliberately omitted \u2014 `req.destroy(Error)` propagates\n // through `req.on(\"error\")` below where A7 already logs it.\n // v0.10.0 (M1): carries the ABORTED code so the adapter routes an\n // expected shutdown-abort to debug instead of an error log line.\n req.destroy(apiError(\"Request aborted\", \"ABORTED\"));\n });\n\n req.on(\"timeout\", () => {\n req.destroy();\n cleanup();\n // v0.4.3 (A5): trace timeout with elapsed-ms.\n this.log?.debug(`HTTP timeout ${method} ${path} (${Date.now() - startedAt}ms)`);\n reject(apiError(\"Request timeout\", \"TIMEOUT\"));\n });\n\n req.on(\"error\", err => {\n cleanup();\n // v0.4.3 (A7): trace network / abort / TLS / DNS errors with elapsed.\n // Also catches the abort case (req.destroy(ApiError)) \u2014 A6 deliberately\n // not emitted to avoid double-log.\n this.log?.debug(`HTTP error ${method} ${path} (${Date.now() - startedAt}ms): ${err.message}`);\n reject(err);\n });\n\n // v0.10.0 (I8): a synchronous throw from stringify/write/end (circular\n // body, stream state) must not strand the AbortController in `inflight`\n // \u2014 cancelAll's invariant is \"inflight mirrors live requests exactly\".\n try {\n if (body) {\n req.write(JSON.stringify(body));\n }\n req.end();\n } catch (err) {\n cleanup();\n req.destroy();\n reject(apiError(`Request write failed: ${errText(err)}`, \"API_ERROR\"));\n }\n });\n }\n\n /**\n * Map a non-2xx HTTP status to its {@link ApiError}. Pure \u2014 extracted from\n * the end-handler so the 401/403/429 rules read in isolation (v0.10.0, L17).\n *\n * @param statusCode HTTP status code (non-2xx)\n * @param statusMessage HTTP status message\n * @param retryAfterHeader Raw Retry-After header value (429 only)\n */\n private static mapHttpStatusError(\n statusCode: number,\n statusMessage: string | undefined,\n retryAfterHeader: string | undefined,\n ): ApiError {\n if (statusCode === 429) {\n // v0.4.2 (P6): clamp Retry-After. Bogus values (0, negative, NaN) fall\n // back to the default; extreme values are capped.\n const retryAfter = parseInt(retryAfterHeader || \"\", 10);\n const retryAfterSeconds =\n Number.isFinite(retryAfter) && retryAfter > 0\n ? Math.min(RETRY_AFTER_MAX_SEC, retryAfter)\n : RETRY_AFTER_DEFAULT_SEC;\n return apiError(\"Rate limit exceeded\", \"RATE_LIMITED\", { retryAfterSeconds });\n }\n // v0.4.2 (P3): split 401 (invalid key) from 403 (permission / no premium).\n // Adapter treats them differently \u2014 INVALID_API_KEY says \"fix the key\",\n // FORBIDDEN says \"fix the account\".\n const code: ApiErrorCode = statusCode === 401 ? \"INVALID_API_KEY\" : statusCode === 403 ? \"FORBIDDEN\" : \"HTTP_ERROR\";\n return apiError(`HTTP ${statusCode}: ${statusMessage}`, code);\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAAsB;AACtB,YAAuB;AACvB,oBAA4C;AAW5C,MAAM,WAAW;AAEjB,MAAM,kBAAkB;AAOxB,MAAM,sBAAsB;AAErB,MAAM,sBAAsB,KAAK;AACjC,MAAM,0BAA0B,IAAI;AAE3C,MAAM,mBAAmB;AAgCzB,MAAM,iBAAiB,KAAK;AAY5B,SAAS,SAAS,SAAiB,MAAoB,OAA2C;AAChG,QAAM,MAAM,IAAI,MAAM,OAAO;AAC7B,MAAI,OAAO;AACX,MAAI,OAAO;AACT,WAAO,OAAO,KAAK,KAAK;AAAA,EAC1B;AACA,SAAO;AACT;AAGO,MAAM,aAAa;AAAA,EAChB;AAAA,EACA,eAAkC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQlC,uBAAmD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO1C,WAAW,oBAAI,IAAqB;AAAA;AAAA,EAEpC;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQjB,YACE,QACA,KACA,UAAkB,UAClB,WAAiC,CAAC,GAClC;AAvHJ;AAwHI,SAAK,SAAS;AACd,SAAK,MAAM;AACX,SAAK,UAAU;AACf,SAAK,iBAAgB,cAAS,WAAT,YAAmB;AACxC,SAAK,cAAa,cAAS,eAAT,YAAuB;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA,EAMpB,YAAkB;AA5IpB;AA+II,eAAK,QAAL,mBAAU,MAAM,uBAAuB,KAAK,SAAS,IAAI;AACzD,SAAK,YAAY;AACjB,eAAW,QAAQ,KAAK,UAAU;AAChC,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,cAAc,aAAkC,UAAqC;AA9J7F;AA+JI,UAAM,WAAW,MAAM,KAAK,QAA2B,OAAO,4BAA4B,UAAU,IAAI,IAAI;AAG5G,QAAI,CAAC,YAAY,OAAO,aAAa,UAAU;AAE7C,iBAAK,QAAL,mBAAU,MAAM,sCAAsC,OAAO,QAAQ;AACrE,YAAM,SAAS,iCAAiC,WAAW;AAAA,IAC7D;AAEA,QAAI,KAAC,yBAAU,SAAS,OAAO,GAAG;AAIhC,YAAM,SACJ,OAAO,SAAS,kBAAkB,eAAW,uBAAQ,SAAS,aAAa,EAAE,MAAM,GAAG,gBAAgB,IAAI;AAI5G,iBAAK,QAAL,mBAAU,MAAM,kCAAkC,MAAM;AACxD,YAAM,SAAS,cAAc,UAAU,SAAS,IAAI,WAAW;AAAA,IACjE;AAQA,QAAI,SAAS,cAAc,MAAM;AAC/B,aAAO,CAAC;AAAA,IACV;AACA,QAAI,CAAC,MAAM,QAAQ,SAAS,UAAU,GAAG;AACvC,iBAAK,QAAL,mBAAU,MAAM,2CAA2C,OAAO,SAAS,UAAU;AACrF,YAAM,SAAS,sCAAsC,WAAW;AAAA,IAClE;AACA,WAAO,SAAS;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,
|
|
4
|
+
"sourcesContent": ["import * as http from \"node:http\";\nimport * as https from \"node:https\";\nimport { errText, isTrueish, oneLine } from \"./coerce\";\nimport type {\n ApiError,\n ApiErrorCode,\n ParcelApiResponse,\n ParcelDelivery,\n AddDeliveryRequest,\n AddDeliveryResponse,\n CarrierMap,\n} from \"./types\";\n\nconst API_BASE = \"https://api.parcel.app/external\";\n/** Socket IDLE timeout \u2014 fires only when the connection goes silent. */\nconst REQUEST_TIMEOUT = 15_000;\n/**\n * Hard per-request deadline. The socket idle timeout above never fires against\n * a trickle response (a byte every few seconds), which would otherwise pin\n * `isPolling` forever and silently stop the poll loop until a restart. This\n * timer caps the TOTAL request duration regardless of socket activity.\n */\nconst REQUEST_DEADLINE_MS = 60_000;\n/** Shared Retry-After clamps (used by the client parser and the adapter cooldown). */\nexport const RETRY_AFTER_MAX_SEC = 24 * 3600;\nexport const RETRY_AFTER_DEFAULT_SEC = 5 * 60;\n/** Max chars of a response body quoted into a debug log line. */\nconst BODY_SNIPPET_LEN = 200;\n\n/**\n * v0.4.3: optional logger injected by the adapter so the HTTPS client can\n * trace its own request/response lifecycle. When omitted (e.g. in tests),\n * every `this.log?.debug(...)` call is a no-op \u2014 keeps the bare-`apiKey`\n * constructor signature backward-compatible.\n */\nexport interface ParcelClientLogger {\n /** Adapter debug log. Called per request/response outcome (drift, status, parse, oversize) \u2014 low-frequency tracing. */\n debug(message: string): void;\n}\n\n/**\n * Timeout overrides. Production never passes these \u2014 the defaults\n * ({@link REQUEST_TIMEOUT} / {@link REQUEST_DEADLINE_MS}) apply. Same seam idea\n * as the `baseUrl` parameter: without it the two watchdogs below could only be\n * exercised by a test that waits 15 respectively 60 seconds, so they stayed\n * untested \u2014 and an untested watchdog is exactly the one that silently stops\n * working (test audit 2026-08-22, finding C14).\n */\nexport interface ParcelClientTimeouts {\n /** Socket idle timeout (ms). */\n idleMs?: number;\n /** Hard per-request deadline (ms). */\n deadlineMs?: number;\n}\n/**\n * v0.4.2 (P9): hard cap on response body size. parcel.app deliveries lists\n * are tiny (~1 kB per package, max ~50 packages = 50 kB), so a 1 MiB cap is\n * 20\u00D7 the realistic max while still defending against a runaway response.\n */\nconst MAX_BODY_BYTES = 1 << 20; // 1 MiB\n\n/**\n * Build an {@link ApiError} carrying a typed `code` (and optional extra fields\n * such as `retryAfterSeconds`). Centralizes the `new Error(...)` + `err.code`\n * pattern; the `ApiErrorCode` union makes a typo on either side of the\n * client\u2194adapter contract a compile error.\n *\n * @param message Human-readable error message.\n * @param code Machine-readable error code used by the adapter for classification.\n * @param extra Optional additional own-properties to attach to the error.\n */\nfunction apiError(message: string, code: ApiErrorCode, extra?: Record<string, unknown>): ApiError {\n const err = new Error(message) as ApiError;\n err.code = code;\n if (extra) {\n Object.assign(err, extra);\n }\n return err;\n}\n\n/** HTTP client for the parcel.app API */\nexport class ParcelClient {\n private apiKey: string;\n private carrierCache: CarrierMap | null = null;\n /**\n * v0.7.2: in-flight fetch for the carrier list. The per-delivery updates run\n * in parallel (Promise.all) and each resolves carrier names \u2014 without this\n * mutex the first poll with N packages fired N identical concurrent fetches\n * of the static carrier-list file (and a persistently failing endpoint was\n * retried N times per poll). Same pattern as beszel's auth mutex (B1).\n */\n private carrierFetchInFlight: Promise<CarrierMap> | null = null;\n /**\n * v0.4.2 (P1): per-request AbortController. `cancelAll()` aborts every\n * pending HTTPS request \u2014 called from the adapter's `onUnload` so a slow\n * parcel.app endpoint can't keep the adapter alive past js-controller's\n * 4-second kill deadline.\n */\n private readonly inflight = new Set<AbortController>();\n /** v0.4.3: optional logger for the HTTPS-layer trace. See {@link ParcelClientLogger}. */\n private readonly log?: ParcelClientLogger;\n /** API base URL. Overridable so tests can run the real `request()` against a local mock server. */\n private readonly baseUrl: string;\n /** Socket idle timeout in ms \u2014 same seam idea as {@link baseUrl}, see {@link ParcelClientTimeouts}. */\n private readonly idleTimeoutMs: number;\n /** Hard per-request deadline in ms \u2014 see {@link ParcelClientTimeouts}. */\n private readonly deadlineMs: number;\n\n /**\n * @param apiKey The parcel.app API key\n * @param log Optional adapter logger for HTTPS-layer trace (v0.4.3)\n * @param baseUrl API base URL \u2014 defaults to the production endpoint; overridden in tests\n * @param timeouts Timeout overrides \u2014 production always uses the defaults\n */\n constructor(\n apiKey: string,\n log?: ParcelClientLogger,\n baseUrl: string = API_BASE,\n timeouts: ParcelClientTimeouts = {},\n ) {\n this.apiKey = apiKey;\n this.log = log;\n this.baseUrl = baseUrl;\n this.idleTimeoutMs = timeouts.idleMs ?? REQUEST_TIMEOUT;\n this.deadlineMs = timeouts.deadlineMs ?? REQUEST_DEADLINE_MS;\n }\n\n /**\n * v0.10.0 (L3): once cancelAll ran, the client is terminal \u2014 a request\n * STARTED after the abort (e.g. the carrier fetch kicked off by a poll\n * batch that was already past getDeliveries at unload) must not open a\n * fresh HTTPS connection that could outlive js-controller's 4s kill\n * deadline. `request()` rejects immediately when this is set.\n */\n private cancelled = false;\n\n /**\n * v0.4.2 (P1): abort every in-flight HTTPS request and refuse new ones.\n * Idempotent.\n */\n cancelAll(): void {\n // v0.4.3 (A12): trace the shutdown anchor so the adapter log shows\n // exactly how many HTTPS calls were aborted at unload.\n this.log?.debug(`cancelAll: aborting ${this.inflight.size} inflight requests`);\n this.cancelled = true;\n for (const ctrl of this.inflight) {\n ctrl.abort();\n }\n }\n\n /**\n * Fetch deliveries from parcel.app.\n *\n * Error style: rejects with a code-bearing {@link ApiError} on every failure\n * (HTTP status, drift, transport) \u2014 callers classify via `error.code`.\n *\n * @param filterMode Filter active or recent deliveries\n */\n async getDeliveries(filterMode: \"active\" | \"recent\" = \"active\"): Promise<ParcelDelivery[]> {\n const response = await this.request<ParcelApiResponse>(\"GET\", `/deliveries/?filter_mode=${filterMode}`, true);\n\n // API-drift guard: response may be null or a non-object\n if (!response || typeof response !== \"object\") {\n // v0.4.3 (A11a): trace malformed-response drift before throwing.\n this.log?.debug(`API drift: malformed response (got ${typeof response})`);\n throw apiError(\"API error: malformed response\", \"API_ERROR\");\n }\n\n if (!isTrueish(response.success)) {\n // v0.10.0 (M6): the external error_message is flattened + capped before it\n // reaches any log sink \u2014 it bubbles into the poll error-log via the Error\n // message, and an unsanitized multi-line value would forge log lines.\n const rawMsg =\n typeof response.error_message === \"string\" ? oneLine(response.error_message).slice(0, BODY_SNIPPET_LEN) : \"\";\n // v0.4.3 (A11b): trace API-side error before throwing. An invalid key is\n // reported via HTTP 401 (handled in request()), not via a body field \u2014\n // so a `success:false` body is always a generic API_ERROR.\n this.log?.debug(`API drift: success=false, msg='${rawMsg}'`);\n throw apiError(`API error: ${rawMsg || \"UNKNOWN\"}`, \"API_ERROR\");\n }\n\n // API-drift guard. An absent OR null `deliveries` is the API's \"no\n // deliveries\" shape \u2192 [] (zero active packages is the common state; a false\n // throw there would flip the adapter to disconnected on every poll). Only a\n // PRESENT, NON-NULL, wrong-typed value (string/number/object/boolean) is\n // real drift \u2014 throw so the poll keeps the existing states stale instead of\n // reading garbage as \"zero deliveries\" and deleting every package's states.\n if (response.deliveries == null) {\n return [];\n }\n if (!Array.isArray(response.deliveries)) {\n this.log?.debug(`API drift: deliveries not an array (got ${typeof response.deliveries})`);\n throw apiError(\"API error: deliveries not an array\", \"API_ERROR\");\n }\n // Same drift class one level down: an entry that is not an object (null,\n // number, string, nested array) would blow up deep inside the poll \u2014 a\n // TypeError from parseStatus reading `.status_code`, classified UNKNOWN and\n // logged as \"Poll failed: Cannot read properties of null\". Fail the same way\n // the wrong-typed list does: a coded API error, existing states kept stale.\n const entries: unknown[] = response.deliveries;\n const bad = entries.findIndex(d => d === null || typeof d !== \"object\" || Array.isArray(d));\n if (bad !== -1) {\n this.log?.debug(\n `API drift: deliveries[${bad}] is not an object (got ${Array.isArray(entries[bad]) ? \"array\" : typeof entries[bad]})`,\n );\n throw apiError(\"API error: malformed delivery entry\", \"API_ERROR\");\n }\n return response.deliveries;\n }\n\n /**\n * Add a new delivery to parcel.app.\n *\n * Error style: transport/HTTP failures reject with {@link ApiError}; a 2xx\n * body is returned as-is once it is a plain object (its fields are not\n * validated) \u2014 `success: false` is passed through unchanged because sendTo\n * callers receive this object verbatim.\n *\n * @param delivery The delivery to add\n */\n async addDelivery(delivery: AddDeliveryRequest): Promise<AddDeliveryResponse> {\n const response = await this.request<unknown>(\"POST\", \"/add-delivery/\", true, delivery);\n // API-drift guard: the body goes to the sendTo caller verbatim and the\n // adapter reads `.success` from it \u2014 a null/array/primitive body used to\n // surface as a TypeError (\"Cannot read properties of null\") instead of a\n // clear API error.\n if (!response || typeof response !== \"object\" || Array.isArray(response)) {\n this.log?.debug(\n `API drift: malformed add-delivery response (got ${Array.isArray(response) ? \"array\" : typeof response})`,\n );\n throw apiError(\"API error: malformed response\", \"API_ERROR\");\n }\n return response as AddDeliveryResponse;\n }\n\n /** Get carrier names (cached after first call; concurrent callers share one fetch) */\n async getCarrierNames(): Promise<CarrierMap> {\n if (this.carrierCache) {\n return this.carrierCache;\n }\n // v0.7.2: share one in-flight fetch between the parallel per-delivery\n // updates instead of firing N identical requests on the first poll.\n if (!this.carrierFetchInFlight) {\n this.carrierFetchInFlight = this.fetchCarrierNames().finally(() => {\n this.carrierFetchInFlight = null;\n });\n }\n return this.carrierFetchInFlight;\n }\n\n /**\n * One actual carrier-list fetch. Failure \u2192 empty map, NOT cached \u2014 retried by\n * the next update batch (the mutex above only dedupes CONCURRENT callers, so\n * a poll with several 25er batches may retry once per batch; the endpoint is\n * a static, unauthenticated file without a rate limit).\n */\n private async fetchCarrierNames(): Promise<CarrierMap> {\n try {\n const raw = await this.request<unknown>(\"GET\", \"/supported_carriers.json\", false);\n // API-drift guard: must be a plain object (not null, array, or primitive)\n if (raw && typeof raw === \"object\" && !Array.isArray(raw)) {\n // v0.9.0 (C6): keep only string-valued entries instead of asserting the\n // whole object is Record<string,string>. A drifted non-string value is\n // dropped here, so the cache is honestly typed (no `as CarrierMap`).\n const clean: CarrierMap = {};\n for (const [code, name] of Object.entries(raw)) {\n if (typeof name === \"string\") {\n clean[code] = name;\n }\n }\n this.carrierCache = clean;\n // v0.4.3 (D1): trace the one-time cache fill so a successful warm-up\n // is visible in the debug log (happens once per adapter restart).\n this.log?.debug(`carriers: fetched ${Object.keys(this.carrierCache).length} entries`);\n return this.carrierCache;\n }\n // v0.4.3 (D3): non-object drift \u2014 supported_carriers.json returned\n // something that isn't an object. Empty map is returned, NOT cached.\n this.log?.debug(\n `carriers: drift (got ${Array.isArray(raw) ? \"array\" : typeof raw}, expected object), kept empty`,\n );\n return {};\n } catch (err) {\n // v0.4.3 (D2): trace the fetch-fail so the empty-map fallback isn't\n // silent. NOT cached \u2014 next poll retries; the trace then shows the\n // retry, too. Without this the user sees carrier codes instead of\n // names with no log entry explaining why.\n this.log?.debug(`carriers: fetch failed (kept empty, will retry): ${errText(err)}`);\n // Return empty map but don't cache it \u2014 allow retry next time\n return {};\n }\n }\n\n /**\n * Resolve a carrier code to a display name.\n *\n * @param carrierCode The carrier code from API\n */\n async getCarrierName(carrierCode: unknown): Promise<string> {\n // API-drift guard: non-string codes fall back to \"UNKNOWN\"\n if (typeof carrierCode !== \"string\" || carrierCode.length === 0) {\n // v0.4.3 (D4): trace non-string code drift. Helps diagnose \"all my\n // packages show UNKNOWN carrier\" reports.\n this.log?.debug(`getCarrierName: non-string code (got ${typeof carrierCode}), returning UNKNOWN`);\n return \"UNKNOWN\";\n }\n const carriers = await this.getCarrierNames();\n const mapped = carriers[carrierCode];\n return typeof mapped === \"string\" && mapped.length > 0 ? mapped : carrierCode.toUpperCase();\n }\n\n /**\n * Test if the API key is valid.\n *\n * Error style: never throws \u2014 failures are folded into the returned\n * `{ success: false, message }` result object.\n */\n async testConnection(): Promise<{ success: boolean; message: string }> {\n try {\n await this.getDeliveries(\"active\");\n return { success: true, message: \"Connection successful\" };\n } catch (err) {\n const error = err as Error & { code?: string };\n if (error.code === \"INVALID_API_KEY\") {\n return { success: false, message: \"Invalid API key\" };\n }\n return { success: false, message: error.message };\n }\n }\n\n /**\n * Execute an HTTP request against the parcel.app API.\n *\n * @param method HTTP method\n * @param path API path\n * @param authenticated Whether to send the API key\n * @param body Optional request body\n */\n private request<T>(method: string, path: string, authenticated: boolean, body?: unknown): Promise<T> {\n // v0.4.3 (A0): start timestamp for elapsed-ms in the success/timeout/error\n // log lines. One LOC, no behavior change.\n const startedAt = Date.now();\n // v0.4.3 (A1): trace request entry. ~144 calls/day at the default 10-min\n // poll interval \u2014 acceptable at debug.\n this.log?.debug(`HTTP ${method} ${path}`);\n return new Promise((resolve, reject) => {\n // v0.10.0 (L3): terminal after cancelAll \u2014 a request started AFTER the\n // shutdown abort must not open a fresh connection.\n if (this.cancelled) {\n this.log?.debug(`HTTP ${method} ${path} refused \u2014 client cancelled`);\n reject(apiError(\"Client cancelled\", \"ABORTED\"));\n return;\n }\n // v0.4.2 (E3): URL-shape validation defensive \u2014 paths are hardcoded\n // upstream but a future caller could pass garbage; surface a clear\n // error class instead of a TypeError thrown sync from the executor.\n let url: URL;\n try {\n url = new URL(`${this.baseUrl}${path}`);\n } catch {\n // v0.4.3 (A10): trace invalid-URL drift before throwing.\n this.log?.debug(`HTTP invalid URL: ${this.baseUrl}${path}`);\n reject(apiError(`Invalid URL: ${this.baseUrl}${path}`, \"INVALID_URL\"));\n return;\n }\n\n const headers: Record<string, string> = {};\n if (authenticated) {\n headers[\"api-key\"] = this.apiKey;\n }\n if (body) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n const options: https.RequestOptions = {\n hostname: url.hostname,\n port: url.port || 443,\n path: url.pathname + url.search,\n method,\n headers,\n timeout: this.idleTimeoutMs,\n };\n\n // v0.4.2 (P1): per-request AbortController. `cancelAll()` (called\n // from `onUnload`) aborts everything pending without waiting for\n // the configured timeout.\n const ctrl = new AbortController();\n this.inflight.add(ctrl);\n // Marks the request as finished for the deadline listener below \u2014\n // every terminal path runs cleanup().\n let settled = false;\n const cleanup = (): void => {\n settled = true;\n this.inflight.delete(ctrl);\n };\n\n // Pick transport from the URL protocol so tests can run the real\n // request() against a local http mock server; production is always https.\n const transportRequest: (\n opts: https.RequestOptions,\n callback: (res: http.IncomingMessage) => void,\n ) => http.ClientRequest = url.protocol === \"http:\" ? http.request : https.request;\n\n const req = transportRequest(options, res => {\n const chunks: Buffer[] = [];\n let bodyBytes = 0;\n let oversized = false;\n\n res.on(\"error\", err => {\n cleanup();\n reject(err);\n });\n res.on(\"data\", (chunk: Buffer) => {\n // Not covered by a test on purpose: this branch only runs for a chunk\n // that was already buffered when `req.destroy()` below fired, i.e. a\n // delivery race no test can trigger deterministically. A test that\n // hits it \"usually\" would be exactly the kind of flaky check the\n // 2026-08-22 audit removed elsewhere.\n if (oversized) {\n return;\n }\n bodyBytes += chunk.length;\n // v0.4.2 (P9): drop oversized responses so a compromised or\n // misconfigured endpoint can't OOM the adapter. Reject with the\n // stable BODY_TOO_LARGE code here, then destroy WITHOUT an error so\n // req.on(\"error\") doesn't fire a second, codeless rejection (the\n // earlier `req.destroy(Error)` preempted the end-handler's code).\n if (bodyBytes > MAX_BODY_BYTES) {\n oversized = true;\n // v0.4.3 (A9): trace the oversize-drop before destroying.\n this.log?.debug(`HTTP body oversized ${path}: dropping at ${bodyBytes}B`);\n cleanup();\n reject(apiError(\"Response body too large\", \"BODY_TOO_LARGE\"));\n req.destroy();\n return;\n }\n chunks.push(chunk);\n });\n res.on(\"end\", () => {\n if (oversized) {\n return; // already cleaned up + rejected in the data handler\n }\n cleanup();\n // The MAX_BODY_BYTES cap above bounds `chunks`, so concat stays well\n // under Buffer's max length and toString won't throw here.\n const raw = Buffer.concat(chunks).toString(\"utf-8\");\n\n if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {\n const httpError = ParcelClient.mapHttpStatusError(\n res.statusCode,\n res.statusMessage,\n res.headers[\"retry-after\"],\n );\n // v0.4.3 (A3/A4): trace 4xx/5xx with code, retry-after and body-snippet.\n this.log?.debug(\n `HTTP ${method} ${path} \u2192 ${res.statusCode} ${httpError.code}` +\n `${httpError.retryAfterSeconds !== undefined ? ` retry-after=${httpError.retryAfterSeconds}s` : \"\"}` +\n ` (body=${oneLine(raw.substring(0, BODY_SNIPPET_LEN))})`,\n );\n reject(httpError);\n return;\n }\n\n try {\n const parsed = JSON.parse(raw) as T;\n // v0.4.3 (A2): trace successful response with elapsed-ms + bytes.\n this.log?.debug(`HTTP ${method} ${path} \u2192 ${res.statusCode} (${Date.now() - startedAt}ms, ${bodyBytes}B)`);\n resolve(parsed);\n } catch {\n // v0.4.3 (A8): trace JSON parse-fail with snippet (debug only).\n this.log?.debug(`HTTP JSON parse fail ${path}: ${oneLine(raw.substring(0, BODY_SNIPPET_LEN))}`);\n // v0.9.0 (S1): keep the raw body OUT of the Error message \u2014 it\n // bubbles to a poll error-log; a malformed PII-bearing body must\n // not reach error level. The snippet stays in the debug line above.\n reject(apiError(`JSON parse error (${raw.length} bytes)`, \"PARSE_ERROR\"));\n }\n });\n });\n\n // v0.10.0 (M4): arm the hard deadline via AbortSignal.timeout \u2014 an\n // unref'd platform timer (never keeps the process alive, no adapter\n // context needed). Destroying with a TIMEOUT-coded ApiError routes\n // through req.on(\"error\") below, which rejects + cleans up \u2014 a trickle\n // response (a byte every few seconds) can no longer pin the poll loop.\n AbortSignal.timeout(this.deadlineMs).addEventListener(\"abort\", () => {\n if (settled) {\n return; // request finished long ago \u2014 nothing to kill, nothing to log\n }\n this.log?.debug(`HTTP deadline ${method} ${path} (${Date.now() - startedAt}ms > ${this.deadlineMs}ms)`);\n req.destroy(apiError(`Request deadline exceeded (${this.deadlineMs / 1000}s)`, \"TIMEOUT\"));\n });\n\n ctrl.signal.addEventListener(\"abort\", () => {\n // v0.4.3: A6 deliberately omitted \u2014 `req.destroy(Error)` propagates\n // through `req.on(\"error\")` below where A7 already logs it.\n // v0.10.0 (M1): carries the ABORTED code so the adapter routes an\n // expected shutdown-abort to debug instead of an error log line.\n req.destroy(apiError(\"Request aborted\", \"ABORTED\"));\n });\n\n req.on(\"timeout\", () => {\n req.destroy();\n cleanup();\n // v0.4.3 (A5): trace timeout with elapsed-ms.\n this.log?.debug(`HTTP timeout ${method} ${path} (${Date.now() - startedAt}ms)`);\n reject(apiError(\"Request timeout\", \"TIMEOUT\"));\n });\n\n req.on(\"error\", err => {\n cleanup();\n // v0.4.3 (A7): trace network / abort / TLS / DNS errors with elapsed.\n // Also catches the abort case (req.destroy(ApiError)) \u2014 A6 deliberately\n // not emitted to avoid double-log.\n this.log?.debug(`HTTP error ${method} ${path} (${Date.now() - startedAt}ms): ${err.message}`);\n reject(err);\n });\n\n // v0.10.0 (I8): a synchronous throw from stringify/write/end (circular\n // body, stream state) must not strand the AbortController in `inflight`\n // \u2014 cancelAll's invariant is \"inflight mirrors live requests exactly\".\n try {\n if (body) {\n req.write(JSON.stringify(body));\n }\n req.end();\n } catch (err) {\n cleanup();\n req.destroy();\n reject(apiError(`Request write failed: ${errText(err)}`, \"API_ERROR\"));\n }\n });\n }\n\n /**\n * Map a non-2xx HTTP status to its {@link ApiError}. Pure \u2014 extracted from\n * the end-handler so the 401/403/429 rules read in isolation (v0.10.0, L17).\n *\n * @param statusCode HTTP status code (non-2xx)\n * @param statusMessage HTTP status message\n * @param retryAfterHeader Raw Retry-After header value (429 only)\n */\n private static mapHttpStatusError(\n statusCode: number,\n statusMessage: string | undefined,\n retryAfterHeader: string | undefined,\n ): ApiError {\n if (statusCode === 429) {\n // v0.4.2 (P6): clamp Retry-After. Bogus values (0, negative, NaN) fall\n // back to the default; extreme values are capped.\n const retryAfter = parseInt(retryAfterHeader || \"\", 10);\n const retryAfterSeconds =\n Number.isFinite(retryAfter) && retryAfter > 0\n ? Math.min(RETRY_AFTER_MAX_SEC, retryAfter)\n : RETRY_AFTER_DEFAULT_SEC;\n return apiError(\"Rate limit exceeded\", \"RATE_LIMITED\", { retryAfterSeconds });\n }\n // v0.4.2 (P3): split 401 (invalid key) from 403 (permission / no premium).\n // Adapter treats them differently \u2014 INVALID_API_KEY says \"fix the key\",\n // FORBIDDEN says \"fix the account\".\n const code: ApiErrorCode = statusCode === 401 ? \"INVALID_API_KEY\" : statusCode === 403 ? \"FORBIDDEN\" : \"HTTP_ERROR\";\n return apiError(`HTTP ${statusCode}: ${statusMessage}`, code);\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["_a", "_b"]
|
|
7
7
|
}
|