universal-parcel-scraper 0.636.0-main.2999 → 0.637.1
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 +1 -1
- package/dist/carriers/ctt-express/adapter.js +33 -21
- package/dist/carriers/ctt-express/parser.d.ts +4 -0
- package/dist/carriers/ctt-express/parser.js +4 -1
- package/dist/carriers/parcelforce/carrier.json +2 -1
- package/dist/carriers/royal-mail/adapter.js +14 -3
- package/dist/carriers/royal-mail/carrier.json +2 -1
- package/dist/carriers/seur/parser.js +4 -3
- package/dist/carriers/seur/status.js +2 -0
- package/dist/carriers/seur/statuses.json +9 -0
- package/dist/carriers/ups/adapter.d.ts +1 -1
- package/dist/carriers/ups/adapter.js +51 -22
- package/dist/carriers/wanbexpress/adapter.js +3 -1
- package/dist/providers/status.js +5 -2
- package/dist/server/openapi.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -155,7 +155,7 @@ createTracker({ providers: ['ParcelsApp', 'Ship24', '17TRACK', 'Postal Ninja', '
|
|
|
155
155
|
<!-- GENERATED:stages -->
|
|
156
156
|
<img src="docs/assets/stages.svg" alt="DHL: Die Sendung wurde in das Zustellfahrzeug geladen.; Mondial Relay: En cours de livraison; Correios Brazil: Objeto saiu para entrega ao destinatário; Correos Express: EN REPARTO; Yamato Transport: 配達中; La Poste / Colissimo: DISTOU. All are filed under out_for_delivery." width="760">
|
|
157
157
|
|
|
158
|
-
The carrier folders record 16,
|
|
158
|
+
The carrier folders record 16,380 such statuses, each filed under one stage.
|
|
159
159
|
<!-- /GENERATED:stages -->
|
|
160
160
|
|
|
161
161
|
Wording nobody recorded yet goes through a classifier that reads seven European languages.
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { accepted, recognizeFromLookup } from '../../core/adapter/index.js';
|
|
2
2
|
import { IndeterminateError, SchemaError } from '../../core/errors/index.js';
|
|
3
3
|
import { runSteps } from '../../core/runner/index.js';
|
|
4
|
+
import { firstAttemptMs, isNetworkFailure } from '../../core/runner/networkRetry.js';
|
|
4
5
|
import { NOOP_RECORDER } from '../../core/telemetry/index.js';
|
|
5
6
|
import { fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
|
|
6
|
-
import { normalizeCttExpressNumber, parseCttExpress } from './parser.js';
|
|
7
|
+
import { CttExpressErrorReply, normalizeCttExpressNumber, parseCttExpress } from './parser.js';
|
|
7
8
|
const ENDPOINT = 'https://wct.cttexpress.com/p_track_redis_v2.php';
|
|
9
|
+
/** A body that is not JSON. */
|
|
10
|
+
class UnreadableReply extends SchemaError {
|
|
11
|
+
}
|
|
8
12
|
export class CttExpressTracker {
|
|
9
13
|
options;
|
|
10
14
|
constructor(options = {}) {
|
|
@@ -12,30 +16,38 @@ export class CttExpressTracker {
|
|
|
12
16
|
}
|
|
13
17
|
fetch(raw, context = {}) {
|
|
14
18
|
const number = normalizeCttExpressNumber(raw);
|
|
19
|
+
const read = async (signal, timeoutMs) => {
|
|
20
|
+
signal.throwIfAborted();
|
|
21
|
+
const url = new URL(ENDPOINT);
|
|
22
|
+
url.searchParams.set('sc', number);
|
|
23
|
+
const { response, bytes } = await fetchBounded(url, { signal, headers: { Accept: 'application/json',
|
|
24
|
+
Origin: 'https://shipping-tracking.production.cloud2.cttexpress.com', 'User-Agent': userAgentOf(this.options.userAgent),
|
|
25
|
+
} }, { provider: 'CTT Express', timeoutMs: Math.max(1, Math.floor(timeoutMs)), maxBytes: 1_000_000,
|
|
26
|
+
allowHttpStatuses: [404, 410], fetcher: this.options.fetcher });
|
|
27
|
+
if (response.status !== 200)
|
|
28
|
+
throw new IndeterminateError('CTT Express', 'CTT Express tracking endpoint is unavailable');
|
|
29
|
+
let payload;
|
|
30
|
+
try {
|
|
31
|
+
payload = parseJsonBytes(bytes, 'CTT Express');
|
|
32
|
+
}
|
|
33
|
+
catch (cause) {
|
|
34
|
+
throw new UnreadableReply('CTT Express', 'CTT Express returned invalid tracking JSON', { cause });
|
|
35
|
+
}
|
|
36
|
+
return parseCttExpress(payload, number);
|
|
37
|
+
};
|
|
38
|
+
// A few percent of lookups get an error reply or a body that is not JSON, and the same
|
|
39
|
+
// parcel reads normally on the next poll. Those, like a request that fails to reach the
|
|
40
|
+
// service, are read once more.
|
|
15
41
|
return runSteps({ carrier: 'ctt-express', budgetMs: context.budgetMs ?? 15_000, signal: context.signal,
|
|
16
|
-
recorder: this.options.recorder ?? NOOP_RECORDER }, [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Origin: 'https://shipping-tracking.production.cloud2.cttexpress.com', 'User-Agent': userAgentOf(this.options.userAgent),
|
|
22
|
-
} }, { provider: 'CTT Express', timeoutMs: Math.max(1, Math.floor(remainingMs)), maxBytes: 1_000_000,
|
|
23
|
-
allowHttpStatuses: [404, 410], fetcher: this.options.fetcher });
|
|
24
|
-
if (response.status !== 200)
|
|
25
|
-
throw new IndeterminateError('CTT Express', 'CTT Express tracking endpoint is unavailable');
|
|
26
|
-
let payload;
|
|
27
|
-
try {
|
|
28
|
-
payload = parseJsonBytes(bytes, 'CTT Express');
|
|
29
|
-
}
|
|
30
|
-
catch (cause) {
|
|
31
|
-
throw new SchemaError('CTT Express', 'CTT Express returned invalid tracking JSON', { cause });
|
|
32
|
-
}
|
|
33
|
-
return parseCttExpress(payload, number);
|
|
34
|
-
} }]);
|
|
42
|
+
recorder: this.options.recorder ?? NOOP_RECORDER }, [
|
|
43
|
+
{ id: 'direct', run: ({ signal, remainingMs }) => read(signal, firstAttemptMs(remainingMs)) },
|
|
44
|
+
{ id: 'retry', recovers: (error) => error instanceof CttExpressErrorReply || error instanceof UnreadableReply || isNetworkFailure(error),
|
|
45
|
+
run: ({ signal, remainingMs }) => read(signal, remainingMs) },
|
|
46
|
+
]);
|
|
35
47
|
}
|
|
36
48
|
}
|
|
37
49
|
export const adapter = (environment) => {
|
|
38
50
|
const tracker = new CttExpressTracker({ fetcher: environment.fetcher, recorder: environment.recorder, userAgent: environment.userAgent });
|
|
39
|
-
return { id: 'ctt-express', recordsSteps: true, steps: ['direct'], track: (input, context) => tracker.fetch(input.number, context),
|
|
51
|
+
return { id: 'ctt-express', recordsSteps: true, steps: ['direct', 'retry'], track: (input, context) => tracker.fetch(input.number, context),
|
|
40
52
|
recognize: (number, context) => recognizeFromLookup(() => tracker.fetch(number, context), () => accepted(() => normalizeCttExpressNumber(number))) };
|
|
41
53
|
};
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { IndeterminateError } from '../../core/errors/index.js';
|
|
1
2
|
import type { CarrierResult } from '../../core/result/index.js';
|
|
3
|
+
/** A reply that carries an error in place of the shipment. */
|
|
4
|
+
export declare class CttExpressErrorReply extends IndeterminateError {
|
|
5
|
+
}
|
|
2
6
|
export declare function normalizeCttExpressNumber(raw: string): string;
|
|
3
7
|
export declare function parseCttExpress(payload: unknown, number: string): CarrierResult;
|
|
@@ -6,6 +6,9 @@ import { calendarDay, explicitOffsetTime } from '../../core/time/index.js';
|
|
|
6
6
|
import { clean } from '../../core/transport/index.js';
|
|
7
7
|
import { isRecord } from '../../core/types.js';
|
|
8
8
|
import { classifyCttExpressStatus } from './status.js';
|
|
9
|
+
/** A reply that carries an error in place of the shipment. */
|
|
10
|
+
export class CttExpressErrorReply extends IndeterminateError {
|
|
11
|
+
}
|
|
9
12
|
export function normalizeCttExpressNumber(raw) {
|
|
10
13
|
const number = raw.replace(/[\s.-]/g, '');
|
|
11
14
|
if (!isCttExpressTrackingNumber(number))
|
|
@@ -33,7 +36,7 @@ export function parseCttExpress(payload, number) {
|
|
|
33
36
|
// An upstream Invalid token reply can precede successful anonymous retrieval.
|
|
34
37
|
// No error signature has established a reliable parcel-negative response.
|
|
35
38
|
if (payload.error != null)
|
|
36
|
-
throw new
|
|
39
|
+
throw new CttExpressErrorReply('CTT Express', 'CTT Express returned an inconclusive tracking response');
|
|
37
40
|
const data = payload.data;
|
|
38
41
|
if (!isRecord(data) || data.shipping_code !== requested || !isRecord(data.shipping_history)) {
|
|
39
42
|
throw new SchemaError('CTT Express', 'CTT Express returned a different or incomplete shipment');
|
|
@@ -13,6 +13,8 @@ const SETTLE_TIMEOUT_MS = 15_000;
|
|
|
13
13
|
// A healthy lookup needs a fraction of this. A browser that is down fails every
|
|
14
14
|
// parcel the same way, and each failure must not hold a consumer's run for a minute.
|
|
15
15
|
const BROWSER_TIMEOUT_MS = 30_000;
|
|
16
|
+
// A fresh consent flow and the separate history request need time to complete.
|
|
17
|
+
const BROWSER_RETRY_MIN_MS = 15_000;
|
|
16
18
|
const MAX_CAPTURED = 20;
|
|
17
19
|
export class RoyalMailTracker {
|
|
18
20
|
timeoutMs;
|
|
@@ -39,8 +41,17 @@ export class RoyalMailTracker {
|
|
|
39
41
|
async fetch(trackingNumber, context = {}) {
|
|
40
42
|
const number = normalizeRoyalMailNumber(trackingNumber);
|
|
41
43
|
if (this.#browserWsEndpoint || this.#executablePath) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
const budgetMs = Math.min(BROWSER_TIMEOUT_MS, context.budgetMs ?? this.timeoutMs);
|
|
45
|
+
const started = performance.now();
|
|
46
|
+
const run = ({ remainingMs, signal }) => fetchRoyalMailInBrowser(number, this.#executablePath ?? '', signal, remainingMs, this.#fullHistory, this.#browserProxy, this.#browserWsEndpoint);
|
|
47
|
+
return runSteps({ carrier: 'royal-mail', budgetMs,
|
|
48
|
+
signal: context.signal, recorder: this.#recorder }, [
|
|
49
|
+
{ id: 'browser', run },
|
|
50
|
+
// Each call disposes its context before returning. A fresh session can
|
|
51
|
+
// recover an intermittent edge refusal without retaining issued tokens.
|
|
52
|
+
{ id: 'retry', run, recovers: error => error instanceof ChallengeError
|
|
53
|
+
&& budgetMs - (performance.now() - started) >= BROWSER_RETRY_MIN_MS },
|
|
54
|
+
]);
|
|
44
55
|
}
|
|
45
56
|
// The legacy service remains available through explicit tracker instances.
|
|
46
57
|
const trawl = this.#trawl;
|
|
@@ -171,7 +182,7 @@ export const adapter = environment => {
|
|
|
171
182
|
const tracker = new RoyalMailTracker({ executablePath: environment.browserExecutablePath,
|
|
172
183
|
browserWsEndpoint: endpoint, browserProxy: environment.env.ROYAL_MAIL_BROWSER_PROXY, recorder: environment.recorder, fullHistory: true });
|
|
173
184
|
return {
|
|
174
|
-
id: 'royal-mail', recordsSteps: true, steps: ['browser'],
|
|
185
|
+
id: 'royal-mail', recordsSteps: true, steps: ['browser', 'retry'],
|
|
175
186
|
track: (input, context) => {
|
|
176
187
|
normalizeRoyalMailNumber(input.number);
|
|
177
188
|
if (!endpoint && !environment.browserExecutablePath)
|
|
@@ -5,9 +5,10 @@ import { isRecord } from '../../core/types.js';
|
|
|
5
5
|
import { classifySeurStatus } from './status.js';
|
|
6
6
|
export function normalizeSeurNumber(raw) {
|
|
7
7
|
const number = raw.replace(/\s/g, '');
|
|
8
|
-
// Twenty-two digits is a sender's reference, which the lookup also finds
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
// Twenty-two digits is a sender's reference, which the lookup also finds, as
|
|
9
|
+
// it finds twelve digits a provider names SEUR for.
|
|
10
|
+
if (!/^(?:\d{7}|\d{12}|\d{14}|\d{21}|\d{22})$/.test(number))
|
|
11
|
+
throw new InvalidInputError('SEUR', 'SEUR requires a seven, twelve, fourteen, twenty-one or twenty-two digit identifier');
|
|
11
12
|
return number;
|
|
12
13
|
}
|
|
13
14
|
/**
|
|
@@ -8,6 +8,8 @@ const observed = {
|
|
|
8
8
|
LC003: { group: 'EN REPARTO', wording: 'EL ENVÍO ESTÁ EN REPARTO.', mapped: { status: 'out_for_delivery', stage: 'out_for_delivery' } },
|
|
9
9
|
LI300: { group: 'EN DEMORA', wording: 'EL ENVÍO HA SUFRIDO UN RETRASO Y ES POSIBLE QUE SE DEMORE LA ENTREGA. DISCULPA LAS MOLESTIAS.', mapped: exception },
|
|
10
10
|
LI582: { group: 'EN DEMORA', wording: 'EL ENVÍO HA SUFRIDO UN RETRASO PROVOCANDO UNA POSIBLE DEMORA EN LA ENTREGA. DISCULPA LAS MOLESTIAS.', mapped: exception },
|
|
11
|
+
// Recorded after the day's delivery round, which starts again the next day.
|
|
12
|
+
LI521: { group: 'EN INCIDENCIA', wording: 'EL ENVÍO NO SE HA ENTREGADO PORQUE LA DIRECCIÓN DE ENTREGA ESTÁ INCOMPLETA O ERRÓNEA.', mapped: { status: 'exception', stage: 'failed_attempt' } },
|
|
11
13
|
LI523: { group: 'EN INCIDENCIA', wording: 'EL ENVÍO NO SE HA ENTREGADO POR AUSENCIA O CIERRE. SE PUEDE REPROGRAMAR UNA NUEVA ENTREGA.', mapped: { status: 'exception', stage: 'failed_attempt' } },
|
|
12
14
|
LI524: { group: 'EN INCIDENCIA', wording: 'EL ENVÍO NO SE HA ENTREGADO PORQUE NO ES LA DIRECCIÓN ACTUAL DEL DESTINATARIO.', mapped: exception },
|
|
13
15
|
LI530: { group: 'ENTREGA EN TIENDA', wording: 'EL ENVÍO ESTÁ DISPONIBLE PARA RECOGER EN EL PUNTO SEUR PICKUP.', mapped: { status: 'in_transit', stage: 'ready_for_pickup' } },
|
|
@@ -89,6 +89,15 @@
|
|
|
89
89
|
"stage": "out_for_delivery",
|
|
90
90
|
"confirmedBy": "live"
|
|
91
91
|
},
|
|
92
|
+
{
|
|
93
|
+
"code": "LI521",
|
|
94
|
+
"wording": "EL ENVÍO NO SE HA ENTREGADO PORQUE LA DIRECCIÓN DE ENTREGA ESTÁ INCOMPLETA O ERRÓNEA.",
|
|
95
|
+
"language": "es",
|
|
96
|
+
"stage": "failed_attempt",
|
|
97
|
+
"confirmedBy": "live",
|
|
98
|
+
"firstSeen": "2026-10-22",
|
|
99
|
+
"note": "Recorded hours after each day's delivery round, which starts again the next day: an attempt the address held up."
|
|
100
|
+
},
|
|
92
101
|
{
|
|
93
102
|
"code": "LI523",
|
|
94
103
|
"wording": "EL ENVÍO NO SE HA ENTREGADO POR AUSENCIA O CIERRE. SE PUEDE REPROGRAMAR UNA NUEVA ENTREGA.",
|
|
@@ -24,7 +24,7 @@ export declare class UPSUnavailableError extends IndeterminateError {
|
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* The browser step, skipped while UPS's status call would likely answer HTTP 500
|
|
27
|
-
* again: inconclusive, so another source may answer.
|
|
27
|
+
* again, or Akamai block the page again: inconclusive, so another source may answer.
|
|
28
28
|
*/
|
|
29
29
|
export declare class UPSStatusHeldError extends IndeterminateError {
|
|
30
30
|
readonly heldMs: number;
|
|
@@ -27,8 +27,10 @@ const SETTLE_TIMEOUT_MS = 15_000;
|
|
|
27
27
|
const MAX_CAPTURED = 20;
|
|
28
28
|
// UPS's status call answers HTTP 500 in bursts. During one, the browser's call failed again
|
|
29
29
|
// for every lookup that followed within a minute and nearly every one within five, while
|
|
30
|
-
// lookups after a longer pause were answered.
|
|
31
|
-
// the
|
|
30
|
+
// lookups after a longer pause were answered. Akamai's blocks, which keep the browser on its
|
|
31
|
+
// interstitial, come in bursts too: 84% of the lookups within five minutes of one were
|
|
32
|
+
// blocked, against 3% otherwise. After either the browser rests that long, and the caller's
|
|
33
|
+
// next source answers meanwhile.
|
|
32
34
|
export const STATUS_HOLD_MS = 5 * 60_000;
|
|
33
35
|
// Provider strings are short; the rendered page's whole text is not, and the
|
|
34
36
|
// identity check reads all of it, so it passes its own limit to `clean`.
|
|
@@ -65,22 +67,41 @@ export class UPSUnavailableError extends IndeterminateError {
|
|
|
65
67
|
}
|
|
66
68
|
/**
|
|
67
69
|
* The browser step, skipped while UPS's status call would likely answer HTTP 500
|
|
68
|
-
* again: inconclusive, so another source may answer.
|
|
70
|
+
* again, or Akamai block the page again: inconclusive, so another source may answer.
|
|
69
71
|
*/
|
|
70
72
|
export class UPSStatusHeldError extends IndeterminateError {
|
|
71
73
|
heldMs;
|
|
72
74
|
constructor(heldMs) {
|
|
73
|
-
super('UPS', `UPS's
|
|
75
|
+
super('UPS', `UPS's tracking failed in the browser moments ago; the browser waits ${Math.ceil(heldMs / 1_000)} s more`);
|
|
74
76
|
this.heldMs = heldMs;
|
|
75
77
|
this.name = 'UPSStatusHeldError';
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
80
|
/**
|
|
79
|
-
* When the status call last answered HTTP 500,
|
|
80
|
-
* tracker in the process that uses the service
|
|
81
|
-
* registries do; a restart forgets it.
|
|
81
|
+
* When the status call last answered HTTP 500, or Akamai last blocked the
|
|
82
|
+
* page, by browser service. Every tracker in the process that uses the service
|
|
83
|
+
* shares it, as a host's several registries do; a restart forgets it.
|
|
82
84
|
*/
|
|
83
85
|
const statusFailedAt = new Map();
|
|
86
|
+
/**
|
|
87
|
+
* Whether the browser service's HTTP 500 says its last browser tier stayed
|
|
88
|
+
* blocked, as on Akamai's interstitial, until the tiers or the deadline ran
|
|
89
|
+
* out. An excerpt cut short still shows the status.
|
|
90
|
+
*/
|
|
91
|
+
function trawlBlocked(error) {
|
|
92
|
+
if (!(error instanceof UpstreamHttpError) || error.status !== 500)
|
|
93
|
+
return false;
|
|
94
|
+
const excerpt = error.diagnostics?.body_excerpt ?? '';
|
|
95
|
+
let reply;
|
|
96
|
+
try {
|
|
97
|
+
reply = JSON.parse(excerpt);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return /"status"\s*:\s*"blocked"/.test(excerpt);
|
|
101
|
+
}
|
|
102
|
+
const last = isRecord(reply) && Array.isArray(reply.timings) ? reply.timings.at(-1) : undefined;
|
|
103
|
+
return isRecord(last) && (last.tier === 2 || last.tier === 3) && last.status === 'blocked';
|
|
104
|
+
}
|
|
84
105
|
/** How long the browser service still rests after a 500, or 0. */
|
|
85
106
|
function statusHeldMs(trawl) {
|
|
86
107
|
const failedAt = statusFailedAt.get(trawl.endpoint.href);
|
|
@@ -579,21 +600,29 @@ export class UPSTracker {
|
|
|
579
600
|
* validated in the page. The page the browser rendered is the last resort.
|
|
580
601
|
*/
|
|
581
602
|
async #trawlResult(trawl, number, timeoutMs, signal) {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
603
|
+
let page;
|
|
604
|
+
try {
|
|
605
|
+
page = await trawl.scrape({
|
|
606
|
+
url: upsTrackingUrl(number),
|
|
607
|
+
skipHttp: true,
|
|
608
|
+
maxTier: 3,
|
|
609
|
+
maxTimeout: timeoutMs,
|
|
610
|
+
captureResponses: [STATUS_API],
|
|
611
|
+
settleTimeout: SETTLE_TIMEOUT_MS,
|
|
612
|
+
}, {
|
|
613
|
+
provider: TRAWL_PROVIDER,
|
|
614
|
+
timeoutMs,
|
|
615
|
+
maxBytes: MAX_BYTES,
|
|
616
|
+
fetcher: this.#fetcher,
|
|
617
|
+
signal,
|
|
618
|
+
captureWindow: 'last',
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
catch (error) {
|
|
622
|
+
if (trawlBlocked(error))
|
|
623
|
+
statusFailedAt.set(trawl.endpoint.href, Date.now());
|
|
624
|
+
throw error;
|
|
625
|
+
}
|
|
597
626
|
// The last matching response is authoritative. An earlier success cannot
|
|
598
627
|
// answer after the page's retry was refused or returned invalid data.
|
|
599
628
|
const entry = page.capturedResponses.filter((capture) => capture.url === STATUS_API).slice(-MAX_CAPTURED).at(-1);
|
|
@@ -18,8 +18,10 @@ export class WanbTracker {
|
|
|
18
18
|
}
|
|
19
19
|
async direct(raw, context = {}) {
|
|
20
20
|
const number = normalizeWanbNumber(raw);
|
|
21
|
+
// Node's fetch sends `Accept-Language: *` unless told otherwise, and the
|
|
22
|
+
// tracker fails on it with HTTP 500, often after several seconds.
|
|
21
23
|
const { bytes } = await fetchBounded(`${API}?TrackingNumber=${number}`, { signal: context.signal,
|
|
22
|
-
headers: { Accept: 'application/json', 'User-Agent': userAgentOf(this.environment.userAgent) } }, { provider: PROVIDER, timeoutMs: Math.max(1, Math.floor(context.budgetMs ?? 5000)), maxBytes: MAX_BYTES, fetcher: this.environment.fetcher });
|
|
24
|
+
headers: { Accept: 'application/json', 'Accept-Language': 'en', 'User-Agent': userAgentOf(this.environment.userAgent) } }, { provider: PROVIDER, timeoutMs: Math.max(1, Math.floor(context.budgetMs ?? 5000)), maxBytes: MAX_BYTES, fetcher: this.environment.fetcher });
|
|
23
25
|
return this.parse(decodeText(bytes), number);
|
|
24
26
|
}
|
|
25
27
|
parse(body, number) {
|
package/dist/providers/status.js
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* rules). A stage a provider declares and a UPU event code it maps are
|
|
9
9
|
* `carrier_map`, so their scans never reach the app's review. Wording no rule
|
|
10
10
|
* reads has no stage. Chronopost's notice to the recipient, a bare delivery
|
|
11
|
-
* estimate
|
|
12
|
-
* scan, are left out on purpose.
|
|
11
|
+
* estimate, a note that the recipient's details changed and SEUR's delay
|
|
12
|
+
* notice, none of them a scan, are left out on purpose.
|
|
13
13
|
*/
|
|
14
14
|
import { CHRONOPOST_NOTIFICATION } from '../carriers/chronopost/status.js';
|
|
15
15
|
import { normalizeStatusWording } from '../core/status/statusMap.js';
|
|
@@ -29,5 +29,8 @@ export const statusMap = {
|
|
|
29
29
|
}, {
|
|
30
30
|
wording: 'recipient information updated',
|
|
31
31
|
note: "A note that the recipient's details changed, after the local carrier's receipt: not a scan, so it keeps the stage the parcel already had.",
|
|
32
|
+
}, {
|
|
33
|
+
wording: 'en demora - el envío ha sufrido un retraso provocando una posible demora en la entrega.',
|
|
34
|
+
note: "SEUR's delay notice as Ship24 relays it, group before wording: not a scan, so it keeps the stage the parcel already had.",
|
|
32
35
|
}],
|
|
33
36
|
};
|
package/dist/server/openapi.json
CHANGED
package/package.json
CHANGED