universal-parcel-scraper 0.2.0 → 0.3.0-main.298
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 +209 -71
- package/data/carrier.schema.json +6 -4
- package/data/catalog.json +6 -6
- package/dist/app.d.ts +11 -0
- package/dist/app.js +11 -0
- package/dist/browser/scraper.js +1386 -1568
- package/dist/carriers/aliexpress/adapter.d.ts +5 -3
- package/dist/carriers/aliexpress/adapter.js +28 -8
- package/dist/carriers/amazon-shipping/adapter.d.ts +3 -2
- package/dist/carriers/amazon-shipping/adapter.js +15 -10
- package/dist/carriers/amazon-shipping/eligibility.d.ts +2 -1
- package/dist/carriers/amazon-shipping/eligibility.js +2 -2
- package/dist/carriers/aramex/adapter.d.ts +2 -0
- package/dist/carriers/aramex/adapter.js +5 -3
- package/dist/carriers/aramex/parser.js +2 -2
- package/dist/carriers/asendia/adapter.d.ts +8 -0
- package/dist/carriers/asendia/adapter.js +66 -11
- package/dist/carriers/asendia/probe.d.ts +2 -0
- package/dist/carriers/asendia/probe.js +9 -7
- package/dist/carriers/australia-post/adapter.js +2 -2
- package/dist/carriers/austrian-post/adapter.js +2 -2
- package/dist/carriers/blue-dart/parser.js +2 -2
- package/dist/carriers/bpost/parser.js +2 -2
- package/dist/carriers/bring-posten/parser.js +2 -2
- package/dist/carriers/brt/parser.js +2 -2
- package/dist/carriers/c-chez-vous/adapter.d.ts +3 -2
- package/dist/carriers/c-chez-vous/adapter.js +15 -10
- package/dist/carriers/canada-post/adapter.d.ts +1 -0
- package/dist/carriers/canada-post/adapter.js +5 -3
- package/dist/carriers/canada-post/parser.js +2 -2
- package/dist/carriers/canpar/parser.js +2 -2
- package/dist/carriers/ciblex/parser.js +2 -2
- package/dist/carriers/colis-prive/adapter.d.ts +3 -2
- package/dist/carriers/colis-prive/adapter.js +0 -0
- package/dist/carriers/colisweb/adapter.d.ts +3 -2
- package/dist/carriers/colisweb/adapter.js +13 -8
- package/dist/carriers/correios-br/parser.js +2 -2
- package/dist/carriers/correos-chile/parser.js +3 -3
- package/dist/carriers/correos-express/parser.js +2 -2
- package/dist/carriers/correos-spain/adapter.d.ts +4 -2
- package/dist/carriers/correos-spain/adapter.js +16 -8
- package/dist/carriers/ctt/adapter.d.ts +2 -2
- package/dist/carriers/ctt/adapter.js +30 -25
- package/dist/carriers/ctt-express/parser.js +2 -2
- package/dist/carriers/dachser/adapter.d.ts +3 -2
- package/dist/carriers/dachser/adapter.js +34 -16
- package/dist/carriers/delhivery/parser.js +2 -2
- package/dist/carriers/dhl/adapter.d.ts +2 -2
- package/dist/carriers/dhl/adapter.js +50 -22
- package/dist/carriers/dhl-ecommerce/adapter.d.ts +3 -3
- package/dist/carriers/dhl-ecommerce/adapter.js +8 -8
- package/dist/carriers/dpd/adapter.d.ts +3 -3
- package/dist/carriers/dpd/adapter.js +113 -57
- package/dist/carriers/dpd/carrier.json +1 -1
- package/dist/carriers/dpd-fr/adapter.d.ts +2 -2
- package/dist/carriers/dpd-fr/adapter.js +29 -20
- package/dist/carriers/dtdc/parser.js +2 -2
- package/dist/carriers/ecoscooting/parser.js +2 -2
- package/dist/carriers/ems/adapter.js +2 -2
- package/dist/carriers/estafeta/parser.js +2 -2
- package/dist/carriers/evri/adapter.js +2 -2
- package/dist/carriers/fedex/adapter.d.ts +2 -2
- package/dist/carriers/fedex/adapter.js +14 -10
- package/dist/carriers/four-px/adapter.js +2 -2
- package/dist/carriers/geodis/adapter.d.ts +3 -2
- package/dist/carriers/geodis/adapter.js +13 -8
- package/dist/carriers/gls-ch/adapter.d.ts +4 -3
- package/dist/carriers/gls-ch/adapter.js +27 -19
- package/dist/carriers/gls-ch/carrier.json +1 -1
- package/dist/carriers/gls-de/adapter.d.ts +3 -3
- package/dist/carriers/gls-de/adapter.js +14 -9
- package/dist/carriers/gls-de/carrier.json +1 -1
- package/dist/carriers/gls-fr/adapter.d.ts +3 -2
- package/dist/carriers/gls-fr/adapter.js +16 -11
- package/dist/carriers/gofo/parser.js +2 -2
- package/dist/carriers/heppner/adapter.d.ts +4 -2
- package/dist/carriers/heppner/adapter.js +19 -14
- package/dist/carriers/heppner/carrier.json +1 -1
- package/dist/carriers/hermes/adapter.d.ts +3 -2
- package/dist/carriers/hermes/adapter.js +13 -8
- package/dist/carriers/hermes-de/adapter.d.ts +3 -2
- package/dist/carriers/hermes-de/adapter.js +16 -12
- package/dist/carriers/india-post/adapter.d.ts +2 -2
- package/dist/carriers/india-post/adapter.js +42 -21
- package/dist/carriers/inpost/adapter.d.ts +4 -2
- package/dist/carriers/inpost/adapter.js +17 -10
- package/dist/carriers/japan-post/adapter.js +2 -2
- package/dist/carriers/korea-post/adapter.js +2 -2
- package/dist/carriers/la-poste/adapter.d.ts +4 -2
- package/dist/carriers/la-poste/adapter.js +18 -13
- package/dist/carriers/landmark-global/parser.js +2 -2
- package/dist/carriers/mondial-relay/adapter.d.ts +2 -2
- package/dist/carriers/mondial-relay/adapter.js +26 -17
- package/dist/carriers/mondial-relay/carrier.json +1 -1
- package/dist/carriers/mrw/adapter.js +4 -2
- package/dist/carriers/mrw/parser.js +3 -3
- package/dist/carriers/nacex/parser.js +2 -2
- package/dist/carriers/ninja-van/parser.js +2 -2
- package/dist/carriers/nz-post/parser.js +2 -2
- package/dist/carriers/ontrac/parser.js +2 -2
- package/dist/carriers/paack/adapter.d.ts +4 -2
- package/dist/carriers/paack/adapter.js +24 -14
- package/dist/carriers/paack/carrier.json +1 -1
- package/dist/carriers/packeta/adapter.d.ts +4 -2
- package/dist/carriers/packeta/adapter.js +16 -8
- package/dist/carriers/planzer/adapter.d.ts +5 -3
- package/dist/carriers/planzer/adapter.js +35 -12
- package/dist/carriers/planzer/shared.d.ts +4 -1
- package/dist/carriers/planzer/shared.js +20 -6
- package/dist/carriers/poczta-polska/parser.js +2 -2
- package/dist/carriers/pos-malaysia/adapter.d.ts +2 -0
- package/dist/carriers/pos-malaysia/adapter.js +9 -5
- package/dist/carriers/poste-italiane/adapter.d.ts +4 -2
- package/dist/carriers/poste-italiane/adapter.js +16 -8
- package/dist/carriers/posti/adapter.js +2 -2
- package/dist/carriers/postlogistics/adapter.d.ts +6 -4
- package/dist/carriers/postlogistics/adapter.js +49 -17
- package/dist/carriers/postlogistics/status.d.ts +2 -0
- package/dist/carriers/postlogistics/status.js +2 -0
- package/dist/carriers/postnord/parser.js +2 -2
- package/dist/carriers/purolator/parser.js +2 -2
- package/dist/carriers/relais-colis/adapter.js +2 -2
- package/dist/carriers/royal-mail/adapter.d.ts +2 -2
- package/dist/carriers/royal-mail/adapter.js +14 -10
- package/dist/carriers/seur/parser.js +2 -2
- package/dist/carriers/sf-express/parser.js +2 -2
- package/dist/carriers/singapore-post/adapter.js +2 -2
- package/dist/carriers/spring-gds/adapter.d.ts +2 -0
- package/dist/carriers/spring-gds/adapter.js +16 -15
- package/dist/carriers/sunyou/adapter.d.ts +5 -3
- package/dist/carriers/sunyou/adapter.js +16 -9
- package/dist/carriers/swiss-post/adapter.d.ts +4 -4
- package/dist/carriers/swiss-post/adapter.js +33 -15
- package/dist/carriers/swiss-post-cargo/adapter.d.ts +3 -2
- package/dist/carriers/swiss-post-cargo/adapter.js +14 -9
- package/dist/carriers/the-courier-guy/number.js +3 -2
- package/dist/carriers/tipsa/parser.js +3 -3
- package/dist/carriers/tnt/adapter.js +3 -3
- package/dist/carriers/ukrposhta/adapter.js +20 -62
- package/dist/carriers/ukrposhta/parser.js +2 -2
- package/dist/carriers/uniuni/parser.js +3 -3
- package/dist/carriers/ups/adapter.d.ts +2 -2
- package/dist/carriers/ups/adapter.js +40 -32
- package/dist/carriers/usps/adapter.d.ts +2 -2
- package/dist/carriers/usps/adapter.js +14 -10
- package/dist/carriers/yamato/adapter.js +2 -2
- package/dist/carriers/yanwen/adapter.js +2 -2
- package/dist/carriers/yto/parser.js +2 -2
- package/dist/carriers/yunda/parser.js +2 -2
- package/dist/carriers/yunexpress/adapter.js +21 -39
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +49 -19
- package/dist/core/adapter/index.d.ts +18 -1
- package/dist/core/adapter/index.js +28 -4
- package/dist/core/adapter/track.js +21 -7
- package/dist/core/catalog/index.d.ts +1 -15
- package/dist/core/catalog/index.js +3 -106
- package/dist/core/catalog/inputs.d.ts +2 -2
- package/dist/core/catalog/inputs.js +4 -4
- package/dist/core/catalog/parcel.d.ts +53 -0
- package/dist/core/catalog/parcel.js +105 -0
- package/dist/core/catalog/types.d.ts +1 -29
- package/dist/core/catalog/urls.js +1 -0
- package/dist/core/errors/hint.d.ts +7 -0
- package/dist/core/errors/hint.js +11 -1
- package/dist/core/errors/index.d.ts +8 -1
- package/dist/core/errors/index.js +11 -0
- package/dist/core/runner/index.d.ts +23 -1
- package/dist/core/runner/index.js +71 -14
- package/dist/core/transport/boundedFetch.js +24 -19
- package/dist/core/transport/browser.d.ts +1 -0
- package/dist/core/transport/browser.js +76 -99
- package/dist/core/transport/index.d.ts +4 -1
- package/dist/core/transport/index.js +3 -1
- package/dist/core/transport/localBrowser.d.ts +24 -0
- package/dist/core/transport/localBrowser.js +87 -0
- package/dist/core/transport/trawl.d.ts +6 -0
- package/dist/core/transport/trawl.js +30 -9
- package/dist/core/transport/userAgent.d.ts +8 -0
- package/dist/core/transport/userAgent.js +15 -0
- package/dist/data/catalog.json +6 -6
- package/dist/facade/index.d.ts +3 -0
- package/dist/facade/index.js +29 -11
- package/dist/generated/catalog.d.ts +6 -6
- package/dist/generated/catalog.js +6 -6
- package/dist/index.d.ts +0 -4
- package/dist/index.js +0 -4
- package/dist/providers/parcelsapp/adapter.d.ts +1 -1
- package/dist/providers/parcelsapp/adapter.js +13 -10
- package/dist/providers/parcelsapp/http.d.ts +1 -1
- package/dist/providers/parcelsapp/http.js +2 -2
- package/dist/providers/postal-ninja/adapter.d.ts +2 -2
- package/dist/providers/postal-ninja/adapter.js +9 -8
- package/dist/providers/seventeentrack/adapter.d.ts +1 -1
- package/dist/providers/seventeentrack/adapter.js +7 -7
- package/dist/providers/shared/capture.d.ts +2 -0
- package/dist/providers/shared/capture.js +1 -1
- package/dist/providers/shared/result.js +4 -2
- package/dist/providers/ship24/adapter.d.ts +2 -2
- package/dist/providers/ship24/adapter.js +15 -11
- package/dist/providers/ship24/http.d.ts +1 -1
- package/dist/providers/ship24/http.js +4 -3
- package/dist/providers/universal.d.ts +3 -2
- package/dist/providers/universal.js +20 -2
- package/dist/providers/upu/adapter.js +2 -2
- package/dist/scripts/carrier-canary.js +2 -1
- package/dist/server/index.d.ts +15 -0
- package/dist/server/index.js +90 -12
- package/dist/server/openapi.json +399 -52
- package/package.json +13 -7
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { Buffer } from 'node:buffer';
|
|
18
18
|
import { DateTime } from 'luxon';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
19
|
+
import { lookupBudget } from '../../core/adapter/index.js';
|
|
20
|
+
import { InputRequiredError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
21
|
+
import { cleanScalar, decodeText, fetchBounded, parseJsonBytes, UpstreamHttpError, userAgentOf, } from '../../core/transport/index.js';
|
|
21
22
|
import { isRecord } from '../../core/types.js';
|
|
22
23
|
import { classifyHeppnerEvent, heppnerCode } from './status.js';
|
|
23
24
|
const SEARCH_ENDPOINT = 'https://myportal.heppner-group.com/api/recipient/search/expedition';
|
|
@@ -43,7 +44,7 @@ function eventTime(value) {
|
|
|
43
44
|
export function normalizeHeppnerTrackingNumber(raw) {
|
|
44
45
|
const trackingNumber = raw.replace(/\s/g, '');
|
|
45
46
|
if (!/^\d{8}$/.test(trackingNumber)) {
|
|
46
|
-
throw new
|
|
47
|
+
throw new InvalidInputError('Heppner', 'Heppner tracking numbers must contain exactly 8 digits');
|
|
47
48
|
}
|
|
48
49
|
return trackingNumber;
|
|
49
50
|
}
|
|
@@ -51,7 +52,7 @@ export function normalizeHeppnerCredential(rawTrackingNumber, rawPostcode) {
|
|
|
51
52
|
const trackingNumber = normalizeHeppnerTrackingNumber(rawTrackingNumber);
|
|
52
53
|
const postcode = rawPostcode.trim();
|
|
53
54
|
if (!/^\d{4,5}$/.test(postcode)) {
|
|
54
|
-
throw new
|
|
55
|
+
throw new InvalidInputError('Heppner', 'Heppner requires a four-digit Swiss or five-digit French delivery postcode');
|
|
55
56
|
}
|
|
56
57
|
return {
|
|
57
58
|
trackingNumber,
|
|
@@ -178,30 +179,33 @@ export function parseHeppnerTrackingResponse(payload, rawTrackingNumber) {
|
|
|
178
179
|
events: parsed.map(({ event }) => event),
|
|
179
180
|
};
|
|
180
181
|
}
|
|
181
|
-
function requestHeaders(accept, origin = 'https://www.heppner-group.com') {
|
|
182
|
+
function requestHeaders(userAgent, accept, origin = 'https://www.heppner-group.com') {
|
|
182
183
|
return {
|
|
183
184
|
Accept: accept,
|
|
184
185
|
'Accept-Language': 'fr-FR,fr;q=0.9',
|
|
185
186
|
Origin: origin,
|
|
186
187
|
Referer: 'https://www.heppner-group.com/destinataire-suivez-votre-marchandise/',
|
|
187
|
-
'User-Agent':
|
|
188
|
+
'User-Agent': userAgent,
|
|
188
189
|
};
|
|
189
190
|
}
|
|
190
191
|
export class HeppnerTracker {
|
|
191
192
|
timeoutMs;
|
|
192
193
|
fetcher;
|
|
194
|
+
userAgent;
|
|
193
195
|
constructor(options = {}) {
|
|
194
196
|
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
195
197
|
this.fetcher = options.fetcher;
|
|
196
198
|
if (!Number.isFinite(this.timeoutMs) || this.timeoutMs <= 0) {
|
|
197
199
|
throw new TypeError('Heppner timeout must be positive');
|
|
198
200
|
}
|
|
201
|
+
this.userAgent = userAgentOf(options.userAgent);
|
|
199
202
|
}
|
|
200
|
-
async fetch(rawTrackingNumber, rawPostcode) {
|
|
203
|
+
async fetch(rawTrackingNumber, rawPostcode, context = {}) {
|
|
201
204
|
const credential = normalizeHeppnerCredential(rawTrackingNumber, rawPostcode);
|
|
202
|
-
const
|
|
205
|
+
const budget = lookupBudget(context, 2 * this.timeoutMs);
|
|
206
|
+
const search = await fetchBounded(heppnerSearchUrl(credential.trackingNumber, credential.postcode), { signal: budget.signal, headers: requestHeaders(this.userAgent, 'text/plain,*/*;q=0.8') }, {
|
|
203
207
|
provider: 'Heppner shipment search',
|
|
204
|
-
timeoutMs: this.timeoutMs,
|
|
208
|
+
timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
|
|
205
209
|
maxBytes: 2_048,
|
|
206
210
|
fetcher: this.fetcher,
|
|
207
211
|
allowHttpError: true,
|
|
@@ -213,13 +217,14 @@ export class HeppnerTracker {
|
|
|
213
217
|
}
|
|
214
218
|
const capability = parseHeppnerCapability(decodeText(search.bytes), credential.trackingNumber, credential.postcode);
|
|
215
219
|
const detail = await fetchBounded(heppnerDetailUrl(capability), {
|
|
220
|
+
signal: budget.signal,
|
|
216
221
|
headers: {
|
|
217
|
-
...requestHeaders('application/json', 'https://myportal.heppner-group.com'),
|
|
222
|
+
...requestHeaders(this.userAgent, 'application/json', 'https://myportal.heppner-group.com'),
|
|
218
223
|
Referer: heppnerTrackingPageUrl(capability),
|
|
219
224
|
},
|
|
220
225
|
}, {
|
|
221
226
|
provider: 'Heppner tracking',
|
|
222
|
-
timeoutMs: this.timeoutMs,
|
|
227
|
+
timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
|
|
223
228
|
maxBytes: MAX_RESPONSE_BYTES,
|
|
224
229
|
fetcher: this.fetcher,
|
|
225
230
|
allowHttpError: true,
|
|
@@ -233,15 +238,15 @@ export class HeppnerTracker {
|
|
|
233
238
|
}
|
|
234
239
|
}
|
|
235
240
|
export const adapter = (environment) => {
|
|
236
|
-
const tracker = new HeppnerTracker({ fetcher: environment.fetcher });
|
|
241
|
+
const tracker = new HeppnerTracker({ fetcher: environment.fetcher, userAgent: environment.userAgent });
|
|
237
242
|
return {
|
|
238
243
|
id: 'heppner',
|
|
239
244
|
steps: ['direct'],
|
|
240
|
-
track: async (input) => {
|
|
245
|
+
track: async (input, context) => {
|
|
241
246
|
const postcode = input.postcode?.trim() ?? '';
|
|
242
247
|
if (!postcode)
|
|
243
248
|
throw new InputRequiredError('Heppner', 'the delivery postcode');
|
|
244
|
-
return tracker.fetch(input.number, postcode);
|
|
249
|
+
return tracker.fetch(input.number, postcode, context);
|
|
245
250
|
},
|
|
246
251
|
};
|
|
247
252
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
|
|
2
2
|
import type { CarrierResult } from '../../core/result/index.js';
|
|
3
3
|
import { hermesStatus } from './status.js';
|
|
4
4
|
export { hermesStatus };
|
|
@@ -6,12 +6,13 @@ export interface HermesOptions {
|
|
|
6
6
|
timeoutMs?: number;
|
|
7
7
|
/** Test seam; production uses the global fetch. */
|
|
8
8
|
fetcher?: typeof fetch;
|
|
9
|
+
userAgent?: string;
|
|
9
10
|
}
|
|
10
11
|
export declare function parseHermesTrackingResponse(payload: unknown, trackingNumber: string): CarrierResult;
|
|
11
12
|
export declare class HermesTracker {
|
|
12
13
|
#private;
|
|
13
14
|
readonly timeoutMs: number;
|
|
14
15
|
constructor(options?: number | HermesOptions);
|
|
15
|
-
fetch(trackingNumber: string): Promise<CarrierResult>;
|
|
16
|
+
fetch(trackingNumber: string, context?: TrackingContext): Promise<CarrierResult>;
|
|
16
17
|
}
|
|
17
18
|
export declare const adapter: AdapterFactory;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { lookupBudget } from '../../core/adapter/index.js';
|
|
1
2
|
import { NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
2
|
-
import { fetchBounded, parseJsonBytes } from '../../core/transport/index.js';
|
|
3
|
+
import { fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
|
|
3
4
|
import { isRecord } from '../../core/types.js';
|
|
4
5
|
import { hermesEventStage, hermesStatus } from './status.js';
|
|
5
6
|
export { hermesStatus };
|
|
@@ -70,34 +71,38 @@ export function parseHermesTrackingResponse(payload, trackingNumber) {
|
|
|
70
71
|
export class HermesTracker {
|
|
71
72
|
timeoutMs;
|
|
72
73
|
#fetcher;
|
|
74
|
+
#userAgent;
|
|
73
75
|
constructor(options = {}) {
|
|
74
|
-
const { timeoutMs = DEFAULT_TIMEOUT_MS, fetcher } = typeof options === 'number'
|
|
75
|
-
? { timeoutMs: options, fetcher: undefined }
|
|
76
|
+
const { timeoutMs = DEFAULT_TIMEOUT_MS, fetcher, userAgent } = typeof options === 'number'
|
|
77
|
+
? { timeoutMs: options, fetcher: undefined, userAgent: undefined }
|
|
76
78
|
: options;
|
|
77
79
|
this.timeoutMs = timeoutMs;
|
|
78
80
|
this.#fetcher = fetcher;
|
|
81
|
+
this.#userAgent = userAgentOf(userAgent);
|
|
79
82
|
}
|
|
80
|
-
async fetch(trackingNumber) {
|
|
83
|
+
async fetch(trackingNumber, context = {}) {
|
|
81
84
|
const url = new URL(HERMES_API);
|
|
82
85
|
url.searchParams.set('parcelNumber', trackingNumber);
|
|
86
|
+
const budget = lookupBudget(context, this.timeoutMs);
|
|
83
87
|
const { bytes } = await fetchBounded(url, {
|
|
88
|
+
signal: budget.signal,
|
|
84
89
|
headers: {
|
|
85
90
|
Accept: 'application/json',
|
|
86
|
-
'User-Agent':
|
|
91
|
+
'User-Agent': this.#userAgent,
|
|
87
92
|
},
|
|
88
93
|
}, {
|
|
89
94
|
provider: 'Hermes tracking',
|
|
90
|
-
timeoutMs: this.timeoutMs,
|
|
95
|
+
timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
|
|
91
96
|
...(this.#fetcher ? { fetcher: this.#fetcher } : {}),
|
|
92
97
|
});
|
|
93
98
|
return parseHermesTrackingResponse(parseJsonBytes(bytes, PROVIDER), trackingNumber);
|
|
94
99
|
}
|
|
95
100
|
}
|
|
96
101
|
export const adapter = (environment) => {
|
|
97
|
-
const tracker = new HermesTracker({ fetcher: environment.fetcher });
|
|
102
|
+
const tracker = new HermesTracker({ fetcher: environment.fetcher, userAgent: environment.userAgent });
|
|
98
103
|
return {
|
|
99
104
|
id: 'hermes',
|
|
100
105
|
steps: ['direct'],
|
|
101
|
-
track: (input) => tracker.fetch(input.number),
|
|
106
|
+
track: (input, context) => tracker.fetch(input.number, context),
|
|
102
107
|
};
|
|
103
108
|
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { type AdapterFactory } from '../../core/adapter/index.js';
|
|
1
|
+
import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
|
|
2
2
|
import { NotFoundError } from '../../core/errors/index.js';
|
|
3
3
|
import type { CarrierResult } from '../../core/result/index.js';
|
|
4
4
|
export interface HermesGermanyOptions {
|
|
5
5
|
timeoutMs?: number;
|
|
6
6
|
/** Test seam; production uses the global fetch. */
|
|
7
7
|
fetcher?: typeof fetch;
|
|
8
|
+
userAgent?: string;
|
|
8
9
|
}
|
|
9
10
|
export { STATUSES } from './status.js';
|
|
10
11
|
/** Kept as a named class: the grouped live suite asserts this error's name. */
|
|
@@ -17,6 +18,6 @@ export declare class HermesGermanyTracker {
|
|
|
17
18
|
#private;
|
|
18
19
|
readonly timeoutMs: number;
|
|
19
20
|
constructor(options?: number | HermesGermanyOptions);
|
|
20
|
-
fetch(trackingNumber: string): Promise<CarrierResult>;
|
|
21
|
+
fetch(trackingNumber: string, context?: TrackingContext): Promise<CarrierResult>;
|
|
21
22
|
}
|
|
22
23
|
export declare const adapter: AdapterFactory;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DateTime } from 'luxon';
|
|
2
|
-
import { recognizeFromLookup } from '../../core/adapter/index.js';
|
|
3
|
-
import {
|
|
4
|
-
import { clean, fetchBounded, parseJsonBytes } from '../../core/transport/index.js';
|
|
2
|
+
import { lookupBudget, recognizeFromLookup } from '../../core/adapter/index.js';
|
|
3
|
+
import { InvalidInputError, NotFoundError, SchemaError, UpstreamHttpError } from '../../core/errors/index.js';
|
|
4
|
+
import { clean, fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
|
|
5
5
|
import { isRecord } from '../../core/types.js';
|
|
6
6
|
import { hermesGermanyMilestone, IGNORED_BOOKING_STATUS } from './status.js';
|
|
7
7
|
// Public recipient protocol inspected 2026-09-08:
|
|
@@ -36,7 +36,7 @@ export class HermesGermanyTrackingError extends NotFoundError {
|
|
|
36
36
|
export function normalizeHermesGermanyNumber(raw) {
|
|
37
37
|
const value = raw.toUpperCase().replace(/[\s.-]/g, '');
|
|
38
38
|
if (!/^(?=.*\d)[A-Z0-9]{8,20}$/.test(value)) {
|
|
39
|
-
throw new
|
|
39
|
+
throw new InvalidInputError(CARRIER, 'Hermes Germany requires an 8-to-20-character tracking number');
|
|
40
40
|
}
|
|
41
41
|
return value;
|
|
42
42
|
}
|
|
@@ -125,27 +125,31 @@ export function parseHermesGermanyResponse(payload, trackingNumber) {
|
|
|
125
125
|
export class HermesGermanyTracker {
|
|
126
126
|
timeoutMs;
|
|
127
127
|
#fetcher;
|
|
128
|
+
#userAgent;
|
|
128
129
|
constructor(options = {}) {
|
|
129
|
-
const { timeoutMs = DEFAULT_TIMEOUT_MS, fetcher } = typeof options === 'number'
|
|
130
|
-
? { timeoutMs: options, fetcher: undefined }
|
|
130
|
+
const { timeoutMs = DEFAULT_TIMEOUT_MS, fetcher, userAgent } = typeof options === 'number'
|
|
131
|
+
? { timeoutMs: options, fetcher: undefined, userAgent: undefined }
|
|
131
132
|
: options;
|
|
132
133
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
|
|
133
134
|
throw new TypeError('Hermes Germany timeout must be positive');
|
|
134
135
|
this.timeoutMs = timeoutMs;
|
|
135
136
|
this.#fetcher = fetcher;
|
|
137
|
+
this.#userAgent = userAgentOf(userAgent);
|
|
136
138
|
}
|
|
137
|
-
async fetch(trackingNumber) {
|
|
139
|
+
async fetch(trackingNumber, context = {}) {
|
|
138
140
|
const number = normalizeHermesGermanyNumber(trackingNumber);
|
|
141
|
+
const budget = lookupBudget(context, this.timeoutMs);
|
|
139
142
|
const { response, bytes } = await fetchBounded(`${API}${encodeURIComponent(number)}`, {
|
|
143
|
+
signal: budget.signal,
|
|
140
144
|
headers: {
|
|
141
145
|
Accept: 'application/json',
|
|
142
146
|
'X-Language': 'de',
|
|
143
147
|
Referer: 'https://www.myhermes.de/',
|
|
144
|
-
'User-Agent':
|
|
148
|
+
'User-Agent': this.#userAgent,
|
|
145
149
|
},
|
|
146
150
|
}, {
|
|
147
151
|
provider: PROVIDER,
|
|
148
|
-
timeoutMs: this.timeoutMs,
|
|
152
|
+
timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
|
|
149
153
|
maxBytes: 750_000,
|
|
150
154
|
allowHttpError: true,
|
|
151
155
|
...(this.#fetcher ? { fetcher: this.#fetcher } : {}),
|
|
@@ -158,11 +162,11 @@ export class HermesGermanyTracker {
|
|
|
158
162
|
}
|
|
159
163
|
}
|
|
160
164
|
export const adapter = (environment) => {
|
|
161
|
-
const tracker = new HermesGermanyTracker({ fetcher: environment.fetcher });
|
|
165
|
+
const tracker = new HermesGermanyTracker({ fetcher: environment.fetcher, userAgent: environment.userAgent });
|
|
162
166
|
return {
|
|
163
167
|
id: 'hermes-de',
|
|
164
168
|
steps: ['direct'],
|
|
165
|
-
track: (input) => tracker.fetch(input.number),
|
|
166
|
-
recognize: (number) => recognizeFromLookup(() => tracker.fetch(number)),
|
|
169
|
+
track: (input, context) => tracker.fetch(input.number, context),
|
|
170
|
+
recognize: (number, context) => recognizeFromLookup(() => tracker.fetch(number, context)),
|
|
167
171
|
};
|
|
168
172
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
|
|
2
2
|
import { ChallengeError } from '../../core/errors/index.js';
|
|
3
3
|
import { type CarrierResult } from '../../core/result/index.js';
|
|
4
4
|
import { classifyIndiaPostEvent } from './status.js';
|
|
@@ -26,7 +26,7 @@ export declare class IndiaPostTracker {
|
|
|
26
26
|
constructor(options?: IndiaPostTrackerOptions);
|
|
27
27
|
private request;
|
|
28
28
|
private update;
|
|
29
|
-
fetch(trackingNumber: string): Promise<CarrierResult>;
|
|
29
|
+
fetch(trackingNumber: string, context?: TrackingContext): Promise<CarrierResult>;
|
|
30
30
|
/** Send the first calls, then poll `fetchStatus` until the component completes. */
|
|
31
31
|
private complete;
|
|
32
32
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { load } from 'cheerio';
|
|
2
2
|
import makeFetchCookie from 'fetch-cookie';
|
|
3
3
|
import { CookieJar } from 'tough-cookie';
|
|
4
|
-
import {
|
|
4
|
+
import { lookupBudget } from '../../core/adapter/index.js';
|
|
5
|
+
import { ChallengeError, IndeterminateError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
5
6
|
import { eventPoint } from '../../core/result/index.js';
|
|
6
7
|
import { isValidS10TrackingNumber } from '../../core/detection/index.js';
|
|
7
8
|
import { isoTime } from '../../core/time/index.js';
|
|
@@ -38,7 +39,7 @@ export class IndiaPostChallengeError extends ChallengeError {
|
|
|
38
39
|
export function normalizeIndiaPostTrackingNumber(raw) {
|
|
39
40
|
const value = raw.toLocaleUpperCase('en-US').replace(/[\s.-]/g, '');
|
|
40
41
|
if (!/^[A-Z]{2}\d{9}IN$/.test(value) || !isValidS10TrackingNumber(value)) {
|
|
41
|
-
throw new
|
|
42
|
+
throw new InvalidInputError('India Post', 'India Post tracking requires a valid 13-character S10 number ending in IN');
|
|
42
43
|
}
|
|
43
44
|
return value;
|
|
44
45
|
}
|
|
@@ -261,10 +262,22 @@ function challengePage(status, html, headers) {
|
|
|
261
262
|
|| headers.get('cf-mitigated') === 'challenge'
|
|
262
263
|
|| /Just a moment|Enable JavaScript and cookies|cf-chl-|_cf_chl_opt/i.test(html);
|
|
263
264
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
265
|
+
/** The poll interval, cut short as soon as the lookup's signal aborts. */
|
|
266
|
+
async function pause(milliseconds, signal) {
|
|
267
|
+
signal.throwIfAborted();
|
|
268
|
+
if (milliseconds <= 0)
|
|
269
|
+
return;
|
|
270
|
+
await new Promise((resolve, reject) => {
|
|
271
|
+
const abort = () => {
|
|
272
|
+
clearTimeout(timer);
|
|
273
|
+
reject(signal.reason);
|
|
274
|
+
};
|
|
275
|
+
const timer = setTimeout(() => {
|
|
276
|
+
signal.removeEventListener('abort', abort);
|
|
277
|
+
resolve();
|
|
278
|
+
}, milliseconds);
|
|
279
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
280
|
+
});
|
|
268
281
|
}
|
|
269
282
|
export class IndiaPostTracker {
|
|
270
283
|
timeoutMs;
|
|
@@ -288,10 +301,10 @@ export class IndiaPostTracker {
|
|
|
288
301
|
throw new TypeError('India Post poll attempts must be positive');
|
|
289
302
|
}
|
|
290
303
|
}
|
|
291
|
-
async request(fetcher, url, init) {
|
|
292
|
-
const result = await fetchBounded(url, init, {
|
|
304
|
+
async request(fetcher, budget, url, init) {
|
|
305
|
+
const result = await fetchBounded(url, { ...init, signal: budget.signal }, {
|
|
293
306
|
provider: 'India Post tracking',
|
|
294
|
-
timeoutMs: this.timeoutMs,
|
|
307
|
+
timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
|
|
295
308
|
maxBytes: MAX_RESPONSE_BYTES,
|
|
296
309
|
redirect: 'manual',
|
|
297
310
|
fetcher,
|
|
@@ -306,8 +319,8 @@ export class IndiaPostTracker {
|
|
|
306
319
|
}
|
|
307
320
|
return { bytes: result.bytes, html };
|
|
308
321
|
}
|
|
309
|
-
async update(fetcher, trackingNumber, pageUrl, token, snapshot, calls, updates = {}) {
|
|
310
|
-
const result = await this.request(fetcher, LIVEWIRE_UPDATE, {
|
|
322
|
+
async update(fetcher, budget, trackingNumber, pageUrl, token, snapshot, calls, updates = {}) {
|
|
323
|
+
const result = await this.request(fetcher, budget, LIVEWIRE_UPDATE, {
|
|
311
324
|
method: 'POST',
|
|
312
325
|
headers: {
|
|
313
326
|
Accept: 'application/json',
|
|
@@ -325,13 +338,16 @@ export class IndiaPostTracker {
|
|
|
325
338
|
});
|
|
326
339
|
return parseLivewireUpdate(parseJsonBytes(result.bytes, 'India Post tracking'), trackingNumber);
|
|
327
340
|
}
|
|
328
|
-
async fetch(trackingNumber) {
|
|
341
|
+
async fetch(trackingNumber, context = {}) {
|
|
329
342
|
const normalized = normalizeIndiaPostTrackingNumber(trackingNumber);
|
|
330
343
|
const pageUrl = indiaPostTrackingUrl(normalized);
|
|
344
|
+
// The default budget covers the page, the first update, then every poll
|
|
345
|
+
// after its pause.
|
|
346
|
+
const budget = lookupBudget(context, (this.maxPollAttempts + 2) * this.timeoutMs + this.maxPollAttempts * this.pollIntervalMs);
|
|
331
347
|
// The Livewire flow is stateful: the session cookie issued with the page
|
|
332
348
|
// must travel with every /livewire/update call, so the jar is per lookup.
|
|
333
349
|
const fetcher = makeFetchCookie(this.fetcher, new CookieJar());
|
|
334
|
-
const page = await this.request(fetcher, pageUrl, {
|
|
350
|
+
const page = await this.request(fetcher, budget, pageUrl, {
|
|
335
351
|
headers: {
|
|
336
352
|
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
337
353
|
'Accept-Language': 'en-US,en;q=0.9',
|
|
@@ -347,13 +363,18 @@ export class IndiaPostTracker {
|
|
|
347
363
|
// What the page's Refresh button sends. A failed refresh still leaves
|
|
348
364
|
// the cached history, which is better than no answer.
|
|
349
365
|
try {
|
|
350
|
-
return await this.complete(fetcher, normalized, pageUrl, csrfToken(page.html), initial.snapshot, [{
|
|
366
|
+
return await this.complete(fetcher, budget, normalized, pageUrl, csrfToken(page.html), initial.snapshot, [{
|
|
351
367
|
path: '',
|
|
352
368
|
method: '__dispatch',
|
|
353
369
|
params: ['refresh_consignment', { userTimezone: USER_TIMEZONE }],
|
|
354
370
|
}], { userTimezone: USER_TIMEZONE });
|
|
355
371
|
}
|
|
356
372
|
catch {
|
|
373
|
+
// A caller that cancelled is not waiting for an answer; one whose
|
|
374
|
+
// deadline passed still takes the history already in hand.
|
|
375
|
+
const reason = context.signal?.reason;
|
|
376
|
+
if (context.signal?.aborted && !(reason instanceof DOMException && reason.name === 'TimeoutError'))
|
|
377
|
+
throw reason;
|
|
357
378
|
return cached;
|
|
358
379
|
}
|
|
359
380
|
}
|
|
@@ -362,12 +383,12 @@ export class IndiaPostTracker {
|
|
|
362
383
|
}
|
|
363
384
|
const token = csrfToken(page.html);
|
|
364
385
|
return initial.status === 'Processing'
|
|
365
|
-
? this.complete(fetcher, normalized, pageUrl, token, initial.snapshot, [{
|
|
386
|
+
? this.complete(fetcher, budget, normalized, pageUrl, token, initial.snapshot, [{
|
|
366
387
|
path: '',
|
|
367
388
|
method: 'fetchStatus',
|
|
368
389
|
params: [],
|
|
369
390
|
}])
|
|
370
|
-
: this.complete(fetcher, normalized, pageUrl, token, initial.snapshot, [{
|
|
391
|
+
: this.complete(fetcher, budget, normalized, pageUrl, token, initial.snapshot, [{
|
|
371
392
|
path: '',
|
|
372
393
|
method: '__dispatch',
|
|
373
394
|
params: ['set_consignment_number', { consignment_number: normalized }],
|
|
@@ -378,8 +399,8 @@ export class IndiaPostTracker {
|
|
|
378
399
|
}], { userTimezone: USER_TIMEZONE });
|
|
379
400
|
}
|
|
380
401
|
/** Send the first calls, then poll `fetchStatus` until the component completes. */
|
|
381
|
-
async complete(fetcher, normalized, pageUrl, token, snapshot, calls, updates = {}) {
|
|
382
|
-
let update = await this.update(fetcher, normalized, pageUrl, token, snapshot, calls, updates);
|
|
402
|
+
async complete(fetcher, budget, normalized, pageUrl, token, snapshot, calls, updates = {}) {
|
|
403
|
+
let update = await this.update(fetcher, budget, normalized, pageUrl, token, snapshot, calls, updates);
|
|
383
404
|
for (let attempt = 0; attempt <= this.maxPollAttempts; attempt += 1) {
|
|
384
405
|
const names = dispatchNames(update.effects);
|
|
385
406
|
if (names.has('consignment_not_found'))
|
|
@@ -395,8 +416,8 @@ export class IndiaPostTracker {
|
|
|
395
416
|
}
|
|
396
417
|
if (attempt === this.maxPollAttempts)
|
|
397
418
|
break;
|
|
398
|
-
await pause(this.pollIntervalMs);
|
|
399
|
-
update = await this.update(fetcher, normalized, pageUrl, token, update.component.snapshot, [{ path: '', method: 'fetchStatus', params: [] }]);
|
|
419
|
+
await pause(this.pollIntervalMs, budget.signal);
|
|
420
|
+
update = await this.update(fetcher, budget, normalized, pageUrl, token, update.component.snapshot, [{ path: '', method: 'fetchStatus', params: [] }]);
|
|
400
421
|
}
|
|
401
422
|
// The backend kept saying "Processing": it answered, but it proved nothing
|
|
402
423
|
// about the shipment, so this must not become a not-found cooldown.
|
|
@@ -410,6 +431,6 @@ export const adapter = (environment) => {
|
|
|
410
431
|
// The Livewire submit-and-poll cycle is part of the direct call: it is one
|
|
411
432
|
// stateful conversation with one host, not a fallback tier.
|
|
412
433
|
steps: ['direct'],
|
|
413
|
-
track: (input) => tracker.fetch(input.number),
|
|
434
|
+
track: (input, context) => tracker.fetch(input.number, context),
|
|
414
435
|
};
|
|
415
436
|
};
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { type AdapterFactory } from '../../core/adapter/index.js';
|
|
1
|
+
import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
|
|
2
2
|
import type { CarrierResult } from '../../core/result/index.js';
|
|
3
3
|
export declare function normalizeInpostTrackingNumber(raw: string): string;
|
|
4
4
|
export declare function parseInpostTrackingResponse(payload: unknown, trackingNumber: string): CarrierResult;
|
|
5
5
|
export declare class InpostTracker {
|
|
6
6
|
readonly timeoutMs: number;
|
|
7
7
|
readonly fetcher: typeof fetch | undefined;
|
|
8
|
+
private readonly userAgent;
|
|
8
9
|
constructor(options?: {
|
|
9
10
|
timeoutMs?: number;
|
|
10
11
|
fetcher?: typeof fetch;
|
|
12
|
+
userAgent?: string;
|
|
11
13
|
});
|
|
12
|
-
fetch(rawTrackingNumber: string): Promise<CarrierResult>;
|
|
14
|
+
fetch(rawTrackingNumber: string, context?: TrackingContext): Promise<CarrierResult>;
|
|
13
15
|
}
|
|
14
16
|
export declare const adapter: AdapterFactory;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { accepted, recognizeFromLookup } from '../../core/adapter/index.js';
|
|
2
|
-
import { NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
1
|
+
import { accepted, lookupBudget, recognizeFromLookup } from '../../core/adapter/index.js';
|
|
2
|
+
import { InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
3
3
|
import { explicitOffsetTime } from '../../core/time/index.js';
|
|
4
|
-
import { clean, fetchBounded, parseJsonBytes, UpstreamHttpError } from '../../core/transport/index.js';
|
|
4
|
+
import { clean, fetchBounded, parseJsonBytes, UpstreamHttpError, userAgentOf } from '../../core/transport/index.js';
|
|
5
5
|
import { isRecord } from '../../core/types.js';
|
|
6
6
|
import { classifyInpostStatus } from './status.js';
|
|
7
7
|
// Protocol provenance:
|
|
@@ -20,6 +20,8 @@ import { classifyInpostStatus } from './status.js';
|
|
|
20
20
|
// consignments 2026-08-31 by the prior-art client; the map lives in status.ts.
|
|
21
21
|
const TRACKING_ENDPOINT = 'https://inposteasy.com/api/tracking';
|
|
22
22
|
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
23
|
+
/** The pause `fetchBounded` takes before its one retry of a failed request. */
|
|
24
|
+
const TRANSIENT_RETRY_DELAY_MS = 1_000;
|
|
23
25
|
const MAX_RESPONSE_BYTES = 1_000_000;
|
|
24
26
|
const MAX_EVENTS_TO_RETURN = 20;
|
|
25
27
|
function statusCode(value) {
|
|
@@ -28,7 +30,7 @@ function statusCode(value) {
|
|
|
28
30
|
export function normalizeInpostTrackingNumber(raw) {
|
|
29
31
|
const value = raw.toLocaleUpperCase('en-US').replace(/[\s.-]/g, '');
|
|
30
32
|
if (!/^(?:\d{24}|JJD\d{16}|JD\d{16}|8YDR\d{9})$/.test(value)) {
|
|
31
|
-
throw new
|
|
33
|
+
throw new InvalidInputError('InPost', 'InPost tracking requires a 24-digit, JJD/JD legacy or 8YDR identifier');
|
|
32
34
|
}
|
|
33
35
|
return value;
|
|
34
36
|
}
|
|
@@ -101,25 +103,30 @@ export function parseInpostTrackingResponse(payload, trackingNumber) {
|
|
|
101
103
|
export class InpostTracker {
|
|
102
104
|
timeoutMs;
|
|
103
105
|
fetcher;
|
|
106
|
+
userAgent;
|
|
104
107
|
constructor(options = {}) {
|
|
105
108
|
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
106
109
|
this.fetcher = options.fetcher;
|
|
110
|
+
this.userAgent = userAgentOf(options.userAgent);
|
|
107
111
|
if (!Number.isFinite(this.timeoutMs) || this.timeoutMs <= 0) {
|
|
108
112
|
throw new TypeError('InPost timeout must be positive');
|
|
109
113
|
}
|
|
110
114
|
}
|
|
111
|
-
async fetch(rawTrackingNumber) {
|
|
115
|
+
async fetch(rawTrackingNumber, context = {}) {
|
|
112
116
|
const trackingNumber = normalizeInpostTrackingNumber(rawTrackingNumber);
|
|
117
|
+
// The default budget covers the request, the pause and the one transient retry.
|
|
118
|
+
const budget = lookupBudget(context, 2 * this.timeoutMs + TRANSIENT_RETRY_DELAY_MS);
|
|
113
119
|
const url = `${TRACKING_ENDPOINT}/${encodeURIComponent(trackingNumber)}`;
|
|
114
120
|
const { response, bytes } = await fetchBounded(url, {
|
|
121
|
+
signal: budget.signal,
|
|
115
122
|
headers: {
|
|
116
123
|
Accept: 'application/json, text/plain, */*',
|
|
117
124
|
'Accept-Language': 'en-US,en;q=0.9',
|
|
118
|
-
'User-Agent':
|
|
125
|
+
'User-Agent': this.userAgent,
|
|
119
126
|
},
|
|
120
127
|
}, {
|
|
121
128
|
provider: 'InPost tracking',
|
|
122
|
-
timeoutMs: this.timeoutMs,
|
|
129
|
+
timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
|
|
123
130
|
maxBytes: MAX_RESPONSE_BYTES,
|
|
124
131
|
retryTransient: true,
|
|
125
132
|
allowHttpError: true,
|
|
@@ -133,11 +140,11 @@ export class InpostTracker {
|
|
|
133
140
|
}
|
|
134
141
|
}
|
|
135
142
|
export const adapter = (environment) => {
|
|
136
|
-
const tracker = new InpostTracker({ fetcher: environment.fetcher });
|
|
143
|
+
const tracker = new InpostTracker({ fetcher: environment.fetcher, userAgent: environment.userAgent });
|
|
137
144
|
return {
|
|
138
145
|
id: 'inpost',
|
|
139
146
|
steps: ['direct'],
|
|
140
|
-
track: (input) => tracker.fetch(input.number),
|
|
141
|
-
recognize: (number) => recognizeFromLookup(() => tracker.fetch(number), () => accepted(() => normalizeInpostTrackingNumber(number))),
|
|
147
|
+
track: (input, context) => tracker.fetch(input.number, context),
|
|
148
|
+
recognize: (number, context) => recognizeFromLookup(() => tracker.fetch(number, context), () => accepted(() => normalizeInpostTrackingNumber(number))),
|
|
142
149
|
};
|
|
143
150
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { load } from 'cheerio';
|
|
2
2
|
import { DateTime } from 'luxon';
|
|
3
3
|
import { isValidS10TrackingNumber, normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
4
|
-
import { ChallengeError,
|
|
4
|
+
import { ChallengeError, InvalidInputError, NotFoundError, SchemaError, TransportError, UpstreamHttpError } from '../../core/errors/index.js';
|
|
5
5
|
import { clean, decodeText, fetchBounded } from '../../core/transport/index.js';
|
|
6
6
|
import { japanPostStatus } from './status.js';
|
|
7
7
|
const PROVIDER = 'Japan Post';
|
|
@@ -19,7 +19,7 @@ export function normalizeJapanPostNumber(raw) {
|
|
|
19
19
|
// S10 references. U-prefixed customs labels are explicitly not trackable.
|
|
20
20
|
if (!/^\d{11,13}$/.test(number)
|
|
21
21
|
&& (!/^[A-TV-Z][A-Z]\d{9}[A-Z]{2}$/.test(number) || !isValidS10TrackingNumber(number))) {
|
|
22
|
-
throw new
|
|
22
|
+
throw new InvalidInputError(PROVIDER, 'Japan Post requires a domestic tracking number or a valid tracked postal reference');
|
|
23
23
|
}
|
|
24
24
|
return number;
|
|
25
25
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { load } from 'cheerio';
|
|
2
2
|
import { DateTime } from 'luxon';
|
|
3
|
-
import { IndeterminateError,
|
|
3
|
+
import { IndeterminateError, InvalidInputError, NotFoundError, SchemaError, TransportError, UpstreamHttpError } from '../../core/errors/index.js';
|
|
4
4
|
import { runSteps } from '../../core/runner/index.js';
|
|
5
5
|
import { clean, decodeText, fetchBounded } from '../../core/transport/index.js';
|
|
6
6
|
import { koreaPostStatus } from './status.js';
|
|
@@ -8,7 +8,7 @@ const ENDPOINT = 'https://trace.epost.go.kr/xtts/servlet/kpl.tts.common.svl.SttS
|
|
|
8
8
|
export function normalizeKoreaPostNumber(raw) {
|
|
9
9
|
const number = raw.toUpperCase().replace(/[\s.-]/g, '');
|
|
10
10
|
if (!/^[A-Z]{2}\d{9}[A-Z]{2}$/.test(number))
|
|
11
|
-
throw new
|
|
11
|
+
throw new InvalidInputError('Korea Post', 'Korea Post direct tracking supports international postal identifiers');
|
|
12
12
|
return number;
|
|
13
13
|
}
|
|
14
14
|
function eventTime(raw) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AdapterFactory } from '../../core/adapter/index.js';
|
|
1
|
+
import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
|
|
2
2
|
import { CarrierError } from '../../core/errors/index.js';
|
|
3
3
|
import type { CarrierResult } from '../../core/result/index.js';
|
|
4
4
|
import type { StepRecorder } from '../../core/telemetry/index.js';
|
|
@@ -18,12 +18,14 @@ export interface LaPosteTrackerOptions {
|
|
|
18
18
|
timeoutMs?: number;
|
|
19
19
|
fetcher?: typeof fetch;
|
|
20
20
|
recorder?: StepRecorder;
|
|
21
|
+
userAgent?: string;
|
|
21
22
|
}
|
|
22
23
|
export declare class LaPosteTracker {
|
|
23
24
|
readonly timeoutMs: number;
|
|
24
25
|
private readonly fetcher?;
|
|
25
26
|
private readonly recorder?;
|
|
27
|
+
private readonly userAgent;
|
|
26
28
|
constructor(options?: LaPosteTrackerOptions);
|
|
27
|
-
fetch(trackingNumber: string): Promise<CarrierResult>;
|
|
29
|
+
fetch(trackingNumber: string, context?: TrackingContext): Promise<CarrierResult>;
|
|
28
30
|
}
|
|
29
31
|
export declare const adapter: AdapterFactory;
|