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
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { load } from 'cheerio';
|
|
2
|
-
import { ChallengeError, InputRequiredError, RateLimitedError, SchemaError, TransportError, UpstreamHttpError, } from '../../core/errors/index.js';
|
|
2
|
+
import { ChallengeError, InputRequiredError, InvalidInputError, RateLimitedError, SchemaError, TransportError, UpstreamHttpError, } from '../../core/errors/index.js';
|
|
3
3
|
import { runSteps } from '../../core/runner/index.js';
|
|
4
4
|
import { NOOP_RECORDER } from '../../core/telemetry/index.js';
|
|
5
5
|
import { explicitOffsetTime } from '../../core/time/index.js';
|
|
6
|
-
import { clean, cleanScalar, TrawlClient } from '../../core/transport/index.js';
|
|
6
|
+
import { clean, cleanScalar, TRAWL_TRANSPORT_ALLOWANCE_MS, TrawlClient } from '../../core/transport/index.js';
|
|
7
7
|
import { isRecord } from '../../core/types.js';
|
|
8
8
|
import { FEDEX_CODE_STAGE, fedexStage, fedexStatus } from './status.js';
|
|
9
9
|
/**
|
|
@@ -35,7 +35,7 @@ const MAX_EVENTS_TO_RETURN = 100;
|
|
|
35
35
|
export function normalizeFedExTrackingNumber(raw) {
|
|
36
36
|
const value = raw.toLocaleUpperCase('en-US').replace(/[\s.-]/g, '');
|
|
37
37
|
if (!/^\d{12}$/.test(value) && !/^\d{15}$/.test(value)) {
|
|
38
|
-
throw new
|
|
38
|
+
throw new InvalidInputError('FedEx', 'FedEx tracking numbers must contain 12 or 15 digits');
|
|
39
39
|
}
|
|
40
40
|
return value;
|
|
41
41
|
}
|
|
@@ -210,7 +210,7 @@ export class FedExTracker {
|
|
|
210
210
|
return this.#trawl;
|
|
211
211
|
return this.trawlUrl ? new TrawlClient(this.trawlUrl, this.#fetcher) : null;
|
|
212
212
|
}
|
|
213
|
-
async fetch(trackingNumber) {
|
|
213
|
+
async fetch(trackingNumber, context = {}) {
|
|
214
214
|
const number = normalizeFedExTrackingNumber(trackingNumber);
|
|
215
215
|
// Plain HTTP probes were rejected; the dedicated route uses the browser.
|
|
216
216
|
const trawl = this.#browserService();
|
|
@@ -218,9 +218,12 @@ export class FedExTracker {
|
|
|
218
218
|
throw new ChallengeError('FedEx', 'FedEx challenged direct tracking; configure FLARESOLVERR_URL for browser fallback');
|
|
219
219
|
}
|
|
220
220
|
return runSteps({
|
|
221
|
-
|
|
221
|
+
// Without a caller's budget the lookup leaves the service its own time and
|
|
222
|
+
// the request the allowance to bring the answer back.
|
|
223
|
+
carrier: 'fedex', budgetMs: context.budgetMs ?? this.timeoutMs + TRAWL_TRANSPORT_ALLOWANCE_MS, signal: context.signal,
|
|
224
|
+
recorder: this.#recorder,
|
|
222
225
|
}, [
|
|
223
|
-
{ id: 'trawl', run: () => this.#trawlResult(trawl, number) },
|
|
226
|
+
{ id: 'trawl', run: ({ remainingMs, signal }) => this.#trawlResult(trawl, number, Math.max(1, Math.floor(Math.min(this.timeoutMs, remainingMs))), signal) },
|
|
224
227
|
]);
|
|
225
228
|
}
|
|
226
229
|
/**
|
|
@@ -229,19 +232,20 @@ export class FedExTracker {
|
|
|
229
232
|
* over plain HTTP: the edge accepts the call only from the session it
|
|
230
233
|
* validated. The page the browser rendered only tells a challenge apart.
|
|
231
234
|
*/
|
|
232
|
-
async #trawlResult(trawl, number) {
|
|
235
|
+
async #trawlResult(trawl, number, timeoutMs, signal) {
|
|
233
236
|
const page = await trawl.scrape({
|
|
234
237
|
url: fedexTrackingUrl(number),
|
|
235
238
|
skipHttp: true,
|
|
236
239
|
maxTier: 3,
|
|
237
|
-
maxTimeout:
|
|
240
|
+
maxTimeout: timeoutMs,
|
|
238
241
|
captureResponses: [TRACK_API],
|
|
239
242
|
settleTimeout: SETTLE_TIMEOUT_MS,
|
|
240
243
|
}, {
|
|
241
244
|
provider: 'TRAWL while fetching FedEx',
|
|
242
|
-
timeoutMs
|
|
245
|
+
timeoutMs,
|
|
243
246
|
maxBytes: MAX_BYTES,
|
|
244
247
|
fetcher: this.#fetcher,
|
|
248
|
+
signal,
|
|
245
249
|
});
|
|
246
250
|
let captureError;
|
|
247
251
|
// Newest first: a later reply is the page's final answer.
|
|
@@ -302,6 +306,6 @@ export const adapter = (environment) => {
|
|
|
302
306
|
id: 'fedex',
|
|
303
307
|
// Browser-backed direct tracking; universal recovery belongs to the caller.
|
|
304
308
|
steps: ['trawl'],
|
|
305
|
-
track: (input) => tracker.fetch(input.number),
|
|
309
|
+
track: (input, context) => tracker.fetch(input.number, context),
|
|
306
310
|
};
|
|
307
311
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DateTime } from 'luxon';
|
|
2
|
-
import { IndeterminateError, NotFoundError, SchemaError, TransportError, UpstreamHttpError } from '../../core/errors/index.js';
|
|
2
|
+
import { IndeterminateError, InvalidInputError, NotFoundError, SchemaError, TransportError, UpstreamHttpError } from '../../core/errors/index.js';
|
|
3
3
|
import { runSteps } from '../../core/runner/index.js';
|
|
4
4
|
import { clean, fetchBounded, parseJsonBytes } from '../../core/transport/index.js';
|
|
5
5
|
import { isRecord } from '../../core/types.js';
|
|
@@ -8,7 +8,7 @@ const ENDPOINT = 'https://track.4px.com/track/v2/front/listTrackV3';
|
|
|
8
8
|
export function normalizeFourPxNumber(raw) {
|
|
9
9
|
const number = raw.toUpperCase().replace(/[\s.-]/g, '');
|
|
10
10
|
if (!/^[A-Z0-9]{8,40}$/.test(number))
|
|
11
|
-
throw new
|
|
11
|
+
throw new InvalidInputError('4PX', '4PX requires an alphanumeric parcel reference');
|
|
12
12
|
return number;
|
|
13
13
|
}
|
|
14
14
|
function eventTime(raw) {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* its result from an allowlist of status, timeline and operational-location
|
|
8
8
|
* fields, so none of that reaches the result or the logs.
|
|
9
9
|
*/
|
|
10
|
-
import type
|
|
10
|
+
import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
|
|
11
11
|
import { NotFoundError } from '../../core/errors/index.js';
|
|
12
12
|
import type { CarrierResult } from '../../core/result/index.js';
|
|
13
13
|
export { classifyStatus } from './status.js';
|
|
@@ -27,11 +27,12 @@ export interface GeodisTrackerOptions {
|
|
|
27
27
|
timeoutMs?: number;
|
|
28
28
|
/** Test seam; production uses the global fetch. */
|
|
29
29
|
fetcher?: typeof fetch;
|
|
30
|
+
userAgent?: string;
|
|
30
31
|
}
|
|
31
32
|
export declare class GeodisTracker {
|
|
32
33
|
#private;
|
|
33
34
|
readonly timeoutMs: number;
|
|
34
35
|
constructor(options?: GeodisTrackerOptions);
|
|
35
|
-
fetch(rawTrackingNumber: string): Promise<CarrierResult>;
|
|
36
|
+
fetch(rawTrackingNumber: string, context?: TrackingContext): Promise<CarrierResult>;
|
|
36
37
|
}
|
|
37
38
|
export declare const adapter: AdapterFactory;
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* fields, so none of that reaches the result or the logs.
|
|
9
9
|
*/
|
|
10
10
|
import { createHash } from 'node:crypto';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { lookupBudget } from '../../core/adapter/index.js';
|
|
12
|
+
import { IndeterminateError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
13
|
+
import { clean, fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
|
|
13
14
|
import { isRecord } from '../../core/types.js';
|
|
14
15
|
import { classifyStatus, comparableText, includesAny } from './status.js';
|
|
15
16
|
export { classifyStatus } from './status.js';
|
|
@@ -152,7 +153,7 @@ function isNotFoundCode(value) {
|
|
|
152
153
|
export function normalizeGeodisTrackingNumber(raw) {
|
|
153
154
|
const trackingNumber = raw.trim().toLocaleUpperCase('en-US');
|
|
154
155
|
if (!/^1G[A-Z0-9]{10}$/.test(trackingNumber)) {
|
|
155
|
-
throw new
|
|
156
|
+
throw new InvalidInputError(PROVIDER, 'GEODIS tracking numbers must start with 1G and contain 12 letters and digits');
|
|
156
157
|
}
|
|
157
158
|
return trackingNumber;
|
|
158
159
|
}
|
|
@@ -233,32 +234,36 @@ export function parseGeodisTrackingResponse(payload, rawTrackingNumber) {
|
|
|
233
234
|
export class GeodisTracker {
|
|
234
235
|
timeoutMs;
|
|
235
236
|
#fetcher;
|
|
237
|
+
#userAgent;
|
|
236
238
|
constructor(options = {}) {
|
|
237
239
|
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
238
240
|
if (!Number.isFinite(this.timeoutMs) || this.timeoutMs <= 0) {
|
|
239
241
|
throw new TypeError('GEODIS timeout must be positive');
|
|
240
242
|
}
|
|
241
243
|
this.#fetcher = options.fetcher;
|
|
244
|
+
this.#userAgent = userAgentOf(options.userAgent);
|
|
242
245
|
}
|
|
243
|
-
async fetch(rawTrackingNumber) {
|
|
246
|
+
async fetch(rawTrackingNumber, context = {}) {
|
|
244
247
|
const trackingNumber = normalizeGeodisTrackingNumber(rawTrackingNumber);
|
|
248
|
+
const budget = lookupBudget(context, this.timeoutMs);
|
|
245
249
|
const body = geodisRequestBody(trackingNumber);
|
|
246
250
|
const timestamp = Date.now();
|
|
247
251
|
const { bytes } = await fetchBounded(geodisTrackingUrl(), {
|
|
248
252
|
method: 'POST',
|
|
253
|
+
signal: budget.signal,
|
|
249
254
|
headers: {
|
|
250
255
|
Accept: 'application/json',
|
|
251
256
|
'Accept-Language': 'fr-FR,fr;q=0.9',
|
|
252
257
|
'Content-Type': 'application/json',
|
|
253
258
|
Origin: 'https://espace-client.geodis.com',
|
|
254
259
|
Referer: TRACKING_PAGE,
|
|
255
|
-
'User-Agent':
|
|
260
|
+
'User-Agent': this.#userAgent,
|
|
256
261
|
'X-GEODIS-Service': geodisServiceHeader(trackingNumber, timestamp),
|
|
257
262
|
},
|
|
258
263
|
body,
|
|
259
264
|
}, {
|
|
260
265
|
provider: 'GEODIS tracking',
|
|
261
|
-
timeoutMs: this.timeoutMs,
|
|
266
|
+
timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
|
|
262
267
|
maxBytes: MAX_RESPONSE_BYTES,
|
|
263
268
|
fetcher: this.#fetcher,
|
|
264
269
|
});
|
|
@@ -266,10 +271,10 @@ export class GeodisTracker {
|
|
|
266
271
|
}
|
|
267
272
|
}
|
|
268
273
|
export const adapter = (environment) => {
|
|
269
|
-
const tracker = new GeodisTracker({ fetcher: environment.fetcher });
|
|
274
|
+
const tracker = new GeodisTracker({ fetcher: environment.fetcher, userAgent: environment.userAgent });
|
|
270
275
|
return {
|
|
271
276
|
id: 'geodis',
|
|
272
277
|
steps: ['direct'],
|
|
273
|
-
track: (input) => tracker.fetch(input.number),
|
|
278
|
+
track: (input, context) => tracker.fetch(input.number, context),
|
|
274
279
|
};
|
|
275
280
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
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
|
import { type JsonObject } from '../../core/types.js';
|
|
@@ -8,6 +8,7 @@ export interface GLSSwitzerlandOptions {
|
|
|
8
8
|
now?: () => number;
|
|
9
9
|
/** Test seam; production uses the global fetch. */
|
|
10
10
|
fetcher?: typeof fetch;
|
|
11
|
+
userAgent?: string;
|
|
11
12
|
}
|
|
12
13
|
/** Kept as a named class: the German folder narrows on it with `instanceof`. */
|
|
13
14
|
export declare class GLSSwitzerlandTrackingError extends NotFoundError {
|
|
@@ -25,13 +26,13 @@ export declare class GLSSwitzerlandTracker {
|
|
|
25
26
|
readonly timeoutMs: number;
|
|
26
27
|
readonly now: () => number;
|
|
27
28
|
constructor(options?: number | GLSSwitzerlandOptions);
|
|
28
|
-
fetch(rawTrackingNumber: string, rawPostcode?: string): Promise<CarrierResult>;
|
|
29
|
+
fetch(rawTrackingNumber: string, rawPostcode?: string, context?: TrackingContext): Promise<CarrierResult>;
|
|
29
30
|
/**
|
|
30
31
|
* Whether GLS knows a number, from the overview alone: no postcode is sent.
|
|
31
32
|
* Only a 404 whose body carries GLS's `lastError: E000` is a not-found here;
|
|
32
33
|
* a 400 or 403 can be a challenge, so it stays a failure.
|
|
33
34
|
*/
|
|
34
|
-
recognizes(rawTrackingNumber: string): Promise<boolean>;
|
|
35
|
+
recognizes(rawTrackingNumber: string, context?: TrackingContext): Promise<boolean>;
|
|
35
36
|
private request;
|
|
36
37
|
}
|
|
37
38
|
/** Keep the verified overview's delivery owner when the postcode detail omits it. */
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { load } from 'cheerio';
|
|
2
2
|
import { DateTime } from 'luxon';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { lookupBudget } from '../../core/adapter/index.js';
|
|
4
|
+
import { InputRequiredError, InvalidInputError, NotFoundError, SchemaError, UpstreamHttpError } from '../../core/errors/index.js';
|
|
5
|
+
import { cleanScalar, fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
|
|
5
6
|
import { isRecord } from '../../core/types.js';
|
|
6
7
|
import { classifyDescription, GLS_STATUSES, statusCode } from './status.js';
|
|
7
8
|
// Protocol provenance (inspected 2026-08-30):
|
|
@@ -44,7 +45,7 @@ function isReturnParcel(parcel) {
|
|
|
44
45
|
export function normalizeGLSSwitzerlandTrackingNumber(raw) {
|
|
45
46
|
const value = raw.toLocaleUpperCase('en-US').replace(/[\s.-]/g, '');
|
|
46
47
|
if (!/^(?:(?=[A-Z0-9]{8}$)(?=.*[A-Z])(?=.*\d)[A-Z0-9]{8}|\d{11,14})$/.test(value)) {
|
|
47
|
-
throw new
|
|
48
|
+
throw new InvalidInputError('GLS', 'GLS tracking requires an 8-character Track ID or an 11-to-14-digit parcel number');
|
|
48
49
|
}
|
|
49
50
|
return value;
|
|
50
51
|
}
|
|
@@ -302,22 +303,23 @@ export function parseGLSSwitzerlandTrackingResponse(payload, rawTrackingNumber)
|
|
|
302
303
|
function ownerCode(parcel) {
|
|
303
304
|
return records(parcel.owners).reduce((found, owner) => (found || (statusCode(owner.type) === 'REQUEST' ? text(owner.code, 32) : '')), '');
|
|
304
305
|
}
|
|
305
|
-
function pageHeaders() {
|
|
306
|
+
function pageHeaders(userAgent) {
|
|
306
307
|
return {
|
|
307
308
|
Accept: 'application/json',
|
|
308
309
|
'Accept-Language': 'en-CH,en;q=0.9',
|
|
309
310
|
Origin: 'https://gls-group.eu',
|
|
310
311
|
Referer: `${TRACKING_PAGE}/`,
|
|
311
|
-
'User-Agent':
|
|
312
|
+
'User-Agent': userAgent,
|
|
312
313
|
};
|
|
313
314
|
}
|
|
314
315
|
export class GLSSwitzerlandTracker {
|
|
315
316
|
timeoutMs;
|
|
316
317
|
now;
|
|
317
318
|
#fetcher;
|
|
319
|
+
#userAgent;
|
|
318
320
|
constructor(options = {}) {
|
|
319
|
-
const { timeoutMs = DEFAULT_TIMEOUT_MS, now = Date.now, fetcher } = typeof options === 'number'
|
|
320
|
-
? { timeoutMs: options, now: Date.now, fetcher: undefined }
|
|
321
|
+
const { timeoutMs = DEFAULT_TIMEOUT_MS, now = Date.now, fetcher, userAgent } = typeof options === 'number'
|
|
322
|
+
? { timeoutMs: options, now: Date.now, fetcher: undefined, userAgent: undefined }
|
|
321
323
|
: options;
|
|
322
324
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
323
325
|
throw new TypeError('GLS Switzerland timeout must be positive');
|
|
@@ -325,10 +327,13 @@ export class GLSSwitzerlandTracker {
|
|
|
325
327
|
this.timeoutMs = timeoutMs;
|
|
326
328
|
this.now = now;
|
|
327
329
|
this.#fetcher = fetcher;
|
|
330
|
+
this.#userAgent = userAgentOf(userAgent);
|
|
328
331
|
}
|
|
329
|
-
async fetch(rawTrackingNumber, rawPostcode = '') {
|
|
332
|
+
async fetch(rawTrackingNumber, rawPostcode = '', context = {}) {
|
|
330
333
|
const trackingNumber = normalizeGLSSwitzerlandTrackingNumber(rawTrackingNumber);
|
|
331
|
-
|
|
334
|
+
// Up to two requests, each with the full request timeout.
|
|
335
|
+
const budget = lookupBudget(context, 2 * this.timeoutMs);
|
|
336
|
+
const overview = await this.request(glsSwitzerlandOverviewApiUrl(trackingNumber, this.now()), budget);
|
|
332
337
|
const parcel = selectGLSParcel(overview, trackingNumber);
|
|
333
338
|
if (!rawPostcode.trim())
|
|
334
339
|
return parseGLSSwitzerlandTrackingResponse(overview, trackingNumber);
|
|
@@ -336,7 +341,7 @@ export class GLSSwitzerlandTracker {
|
|
|
336
341
|
if (!/^\d{11,14}$/.test(parcelNumber)) {
|
|
337
342
|
throw new SchemaError(PROVIDER, 'GLS did not return a numeric parcel number');
|
|
338
343
|
}
|
|
339
|
-
const detail = await this.request(glsSwitzerlandDetailApiUrl(parcelNumber, rawPostcode, this.now(), ownerCode(parcel)));
|
|
344
|
+
const detail = await this.request(glsSwitzerlandDetailApiUrl(parcelNumber, rawPostcode, this.now(), ownerCode(parcel)), budget);
|
|
340
345
|
const result = parseGLSSwitzerlandTrackingResponse(detail, parcelNumber);
|
|
341
346
|
const overviewResult = parseGLSSwitzerlandTrackingResponse(overview, trackingNumber);
|
|
342
347
|
return { ...result, ...glsDeliveryReference(overviewResult) };
|
|
@@ -346,13 +351,15 @@ export class GLSSwitzerlandTracker {
|
|
|
346
351
|
* Only a 404 whose body carries GLS's `lastError: E000` is a not-found here;
|
|
347
352
|
* a 400 or 403 can be a challenge, so it stays a failure.
|
|
348
353
|
*/
|
|
349
|
-
async recognizes(rawTrackingNumber) {
|
|
354
|
+
async recognizes(rawTrackingNumber, context = {}) {
|
|
350
355
|
const trackingNumber = normalizeGLSSwitzerlandTrackingNumber(rawTrackingNumber);
|
|
356
|
+
const budget = lookupBudget(context, this.timeoutMs);
|
|
351
357
|
const { response, bytes } = await fetchBounded(glsSwitzerlandOverviewApiUrl(trackingNumber, this.now()), {
|
|
352
|
-
|
|
358
|
+
signal: budget.signal,
|
|
359
|
+
headers: pageHeaders(this.#userAgent),
|
|
353
360
|
}, {
|
|
354
361
|
provider: 'GLS Switzerland tracking',
|
|
355
|
-
timeoutMs: this.timeoutMs,
|
|
362
|
+
timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
|
|
356
363
|
maxBytes: MAX_RESPONSE_BYTES,
|
|
357
364
|
allowHttpError: true,
|
|
358
365
|
...(this.#fetcher ? { fetcher: this.#fetcher } : {}),
|
|
@@ -373,12 +380,13 @@ export class GLSSwitzerlandTracker {
|
|
|
373
380
|
throw error;
|
|
374
381
|
}
|
|
375
382
|
}
|
|
376
|
-
async request(url) {
|
|
383
|
+
async request(url, budget) {
|
|
377
384
|
const { response, bytes } = await fetchBounded(url, {
|
|
378
|
-
|
|
385
|
+
signal: budget.signal,
|
|
386
|
+
headers: pageHeaders(this.#userAgent),
|
|
379
387
|
}, {
|
|
380
388
|
provider: 'GLS Switzerland tracking',
|
|
381
|
-
timeoutMs: this.timeoutMs,
|
|
389
|
+
timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
|
|
382
390
|
maxBytes: MAX_RESPONSE_BYTES,
|
|
383
391
|
allowHttpError: true,
|
|
384
392
|
...(this.#fetcher ? { fetcher: this.#fetcher } : {}),
|
|
@@ -395,11 +403,11 @@ export function glsDeliveryReference(result) {
|
|
|
395
403
|
return result.delivery_carrier ? { delivery_carrier: result.delivery_carrier, delivery_tracking_number: result.delivery_tracking_number } : {};
|
|
396
404
|
}
|
|
397
405
|
export const adapter = (environment) => {
|
|
398
|
-
const tracker = new GLSSwitzerlandTracker({ fetcher: environment.fetcher });
|
|
406
|
+
const tracker = new GLSSwitzerlandTracker({ fetcher: environment.fetcher, userAgent: environment.userAgent });
|
|
399
407
|
return {
|
|
400
408
|
id: 'gls-ch',
|
|
401
409
|
steps: ['direct'],
|
|
402
|
-
track: (input) => tracker.fetch(input.number, input.postcode ?? ''),
|
|
403
|
-
recognize: async (number) => ({ known: await tracker.recognizes(number) }),
|
|
410
|
+
track: (input, context) => tracker.fetch(input.number, input.postcode ?? '', context),
|
|
411
|
+
recognize: async (number, context) => ({ known: await tracker.recognizes(number, context) }),
|
|
404
412
|
};
|
|
405
413
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
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 GLSGermanyOptions {
|
|
@@ -16,13 +16,13 @@ export declare class GLSGermanyTracker {
|
|
|
16
16
|
readonly timeoutMs: number;
|
|
17
17
|
readonly now: () => number;
|
|
18
18
|
constructor(options?: number | GLSGermanyOptions);
|
|
19
|
-
fetch(rawTrackingNumber: string, rawPostcode: string): Promise<CarrierResult>;
|
|
19
|
+
fetch(rawTrackingNumber: string, rawPostcode: string, context?: TrackingContext): Promise<CarrierResult>;
|
|
20
20
|
/**
|
|
21
21
|
* Whether GLS Germany itself recognizes an ambiguous numeric shape. Used by
|
|
22
22
|
* the host's carrier-detection route, which promotes a number to `gls-de`
|
|
23
23
|
* only after this returns true; a provider failure must stay a failure.
|
|
24
24
|
*/
|
|
25
|
-
recognizes(rawTrackingNumber: string): Promise<boolean>;
|
|
25
|
+
recognizes(rawTrackingNumber: string, context?: TrackingContext): Promise<boolean>;
|
|
26
26
|
private request;
|
|
27
27
|
}
|
|
28
28
|
export declare const adapter: AdapterFactory;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { lookupBudget } from '../../core/adapter/index.js';
|
|
1
2
|
import { NotFoundError, SchemaError, UpstreamHttpError } from '../../core/errors/index.js';
|
|
2
3
|
import { fetchBounded, parseJsonBytes } from '../../core/transport/index.js';
|
|
3
4
|
import { isRecord } from '../../core/types.js';
|
|
@@ -28,10 +29,12 @@ export class GLSGermanyTracker {
|
|
|
28
29
|
this.now = now;
|
|
29
30
|
this.#fetcher = fetcher;
|
|
30
31
|
}
|
|
31
|
-
async fetch(rawTrackingNumber, rawPostcode) {
|
|
32
|
+
async fetch(rawTrackingNumber, rawPostcode, context = {}) {
|
|
32
33
|
const number = normalizeGLSSwitzerlandTrackingNumber(rawTrackingNumber);
|
|
33
34
|
const postcode = normalizeGLSSwitzerlandPostcode(rawPostcode, '4,5');
|
|
34
|
-
|
|
35
|
+
// Two requests, each with the full request timeout.
|
|
36
|
+
const budget = lookupBudget(context, 2 * this.timeoutMs);
|
|
37
|
+
const overview = await this.request(glsSwitzerlandOverviewApiUrl(number, this.now()), budget);
|
|
35
38
|
// Validate the overview identity before resolving a Track ID to a parcel number.
|
|
36
39
|
try {
|
|
37
40
|
parseGLSSwitzerlandTrackingResponse(overview, number);
|
|
@@ -50,7 +53,7 @@ export class GLSGermanyTracker {
|
|
|
50
53
|
}
|
|
51
54
|
const owners = Array.isArray(parcel.owners) ? parcel.owners.filter(isRecord) : [];
|
|
52
55
|
const owner = owners.find((row) => row.type === 'REQUEST');
|
|
53
|
-
const detail = await this.request(glsSwitzerlandDetailApiUrl(String(parcel.tuNo), postcode, this.now(), String(owner?.code ?? ''), '4,5'));
|
|
56
|
+
const detail = await this.request(glsSwitzerlandDetailApiUrl(String(parcel.tuNo), postcode, this.now(), String(owner?.code ?? ''), '4,5'), budget);
|
|
54
57
|
try {
|
|
55
58
|
const result = parseGLSSwitzerlandTrackingResponse(detail, String(parcel.tuNo));
|
|
56
59
|
// Both services use CET/CEST; expose the regional timezone to clients.
|
|
@@ -67,11 +70,12 @@ export class GLSGermanyTracker {
|
|
|
67
70
|
* the host's carrier-detection route, which promotes a number to `gls-de`
|
|
68
71
|
* only after this returns true; a provider failure must stay a failure.
|
|
69
72
|
*/
|
|
70
|
-
async recognizes(rawTrackingNumber) {
|
|
73
|
+
async recognizes(rawTrackingNumber, context = {}) {
|
|
71
74
|
const number = normalizeGLSSwitzerlandTrackingNumber(rawTrackingNumber);
|
|
75
|
+
const budget = lookupBudget(context, this.timeoutMs);
|
|
72
76
|
try {
|
|
73
77
|
const url = glsSwitzerlandOverviewApiUrl(number, this.now()).replace('/GROUP/en/', '/DE/en/');
|
|
74
|
-
const overview = await this.request(url);
|
|
78
|
+
const overview = await this.request(url, budget);
|
|
75
79
|
const result = parseGLSSwitzerlandTrackingResponse(overview, number);
|
|
76
80
|
return result.status !== 'unknown';
|
|
77
81
|
}
|
|
@@ -81,12 +85,13 @@ export class GLSGermanyTracker {
|
|
|
81
85
|
throw error;
|
|
82
86
|
}
|
|
83
87
|
}
|
|
84
|
-
async request(url) {
|
|
88
|
+
async request(url, budget) {
|
|
85
89
|
const { response, bytes } = await fetchBounded(url, {
|
|
90
|
+
signal: budget.signal,
|
|
86
91
|
headers: { Accept: 'application/json', Referer: 'https://gls-group.eu/EU/en/parcel-tracking' },
|
|
87
92
|
}, {
|
|
88
93
|
provider: PROVIDER,
|
|
89
|
-
timeoutMs: this.timeoutMs,
|
|
94
|
+
timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
|
|
90
95
|
maxBytes: 1_000_000,
|
|
91
96
|
allowHttpError: true,
|
|
92
97
|
...(this.#fetcher ? { fetcher: this.#fetcher } : {}),
|
|
@@ -107,7 +112,7 @@ export const adapter = (environment) => {
|
|
|
107
112
|
return {
|
|
108
113
|
id: 'gls-de',
|
|
109
114
|
steps: ['direct'],
|
|
110
|
-
track: (input) => tracker.fetch(input.number, input.postcode ?? ''),
|
|
111
|
-
recognize: async (number) => ({ known: await tracker.recognizes(number) }),
|
|
115
|
+
track: (input, context) => tracker.fetch(input.number, input.postcode ?? '', context),
|
|
116
|
+
recognize: async (number, context) => ({ known: await tracker.recognizes(number, context) }),
|
|
112
117
|
};
|
|
113
118
|
};
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* operational-location fields, so recipient names, street addresses, contacts,
|
|
8
8
|
* signatures and delivery instructions never leave this module.
|
|
9
9
|
*/
|
|
10
|
-
import type
|
|
10
|
+
import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
|
|
11
11
|
import type { CarrierResult } from '../../core/result/index.js';
|
|
12
12
|
export { glsFranceStatus } from './status.js';
|
|
13
13
|
export declare function normalizeGLSFranceTrackingNumber(raw: string): string;
|
|
@@ -18,12 +18,13 @@ export interface GLSFranceTrackerOptions {
|
|
|
18
18
|
timeoutMs?: number;
|
|
19
19
|
/** Test seam; production uses the global fetch. */
|
|
20
20
|
fetcher?: typeof fetch;
|
|
21
|
+
userAgent?: string;
|
|
21
22
|
}
|
|
22
23
|
export declare class GLSFranceTracker {
|
|
23
24
|
#private;
|
|
24
25
|
readonly timeoutMs: number;
|
|
25
26
|
constructor(options?: GLSFranceTrackerOptions);
|
|
26
|
-
fetch(trackingNumber: string): Promise<CarrierResult>;
|
|
27
|
+
fetch(trackingNumber: string, context?: TrackingContext): Promise<CarrierResult>;
|
|
27
28
|
private lookup;
|
|
28
29
|
}
|
|
29
30
|
export declare const adapter: AdapterFactory;
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
* signatures and delivery instructions never leave this module.
|
|
9
9
|
*/
|
|
10
10
|
import { DateTime } from 'luxon';
|
|
11
|
+
import { lookupBudget } from '../../core/adapter/index.js';
|
|
11
12
|
import { isValidGlsParcelNumber } from '../../core/detection/index.js';
|
|
12
|
-
import { SchemaError, UpstreamHttpError } from '../../core/errors/index.js';
|
|
13
|
+
import { InvalidInputError, SchemaError, UpstreamHttpError } from '../../core/errors/index.js';
|
|
13
14
|
import { EXPLICIT_OFFSET_PATTERN } from '../../core/time/index.js';
|
|
14
|
-
import { cleanScalar, fetchBounded, parseJsonBytes } from '../../core/transport/index.js';
|
|
15
|
+
import { cleanScalar, fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
|
|
15
16
|
import { isRecord } from '../../core/types.js';
|
|
16
17
|
import { FAILED_DELAYED_DELIVERY, glsFranceStatusCode, glsFranceStatusMetadata, } from './status.js';
|
|
17
18
|
export { glsFranceStatus } from './status.js';
|
|
@@ -72,7 +73,7 @@ function printedNumber(raw) {
|
|
|
72
73
|
export function normalizeGLSFranceTrackingNumber(raw) {
|
|
73
74
|
const value = parcelNumber(printedNumber(raw));
|
|
74
75
|
if (!value) {
|
|
75
|
-
throw new
|
|
76
|
+
throw new InvalidInputError(PROVIDER, 'GLS France tracking numbers must contain 8 letters or digits, or 11 digits (12 with a valid check digit)');
|
|
76
77
|
}
|
|
77
78
|
return value;
|
|
78
79
|
}
|
|
@@ -171,39 +172,43 @@ export function parseGLSFranceTrackingResponse(payload, trackingNumber) {
|
|
|
171
172
|
export class GLSFranceTracker {
|
|
172
173
|
timeoutMs;
|
|
173
174
|
#fetcher;
|
|
175
|
+
#userAgent;
|
|
174
176
|
constructor(options = {}) {
|
|
175
177
|
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
176
178
|
if (!Number.isFinite(this.timeoutMs) || this.timeoutMs <= 0) {
|
|
177
179
|
throw new TypeError('GLS France timeout must be positive');
|
|
178
180
|
}
|
|
179
181
|
this.#fetcher = options.fetcher;
|
|
182
|
+
this.#userAgent = userAgentOf(options.userAgent);
|
|
180
183
|
}
|
|
181
|
-
async fetch(trackingNumber) {
|
|
184
|
+
async fetch(trackingNumber, context = {}) {
|
|
182
185
|
const normalized = normalizeGLSFranceTrackingNumber(trackingNumber);
|
|
183
186
|
const printed = printedNumber(trackingNumber);
|
|
187
|
+
const budget = lookupBudget(context, this.timeoutMs);
|
|
184
188
|
try {
|
|
185
|
-
return await this.lookup(normalized, normalized);
|
|
189
|
+
return await this.lookup(normalized, normalized, budget);
|
|
186
190
|
}
|
|
187
191
|
catch (error) {
|
|
188
192
|
// The 11-digit parcel number is what GLS keys a parcel by. Should the
|
|
189
193
|
// French backend only know the number as printed, ask for it once too.
|
|
190
194
|
if (printed === normalized || !(error instanceof UpstreamHttpError) || error.status !== 404)
|
|
191
195
|
throw error;
|
|
192
|
-
return await this.lookup(printed, normalized);
|
|
196
|
+
return await this.lookup(printed, normalized, budget);
|
|
193
197
|
}
|
|
194
198
|
}
|
|
195
|
-
async lookup(code, normalized) {
|
|
199
|
+
async lookup(code, normalized, budget) {
|
|
196
200
|
const { bytes } = await fetchBounded(`${TRACKING_API}/${encodeURIComponent(code)}`, {
|
|
201
|
+
signal: budget.signal,
|
|
197
202
|
headers: {
|
|
198
203
|
Accept: 'application/json',
|
|
199
204
|
'Accept-Language': 'fr-FR,fr;q=0.9',
|
|
200
205
|
Origin: 'https://moncolis.gls-france.com',
|
|
201
206
|
Referer: `${TRACKING_PAGE}/`,
|
|
202
|
-
'User-Agent':
|
|
207
|
+
'User-Agent': this.#userAgent,
|
|
203
208
|
},
|
|
204
209
|
}, {
|
|
205
210
|
provider: 'GLS France tracking',
|
|
206
|
-
timeoutMs: this.timeoutMs,
|
|
211
|
+
timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
|
|
207
212
|
maxBytes: MAX_RESPONSE_BYTES,
|
|
208
213
|
fetcher: this.#fetcher,
|
|
209
214
|
});
|
|
@@ -211,10 +216,10 @@ export class GLSFranceTracker {
|
|
|
211
216
|
}
|
|
212
217
|
}
|
|
213
218
|
export const adapter = (environment) => {
|
|
214
|
-
const tracker = new GLSFranceTracker({ fetcher: environment.fetcher });
|
|
219
|
+
const tracker = new GLSFranceTracker({ fetcher: environment.fetcher, userAgent: environment.userAgent });
|
|
215
220
|
return {
|
|
216
221
|
id: 'gls-fr',
|
|
217
222
|
steps: ['direct'],
|
|
218
|
-
track: (input) => tracker.fetch(input.number),
|
|
223
|
+
track: (input, context) => tracker.fetch(input.number, context),
|
|
219
224
|
};
|
|
220
225
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DateTime, IANAZone } from 'luxon';
|
|
2
2
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
3
|
-
import { IndeterminateError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
3
|
+
import { IndeterminateError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
4
4
|
import { explicitOffsetTime } from '../../core/time/index.js';
|
|
5
5
|
import { clean } from '../../core/transport/index.js';
|
|
6
6
|
import { isRecord } from '../../core/types.js';
|
|
@@ -15,7 +15,7 @@ const SUPPORT_LINE = new RegExp(String.raw `\s*(?:For delivery issues (?:&|and)
|
|
|
15
15
|
export function normalizeGofoNumber(raw) {
|
|
16
16
|
const number = normalizeTrackingNumber(raw);
|
|
17
17
|
if (!/^GFUS\d{14}$/.test(number))
|
|
18
|
-
throw new
|
|
18
|
+
throw new InvalidInputError('GOFO', 'GOFO US requires a GFUS parcel reference');
|
|
19
19
|
return number;
|
|
20
20
|
}
|
|
21
21
|
function scanClock(value, scanZone) {
|
|
@@ -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
|
interface HeppnerCredential {
|
|
4
4
|
trackingNumber: string;
|
|
@@ -16,12 +16,14 @@ export interface HeppnerTrackerOptions {
|
|
|
16
16
|
timeoutMs?: number;
|
|
17
17
|
/** Test seam; production uses the global fetch. */
|
|
18
18
|
fetcher?: typeof fetch;
|
|
19
|
+
userAgent?: string;
|
|
19
20
|
}
|
|
20
21
|
export declare class HeppnerTracker {
|
|
21
22
|
readonly timeoutMs: number;
|
|
22
23
|
readonly fetcher?: typeof fetch;
|
|
24
|
+
private readonly userAgent;
|
|
23
25
|
constructor(options?: HeppnerTrackerOptions);
|
|
24
|
-
fetch(rawTrackingNumber: string, rawPostcode: string): Promise<CarrierResult>;
|
|
26
|
+
fetch(rawTrackingNumber: string, rawPostcode: string, context?: TrackingContext): Promise<CarrierResult>;
|
|
25
27
|
}
|
|
26
28
|
export declare const adapter: AdapterFactory;
|
|
27
29
|
export {};
|