universal-parcel-scraper 0.133.0 → 0.133.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/dist/carriers/apc-overnight/parser.js +2 -1
- package/dist/carriers/asendia/probe.js +2 -1
- package/dist/carriers/cdek/parser.js +3 -2
- package/dist/carriers/correos-de-mexico/parser.js +2 -1
- package/dist/carriers/cyprus-post/parser.js +2 -1
- package/dist/carriers/dai-post/parser.js +2 -1
- package/dist/carriers/dao/parser.js +3 -2
- package/dist/carriers/dhl-ecommerce-pl/adapter.js +61 -39
- package/dist/carriers/dhl-ecommerce-pl/carrier.json +2 -1
- package/dist/carriers/dsv/parser.js +5 -4
- package/dist/carriers/dynalogic/parser.js +2 -1
- package/dist/carriers/express-one/parser.js +2 -1
- package/dist/carriers/fan-courier/parser.js +3 -2
- package/dist/carriers/foxpost/parser.js +2 -1
- package/dist/carriers/gebrueder-weiss/parser.js +2 -1
- package/dist/carriers/gls-it/parser.js +2 -1
- package/dist/carriers/go-express/parser.js +2 -1
- package/dist/carriers/hrvatska-posta/parser.js +2 -1
- package/dist/carriers/japan-post/adapter.js +2 -1
- package/dist/carriers/jd-logistics/parser.js +2 -1
- package/dist/carriers/kuehne-nagel/parser.js +3 -2
- package/dist/carriers/landmark-global/parser.js +2 -1
- package/dist/carriers/maltapost/parser.js +2 -1
- package/dist/carriers/matkahuolto/parser.js +2 -1
- package/dist/carriers/meest/parser.js +3 -2
- package/dist/carriers/nova-poshta/parser.js +2 -1
- package/dist/carriers/passport/parser.js +2 -1
- package/dist/carriers/speedy/parser.js +2 -1
- package/dist/carriers/xdp/parser.js +2 -1
- package/dist/carriers/yto/parser.js +3 -2
- package/dist/carriers/yunda/parser.js +2 -1
- package/dist/core/detection/returned.d.ts +2 -0
- package/dist/core/detection/returned.js +7 -0
- package/dist/server/openapi.json +1 -1
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { load } from 'cheerio';
|
|
2
3
|
import { DateTime } from 'luxon';
|
|
3
4
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
@@ -54,7 +55,7 @@ function searchPage($, html, number) {
|
|
|
54
55
|
const notice = notices.length === 1 ? clean(notices.text(), 200) : '';
|
|
55
56
|
// The page puts the identifier it was asked for back into its search field.
|
|
56
57
|
const fields = $('input#ConsignmentNumber');
|
|
57
|
-
const echoed = fields.length === 1 ?
|
|
58
|
+
const echoed = fields.length === 1 ? returnedTrackingNumber(fields.attr('value')) : '';
|
|
58
59
|
if (notice === NOT_FOUND || notice === INVALID) {
|
|
59
60
|
if (echoed !== number)
|
|
60
61
|
throw new SchemaError(PROVIDER, 'APC Overnight answered for a different number');
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* event from an explicit allowlist (time, harmonized wording, scanning
|
|
22
22
|
* location, harmonized code) and copies nothing else.
|
|
23
23
|
*/
|
|
24
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
24
25
|
import { createHash } from 'node:crypto';
|
|
25
26
|
import { load } from 'cheerio';
|
|
26
27
|
import { DateTime } from 'luxon';
|
|
@@ -140,7 +141,7 @@ function parseTenantConfig(payload) {
|
|
|
140
141
|
function responseIdentifier(parcel) {
|
|
141
142
|
for (const value of [parcel.tracking_id, parcel.upper_tracking_id]) {
|
|
142
143
|
try {
|
|
143
|
-
return normalizeAsendiaTrackingNumber(
|
|
144
|
+
return normalizeAsendiaTrackingNumber(returnedTrackingNumber(value, true));
|
|
144
145
|
}
|
|
145
146
|
catch {
|
|
146
147
|
// Try the alternate provider field.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
2
3
|
import { IndeterminateError, InvalidInputError, NoHistoryError, SchemaError } from '../../core/errors/index.js';
|
|
3
4
|
import { languageStageStatus } from '../../core/status/index.js';
|
|
@@ -71,8 +72,8 @@ export function parseCdek(payload, raw) {
|
|
|
71
72
|
const data = payload.data;
|
|
72
73
|
if (!isRecord(data))
|
|
73
74
|
throw new SchemaError(PROVIDER, 'CDEK did not return an order');
|
|
74
|
-
const order =
|
|
75
|
-
const outer = payload.orderNumber === undefined ? order :
|
|
75
|
+
const order = returnedTrackingNumber(data.orderNumber);
|
|
76
|
+
const outer = payload.orderNumber === undefined ? order : returnedTrackingNumber(payload.orderNumber);
|
|
76
77
|
if (!ISSUED.test(order) || outer !== order)
|
|
77
78
|
throw new SchemaError(PROVIDER, 'CDEK returned an order without its number');
|
|
78
79
|
// CDEK finds an order by a number it links to it and answers with the order's own number.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { load } from 'cheerio';
|
|
2
3
|
import { isValidS10TrackingNumber, normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
3
4
|
import { ChallengeError, IndeterminateError, InvalidInputError, SchemaError } from '../../core/errors/index.js';
|
|
@@ -125,7 +126,7 @@ export function readCorreosDeMexicoPage(html, rawNumber, askedYear) {
|
|
|
125
126
|
const guia = form.find('input#Guia[name="Guia"]');
|
|
126
127
|
if (guia.length !== 1)
|
|
127
128
|
throw new SchemaError(PROVIDER, 'Correos de México tracking form changed');
|
|
128
|
-
if (
|
|
129
|
+
if (returnedTrackingNumber(guia.attr('value')) !== number) {
|
|
129
130
|
throw new SchemaError(PROVIDER, 'Correos de México answered for a different number');
|
|
130
131
|
}
|
|
131
132
|
const options = form.find('select#Periodo[name="Periodo"] > option');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { load } from 'cheerio';
|
|
2
3
|
import { DateTime } from 'luxon';
|
|
3
4
|
import { isValidS10TrackingNumber, normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
@@ -192,7 +193,7 @@ function itemFor($, html, number) {
|
|
|
192
193
|
const panel = panels.first();
|
|
193
194
|
const titles = panel.find('.panel-heading .panel-title a');
|
|
194
195
|
const bodies = panel.children('.panel-collapse');
|
|
195
|
-
if (titles.length !== 1 ||
|
|
196
|
+
if (titles.length !== 1 || returnedTrackingNumber(titles.text()) !== number
|
|
196
197
|
|| bodies.length !== 1 || bodies.attr('id') !== `collapse_${number}`) {
|
|
197
198
|
throw new SchemaError(PROVIDER, 'Cyprus Post answered for a different item');
|
|
198
199
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { DateTime } from 'luxon';
|
|
2
3
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
3
4
|
import { ChallengeError, IndeterminateError, InvalidInputError, NoHistoryError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
@@ -133,7 +134,7 @@ function scanRows(rows) {
|
|
|
133
134
|
if (props && node[1] === 'input' && props.name === 'tracknbr' && typeof props.defaultValue === 'string') {
|
|
134
135
|
const value = flightText(props.defaultValue);
|
|
135
136
|
if (value !== undefined)
|
|
136
|
-
echoed.add(
|
|
137
|
+
echoed.add(returnedTrackingNumber(value));
|
|
137
138
|
}
|
|
138
139
|
for (const child of node)
|
|
139
140
|
stack.push(child);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { carrierIdFromPartner } from '../../core/catalog/hints.js';
|
|
2
3
|
import { isValidSscc, normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
3
4
|
import { IndeterminateError, InvalidInputError, NoHistoryError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
@@ -30,7 +31,7 @@ export function normalizeDaoNumber(raw) {
|
|
|
30
31
|
}
|
|
31
32
|
/** The parcel number the reply names, in the forms DAO issues. */
|
|
32
33
|
function issued(value) {
|
|
33
|
-
const barcode =
|
|
34
|
+
const barcode = returnedTrackingNumber(value, true);
|
|
34
35
|
return /^(?:00\d{18}|7\d{12}|\d{9})$/.test(barcode) ? barcode : '';
|
|
35
36
|
}
|
|
36
37
|
/** A clock as a wall time, validated. The reply gives no offset. */
|
|
@@ -76,7 +77,7 @@ function scanOf(row, index) {
|
|
|
76
77
|
}
|
|
77
78
|
/** The partner and number a parcel sent abroad is tracked under, as the page links them. */
|
|
78
79
|
function abroad(parcel, number) {
|
|
79
|
-
const partnerNumber =
|
|
80
|
+
const partnerNumber = returnedTrackingNumber(parcel.dist_barcode);
|
|
80
81
|
if (!/^[A-Z0-9]{4,40}$/.test(partnerNumber) || partnerNumber === number)
|
|
81
82
|
return {};
|
|
82
83
|
const partner = carrierIdFromPartner('', clean(parcel.external_tracking, 500));
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { accepted, recognizeFromLookup } from '../../core/adapter/index.js';
|
|
3
|
-
import { BudgetExceededError, IndeterminateError } from '../../core/errors/index.js';
|
|
3
|
+
import { BudgetExceededError, ChallengeError, IndeterminateError, UpstreamHttpError, UpstreamNetworkError } from '../../core/errors/index.js';
|
|
4
4
|
import { runSteps } from '../../core/runner/index.js';
|
|
5
|
+
import { firstAttemptMs, networkRetryStep } from '../../core/runner/networkRetry.js';
|
|
5
6
|
import { NOOP_RECORDER } from '../../core/telemetry/index.js';
|
|
6
|
-
import { fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
|
|
7
|
+
import { decodeText, fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
|
|
7
8
|
import { normalizeDhlEcommercePlNumber, parseDhlEcommercePl, parseDhlEcommercePlChallenge, parseDhlEcommercePlRejection } from './parser.js';
|
|
8
9
|
const PROVIDER = 'DHL eCommerce Poland';
|
|
9
10
|
// The guest routes behind mojdhl.pl. They return the current status and none
|
|
@@ -37,47 +38,68 @@ export class DhlEcommercePlTracker {
|
|
|
37
38
|
fetch(raw, context = {}) {
|
|
38
39
|
const number = normalizeDhlEcommercePlNumber(raw);
|
|
39
40
|
const budgetMs = context.budgetMs ?? 15_000;
|
|
41
|
+
const read = async (signal, remainingMs) => {
|
|
42
|
+
signal.throwIfAborted();
|
|
43
|
+
// One deadline covers the challenge, its solution and the lookup.
|
|
44
|
+
const deadline = performance.now() + remainingMs;
|
|
45
|
+
const left = () => {
|
|
46
|
+
const ms = Math.floor(deadline - performance.now());
|
|
47
|
+
if (ms <= 0)
|
|
48
|
+
throw new BudgetExceededError(PROVIDER, budgetMs);
|
|
49
|
+
return ms;
|
|
50
|
+
};
|
|
51
|
+
// A page in place of JSON is a block, whatever its status says.
|
|
52
|
+
const json = (bytes) => {
|
|
53
|
+
try {
|
|
54
|
+
return parseJsonBytes(bytes, PROVIDER);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
if (/Request Rejected|The requested URL was rejected|Access Denied|cf-chl-|_cf_chl_opt|cf-turnstile|Incapsula incident|<title>\s*(?:Just a moment|Attention Required)/i.test(decodeText(bytes.subarray(0, 64000)))) {
|
|
58
|
+
throw new ChallengeError(PROVIDER, 'DHL eCommerce Poland returned a browser challenge');
|
|
59
|
+
}
|
|
60
|
+
throw new IndeterminateError(PROVIDER, 'DHL eCommerce Poland returned a page instead of tracking JSON');
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
// The signed per-request proof stays out of failure diagnostics. Each
|
|
64
|
+
// retry obtains a fresh challenge rather than replaying a spent proof.
|
|
65
|
+
const request = async (url, init, options) => {
|
|
66
|
+
try {
|
|
67
|
+
return await fetchBounded(url, { ...init, signal }, options);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (error instanceof UpstreamHttpError)
|
|
71
|
+
throw new UpstreamHttpError(PROVIDER, error.status, error.retryAfterMs);
|
|
72
|
+
if (error instanceof UpstreamNetworkError)
|
|
73
|
+
throw new UpstreamNetworkError(PROVIDER, new Error('DHL eCommerce Poland tracking request failed'));
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const headers = { Accept: 'application/json', 'Accept-Language': 'en', 'User-Agent': userAgentOf(this.options.userAgent) };
|
|
78
|
+
const issued = await request(`${BASE}/auth/captcha/challenge`, { signal, headers }, {
|
|
79
|
+
provider: PROVIDER, timeoutMs: left(), maxBytes: 20_000, fetcher: this.options.fetcher,
|
|
80
|
+
});
|
|
81
|
+
const proof = await solveDhlEcommercePlChallenge(parseDhlEcommercePlChallenge(json(issued.bytes)), signal, deadline, budgetMs);
|
|
82
|
+
signal.throwIfAborted();
|
|
83
|
+
const { response, bytes } = await request(`${BASE}/shipment/status`, { method: 'POST', signal,
|
|
84
|
+
headers: { ...headers, 'Content-Type': 'application/json' }, body: JSON.stringify({ number1: number, 'captcha-payload': proof }) }, {
|
|
85
|
+
provider: PROVIDER, timeoutMs: left(), maxBytes: 500_000, allowHttpStatuses: [400, 422], fetcher: this.options.fetcher,
|
|
86
|
+
});
|
|
87
|
+
if (response.status === 422)
|
|
88
|
+
return parseDhlEcommercePlRejection(json(bytes));
|
|
89
|
+
// A 400 is the portal refusing the proof: nothing was looked up.
|
|
90
|
+
if (response.status !== 200)
|
|
91
|
+
throw new IndeterminateError(PROVIDER, 'DHL eCommerce Poland returned an inconclusive tracking response');
|
|
92
|
+
return parseDhlEcommercePl(json(bytes), number);
|
|
93
|
+
};
|
|
40
94
|
return runSteps({ carrier: 'dhl-ecommerce-pl', budgetMs, signal: context.signal,
|
|
41
|
-
recorder: this.options.recorder ?? NOOP_RECORDER }, [
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const left = () => {
|
|
46
|
-
const ms = Math.floor(deadline - performance.now());
|
|
47
|
-
if (ms <= 0)
|
|
48
|
-
throw new BudgetExceededError(PROVIDER, budgetMs);
|
|
49
|
-
return ms;
|
|
50
|
-
};
|
|
51
|
-
// A page in place of JSON is a block, whatever its status says.
|
|
52
|
-
const json = (bytes) => {
|
|
53
|
-
try {
|
|
54
|
-
return parseJsonBytes(bytes, PROVIDER);
|
|
55
|
-
}
|
|
56
|
-
catch (cause) {
|
|
57
|
-
throw new IndeterminateError(PROVIDER, 'DHL eCommerce Poland returned a page instead of tracking JSON', { cause });
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
const headers = { Accept: 'application/json', 'Accept-Language': 'en', 'User-Agent': userAgentOf(this.options.userAgent) };
|
|
61
|
-
const issued = await fetchBounded(`${BASE}/auth/captcha/challenge`, { signal, headers }, {
|
|
62
|
-
provider: PROVIDER, timeoutMs: left(), maxBytes: 20_000, fetcher: this.options.fetcher,
|
|
63
|
-
});
|
|
64
|
-
const proof = await solveDhlEcommercePlChallenge(parseDhlEcommercePlChallenge(json(issued.bytes)), signal, deadline, budgetMs);
|
|
65
|
-
signal.throwIfAborted();
|
|
66
|
-
const { response, bytes } = await fetchBounded(`${BASE}/shipment/status`, { method: 'POST', signal,
|
|
67
|
-
headers: { ...headers, 'Content-Type': 'application/json' }, body: JSON.stringify({ number1: number, 'captcha-payload': proof }) }, {
|
|
68
|
-
provider: PROVIDER, timeoutMs: left(), maxBytes: 500_000, allowHttpStatuses: [400, 422], fetcher: this.options.fetcher,
|
|
69
|
-
});
|
|
70
|
-
if (response.status === 422)
|
|
71
|
-
return parseDhlEcommercePlRejection(json(bytes));
|
|
72
|
-
// A 400 is the portal refusing the proof: nothing was looked up.
|
|
73
|
-
if (response.status !== 200)
|
|
74
|
-
throw new IndeterminateError(PROVIDER, 'DHL eCommerce Poland returned an inconclusive tracking response');
|
|
75
|
-
return parseDhlEcommercePl(json(bytes), number);
|
|
76
|
-
} }]);
|
|
95
|
+
recorder: this.options.recorder ?? NOOP_RECORDER }, [
|
|
96
|
+
{ id: 'direct', run: ({ signal, remainingMs }) => read(signal, firstAttemptMs(remainingMs)) },
|
|
97
|
+
networkRetryStep(({ signal, remainingMs }) => read(signal, remainingMs)),
|
|
98
|
+
]);
|
|
77
99
|
}
|
|
78
100
|
}
|
|
79
101
|
export const adapter = (environment) => {
|
|
80
102
|
const tracker = new DhlEcommercePlTracker({ fetcher: environment.fetcher, recorder: environment.recorder, userAgent: environment.userAgent });
|
|
81
|
-
return { id: 'dhl-ecommerce-pl', recordsSteps: true, steps: ['direct'], track: (input, context) => tracker.fetch(input.number, context),
|
|
103
|
+
return { id: 'dhl-ecommerce-pl', recordsSteps: true, steps: ['direct', 'retry'], track: (input, context) => tracker.fetch(input.number, context),
|
|
82
104
|
recognize: (number, context) => recognizeFromLookup(() => tracker.fetch(number, context), () => accepted(() => normalizeDhlEcommercePlNumber(number))) };
|
|
83
105
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DateTime } from 'luxon';
|
|
2
2
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
3
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
3
4
|
import { IndeterminateError, InvalidInputError, SchemaError } from '../../core/errors/index.js';
|
|
4
5
|
import { languageStageStatus } from '../../core/status/index.js';
|
|
5
6
|
import { clean, cleanScalar } from '../../core/transport/index.js';
|
|
@@ -31,14 +32,14 @@ function ownIds(shipment) {
|
|
|
31
32
|
const ids = new Set();
|
|
32
33
|
const calculated = Array.isArray(shipment.calculatedShipmentId) ? shipment.calculatedShipmentId : [];
|
|
33
34
|
for (const value of [shipment.mainTmsShipmentId, shipment.shipmentId, shipment.bookingId, ...calculated]) {
|
|
34
|
-
const id =
|
|
35
|
+
const id = returnedTrackingNumber(value, true);
|
|
35
36
|
if (id)
|
|
36
37
|
ids.add(id);
|
|
37
38
|
}
|
|
38
39
|
return ids;
|
|
39
40
|
}
|
|
40
41
|
function shipmentOf(entry, own) {
|
|
41
|
-
const key =
|
|
42
|
+
const key = typeof entry.randomIdentifier === 'string' ? entry.randomIdentifier : '';
|
|
42
43
|
if (!KEY.test(key))
|
|
43
44
|
throw new SchemaError(PROVIDER, 'myDSV returned a shipment without its key');
|
|
44
45
|
return { key, mode: clean(entry.modeOfTransport, 40).toUpperCase(), own };
|
|
@@ -61,7 +62,7 @@ export function pickDsvShipment(payload, rawNumber) {
|
|
|
61
62
|
const own = new Map();
|
|
62
63
|
for (const entry of entries) {
|
|
63
64
|
if (ownIds(entry).has(number))
|
|
64
|
-
own.set(
|
|
65
|
+
own.set(shipmentOf(entry, true).key, entry);
|
|
65
66
|
}
|
|
66
67
|
if (own.size === 1)
|
|
67
68
|
return shipmentOf([...own.values()][0], true);
|
|
@@ -82,7 +83,7 @@ export function confirmDsvShipment(payload, rawNumber) {
|
|
|
82
83
|
const number = normalizeDsvNumber(rawNumber);
|
|
83
84
|
if (!isRecord(payload))
|
|
84
85
|
throw new SchemaError(PROVIDER, 'myDSV did not return the shipment');
|
|
85
|
-
if (ownIds(payload).has(number) ||
|
|
86
|
+
if (ownIds(payload).has(number) || returnedTrackingNumber(payload.houseBillNumber, true) === number)
|
|
86
87
|
return;
|
|
87
88
|
throw new IndeterminateError(PROVIDER, 'The number matched only a reference of a DSV shipment', { reason: 'reference_only' });
|
|
88
89
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
2
3
|
import { IndeterminateError, InvalidInputError, SchemaError } from '../../core/errors/index.js';
|
|
3
4
|
import { languageStageStatus } from '../../core/status/index.js';
|
|
@@ -50,7 +51,7 @@ export function parseDynalogic(payload, rawNumber) {
|
|
|
50
51
|
throw new SchemaError(PROVIDER, 'Dynalogic returned an invalid order reply');
|
|
51
52
|
}
|
|
52
53
|
const order = payload.data;
|
|
53
|
-
const answered = typeof order.TrackAndTraceNumber === 'string' ?
|
|
54
|
+
const answered = typeof order.TrackAndTraceNumber === 'string' ? returnedTrackingNumber(order.TrackAndTraceNumber) : '';
|
|
54
55
|
if (!answered)
|
|
55
56
|
throw new SchemaError(PROVIDER, 'Dynalogic returned an order without its number');
|
|
56
57
|
// The service also finds an order by the merchant's order number or its end;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { load } from 'cheerio';
|
|
2
3
|
import { DateTime } from 'luxon';
|
|
3
4
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
@@ -120,7 +121,7 @@ export function parseExpressOne(html, raw, status = 200) {
|
|
|
120
121
|
if (status === 404 || !blocks.length) {
|
|
121
122
|
const notice = clean(notices.find('p b').first().text(), 200);
|
|
122
123
|
if (status === 404 && !blocks.length && notices.length === 1 && ABSENT.has(notice)
|
|
123
|
-
&&
|
|
124
|
+
&& returnedTrackingNumber($('input#plc_number').val()) === number)
|
|
124
125
|
throw new NotFoundError(PROVIDER);
|
|
125
126
|
throw new IndeterminateError(PROVIDER, 'Express One did not return the parcel');
|
|
126
127
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { DateTime } from 'luxon';
|
|
2
3
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
3
4
|
import { IndeterminateError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
@@ -42,14 +43,14 @@ export function parseFanCourier(payload, raw) {
|
|
|
42
43
|
const message = clean(payload.message, 200).toLowerCase().replace(/\.$/, '');
|
|
43
44
|
if (!ABSENT.has(message))
|
|
44
45
|
throw new IndeterminateError(PROVIDER, 'FAN Courier did not return the AWB');
|
|
45
|
-
if (
|
|
46
|
+
if (returnedTrackingNumber(payload.content, true) !== number) {
|
|
46
47
|
throw new SchemaError(PROVIDER, 'FAN Courier answered for a different number');
|
|
47
48
|
}
|
|
48
49
|
throw new NotFoundError(PROVIDER);
|
|
49
50
|
}
|
|
50
51
|
if (typeof payload.awbNumber !== 'string' && typeof payload.awbNumber !== 'number')
|
|
51
52
|
throw new SchemaError(PROVIDER);
|
|
52
|
-
if (
|
|
53
|
+
if (returnedTrackingNumber(payload.awbNumber, true) !== number) {
|
|
53
54
|
throw new SchemaError(PROVIDER, 'FAN Courier answered for a different AWB');
|
|
54
55
|
}
|
|
55
56
|
const rows = payload.events;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { load } from 'cheerio';
|
|
2
3
|
import { DateTime } from 'luxon';
|
|
3
4
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
@@ -72,7 +73,7 @@ export function parseFoxpost(html, raw) {
|
|
|
72
73
|
}
|
|
73
74
|
if (!shown.length && !rows.length)
|
|
74
75
|
throw new IndeterminateError(PROVIDER, 'Foxpost did not return a parcel');
|
|
75
|
-
if (shown.length !== 1 ||
|
|
76
|
+
if (shown.length !== 1 || returnedTrackingNumber(shown.text()) !== number) {
|
|
76
77
|
throw new SchemaError(PROVIDER, 'Foxpost returned a different parcel');
|
|
77
78
|
}
|
|
78
79
|
if ($('ul.parcel-status-items__list').length !== 1)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { DateTime } from 'luxon';
|
|
2
3
|
import { DELIVERY_POSTCODE, deliveryPostcodeText } from '../../core/catalog/postcode.js';
|
|
3
4
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
@@ -53,7 +54,7 @@ export function normalizeGebruederWeissPostcode(raw) {
|
|
|
53
54
|
function carries(order, number) {
|
|
54
55
|
const references = Array.isArray(order.customer_reference) ? order.customer_reference : [];
|
|
55
56
|
return [order.order_id, order.request_order_id, ...references]
|
|
56
|
-
.some((value) =>
|
|
57
|
+
.some((value) => returnedTrackingNumber(value, true) === number);
|
|
57
58
|
}
|
|
58
59
|
/** The branch a scan happened at, as "Town, AT", and its country. */
|
|
59
60
|
function placeOf(location) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DateTime } from 'luxon';
|
|
2
2
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
3
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
3
4
|
import { IndeterminateError, InvalidInputError, NoHistoryError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
4
5
|
import { languageStageStatus } from '../../core/status/index.js';
|
|
5
6
|
import { clean, textFromHtml } from '../../core/transport/index.js';
|
|
@@ -26,7 +27,7 @@ function records(value) {
|
|
|
26
27
|
return Array.isArray(value) ? value.filter(isRecord) : [];
|
|
27
28
|
}
|
|
28
29
|
function shipmentNumber(row) {
|
|
29
|
-
return typeof row.tuNo === 'string' ?
|
|
30
|
+
return typeof row.tuNo === 'string' ? returnedTrackingNumber(row.tuNo) : '';
|
|
30
31
|
}
|
|
31
32
|
/** The row's own count of parcels: the first row of a several-row reply is the shipment the page shows above its parcels. */
|
|
32
33
|
function isShipmentOverview(row) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { load } from 'cheerio';
|
|
2
3
|
import { DateTime } from 'luxon';
|
|
3
4
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
@@ -54,7 +55,7 @@ function linkedNumber($) {
|
|
|
54
55
|
return null;
|
|
55
56
|
try {
|
|
56
57
|
const reference = new URL(links.attr('data-link') ?? '').searchParams.getAll('reference');
|
|
57
|
-
return reference.length === 1 ?
|
|
58
|
+
return reference.length === 1 ? returnedTrackingNumber(reference[0]) : null;
|
|
58
59
|
}
|
|
59
60
|
catch {
|
|
60
61
|
return null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { load } from 'cheerio';
|
|
2
3
|
import { DateTime } from 'luxon';
|
|
3
4
|
import { isValidS10TrackingNumber, normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
@@ -81,7 +82,7 @@ export function parseHrvatskaPosta(html, rawNumber) {
|
|
|
81
82
|
if (header.length !== 1 || named.length !== 1 || label !== NUMBER_LABEL) {
|
|
82
83
|
throw new SchemaError(PROVIDER, 'Hrvatska pošta did not answer in English');
|
|
83
84
|
}
|
|
84
|
-
if (
|
|
85
|
+
if (returnedTrackingNumber(named.text()) !== number) {
|
|
85
86
|
throw new SchemaError(PROVIDER, 'Hrvatska pošta answered for a different number');
|
|
86
87
|
}
|
|
87
88
|
const groups = root.find('div.track__n__trace-events-content > div.event__status__item');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { load } from 'cheerio';
|
|
2
3
|
import { DateTime } from 'luxon';
|
|
3
4
|
import { isValidS10TrackingNumber, normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
@@ -68,7 +69,7 @@ export function parse(html, trackingNumber) {
|
|
|
68
69
|
const rows = result.find('tr').filter((_, row) => $(row).children('td').length > 0);
|
|
69
70
|
const cells = rows.children('td');
|
|
70
71
|
if (!details.length && !history.length && result.length === 1 && rows.length === 1 && cells.length === 2
|
|
71
|
-
&&
|
|
72
|
+
&& returnedTrackingNumber(cells.eq(0).text()) === number
|
|
72
73
|
&& clean(cells.eq(1).text()) === '** Your item was not found. Confirm your item number and ask at your local office.') {
|
|
73
74
|
throw new NotFoundError(PROVIDER);
|
|
74
75
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { DateTime } from 'luxon';
|
|
2
3
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
3
4
|
import { IndeterminateError, InvalidInputError, SchemaError } from '../../core/errors/index.js';
|
|
@@ -127,7 +128,7 @@ export function parseJdLogistics(payload, raw) {
|
|
|
127
128
|
if (!payload.data.length)
|
|
128
129
|
throw new IndeterminateError(PROVIDER, 'JD Logistics returned no international history');
|
|
129
130
|
if (payload.data.length !== 1 || !isRecord(payload.data[0])
|
|
130
|
-
||
|
|
131
|
+
|| returnedTrackingNumber(payload.data[0].waybillNo) !== number) {
|
|
131
132
|
throw new SchemaError(PROVIDER, 'JD Logistics returned a different or ambiguous shipment');
|
|
132
133
|
}
|
|
133
134
|
const item = payload.data[0];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { DateTime } from 'luxon';
|
|
2
3
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
3
4
|
import { IndeterminateError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
@@ -111,8 +112,8 @@ export function parseKuehneNagelSearch(payload, rawNumber) {
|
|
|
111
112
|
const kind = clean(group.kind, 60);
|
|
112
113
|
for (const shipment of group.shipments) {
|
|
113
114
|
const id = isRecord(shipment) ? cleanScalar(shipment.id, 20) : '';
|
|
114
|
-
const trackingNumber = isRecord(shipment) ?
|
|
115
|
-
if (!isRecord(shipment) || !ID.test(id) || !trackingNumber) {
|
|
115
|
+
const trackingNumber = isRecord(shipment) ? returnedTrackingNumber(shipment.trackingNumber) : '';
|
|
116
|
+
if (!isRecord(shipment) || !ID.test(id) || !trackingNumber || trackingNumber.length > 40) {
|
|
116
117
|
throw new SchemaError(PROVIDER, 'Kuehne+Nagel returned a shipment without its id or tracking number');
|
|
117
118
|
}
|
|
118
119
|
const exact = kind === TRACKING_NUMBER && trackingNumber === number;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { load } from 'cheerio';
|
|
2
3
|
import { DateTime } from 'luxon';
|
|
3
4
|
import { carrierIdFromPartner } from '../../core/catalog/hints.js';
|
|
@@ -48,7 +49,7 @@ export function parseLandmark(html, rawNumber) {
|
|
|
48
49
|
const canonical = identities.length === 1 ? clean(identities.next('div').text(), 64) : '';
|
|
49
50
|
// A merchant reference must be the parcel's order or package reference; its LTN then names the parcel.
|
|
50
51
|
const references = $('.shipment-details-col h6').filter((_, node) => ['Order Reference', 'Package Reference'].includes(clean($(node).text())))
|
|
51
|
-
.map((_, node) =>
|
|
52
|
+
.map((_, node) => returnedTrackingNumber($(node).next('div').text())).get();
|
|
52
53
|
if (identities.length !== 1 || (merchant ? !/^LTN\d{8,9}$/.test(canonical) || !references.includes(number)
|
|
53
54
|
: canonical !== number.replace(/N1$/, '')))
|
|
54
55
|
throw new SchemaError('Landmark Global', 'Landmark returned a different or ambiguous parcel');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { DateTime } from 'luxon';
|
|
2
3
|
import { isValidS10TrackingNumber, normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
3
4
|
import { IndeterminateError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
@@ -47,7 +48,7 @@ function barcodeOf(value) {
|
|
|
47
48
|
return '';
|
|
48
49
|
if (typeof value !== 'string')
|
|
49
50
|
throw new SchemaError(PROVIDER, 'MaltaPost returned an invalid barcode');
|
|
50
|
-
return
|
|
51
|
+
return returnedTrackingNumber(value);
|
|
51
52
|
}
|
|
52
53
|
/** The scan's wording, its reason and the action that follows, as the tracking page lists them. */
|
|
53
54
|
function wordingOf(row) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DateTime } from 'luxon';
|
|
2
2
|
import { isValidS10TrackingNumber, isValidSscc, normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
3
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
3
4
|
import { IndeterminateError, InvalidInputError, NoHistoryError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
4
5
|
import { lastMovement } from '../../core/result/pickup.js';
|
|
5
6
|
import { languageStageStatus } from '../../core/status/index.js';
|
|
@@ -102,7 +103,7 @@ export function parseMatkahuolto(reply, raw) {
|
|
|
102
103
|
throw new NotFoundError(PROVIDER);
|
|
103
104
|
throw new IndeterminateError(PROVIDER, 'Matkahuolto returned an unexpected not-found reply');
|
|
104
105
|
}
|
|
105
|
-
if (typeof reply.parcelNumber !== 'string' || !sameNumber(
|
|
106
|
+
if (typeof reply.parcelNumber !== 'string' || !sameNumber(returnedTrackingNumber(reply.parcelNumber), number)) {
|
|
106
107
|
throw new SchemaError(PROVIDER, 'Matkahuolto answered for a different consignment');
|
|
107
108
|
}
|
|
108
109
|
const rows = reply.trackingEvents;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { carrierIdFromPartner } from '../../core/catalog/hints.js';
|
|
2
3
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
3
4
|
import { IndeterminateError, InvalidInputError, NoHistoryError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
@@ -80,8 +81,8 @@ function rowOf(item, index) {
|
|
|
80
81
|
code,
|
|
81
82
|
description,
|
|
82
83
|
location: [city, country].filter(Boolean).join(', '),
|
|
83
|
-
own: ['ShipmentNumber', 'ShipmentNumberSender', 'Barcode'].map(name =>
|
|
84
|
-
transit:
|
|
84
|
+
own: ['ShipmentNumber', 'ShipmentNumberSender', 'Barcode'].map(name => returnedTrackingNumber(field(item, name))),
|
|
85
|
+
transit: returnedTrackingNumber(field(item, 'ShipmentNumberTransit')),
|
|
85
86
|
destination: clean(field(item, 'CountryDel'), 8),
|
|
86
87
|
// The page hides these, and the Chinese consolidator's own registration before Meest's.
|
|
87
88
|
hidden: code === HIDDEN_CODE || (clean(field(item, 'AgentCode'), 40) === 'MCHINA' && field(item, 'ActionMessages').trim() === 'Зареєстровано'),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
2
3
|
import { IndeterminateError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
3
4
|
import { explicitOffsetTime, zonedTime } from '../../core/time/index.js';
|
|
@@ -185,7 +186,7 @@ function partnerOf(payload) {
|
|
|
185
186
|
for (const reference of references) {
|
|
186
187
|
if (!isRecord(reference) || typeof reference.name !== 'string' || !Object.hasOwn(PARTNERS, reference.name))
|
|
187
188
|
continue;
|
|
188
|
-
const number =
|
|
189
|
+
const number = returnedTrackingNumber(reference.number);
|
|
189
190
|
if (/^[A-Z0-9]{8,40}$/.test(number))
|
|
190
191
|
return { delivery_carrier: PARTNERS[reference.name], delivery_tracking_number: number };
|
|
191
192
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { load } from 'cheerio';
|
|
2
3
|
import { carrierIdFromPartner } from '../../core/catalog/hints.js';
|
|
3
4
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
@@ -52,7 +53,7 @@ function shipmentOf(html, number) {
|
|
|
52
53
|
if (!isRecord(shipment) || typeof shipment.tracking_code !== 'string') {
|
|
53
54
|
throw new SchemaError(PROVIDER, 'Passport returned a shipment without its tracking number');
|
|
54
55
|
}
|
|
55
|
-
if (
|
|
56
|
+
if (returnedTrackingNumber(shipment.tracking_code) !== number) {
|
|
56
57
|
throw new SchemaError(PROVIDER, 'Passport returned another shipment');
|
|
57
58
|
}
|
|
58
59
|
const bought = shipment.time_bought;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
2
3
|
import { ChallengeError, InvalidInputError, NotFoundError, SchemaError, TransportError } from '../../core/errors/index.js';
|
|
3
4
|
import { lastMovement } from '../../core/result/pickup.js';
|
|
@@ -66,7 +67,7 @@ function partnerOf(mappings, number) {
|
|
|
66
67
|
if (!isRecord(mapping) || mapping.mappingFor !== 'RECEIVER')
|
|
67
68
|
continue;
|
|
68
69
|
const carrier = PARTNERS[clean(mapping.externalCarrier, 32)];
|
|
69
|
-
const reference =
|
|
70
|
+
const reference = returnedTrackingNumber(mapping.barcode, true);
|
|
70
71
|
if (carrier && /^[A-Z0-9]{4,40}$/.test(reference) && reference !== number) {
|
|
71
72
|
return { delivery_carrier: carrier, delivery_tracking_number: reference };
|
|
72
73
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { load } from 'cheerio';
|
|
2
3
|
import { DateTime } from 'luxon';
|
|
3
4
|
import { DELIVERY_POSTCODE, deliveryPostcodeText } from '../../core/catalog/postcode.js';
|
|
@@ -103,7 +104,7 @@ export function parseXdp(html, raw) {
|
|
|
103
104
|
const field = $('form#trackform input#c');
|
|
104
105
|
if (field.length !== 1)
|
|
105
106
|
throw new IndeterminateError(PROVIDER, 'XDP did not return its tracking page');
|
|
106
|
-
if (
|
|
107
|
+
if (returnedTrackingNumber(field.attr('value')) !== number) {
|
|
107
108
|
throw new SchemaError(PROVIDER, 'XDP answered for a different consignment');
|
|
108
109
|
}
|
|
109
110
|
const cells = $('#leftCol tr').toArray().map((tr) => $(tr).children('td'))
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
2
3
|
import { IndeterminateError, InvalidInputError, SchemaError } from '../../core/errors/index.js';
|
|
3
4
|
import { zonedTime } from '../../core/time/index.js';
|
|
@@ -21,7 +22,7 @@ export function parseYto(payload, trackingNumber) {
|
|
|
21
22
|
throw new SchemaError(PROVIDER, 'YTO returned an invalid shipment list');
|
|
22
23
|
if (!payload.length)
|
|
23
24
|
throw new IndeterminateError(PROVIDER, 'YTO returned no shipment details');
|
|
24
|
-
if (payload.length !== 1 || !isRecord(payload[0]) ||
|
|
25
|
+
if (payload.length !== 1 || !isRecord(payload[0]) || returnedTrackingNumber(payload[0].waybillNo) !== number) {
|
|
25
26
|
throw new SchemaError(PROVIDER, 'YTO returned missing or ambiguous shipment identity');
|
|
26
27
|
}
|
|
27
28
|
const item = payload[0];
|
|
@@ -39,7 +40,7 @@ export function parseYto(payload, trackingNumber) {
|
|
|
39
40
|
// The official client uses the first row as current. Read oldest first to
|
|
40
41
|
// preserve a return leg when later scans use ordinary movement/signature codes.
|
|
41
42
|
for (const raw of [...history].reverse()) {
|
|
42
|
-
if (!isRecord(raw) ||
|
|
43
|
+
if (!isRecord(raw) || returnedTrackingNumber(raw.waybillNo) !== number) {
|
|
43
44
|
throw new SchemaError(PROVIDER, 'YTO returned mixed or incomplete shipment history');
|
|
44
45
|
}
|
|
45
46
|
const code = clean(raw.opCode, 32);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { returnedTrackingNumber } from '../../core/detection/returned.js';
|
|
1
2
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
2
3
|
import { ChallengeError, IndeterminateError, InvalidInputError, SchemaError } from '../../core/errors/index.js';
|
|
3
4
|
import { zonedTime } from '../../core/time/index.js';
|
|
@@ -58,7 +59,7 @@ export function parseYunda(payload, trackingNumber) {
|
|
|
58
59
|
const events = [];
|
|
59
60
|
let returning = false;
|
|
60
61
|
for (const raw of history.gn) {
|
|
61
|
-
if (!isRecord(raw) ||
|
|
62
|
+
if (!isRecord(raw) || returnedTrackingNumber(raw.mailNo) !== number) {
|
|
62
63
|
throw new SchemaError(PROVIDER, 'Yunda returned mixed or incomplete scan identities');
|
|
63
64
|
}
|
|
64
65
|
const label = clean(raw.status, 100);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { normalizeTrackingNumber } from './normalize.js';
|
|
2
|
+
/** Normalize a complete provider identifier; display-text limits must never hide a suffix. */
|
|
3
|
+
export function returnedTrackingNumber(value, allowNumeric = false) {
|
|
4
|
+
const text = typeof value === 'string' ? value
|
|
5
|
+
: allowNumeric && typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? String(value) : '';
|
|
6
|
+
return text.length <= 4096 ? normalizeTrackingNumber(text) : '';
|
|
7
|
+
}
|
package/dist/server/openapi.json
CHANGED
package/package.json
CHANGED