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
|
@@ -12,19 +12,21 @@
|
|
|
12
12
|
* only when `mailNoSource` is `EXTERNAL`; otherwise Cainiao is still waiting
|
|
13
13
|
* for the seller and the parcel is simply pending.
|
|
14
14
|
*/
|
|
15
|
-
import type
|
|
15
|
+
import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
|
|
16
16
|
import type { CarrierResult } from '../../core/result/index.js';
|
|
17
17
|
/** Projects one `detail.json` payload. Pure: the offline tests target this. */
|
|
18
18
|
export declare function parseCainiaoTrackingResponse(value: unknown, trackingNumber: string): CarrierResult;
|
|
19
19
|
export declare class CainiaoTracker {
|
|
20
20
|
private readonly fetcher;
|
|
21
21
|
private readonly timeoutMs;
|
|
22
|
+
private readonly userAgent;
|
|
22
23
|
constructor(options?: {
|
|
23
24
|
fetcher?: typeof fetch;
|
|
24
25
|
timeoutMs?: number;
|
|
26
|
+
userAgent?: string;
|
|
25
27
|
});
|
|
26
|
-
fetch(trackingNumber: string): Promise<CarrierResult>;
|
|
28
|
+
fetch(trackingNumber: string, context?: TrackingContext): Promise<CarrierResult>;
|
|
27
29
|
}
|
|
28
30
|
/** Kept for the host's legacy dispatch chain until it is deleted. */
|
|
29
|
-
export declare function fetchCainiao(trackingNumber: string): Promise<CarrierResult>;
|
|
31
|
+
export declare function fetchCainiao(trackingNumber: string, context?: TrackingContext): Promise<CarrierResult>;
|
|
30
32
|
export declare const adapter: AdapterFactory;
|
|
@@ -1,7 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AliExpress / Cainiao tracking.
|
|
3
|
+
*
|
|
4
|
+
* Cainiao's public shipment detail endpoint (`global.cainiao.com/global/
|
|
5
|
+
* detail.json`) is the same keyless JSON the consumer tracking page reads. One
|
|
6
|
+
* GET returns every requested `mailNo` as a module, so the adapter selects the
|
|
7
|
+
* module whose identifier equals the number that was asked for and refuses
|
|
8
|
+
* anything else: the endpoint happily echoes other shipments when the query is
|
|
9
|
+
* malformed, and showing a stranger's parcel would be worse than an error.
|
|
10
|
+
*
|
|
11
|
+
* A module with no status, no history and no latest trace is genuinely unknown
|
|
12
|
+
* only when `mailNoSource` is `EXTERNAL`; otherwise Cainiao is still waiting
|
|
13
|
+
* for the seller and the parcel is simply pending.
|
|
14
|
+
*/
|
|
15
|
+
import { lookupBudget } from '../../core/adapter/index.js';
|
|
1
16
|
import { normalizeTrackingNumber } from '../../core/detection/normalize.js';
|
|
2
17
|
import { NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
3
18
|
import { explicitOffsetTime } from '../../core/time/index.js';
|
|
4
|
-
import { fetchBounded, parseJsonBytes } from '../../core/transport/index.js';
|
|
19
|
+
import { fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
|
|
5
20
|
import { isRecord } from '../../core/types.js';
|
|
6
21
|
import { CAINIAO_ACTION_STATUS, CAINIAO_STATUS, cainiaoActionCode, cainiaoStageByStatus, } from './status.js';
|
|
7
22
|
const PROVIDER = 'Cainiao';
|
|
@@ -12,7 +27,6 @@ const MAX_EVENTS_TO_RETURN = 20;
|
|
|
12
27
|
const BASE_HEADERS = {
|
|
13
28
|
Accept: 'application/json, text/plain, */*',
|
|
14
29
|
'Accept-Language': 'en-US,en;q=0.9',
|
|
15
|
-
'User-Agent': 'Mozilla/5.0 (compatible; SwissDeliveryTracker/1.0)',
|
|
16
30
|
};
|
|
17
31
|
function record(value) {
|
|
18
32
|
return isRecord(value) ? value : {};
|
|
@@ -140,25 +154,31 @@ export function parseCainiaoTrackingResponse(value, trackingNumber) {
|
|
|
140
154
|
export class CainiaoTracker {
|
|
141
155
|
fetcher;
|
|
142
156
|
timeoutMs;
|
|
157
|
+
userAgent;
|
|
143
158
|
constructor(options = {}) {
|
|
144
159
|
this.fetcher = options.fetcher;
|
|
145
160
|
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
161
|
+
this.userAgent = userAgentOf(options.userAgent);
|
|
146
162
|
}
|
|
147
|
-
async fetch(trackingNumber) {
|
|
148
|
-
const
|
|
163
|
+
async fetch(trackingNumber, context = {}) {
|
|
164
|
+
const budget = lookupBudget(context, this.timeoutMs);
|
|
165
|
+
const { bytes } = await fetchBounded(`${DETAIL_URL}?${new URLSearchParams({ mailNos: trackingNumber, lang: 'en-US' })}`, {
|
|
166
|
+
signal: budget.signal,
|
|
167
|
+
headers: { ...BASE_HEADERS, 'User-Agent': this.userAgent, Referer: 'https://www.aliexpress.com/' },
|
|
168
|
+
}, { provider: UPSTREAM, timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()), fetcher: this.fetcher });
|
|
149
169
|
return parseCainiaoTrackingResponse(parseJsonBytes(bytes, UPSTREAM), trackingNumber);
|
|
150
170
|
}
|
|
151
171
|
}
|
|
152
172
|
/** Kept for the host's legacy dispatch chain until it is deleted. */
|
|
153
|
-
export async function fetchCainiao(trackingNumber) {
|
|
154
|
-
return new CainiaoTracker().fetch(trackingNumber);
|
|
173
|
+
export async function fetchCainiao(trackingNumber, context = {}) {
|
|
174
|
+
return new CainiaoTracker().fetch(trackingNumber, context);
|
|
155
175
|
}
|
|
156
176
|
export const adapter = (environment) => {
|
|
157
|
-
const tracker = new CainiaoTracker({ fetcher: environment.fetcher });
|
|
177
|
+
const tracker = new CainiaoTracker({ fetcher: environment.fetcher, userAgent: environment.userAgent });
|
|
158
178
|
return {
|
|
159
179
|
id: 'aliexpress',
|
|
160
180
|
// One keyless GET; there is no second tier to fall back to.
|
|
161
181
|
steps: ['direct'],
|
|
162
|
-
track: (input) => tracker.fetch(input.number),
|
|
182
|
+
track: (input, context) => tracker.fetch(input.number, context),
|
|
163
183
|
};
|
|
164
184
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
|
|
2
2
|
import { IndeterminateError, NotFoundError } from '../../core/errors/index.js';
|
|
3
3
|
import type { CarrierResult } from '../../core/result/index.js';
|
|
4
4
|
export { amazonShippingStatus } from './status.js';
|
|
@@ -6,6 +6,7 @@ export interface AmazonShippingOptions {
|
|
|
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
|
/** A confirmed absence, with a public reason consumers can inspect. */
|
|
11
12
|
export declare class AmazonShippingNotFoundError extends NotFoundError {
|
|
@@ -29,6 +30,6 @@ export declare class AmazonShippingTracker {
|
|
|
29
30
|
#private;
|
|
30
31
|
readonly timeoutMs: number;
|
|
31
32
|
constructor(options?: number | AmazonShippingOptions);
|
|
32
|
-
fetch(rawTrackingNumber: string): Promise<CarrierResult>;
|
|
33
|
+
fetch(rawTrackingNumber: string, context?: TrackingContext): Promise<CarrierResult>;
|
|
33
34
|
}
|
|
34
35
|
export declare const adapter: AdapterFactory;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { DateTime } from 'luxon';
|
|
2
|
+
import { lookupBudget } from '../../core/adapter/index.js';
|
|
2
3
|
import { amazonShippingOrigin, amazonShippingUrl } from '../../core/catalog/index.js';
|
|
3
4
|
import { isAmazonTrackingNumber } from '../../core/detection/index.js';
|
|
4
|
-
import { IndeterminateError,
|
|
5
|
-
import { cleanScalar, fetchBounded, parseJsonBytes } from '../../core/transport/index.js';
|
|
5
|
+
import { IndeterminateError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
6
|
+
import { cleanScalar, fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
|
|
6
7
|
import { isRecord } from '../../core/types.js';
|
|
7
8
|
import { classifyStatus, statusKey } from './status.js';
|
|
8
9
|
const PROVIDER = 'Amazon Shipping';
|
|
@@ -155,7 +156,7 @@ function metadataValue(metadata, field) {
|
|
|
155
156
|
export function normalizeAmazonShippingTrackingNumber(raw) {
|
|
156
157
|
const value = raw.trim().toLocaleUpperCase('en-US').replace(/[\s.-]/g, '');
|
|
157
158
|
if (!isAmazonTrackingNumber(value)) {
|
|
158
|
-
throw new
|
|
159
|
+
throw new InvalidInputError(PROVIDER, 'Amazon tracking numbers need a European country prefix and 10 digits, or TBA and 12 digits');
|
|
159
160
|
}
|
|
160
161
|
return value;
|
|
161
162
|
}
|
|
@@ -254,28 +255,32 @@ export function amazonShippingTimezone(number) {
|
|
|
254
255
|
export class AmazonShippingTracker {
|
|
255
256
|
timeoutMs;
|
|
256
257
|
#fetcher;
|
|
258
|
+
#userAgent;
|
|
257
259
|
constructor(options = {}) {
|
|
258
|
-
const { timeoutMs = DEFAULT_TIMEOUT_MS, fetcher } = typeof options === 'number'
|
|
259
|
-
? { timeoutMs: options, fetcher: undefined }
|
|
260
|
+
const { timeoutMs = DEFAULT_TIMEOUT_MS, fetcher, userAgent } = typeof options === 'number'
|
|
261
|
+
? { timeoutMs: options, fetcher: undefined, userAgent: undefined }
|
|
260
262
|
: options;
|
|
261
263
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
262
264
|
throw new TypeError('Amazon Shipping timeout must be positive');
|
|
263
265
|
}
|
|
264
266
|
this.timeoutMs = timeoutMs;
|
|
265
267
|
this.#fetcher = fetcher;
|
|
268
|
+
this.#userAgent = userAgentOf(userAgent);
|
|
266
269
|
}
|
|
267
|
-
async fetch(rawTrackingNumber) {
|
|
270
|
+
async fetch(rawTrackingNumber, context = {}) {
|
|
268
271
|
const trackingNumber = normalizeAmazonShippingTrackingNumber(rawTrackingNumber);
|
|
272
|
+
const budget = lookupBudget(context, this.timeoutMs);
|
|
269
273
|
const { bytes } = await fetchBounded(amazonShippingTrackingApiUrl(trackingNumber), {
|
|
274
|
+
signal: budget.signal,
|
|
270
275
|
headers: {
|
|
271
276
|
Accept: 'application/json',
|
|
272
277
|
'Accept-Language': 'en-US,en;q=0.9',
|
|
273
278
|
Referer: amazonShippingTrackingUrl(trackingNumber),
|
|
274
|
-
'User-Agent':
|
|
279
|
+
'User-Agent': this.#userAgent,
|
|
275
280
|
},
|
|
276
281
|
}, {
|
|
277
282
|
provider: 'Amazon Shipping tracking',
|
|
278
|
-
timeoutMs: this.timeoutMs,
|
|
283
|
+
timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
|
|
279
284
|
maxBytes: MAX_RESPONSE_BYTES,
|
|
280
285
|
...(this.#fetcher ? { fetcher: this.#fetcher } : {}),
|
|
281
286
|
});
|
|
@@ -283,10 +288,10 @@ export class AmazonShippingTracker {
|
|
|
283
288
|
}
|
|
284
289
|
}
|
|
285
290
|
export const adapter = (environment) => {
|
|
286
|
-
const tracker = new AmazonShippingTracker({ fetcher: environment.fetcher });
|
|
291
|
+
const tracker = new AmazonShippingTracker({ fetcher: environment.fetcher, userAgent: environment.userAgent });
|
|
287
292
|
return {
|
|
288
293
|
id: 'amazon-shipping',
|
|
289
294
|
steps: ['direct'],
|
|
290
|
-
track: (input) => tracker.fetch(input.number),
|
|
295
|
+
track: (input, context) => tracker.fetch(input.number, context),
|
|
291
296
|
};
|
|
292
297
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { type AmazonShippingOptions } from './adapter.js';
|
|
2
|
+
import type { TrackingContext } from '../../core/adapter/index.js';
|
|
2
3
|
/** A public eligibility check; transport errors remain errors for the consumer to report. */
|
|
3
|
-
export declare function amazonShippingEligibility(number: string, options?: AmazonShippingOptions): Promise<'available' | 'expired' | 'not-found'>;
|
|
4
|
+
export declare function amazonShippingEligibility(number: string, options?: AmazonShippingOptions, context?: TrackingContext): Promise<'available' | 'expired' | 'not-found'>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { AmazonShippingTracker } from './adapter.js';
|
|
2
2
|
import { CarrierError } from '../../core/errors/index.js';
|
|
3
3
|
/** A public eligibility check; transport errors remain errors for the consumer to report. */
|
|
4
|
-
export async function amazonShippingEligibility(number, options = {}) {
|
|
4
|
+
export async function amazonShippingEligibility(number, options = {}, context = {}) {
|
|
5
5
|
try {
|
|
6
|
-
await new AmazonShippingTracker({ ...options, timeoutMs: options.timeoutMs ?? 5_000 }).fetch(number);
|
|
6
|
+
await new AmazonShippingTracker({ ...options, timeoutMs: options.timeoutMs ?? 5_000 }).fetch(number, context);
|
|
7
7
|
return 'available';
|
|
8
8
|
}
|
|
9
9
|
catch (error) {
|
|
@@ -2,9 +2,11 @@ import type { AdapterFactory, TrackingContext } from '../../core/adapter/index.j
|
|
|
2
2
|
import { type StepRecorder } from '../../core/telemetry/index.js';
|
|
3
3
|
export declare class AramexTracker {
|
|
4
4
|
private readonly options;
|
|
5
|
+
private readonly userAgent;
|
|
5
6
|
constructor(options?: {
|
|
6
7
|
fetcher?: typeof fetch;
|
|
7
8
|
recorder?: StepRecorder;
|
|
9
|
+
userAgent?: string;
|
|
8
10
|
});
|
|
9
11
|
fetch(raw: string, context?: TrackingContext): Promise<import("../../index.js").CarrierResult>;
|
|
10
12
|
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { BudgetExceededError, TransportError, UpstreamHttpError } from '../../core/errors/index.js';
|
|
2
2
|
import { runSteps } from '../../core/runner/index.js';
|
|
3
3
|
import { NOOP_RECORDER } from '../../core/telemetry/index.js';
|
|
4
|
-
import { decodeText, fetchBounded } from '../../core/transport/index.js';
|
|
4
|
+
import { decodeText, fetchBounded, userAgentOf } from '../../core/transport/index.js';
|
|
5
5
|
import { aramexDetailRedirect, aramexDetailUrl, normalizeAramexNumber, parseAramex } from './parser.js';
|
|
6
6
|
export class AramexTracker {
|
|
7
7
|
options;
|
|
8
|
+
userAgent;
|
|
8
9
|
constructor(options = {}) {
|
|
9
10
|
this.options = options;
|
|
11
|
+
this.userAgent = userAgentOf(options.userAgent);
|
|
10
12
|
}
|
|
11
13
|
fetch(raw, context = {}) {
|
|
12
14
|
const number = normalizeAramexNumber(raw);
|
|
@@ -21,7 +23,7 @@ export class AramexTracker {
|
|
|
21
23
|
try {
|
|
22
24
|
// The edge rejects Node's default user agent on server networks.
|
|
23
25
|
const { response, bytes } = await fetchBounded(url, { signal, headers: {
|
|
24
|
-
Accept: 'text/html', 'User-Agent':
|
|
26
|
+
Accept: 'text/html', 'User-Agent': this.userAgent,
|
|
25
27
|
} }, {
|
|
26
28
|
provider: 'Aramex', timeoutMs: Math.max(1, Math.floor(left)), maxBytes: 1_500_000,
|
|
27
29
|
fetcher: this.options.fetcher, ...(regionalRedirect ? { redirect: 'manual', allowHttpStatuses: [301, 302, 303, 307, 308] } : {}),
|
|
@@ -44,6 +46,6 @@ export class AramexTracker {
|
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
48
|
export const adapter = environment => {
|
|
47
|
-
const tracker = new AramexTracker({ fetcher: environment.fetcher, recorder: environment.recorder });
|
|
49
|
+
const tracker = new AramexTracker({ fetcher: environment.fetcher, recorder: environment.recorder, userAgent: environment.userAgent });
|
|
48
50
|
return { id: 'aramex', recordsSteps: true, steps: ['direct'], track: (input, context) => tracker.fetch(input.number, context) };
|
|
49
51
|
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { load } from 'cheerio';
|
|
2
2
|
import { DateTime } from 'luxon';
|
|
3
|
-
import { IndeterminateError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
3
|
+
import { IndeterminateError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
4
4
|
import { clean } from '../../core/transport/index.js';
|
|
5
5
|
import { classifyAramexStatus } from './status.js';
|
|
6
6
|
export function normalizeAramexNumber(raw) {
|
|
7
7
|
const number = raw.replace(/[\s.-]/g, '');
|
|
8
8
|
if (!/^\d{11,12}$/.test(number))
|
|
9
|
-
throw new
|
|
9
|
+
throw new InvalidInputError('Aramex', 'Aramex requires an eleven- or twelve-digit shipment number');
|
|
10
10
|
return number;
|
|
11
11
|
}
|
|
12
12
|
export function aramexDetailUrl(html, number) {
|
|
@@ -61,11 +61,13 @@ export interface AsendiaA1TrackerOptions {
|
|
|
61
61
|
/** Wall clock in milliseconds, for the configuration cache and budgets. */
|
|
62
62
|
now?: () => number;
|
|
63
63
|
budgetMs?: number;
|
|
64
|
+
userAgent?: string;
|
|
64
65
|
}
|
|
65
66
|
export declare class AsendiaA1Tracker {
|
|
66
67
|
private readonly fetcher?;
|
|
67
68
|
private readonly now;
|
|
68
69
|
private readonly budgetMs;
|
|
70
|
+
private readonly userAgent;
|
|
69
71
|
private cached;
|
|
70
72
|
private loading;
|
|
71
73
|
constructor(options?: AsendiaA1TrackerOptions);
|
|
@@ -73,10 +75,16 @@ export declare class AsendiaA1Tracker {
|
|
|
73
75
|
private freshConfig;
|
|
74
76
|
/** One page read at a time; concurrent lookups share it. */
|
|
75
77
|
private loadConfig;
|
|
78
|
+
private startRead;
|
|
79
|
+
/** Waits for a read another lookup started, no longer than this lookup's own signal and budget allow. */
|
|
80
|
+
private whileLive;
|
|
81
|
+
/** Whether a failed read was ended by its own lookup's cancellation or budget rather than by Asendia. */
|
|
82
|
+
private cutShort;
|
|
76
83
|
private readConfig;
|
|
77
84
|
private keyAccepted;
|
|
78
85
|
private tracking;
|
|
79
86
|
private api;
|
|
80
87
|
private requestTimeout;
|
|
88
|
+
private remaining;
|
|
81
89
|
}
|
|
82
90
|
export declare const adapter: AdapterFactory;
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { carrierIdFromPartner } from '../../core/catalog/hints.js';
|
|
2
|
-
import { BudgetExceededError, ChallengeError, IndeterminateError, NotFoundError, SchemaError, UpstreamHttpError, } from '../../core/errors/index.js';
|
|
2
|
+
import { BudgetExceededError, ChallengeError, IndeterminateError, InvalidInputError, NotFoundError, SchemaError, UpstreamHttpError, UpstreamNetworkError } from '../../core/errors/index.js';
|
|
3
3
|
import { languageStageStatus, wordingStage } from '../../core/status/index.js';
|
|
4
4
|
import { explicitOffsetTime } from '../../core/time/index.js';
|
|
5
|
-
import { clean, cleanScalar, decodeText, fetchBounded, parseJsonBytes } from '../../core/transport/index.js';
|
|
5
|
+
import { clean, cleanScalar, decodeText, fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
|
|
6
6
|
import { isRecord } from '../../core/types.js';
|
|
7
7
|
import { classifyAsendiaA1Event } from './status.js';
|
|
8
8
|
const PROVIDER = 'Asendia';
|
|
9
9
|
const PAGE_URL = 'https://a1.asendiausa.com/tracking/';
|
|
10
10
|
const SCRIPT_URL = `${PAGE_URL}js/main.js`;
|
|
11
11
|
const API_HOST_SUFFIXES = ['asendiaprod.com', 'asendia.io', 'asendia.com', 'asendiausa.com'];
|
|
12
|
-
const USER_AGENT = 'Mozilla/5.0 (compatible; SwissDeliveryTracker/1.0)';
|
|
13
12
|
const GUID = /[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/gi;
|
|
14
13
|
const DEFAULT_BUDGET_MS = 25_000;
|
|
15
14
|
const MAX_REQUEST_MS = 10_000;
|
|
15
|
+
/** The longest delay a timer holds; a longer one fires at once. */
|
|
16
|
+
const MAX_TIMER_MS = 2 ** 31 - 1;
|
|
16
17
|
/** The public configuration is re-read at least this often, in case it rotates. */
|
|
17
18
|
const CONFIG_TTL_MS = 6 * 60 * 60_000;
|
|
18
19
|
/** A not-found is only trusted when the tracking key was accepted this recently. */
|
|
@@ -39,7 +40,7 @@ function comparable(value) {
|
|
|
39
40
|
export function normalizeAsendiaA1TrackingNumber(raw) {
|
|
40
41
|
const value = comparable(raw);
|
|
41
42
|
if (!/^[A-Z0-9]{6,40}$/.test(value)) {
|
|
42
|
-
throw new
|
|
43
|
+
throw new InvalidInputError(PROVIDER, 'Asendia tracking numbers must contain 6 to 40 ASCII letters and digits');
|
|
43
44
|
}
|
|
44
45
|
return value;
|
|
45
46
|
}
|
|
@@ -206,6 +207,7 @@ export class AsendiaA1Tracker {
|
|
|
206
207
|
fetcher;
|
|
207
208
|
now;
|
|
208
209
|
budgetMs;
|
|
210
|
+
userAgent;
|
|
209
211
|
cached = null;
|
|
210
212
|
loading = null;
|
|
211
213
|
constructor(options = {}) {
|
|
@@ -214,6 +216,7 @@ export class AsendiaA1Tracker {
|
|
|
214
216
|
this.budgetMs = options.budgetMs ?? DEFAULT_BUDGET_MS;
|
|
215
217
|
if (!Number.isFinite(this.budgetMs) || this.budgetMs <= 0)
|
|
216
218
|
throw new TypeError('Asendia budget must be positive');
|
|
219
|
+
this.userAgent = userAgentOf(options.userAgent);
|
|
217
220
|
}
|
|
218
221
|
async fetch(rawTrackingNumber, context = {}) {
|
|
219
222
|
const trackingNumber = normalizeAsendiaA1TrackingNumber(rawTrackingNumber);
|
|
@@ -256,13 +259,59 @@ export class AsendiaA1Tracker {
|
|
|
256
259
|
return this.cached && this.now() - this.cached.loadedAt < CONFIG_TTL_MS ? this.cached : null;
|
|
257
260
|
}
|
|
258
261
|
/** One page read at a time; concurrent lookups share it. */
|
|
259
|
-
loadConfig(request) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
+
async loadConfig(request) {
|
|
263
|
+
for (;;) {
|
|
264
|
+
const read = this.loading ??= this.startRead(request);
|
|
265
|
+
if (read.owner === request)
|
|
266
|
+
return read.promise;
|
|
267
|
+
const outcome = await this.whileLive(read.promise.then((config) => ({ config }), (error) => ({ error })), request);
|
|
268
|
+
if ('config' in outcome)
|
|
269
|
+
return outcome.config;
|
|
270
|
+
// A read cut short by its own lookup's cancellation or budget says
|
|
271
|
+
// nothing about Asendia: a lookup that shared it reads again.
|
|
272
|
+
if (!read.cut)
|
|
273
|
+
throw outcome.error;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
startRead(owner) {
|
|
277
|
+
const read = {
|
|
278
|
+
owner,
|
|
279
|
+
cut: false,
|
|
280
|
+
promise: this.readConfig(owner).catch((error) => {
|
|
281
|
+
// Judged as the read fails: a host may cancel the lookup once it has settled.
|
|
282
|
+
read.cut = this.cutShort(owner, error);
|
|
283
|
+
throw error;
|
|
284
|
+
}).finally(() => { this.loading = null; }),
|
|
285
|
+
};
|
|
286
|
+
return read;
|
|
287
|
+
}
|
|
288
|
+
/** Waits for a read another lookup started, no longer than this lookup's own signal and budget allow. */
|
|
289
|
+
async whileLive(shared, request) {
|
|
290
|
+
let leave;
|
|
291
|
+
const gone = new Promise((_resolve, reject) => { leave = reject; });
|
|
292
|
+
const cancelled = () => leave(request.signal?.reason);
|
|
293
|
+
const spent = setTimeout(() => leave(new BudgetExceededError(PROVIDER, request.budgetMs)), Math.min(MAX_TIMER_MS, this.remaining(request)));
|
|
294
|
+
request.signal?.addEventListener('abort', cancelled, { once: true });
|
|
295
|
+
try {
|
|
296
|
+
return await Promise.race([shared, gone]);
|
|
297
|
+
}
|
|
298
|
+
finally {
|
|
299
|
+
clearTimeout(spent);
|
|
300
|
+
request.signal?.removeEventListener('abort', cancelled);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/** Whether a failed read was ended by its own lookup's cancellation or budget rather than by Asendia. */
|
|
304
|
+
cutShort(owner, error) {
|
|
305
|
+
if (owner.signal?.aborted || owner.deadline - this.now() < 1)
|
|
306
|
+
return true;
|
|
307
|
+
// A request timer can fire just before the clock reaches the deadline.
|
|
308
|
+
return owner.budgetBound === true && error instanceof UpstreamNetworkError
|
|
309
|
+
&& isRecord(error.cause) && error.cause.name === 'TimeoutError';
|
|
262
310
|
}
|
|
263
311
|
async readConfig(request) {
|
|
264
312
|
const { bytes } = await fetchBounded(SCRIPT_URL, {
|
|
265
|
-
|
|
313
|
+
signal: request.signal,
|
|
314
|
+
headers: { Accept: '*/*', Referer: PAGE_URL, 'User-Agent': this.userAgent },
|
|
266
315
|
}, {
|
|
267
316
|
provider: 'Asendia tracking page', timeoutMs: this.requestTimeout(request), maxBytes: MAX_SCRIPT_BYTES, fetcher: this.fetcher,
|
|
268
317
|
});
|
|
@@ -293,11 +342,12 @@ export class AsendiaA1Tracker {
|
|
|
293
342
|
}
|
|
294
343
|
async api(url, config, request, provider) {
|
|
295
344
|
const { response, bytes } = await fetchBounded(url, {
|
|
345
|
+
signal: request.signal,
|
|
296
346
|
headers: {
|
|
297
347
|
Accept: 'application/json',
|
|
298
348
|
Authorization: config.authorization,
|
|
299
349
|
'X-AsendiaOne-ApiKey': config.apiKey,
|
|
300
|
-
'User-Agent':
|
|
350
|
+
'User-Agent': this.userAgent,
|
|
301
351
|
},
|
|
302
352
|
}, {
|
|
303
353
|
provider, timeoutMs: this.requestTimeout(request), maxBytes: MAX_RESPONSE_BYTES, fetcher: this.fetcher,
|
|
@@ -324,15 +374,20 @@ export class AsendiaA1Tracker {
|
|
|
324
374
|
return payload;
|
|
325
375
|
}
|
|
326
376
|
requestTimeout(request) {
|
|
377
|
+
const remaining = this.remaining(request);
|
|
378
|
+
request.budgetBound = remaining <= MAX_REQUEST_MS;
|
|
379
|
+
return Math.min(MAX_REQUEST_MS, remaining);
|
|
380
|
+
}
|
|
381
|
+
remaining(request) {
|
|
327
382
|
request.signal?.throwIfAborted();
|
|
328
383
|
const remaining = Math.floor(request.deadline - this.now());
|
|
329
384
|
if (remaining < 1)
|
|
330
385
|
throw new BudgetExceededError(PROVIDER, request.budgetMs);
|
|
331
|
-
return
|
|
386
|
+
return remaining;
|
|
332
387
|
}
|
|
333
388
|
}
|
|
334
389
|
export const adapter = (environment) => {
|
|
335
|
-
const tracker = new AsendiaA1Tracker({ fetcher: environment.fetcher });
|
|
390
|
+
const tracker = new AsendiaA1Tracker({ fetcher: environment.fetcher, userAgent: environment.userAgent });
|
|
336
391
|
return {
|
|
337
392
|
id: 'asendia',
|
|
338
393
|
// Plain HTTP against the public page's own API; no second tier.
|
|
@@ -18,12 +18,14 @@ export interface AsendiaTrackerOptions {
|
|
|
18
18
|
turnstileTokenProvider?: () => string | Promise<string>;
|
|
19
19
|
/** Test seam; production uses the global fetch. */
|
|
20
20
|
fetcher?: typeof fetch;
|
|
21
|
+
userAgent?: string;
|
|
21
22
|
}
|
|
22
23
|
export declare class AsendiaTracker {
|
|
23
24
|
readonly timeoutMs: number;
|
|
24
25
|
readonly now: () => Date;
|
|
25
26
|
readonly turnstileTokenProvider: () => string | Promise<string>;
|
|
26
27
|
readonly fetcher?: typeof fetch;
|
|
28
|
+
readonly userAgent: string;
|
|
27
29
|
constructor(options?: AsendiaTrackerOptions);
|
|
28
30
|
fetch(rawTrackingNumber: string): Promise<CarrierResult>;
|
|
29
31
|
private requestText;
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
import { createHash } from 'node:crypto';
|
|
25
25
|
import { load } from 'cheerio';
|
|
26
26
|
import { DateTime } from 'luxon';
|
|
27
|
-
import { ChallengeError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
27
|
+
import { ChallengeError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
28
28
|
import { isoTime, zonedTime } from '../../core/time/index.js';
|
|
29
|
-
import { cleanScalar, decodeText, fetchBounded, parseJsonBytes, UpstreamHttpError, } from '../../core/transport/index.js';
|
|
29
|
+
import { cleanScalar, decodeText, fetchBounded, parseJsonBytes, UpstreamHttpError, userAgentOf, } from '../../core/transport/index.js';
|
|
30
30
|
import { isRecord } from '../../core/types.js';
|
|
31
31
|
import { classifyAsendiaStatus } from './status.js';
|
|
32
32
|
const TRACKING_ORIGIN = 'https://track.asendia.com';
|
|
@@ -59,7 +59,7 @@ function plainText(value, maxLength = 500) {
|
|
|
59
59
|
export function normalizeAsendiaTrackingNumber(raw) {
|
|
60
60
|
const value = raw.toLocaleUpperCase('en-US').replace(/[\s-]/g, '');
|
|
61
61
|
if (!/^[A-Z0-9]{8,40}$/.test(value) || !/\d/.test(value)) {
|
|
62
|
-
throw new
|
|
62
|
+
throw new InvalidInputError('Asendia', 'Asendia tracking numbers must contain 8 to 40 ASCII letters and digits');
|
|
63
63
|
}
|
|
64
64
|
return value;
|
|
65
65
|
}
|
|
@@ -260,7 +260,7 @@ function challengeToken(value) {
|
|
|
260
260
|
}
|
|
261
261
|
return token;
|
|
262
262
|
}
|
|
263
|
-
function baseHeaders() {
|
|
263
|
+
function baseHeaders(userAgent) {
|
|
264
264
|
return {
|
|
265
265
|
Accept: 'application/json',
|
|
266
266
|
'Accept-Language': 'en',
|
|
@@ -270,7 +270,7 @@ function baseHeaders() {
|
|
|
270
270
|
'X-Timezone': ZONE,
|
|
271
271
|
Origin: TRACKING_ORIGIN,
|
|
272
272
|
Referer: `${TRACKING_PAGE}/`,
|
|
273
|
-
'User-Agent':
|
|
273
|
+
'User-Agent': userAgent,
|
|
274
274
|
};
|
|
275
275
|
}
|
|
276
276
|
export class AsendiaTracker {
|
|
@@ -278,6 +278,7 @@ export class AsendiaTracker {
|
|
|
278
278
|
now;
|
|
279
279
|
turnstileTokenProvider;
|
|
280
280
|
fetcher;
|
|
281
|
+
userAgent;
|
|
281
282
|
constructor(options = {}) {
|
|
282
283
|
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
283
284
|
this.now = options.now ?? (() => new Date());
|
|
@@ -287,13 +288,14 @@ export class AsendiaTracker {
|
|
|
287
288
|
if (!Number.isFinite(this.timeoutMs) || this.timeoutMs <= 0) {
|
|
288
289
|
throw new TypeError('Asendia timeout must be positive');
|
|
289
290
|
}
|
|
291
|
+
this.userAgent = userAgentOf(options.userAgent);
|
|
290
292
|
}
|
|
291
293
|
async fetch(rawTrackingNumber) {
|
|
292
294
|
const trackingNumber = normalizeAsendiaTrackingNumber(rawTrackingNumber);
|
|
293
295
|
const token = challengeToken(await this.turnstileTokenProvider());
|
|
294
296
|
const [environmentScript, configPayload] = await Promise.all([
|
|
295
297
|
this.requestText(ENVIRONMENT_SCRIPT, 'Asendia public environment'),
|
|
296
|
-
this.requestJson(CONFIG_API, { headers: baseHeaders() }, 'Asendia tracking configuration'),
|
|
298
|
+
this.requestJson(CONFIG_API, { headers: baseHeaders(this.userAgent) }, 'Asendia tracking configuration'),
|
|
297
299
|
]);
|
|
298
300
|
const publicHitKey = parseAsendiaPublicHitKey(environmentScript);
|
|
299
301
|
const config = parseTenantConfig(configPayload);
|
|
@@ -301,7 +303,7 @@ export class AsendiaTracker {
|
|
|
301
303
|
const hitToken = asendiaHitToken(trackingNumber, date, publicHitKey);
|
|
302
304
|
const payload = await this.requestJson(asendiaTrackingApiUrl(), {
|
|
303
305
|
method: 'POST',
|
|
304
|
-
headers: { ...baseHeaders(), 'X-Hit-Token': hitToken },
|
|
306
|
+
headers: { ...baseHeaders(this.userAgent), 'X-Hit-Token': hitToken },
|
|
305
307
|
body: JSON.stringify({
|
|
306
308
|
ids: [trackingNumber],
|
|
307
309
|
id: config.id,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BudgetExceededError, ChallengeError,
|
|
1
|
+
import { BudgetExceededError, ChallengeError, InvalidInputError, NotFoundError, RateLimitedError, SchemaError, TransportError, UpstreamHttpError } from '../../core/errors/index.js';
|
|
2
2
|
import { runSteps } from '../../core/runner/index.js';
|
|
3
3
|
import { explicitOffsetTime, epochMillisTime } from '../../core/time/index.js';
|
|
4
4
|
import { clean } from '../../core/transport/index.js';
|
|
@@ -12,7 +12,7 @@ const TRANSPORT_ALLOWANCE_MS = 15_000;
|
|
|
12
12
|
export function normalizeAustraliaPostNumber(raw) {
|
|
13
13
|
const number = raw.toUpperCase().replace(/[\s.-]/g, '');
|
|
14
14
|
if (!/^(?=.*\d)[A-Z0-9]{10,34}$/.test(number)) {
|
|
15
|
-
throw new
|
|
15
|
+
throw new InvalidInputError(PROVIDER, 'Australia Post requires a 10-to-34-character tracking number');
|
|
16
16
|
}
|
|
17
17
|
return number;
|
|
18
18
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { accepted, recognizeFromLookup } from '../../core/adapter/index.js';
|
|
2
2
|
import { isValidS10TrackingNumber, normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
3
|
-
import { IndeterminateError, NotFoundError, SchemaError, TransportError, UpstreamHttpError } from '../../core/errors/index.js';
|
|
3
|
+
import { IndeterminateError, InvalidInputError, NotFoundError, SchemaError, TransportError, UpstreamHttpError } from '../../core/errors/index.js';
|
|
4
4
|
import { explicitOffsetTime } from '../../core/time/index.js';
|
|
5
5
|
import { clean, fetchBounded, parseJsonBytes } from '../../core/transport/index.js';
|
|
6
6
|
import { isRecord } from '../../core/types.js';
|
|
@@ -12,7 +12,7 @@ const ENDPOINT = 'https://api.post.at/sendungen/sv/graphqlPublic';
|
|
|
12
12
|
export function normalizeAustrianPostNumber(raw) {
|
|
13
13
|
const number = normalizeTrackingNumber(raw);
|
|
14
14
|
if (!/^\d{22}$/.test(number) && !(/^[A-Z]{2}\d{9}AT$/.test(number) && isValidS10TrackingNumber(number))) {
|
|
15
|
-
throw new
|
|
15
|
+
throw new InvalidInputError(PROVIDER, 'Austrian Post requires a 22-digit or valid AT postal identifier');
|
|
16
16
|
}
|
|
17
17
|
return number;
|
|
18
18
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { load } from 'cheerio';
|
|
2
|
-
import { IndeterminateError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
2
|
+
import { IndeterminateError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
3
3
|
import { zonedTime } from '../../core/time/index.js';
|
|
4
4
|
import { clean } from '../../core/transport/index.js';
|
|
5
5
|
import { classifyBlueDartStatus } from './status.js';
|
|
6
6
|
export function normalizeBlueDartNumber(raw) {
|
|
7
7
|
const number = raw.replace(/[\s.-]/g, '');
|
|
8
8
|
if (!/^\d{11}$/.test(number))
|
|
9
|
-
throw new
|
|
9
|
+
throw new InvalidInputError('Blue Dart', 'Blue Dart requires an eleven-digit waybill');
|
|
10
10
|
return number;
|
|
11
11
|
}
|
|
12
12
|
export function parseBlueDart(html, number) {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { DateTime } from 'luxon';
|
|
2
2
|
import { isValidS10TrackingNumber } from '../../core/detection/s10.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 { clean } from '../../core/transport/index.js';
|
|
5
5
|
import { isRecord } from '../../core/types.js';
|
|
6
6
|
import { classifyBpostStatus } from './status.js';
|
|
7
7
|
export function normalizeBpostNumber(raw) {
|
|
8
8
|
const number = raw.toUpperCase().replace(/[\s.-]/g, '');
|
|
9
9
|
if (!/^(?:\d{18}|\d{24}|\d{30})$/.test(number) && !isValidS10TrackingNumber(number)) {
|
|
10
|
-
throw new
|
|
10
|
+
throw new InvalidInputError('bpost', 'bpost requires a parcel barcode or postal tracking number');
|
|
11
11
|
}
|
|
12
12
|
return number;
|
|
13
13
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DateTime } from 'luxon';
|
|
2
2
|
import { isValidS10TrackingNumber, normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
3
|
-
import { IndeterminateError, SchemaError } from '../../core/errors/index.js';
|
|
3
|
+
import { IndeterminateError, InvalidInputError, 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';
|
|
@@ -8,7 +8,7 @@ import { bringStatus, bringWording } from './status.js';
|
|
|
8
8
|
export function normalizeBringNumber(raw) {
|
|
9
9
|
const number = normalizeTrackingNumber(raw);
|
|
10
10
|
if (!/^\d{17,18}$/.test(number) && !(/^[A-Z]{2}\d{9}NO$/.test(number) && isValidS10TrackingNumber(number))) {
|
|
11
|
-
throw new
|
|
11
|
+
throw new InvalidInputError('Bring', 'Bring requires a parcel or consignment tracking number');
|
|
12
12
|
}
|
|
13
13
|
return number;
|
|
14
14
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { load } from 'cheerio';
|
|
2
|
-
import { IndeterminateError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
2
|
+
import { IndeterminateError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
3
3
|
import { calendarDay } from '../../core/time/index.js';
|
|
4
4
|
import { clean } from '../../core/transport/index.js';
|
|
5
5
|
import { classifyBrtStatus } from './status.js';
|
|
6
6
|
export function normalizeBrtNumber(raw) {
|
|
7
7
|
const number = raw.replace(/\s/g, '');
|
|
8
8
|
if (!/^\d{14}$/.test(number))
|
|
9
|
-
throw new
|
|
9
|
+
throw new InvalidInputError('BRT', 'BRT requires a fourteen-digit BRTcode');
|
|
10
10
|
return number;
|
|
11
11
|
}
|
|
12
12
|
function scanClock(date, clock) {
|