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.
Files changed (210) hide show
  1. package/README.md +209 -71
  2. package/data/carrier.schema.json +6 -4
  3. package/data/catalog.json +6 -6
  4. package/dist/app.d.ts +11 -0
  5. package/dist/app.js +11 -0
  6. package/dist/browser/scraper.js +1386 -1568
  7. package/dist/carriers/aliexpress/adapter.d.ts +5 -3
  8. package/dist/carriers/aliexpress/adapter.js +28 -8
  9. package/dist/carriers/amazon-shipping/adapter.d.ts +3 -2
  10. package/dist/carriers/amazon-shipping/adapter.js +15 -10
  11. package/dist/carriers/amazon-shipping/eligibility.d.ts +2 -1
  12. package/dist/carriers/amazon-shipping/eligibility.js +2 -2
  13. package/dist/carriers/aramex/adapter.d.ts +2 -0
  14. package/dist/carriers/aramex/adapter.js +5 -3
  15. package/dist/carriers/aramex/parser.js +2 -2
  16. package/dist/carriers/asendia/adapter.d.ts +8 -0
  17. package/dist/carriers/asendia/adapter.js +66 -11
  18. package/dist/carriers/asendia/probe.d.ts +2 -0
  19. package/dist/carriers/asendia/probe.js +9 -7
  20. package/dist/carriers/australia-post/adapter.js +2 -2
  21. package/dist/carriers/austrian-post/adapter.js +2 -2
  22. package/dist/carriers/blue-dart/parser.js +2 -2
  23. package/dist/carriers/bpost/parser.js +2 -2
  24. package/dist/carriers/bring-posten/parser.js +2 -2
  25. package/dist/carriers/brt/parser.js +2 -2
  26. package/dist/carriers/c-chez-vous/adapter.d.ts +3 -2
  27. package/dist/carriers/c-chez-vous/adapter.js +15 -10
  28. package/dist/carriers/canada-post/adapter.d.ts +1 -0
  29. package/dist/carriers/canada-post/adapter.js +5 -3
  30. package/dist/carriers/canada-post/parser.js +2 -2
  31. package/dist/carriers/canpar/parser.js +2 -2
  32. package/dist/carriers/ciblex/parser.js +2 -2
  33. package/dist/carriers/colis-prive/adapter.d.ts +3 -2
  34. package/dist/carriers/colis-prive/adapter.js +0 -0
  35. package/dist/carriers/colisweb/adapter.d.ts +3 -2
  36. package/dist/carriers/colisweb/adapter.js +13 -8
  37. package/dist/carriers/correios-br/parser.js +2 -2
  38. package/dist/carriers/correos-chile/parser.js +3 -3
  39. package/dist/carriers/correos-express/parser.js +2 -2
  40. package/dist/carriers/correos-spain/adapter.d.ts +4 -2
  41. package/dist/carriers/correos-spain/adapter.js +16 -8
  42. package/dist/carriers/ctt/adapter.d.ts +2 -2
  43. package/dist/carriers/ctt/adapter.js +30 -25
  44. package/dist/carriers/ctt-express/parser.js +2 -2
  45. package/dist/carriers/dachser/adapter.d.ts +3 -2
  46. package/dist/carriers/dachser/adapter.js +34 -16
  47. package/dist/carriers/delhivery/parser.js +2 -2
  48. package/dist/carriers/dhl/adapter.d.ts +2 -2
  49. package/dist/carriers/dhl/adapter.js +50 -22
  50. package/dist/carriers/dhl-ecommerce/adapter.d.ts +3 -3
  51. package/dist/carriers/dhl-ecommerce/adapter.js +8 -8
  52. package/dist/carriers/dpd/adapter.d.ts +3 -3
  53. package/dist/carriers/dpd/adapter.js +113 -57
  54. package/dist/carriers/dpd/carrier.json +1 -1
  55. package/dist/carriers/dpd-fr/adapter.d.ts +2 -2
  56. package/dist/carriers/dpd-fr/adapter.js +29 -20
  57. package/dist/carriers/dtdc/parser.js +2 -2
  58. package/dist/carriers/ecoscooting/parser.js +2 -2
  59. package/dist/carriers/ems/adapter.js +2 -2
  60. package/dist/carriers/estafeta/parser.js +2 -2
  61. package/dist/carriers/evri/adapter.js +2 -2
  62. package/dist/carriers/fedex/adapter.d.ts +2 -2
  63. package/dist/carriers/fedex/adapter.js +14 -10
  64. package/dist/carriers/four-px/adapter.js +2 -2
  65. package/dist/carriers/geodis/adapter.d.ts +3 -2
  66. package/dist/carriers/geodis/adapter.js +13 -8
  67. package/dist/carriers/gls-ch/adapter.d.ts +4 -3
  68. package/dist/carriers/gls-ch/adapter.js +27 -19
  69. package/dist/carriers/gls-ch/carrier.json +1 -1
  70. package/dist/carriers/gls-de/adapter.d.ts +3 -3
  71. package/dist/carriers/gls-de/adapter.js +14 -9
  72. package/dist/carriers/gls-de/carrier.json +1 -1
  73. package/dist/carriers/gls-fr/adapter.d.ts +3 -2
  74. package/dist/carriers/gls-fr/adapter.js +16 -11
  75. package/dist/carriers/gofo/parser.js +2 -2
  76. package/dist/carriers/heppner/adapter.d.ts +4 -2
  77. package/dist/carriers/heppner/adapter.js +19 -14
  78. package/dist/carriers/heppner/carrier.json +1 -1
  79. package/dist/carriers/hermes/adapter.d.ts +3 -2
  80. package/dist/carriers/hermes/adapter.js +13 -8
  81. package/dist/carriers/hermes-de/adapter.d.ts +3 -2
  82. package/dist/carriers/hermes-de/adapter.js +16 -12
  83. package/dist/carriers/india-post/adapter.d.ts +2 -2
  84. package/dist/carriers/india-post/adapter.js +42 -21
  85. package/dist/carriers/inpost/adapter.d.ts +4 -2
  86. package/dist/carriers/inpost/adapter.js +17 -10
  87. package/dist/carriers/japan-post/adapter.js +2 -2
  88. package/dist/carriers/korea-post/adapter.js +2 -2
  89. package/dist/carriers/la-poste/adapter.d.ts +4 -2
  90. package/dist/carriers/la-poste/adapter.js +18 -13
  91. package/dist/carriers/landmark-global/parser.js +2 -2
  92. package/dist/carriers/mondial-relay/adapter.d.ts +2 -2
  93. package/dist/carriers/mondial-relay/adapter.js +26 -17
  94. package/dist/carriers/mondial-relay/carrier.json +1 -1
  95. package/dist/carriers/mrw/adapter.js +4 -2
  96. package/dist/carriers/mrw/parser.js +3 -3
  97. package/dist/carriers/nacex/parser.js +2 -2
  98. package/dist/carriers/ninja-van/parser.js +2 -2
  99. package/dist/carriers/nz-post/parser.js +2 -2
  100. package/dist/carriers/ontrac/parser.js +2 -2
  101. package/dist/carriers/paack/adapter.d.ts +4 -2
  102. package/dist/carriers/paack/adapter.js +24 -14
  103. package/dist/carriers/paack/carrier.json +1 -1
  104. package/dist/carriers/packeta/adapter.d.ts +4 -2
  105. package/dist/carriers/packeta/adapter.js +16 -8
  106. package/dist/carriers/planzer/adapter.d.ts +5 -3
  107. package/dist/carriers/planzer/adapter.js +35 -12
  108. package/dist/carriers/planzer/shared.d.ts +4 -1
  109. package/dist/carriers/planzer/shared.js +20 -6
  110. package/dist/carriers/poczta-polska/parser.js +2 -2
  111. package/dist/carriers/pos-malaysia/adapter.d.ts +2 -0
  112. package/dist/carriers/pos-malaysia/adapter.js +9 -5
  113. package/dist/carriers/poste-italiane/adapter.d.ts +4 -2
  114. package/dist/carriers/poste-italiane/adapter.js +16 -8
  115. package/dist/carriers/posti/adapter.js +2 -2
  116. package/dist/carriers/postlogistics/adapter.d.ts +6 -4
  117. package/dist/carriers/postlogistics/adapter.js +49 -17
  118. package/dist/carriers/postlogistics/status.d.ts +2 -0
  119. package/dist/carriers/postlogistics/status.js +2 -0
  120. package/dist/carriers/postnord/parser.js +2 -2
  121. package/dist/carriers/purolator/parser.js +2 -2
  122. package/dist/carriers/relais-colis/adapter.js +2 -2
  123. package/dist/carriers/royal-mail/adapter.d.ts +2 -2
  124. package/dist/carriers/royal-mail/adapter.js +14 -10
  125. package/dist/carriers/seur/parser.js +2 -2
  126. package/dist/carriers/sf-express/parser.js +2 -2
  127. package/dist/carriers/singapore-post/adapter.js +2 -2
  128. package/dist/carriers/spring-gds/adapter.d.ts +2 -0
  129. package/dist/carriers/spring-gds/adapter.js +16 -15
  130. package/dist/carriers/sunyou/adapter.d.ts +5 -3
  131. package/dist/carriers/sunyou/adapter.js +16 -9
  132. package/dist/carriers/swiss-post/adapter.d.ts +4 -4
  133. package/dist/carriers/swiss-post/adapter.js +33 -15
  134. package/dist/carriers/swiss-post-cargo/adapter.d.ts +3 -2
  135. package/dist/carriers/swiss-post-cargo/adapter.js +14 -9
  136. package/dist/carriers/the-courier-guy/number.js +3 -2
  137. package/dist/carriers/tipsa/parser.js +3 -3
  138. package/dist/carriers/tnt/adapter.js +3 -3
  139. package/dist/carriers/ukrposhta/adapter.js +20 -62
  140. package/dist/carriers/ukrposhta/parser.js +2 -2
  141. package/dist/carriers/uniuni/parser.js +3 -3
  142. package/dist/carriers/ups/adapter.d.ts +2 -2
  143. package/dist/carriers/ups/adapter.js +40 -32
  144. package/dist/carriers/usps/adapter.d.ts +2 -2
  145. package/dist/carriers/usps/adapter.js +14 -10
  146. package/dist/carriers/yamato/adapter.js +2 -2
  147. package/dist/carriers/yanwen/adapter.js +2 -2
  148. package/dist/carriers/yto/parser.js +2 -2
  149. package/dist/carriers/yunda/parser.js +2 -2
  150. package/dist/carriers/yunexpress/adapter.js +21 -39
  151. package/dist/cli/index.d.ts +2 -0
  152. package/dist/cli/index.js +49 -19
  153. package/dist/core/adapter/index.d.ts +18 -1
  154. package/dist/core/adapter/index.js +28 -4
  155. package/dist/core/adapter/track.js +21 -7
  156. package/dist/core/catalog/index.d.ts +1 -15
  157. package/dist/core/catalog/index.js +3 -106
  158. package/dist/core/catalog/inputs.d.ts +2 -2
  159. package/dist/core/catalog/inputs.js +4 -4
  160. package/dist/core/catalog/parcel.d.ts +53 -0
  161. package/dist/core/catalog/parcel.js +105 -0
  162. package/dist/core/catalog/types.d.ts +1 -29
  163. package/dist/core/catalog/urls.js +1 -0
  164. package/dist/core/errors/hint.d.ts +7 -0
  165. package/dist/core/errors/hint.js +11 -1
  166. package/dist/core/errors/index.d.ts +8 -1
  167. package/dist/core/errors/index.js +11 -0
  168. package/dist/core/runner/index.d.ts +23 -1
  169. package/dist/core/runner/index.js +71 -14
  170. package/dist/core/transport/boundedFetch.js +24 -19
  171. package/dist/core/transport/browser.d.ts +1 -0
  172. package/dist/core/transport/browser.js +76 -99
  173. package/dist/core/transport/index.d.ts +4 -1
  174. package/dist/core/transport/index.js +3 -1
  175. package/dist/core/transport/localBrowser.d.ts +24 -0
  176. package/dist/core/transport/localBrowser.js +87 -0
  177. package/dist/core/transport/trawl.d.ts +6 -0
  178. package/dist/core/transport/trawl.js +30 -9
  179. package/dist/core/transport/userAgent.d.ts +8 -0
  180. package/dist/core/transport/userAgent.js +15 -0
  181. package/dist/data/catalog.json +6 -6
  182. package/dist/facade/index.d.ts +3 -0
  183. package/dist/facade/index.js +29 -11
  184. package/dist/generated/catalog.d.ts +6 -6
  185. package/dist/generated/catalog.js +6 -6
  186. package/dist/index.d.ts +0 -4
  187. package/dist/index.js +0 -4
  188. package/dist/providers/parcelsapp/adapter.d.ts +1 -1
  189. package/dist/providers/parcelsapp/adapter.js +13 -10
  190. package/dist/providers/parcelsapp/http.d.ts +1 -1
  191. package/dist/providers/parcelsapp/http.js +2 -2
  192. package/dist/providers/postal-ninja/adapter.d.ts +2 -2
  193. package/dist/providers/postal-ninja/adapter.js +9 -8
  194. package/dist/providers/seventeentrack/adapter.d.ts +1 -1
  195. package/dist/providers/seventeentrack/adapter.js +7 -7
  196. package/dist/providers/shared/capture.d.ts +2 -0
  197. package/dist/providers/shared/capture.js +1 -1
  198. package/dist/providers/shared/result.js +4 -2
  199. package/dist/providers/ship24/adapter.d.ts +2 -2
  200. package/dist/providers/ship24/adapter.js +15 -11
  201. package/dist/providers/ship24/http.d.ts +1 -1
  202. package/dist/providers/ship24/http.js +4 -3
  203. package/dist/providers/universal.d.ts +3 -2
  204. package/dist/providers/universal.js +20 -2
  205. package/dist/providers/upu/adapter.js +2 -2
  206. package/dist/scripts/carrier-canary.js +2 -1
  207. package/dist/server/index.d.ts +15 -0
  208. package/dist/server/index.js +90 -12
  209. package/dist/server/openapi.json +399 -52
  210. package/package.json +13 -7
@@ -11,7 +11,7 @@
11
11
  * credential: never log it, quote it in an issue, or put a real one in a
12
12
  * fixture.
13
13
  */
14
- import type { AdapterFactory } from '../../core/adapter/index.js';
14
+ import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
15
15
  import type { CarrierResult } from '../../core/result/index.js';
16
16
  export { STEP_DETAILS, stepDetails } from './status.js';
17
17
  export declare function normalizeCChezVousCredential(raw: string): string;
@@ -21,11 +21,12 @@ export interface CChezVousTrackerOptions {
21
21
  timeoutMs?: number;
22
22
  /** Test seam; production uses the global fetch. */
23
23
  fetcher?: typeof fetch;
24
+ userAgent?: string;
24
25
  }
25
26
  export declare class CChezVousTracker {
26
27
  #private;
27
28
  readonly timeoutMs: number;
28
29
  constructor(options?: CChezVousTrackerOptions);
29
- fetch(rawCredential: string): Promise<CarrierResult>;
30
+ fetch(rawCredential: string, context?: TrackingContext): Promise<CarrierResult>;
30
31
  }
31
32
  export declare const adapter: AdapterFactory;
@@ -12,8 +12,9 @@
12
12
  * fixture.
13
13
  */
14
14
  import { load } from 'cheerio';
15
- import { NotFoundError, SchemaError } from '../../core/errors/index.js';
16
- import { UpstreamHttpError, clean, decodeText, fetchBounded } from '../../core/transport/index.js';
15
+ import { lookupBudget } from '../../core/adapter/index.js';
16
+ import { InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
17
+ import { UpstreamHttpError, clean, decodeText, fetchBounded, userAgentOf } from '../../core/transport/index.js';
17
18
  import { isRecord } from '../../core/types.js';
18
19
  import { UNKNOWN_STEP, UNKNOWN_STEP_DESCRIPTION, parcelStep, stepDetails } from './status.js';
19
20
  export { STEP_DETAILS, stepDetails } from './status.js';
@@ -54,7 +55,7 @@ export function normalizeCChezVousCredential(raw) {
54
55
  const composite = /^([A-Z0-9]{11})--(\d{5})$/.exec(value);
55
56
  if (composite) {
56
57
  if (!FRENCH_POSTCODE.test(composite[2])) {
57
- throw new TypeError('C Chez Vous tracking contains an invalid French postcode');
58
+ throw new InvalidInputError(PROVIDER, 'C Chez Vous tracking contains an invalid French postcode');
58
59
  }
59
60
  return `${composite[1]}--${composite[2]}`;
60
61
  }
@@ -63,12 +64,12 @@ export function normalizeCChezVousCredential(raw) {
63
64
  const compactComposite = /^([A-Z0-9]{11})(\d{5})$/.exec(value);
64
65
  if (compactComposite) {
65
66
  if (!FRENCH_POSTCODE.test(compactComposite[2])) {
66
- throw new TypeError('C Chez Vous tracking contains an invalid French postcode');
67
+ throw new InvalidInputError(PROVIDER, 'C Chez Vous tracking contains an invalid French postcode');
67
68
  }
68
69
  return `${compactComposite[1]}--${compactComposite[2]}`;
69
70
  }
70
71
  if (!/^(?=.*\d)[A-Z0-9]{8,15}$/.test(value)) {
71
- throw new TypeError('C Chez Vous tracking requires an 8- to 15-character order number, or an 11-character order followed by -- and a French postcode');
72
+ throw new InvalidInputError(PROVIDER, 'C Chez Vous tracking requires an 8- to 15-character order number, or an 11-character order followed by -- and a French postcode');
72
73
  }
73
74
  return value;
74
75
  }
@@ -138,24 +139,28 @@ export function parseCChezVousTrackingHtml(html, rawCredential) {
138
139
  export class CChezVousTracker {
139
140
  timeoutMs;
140
141
  #fetcher;
142
+ #userAgent;
141
143
  constructor(options = {}) {
142
144
  this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
143
145
  if (!Number.isFinite(this.timeoutMs) || this.timeoutMs <= 0) {
144
146
  throw new TypeError('C Chez Vous timeout must be positive');
145
147
  }
146
148
  this.#fetcher = options.fetcher;
149
+ this.#userAgent = userAgentOf(options.userAgent);
147
150
  }
148
- async fetch(rawCredential) {
151
+ async fetch(rawCredential, context = {}) {
149
152
  const credential = normalizeCChezVousCredential(rawCredential);
153
+ const budget = lookupBudget(context, this.timeoutMs);
150
154
  const { response, bytes } = await fetchBounded(cChezVousTrackingUrl(credential), {
155
+ signal: budget.signal,
151
156
  headers: {
152
157
  Accept: 'text/html,application/xhtml+xml',
153
158
  'Accept-Language': 'fr-FR,fr;q=0.9',
154
- 'User-Agent': 'Mozilla/5.0 (compatible; DeliveryTracker/1.0)',
159
+ 'User-Agent': this.#userAgent,
155
160
  },
156
161
  }, {
157
162
  provider: 'C Chez Vous tracking',
158
- timeoutMs: this.timeoutMs,
163
+ timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
159
164
  maxBytes: MAX_RESPONSE_BYTES,
160
165
  redirect: 'manual',
161
166
  allowHttpError: true,
@@ -171,11 +176,11 @@ export class CChezVousTracker {
171
176
  }
172
177
  }
173
178
  export const adapter = (environment) => {
174
- const tracker = new CChezVousTracker({ fetcher: environment.fetcher });
179
+ const tracker = new CChezVousTracker({ fetcher: environment.fetcher, userAgent: environment.userAgent });
175
180
  return {
176
181
  id: 'c-chez-vous',
177
182
  steps: ['direct'],
178
183
  // The postcode, when the order needs one, is already part of the stored number.
179
- track: (input) => tracker.fetch(input.number),
184
+ track: (input, context) => tracker.fetch(input.number, context),
180
185
  };
181
186
  };
@@ -6,6 +6,7 @@ export interface CanadaPostTrackerOptions {
6
6
  timeoutMs?: number;
7
7
  fetcher?: typeof fetch;
8
8
  recorder?: StepRecorder;
9
+ userAgent?: string;
9
10
  }
10
11
  export declare class CanadaPostTracker {
11
12
  #private;
@@ -2,7 +2,7 @@ import { accepted, recognizeFromLookup } from '../../core/adapter/index.js';
2
2
  import { BudgetExceededError, SchemaError, TransportError, UpstreamHttpError } from '../../core/errors/index.js';
3
3
  import { runSteps } from '../../core/runner/index.js';
4
4
  import { NOOP_RECORDER } from '../../core/telemetry/index.js';
5
- import { fetchBounded, parseJsonBytes } from '../../core/transport/index.js';
5
+ import { fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
6
6
  import { canadaPostLookupKind, normalizeCanadaPostNumber, parseCanadaPostTrackingResponse, resolveCanadaPostPin } from './parser.js';
7
7
  export { normalizeCanadaPostNumber, parseCanadaPostTrackingResponse } from './parser.js';
8
8
  const API_BASE = 'https://www.canadapost-postescanada.ca/track-reperage/rs/track/json/package';
@@ -15,11 +15,13 @@ export function canadaPostTrackingUrl(trackingNumber) {
15
15
  export class CanadaPostTracker {
16
16
  timeoutMs;
17
17
  #options;
18
+ #userAgent;
18
19
  constructor(options = {}) {
19
20
  this.#options = typeof options === 'number' ? { timeoutMs: options } : options;
20
21
  this.timeoutMs = this.#options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
21
22
  if (!Number.isFinite(this.timeoutMs) || this.timeoutMs <= 0)
22
23
  throw new TypeError('Canada Post timeout must be positive');
24
+ this.#userAgent = userAgentOf(this.#options.userAgent);
23
25
  }
24
26
  async fetch(rawTrackingNumber, context = {}) {
25
27
  const number = normalizeCanadaPostNumber(rawTrackingNumber);
@@ -40,7 +42,7 @@ export class CanadaPostTracker {
40
42
  // The public tracking application's interceptor sends empty Basic credentials.
41
43
  Authorization: 'Basic Og==',
42
44
  Referer: 'https://www.canadapost-postescanada.ca/track-reperage/en/home',
43
- 'User-Agent': 'Mozilla/5.0 (compatible; DeliveryTracker/1.0)',
45
+ 'User-Agent': this.#userAgent,
44
46
  'X-Requested-With': 'XMLHttpRequest',
45
47
  } }, { provider: 'canada-post', timeoutMs: Math.max(1, Math.floor(requestBudget)),
46
48
  maxBytes: 1_000_000, fetcher: this.#options.fetcher }));
@@ -72,7 +74,7 @@ export class CanadaPostTracker {
72
74
  }
73
75
  }
74
76
  export const adapter = (environment) => {
75
- const tracker = new CanadaPostTracker({ fetcher: environment.fetcher, recorder: environment.recorder });
77
+ const tracker = new CanadaPostTracker({ fetcher: environment.fetcher, recorder: environment.recorder, userAgent: environment.userAgent });
76
78
  return { id: 'canada-post', recordsSteps: true, steps: ['direct'], track: (input, context) => tracker.fetch(input.number, context),
77
79
  recognize: (number, context) => recognizeFromLookup(() => tracker.fetch(number, context), () => accepted(() => normalizeCanadaPostNumber(number))) };
78
80
  };
@@ -1,6 +1,6 @@
1
1
  import { DateTime } from 'luxon';
2
2
  import { isValidS10TrackingNumber } from '../../core/detection/s10.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 { canadaPostPackageStage, canadaPostScanStage, canadaPostStage, statusFor
8
8
  export function normalizeCanadaPostNumber(raw) {
9
9
  const value = raw.toUpperCase().replace(/[\s.-]/g, '');
10
10
  if (!/^\d{11,24}$/.test(value) && !(value.endsWith('CA') && isValidS10TrackingNumber(value))) {
11
- throw new TypeError('Canada Post requires a parcel PIN, numeric reference or valid Canadian postal tracking number');
11
+ throw new InvalidInputError('canada-post', 'Canada Post requires a parcel PIN, numeric reference or valid Canadian postal tracking number');
12
12
  }
13
13
  return value;
14
14
  }
@@ -1,13 +1,13 @@
1
1
  import { DateTime } from 'luxon';
2
2
  import { 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 { clean } from '../../core/transport/index.js';
5
5
  import { isRecord } from '../../core/types.js';
6
6
  import { canparStatus } from './status.js';
7
7
  export function normalizeCanparNumber(raw) {
8
8
  const number = normalizeTrackingNumber(raw);
9
9
  if (!/^[CDKLSUXZ]\d{21}$/.test(number))
10
- throw new TypeError('Canpar requires a full parcel barcode');
10
+ throw new InvalidInputError('Canpar', 'Canpar requires a full parcel barcode');
11
11
  return number;
12
12
  }
13
13
  function clock(raw) {
@@ -1,13 +1,13 @@
1
1
  import { load } from 'cheerio';
2
2
  import { DateTime } from 'luxon';
3
- import { IndeterminateError, SchemaError } from '../../core/errors/index.js';
3
+ import { IndeterminateError, InvalidInputError, SchemaError } from '../../core/errors/index.js';
4
4
  import { clean } from '../../core/transport/index.js';
5
5
  import { calendarDay, zonedTime } from '../../core/time/index.js';
6
6
  import { classifyCiblexStatus, comparableText } from './status.js';
7
7
  export function normalizeCiblexTrackingNumber(raw) {
8
8
  const number = raw.replace(/\s/g, '');
9
9
  if (!/^(?:\d{14}|\d{24})$/.test(number))
10
- throw new TypeError('Ciblex requires exactly 14 or 24 digits');
10
+ throw new InvalidInputError('Ciblex', 'Ciblex requires exactly 14 or 24 digits');
11
11
  return number;
12
12
  }
13
13
  export function ciblexTrackingUrl(raw) {
@@ -11,7 +11,7 @@
11
11
  * so it is part of the tracking credential: it is never logged, put in an issue
12
12
  * or written into a fixture.
13
13
  */
14
- import { type AdapterFactory } from '../../core/adapter/index.js';
14
+ import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
15
15
  import { NotFoundError } from '../../core/errors/index.js';
16
16
  import type { CarrierResult } from '../../core/result/index.js';
17
17
  export { classifyStatus } from './status.js';
@@ -29,11 +29,12 @@ export interface ColisPriveTrackerOptions {
29
29
  timeoutMs?: number;
30
30
  /** Test seam; production uses the global fetch. */
31
31
  fetcher?: typeof fetch;
32
+ userAgent?: string;
32
33
  }
33
34
  export declare class ColisPriveTracker {
34
35
  #private;
35
36
  readonly timeoutMs: number;
36
37
  constructor(options?: ColisPriveTrackerOptions);
37
- fetch(rawCredential: string): Promise<CarrierResult>;
38
+ fetch(rawCredential: string, context?: TrackingContext): Promise<CarrierResult>;
38
39
  }
39
40
  export declare const adapter: AdapterFactory;
@@ -8,7 +8,7 @@
8
8
  * milestones, the step and the slot's starting day; everything describing a
9
9
  * person or a shop is dropped.
10
10
  */
11
- import type { AdapterFactory } from '../../core/adapter/index.js';
11
+ import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
12
12
  import type { CarrierResult } from '../../core/result/index.js';
13
13
  export { classifyStatus } from './status.js';
14
14
  export declare function normalizeColiswebTrackingNumber(raw: string): string;
@@ -19,11 +19,12 @@ export interface ColiswebTrackerOptions {
19
19
  timeoutMs?: number;
20
20
  /** Test seam; production uses the global fetch. */
21
21
  fetcher?: typeof fetch;
22
+ userAgent?: string;
22
23
  }
23
24
  export declare class ColiswebTracker {
24
25
  #private;
25
26
  readonly timeoutMs: number;
26
27
  constructor(options?: ColiswebTrackerOptions);
27
- fetch(rawTrackingNumber: string): Promise<CarrierResult>;
28
+ fetch(rawTrackingNumber: string, context?: TrackingContext): Promise<CarrierResult>;
28
29
  }
29
30
  export declare const adapter: AdapterFactory;
@@ -8,8 +8,9 @@
8
8
  * milestones, the step and the slot's starting day; everything describing a
9
9
  * person or a shop is dropped.
10
10
  */
11
- import { IndeterminateError, NotFoundError, SchemaError } from '../../core/errors/index.js';
12
- import { UpstreamHttpError, clean, decodeText, fetchBounded, parseJsonBytes } from '../../core/transport/index.js';
11
+ import { lookupBudget } from '../../core/adapter/index.js';
12
+ import { IndeterminateError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
13
+ import { UpstreamHttpError, clean, decodeText, fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
13
14
  import { isRecord } from '../../core/types.js';
14
15
  import { classifyStatus } from './status.js';
15
16
  export { classifyStatus } from './status.js';
@@ -47,7 +48,7 @@ function saysNotFound(value) {
47
48
  export function normalizeColiswebTrackingNumber(raw) {
48
49
  const value = raw.replace(/\s/g, '');
49
50
  if (!/^\d{8,32}$/.test(value)) {
50
- throw new TypeError('Colisweb tracking numbers must contain at least 8 digits');
51
+ throw new InvalidInputError(PROVIDER, 'Colisweb tracking numbers must contain at least 8 digits');
51
52
  }
52
53
  return value;
53
54
  }
@@ -114,28 +115,32 @@ export function parseColiswebTrackingResponse(payload, rawTrackingNumber) {
114
115
  export class ColiswebTracker {
115
116
  timeoutMs;
116
117
  #fetcher;
118
+ #userAgent;
117
119
  constructor(options = {}) {
118
120
  this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
119
121
  if (!Number.isFinite(this.timeoutMs) || this.timeoutMs <= 0) {
120
122
  throw new TypeError('Colisweb timeout must be positive');
121
123
  }
122
124
  this.#fetcher = options.fetcher;
125
+ this.#userAgent = userAgentOf(options.userAgent);
123
126
  }
124
- async fetch(rawTrackingNumber) {
127
+ async fetch(rawTrackingNumber, context = {}) {
125
128
  const trackingNumber = normalizeColiswebTrackingNumber(rawTrackingNumber);
129
+ const budget = lookupBudget(context, this.timeoutMs);
126
130
  const { response, bytes } = await fetchBounded(coliswebTrackingUrl(), {
127
131
  method: 'POST',
132
+ signal: budget.signal,
128
133
  headers: {
129
134
  Accept: 'application/json',
130
135
  'Content-Type': 'application/json',
131
136
  Origin: 'https://www.colisweb.com',
132
137
  Referer: 'https://www.colisweb.com/suivi-livraison',
133
- 'User-Agent': 'Mozilla/5.0 (compatible; DeliveryTracker/1.0)',
138
+ 'User-Agent': this.#userAgent,
134
139
  },
135
140
  body: coliswebRequestBody(trackingNumber),
136
141
  }, {
137
142
  provider: 'Colisweb tracking',
138
- timeoutMs: this.timeoutMs,
143
+ timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
139
144
  maxBytes: MAX_RESPONSE_BYTES,
140
145
  redirect: 'error',
141
146
  allowHttpError: true,
@@ -157,10 +162,10 @@ export class ColiswebTracker {
157
162
  }
158
163
  }
159
164
  export const adapter = (environment) => {
160
- const tracker = new ColiswebTracker({ fetcher: environment.fetcher });
165
+ const tracker = new ColiswebTracker({ fetcher: environment.fetcher, userAgent: environment.userAgent });
161
166
  return {
162
167
  id: 'colisweb',
163
168
  steps: ['direct'],
164
- track: (input) => tracker.fetch(input.number),
169
+ track: (input, context) => tracker.fetch(input.number, context),
165
170
  };
166
171
  };
@@ -1,13 +1,13 @@
1
1
  import { DateTime, IANAZone } from 'luxon';
2
2
  import { isValidS10TrackingNumber } from '../../core/detection/s10.js';
3
- import { ChallengeError, IndeterminateError, SchemaError } from '../../core/errors/index.js';
3
+ import { ChallengeError, IndeterminateError, InvalidInputError, 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 { classifyCorreiosStatus } from './status.js';
7
7
  export function normalizeCorreiosNumber(raw) {
8
8
  const number = raw.toUpperCase().replace(/[\s.-]/g, '');
9
9
  if (!/^[A-Z]{2}\d{9}[A-Z]{2}$/.test(number) || !isValidS10TrackingNumber(number)) {
10
- throw new TypeError('Correios requires a valid postal S10 tracking number');
10
+ throw new InvalidInputError('Correios', 'Correios requires a valid postal S10 tracking number');
11
11
  }
12
12
  return number;
13
13
  }
@@ -1,5 +1,5 @@
1
1
  import { isValidS10TrackingNumber } from '../../core/detection/s10.js';
2
- import { ChallengeError, IndeterminateError, RateLimitedError, SchemaError } from '../../core/errors/index.js';
2
+ import { ChallengeError, IndeterminateError, InvalidInputError, RateLimitedError, 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 { isRecord } from '../../core/types.js';
@@ -10,11 +10,11 @@ const RESOURCE = 'cl_cch_seguimiento_portlet_seguimientoresurcecommand';
10
10
  const ALLOWED_PARAMS = new Set(['p_p_id', 'p_p_lifecycle', 'p_p_state', 'p_p_mode', 'p_p_resource_id', 'p_p_cacheability']);
11
11
  export function normalizeCorreosChileNumber(raw) {
12
12
  if (raw.length > 48)
13
- throw new TypeError('Correos de Chile tracking number is too long');
13
+ throw new InvalidInputError('Correos de Chile', 'Correos de Chile tracking number is too long');
14
14
  const number = raw.toUpperCase().replace(/[\s.-]/g, '');
15
15
  if (/^\d{13}$/.test(number) || (/^[A-Z]{2}\d{9}CL$/.test(number) && isValidS10TrackingNumber(number)))
16
16
  return number;
17
- throw new TypeError('Correos de Chile requires 13 digits or a valid Chilean postal number');
17
+ throw new InvalidInputError('Correos de Chile', 'Correos de Chile requires 13 digits or a valid Chilean postal number');
18
18
  }
19
19
  export function parseCorreosChileBootstrap(html) {
20
20
  if (/Radware (?:Captcha )?Page|We apologize for the inconvenience|Please solve this CAPTCHA/i.test(html)) {
@@ -1,6 +1,6 @@
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 { calendarDay } from '../../core/time/index.js';
5
5
  import { clean } from '../../core/transport/index.js';
6
6
  import { classifyCorreosExpressStatus } from './status.js';
@@ -10,7 +10,7 @@ const FAILED_ROUND = 'Su envío no ha podido ser entregado';
10
10
  export function normalizeCorreosExpressNumber(raw) {
11
11
  const number = raw.replace(/[\s.-]/g, '');
12
12
  if (!/^\d{16}$/.test(number))
13
- throw new TypeError('Correos Express requires a 16-digit shipment number');
13
+ throw new InvalidInputError('Correos Express', 'Correos Express requires a 16-digit shipment number');
14
14
  return number;
15
15
  }
16
16
  function scanClock(raw) {
@@ -1,4 +1,4 @@
1
- import type { AdapterFactory } from '../../core/adapter/index.js';
1
+ import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
2
2
  import type { CarrierResult } from '../../core/result/index.js';
3
3
  export declare function normalizeCorreosSpainTrackingNumber(raw: string): string;
4
4
  export declare function correosSpainTrackingUrl(rawTrackingNumber: string): string;
@@ -6,10 +6,12 @@ export declare function parseCorreosSpainTrackingResponse(payload: unknown, trac
6
6
  export declare class CorreosSpainTracker {
7
7
  readonly timeoutMs: number;
8
8
  readonly fetcher: typeof fetch | undefined;
9
+ private readonly userAgent;
9
10
  constructor(options?: {
10
11
  timeoutMs?: number;
11
12
  fetcher?: typeof fetch;
13
+ userAgent?: string;
12
14
  });
13
- fetch(rawTrackingNumber: string): Promise<CarrierResult>;
15
+ fetch(rawTrackingNumber: string, context?: TrackingContext): Promise<CarrierResult>;
14
16
  }
15
17
  export declare const adapter: AdapterFactory;
@@ -1,6 +1,7 @@
1
- import { NotFoundError, SchemaError } from '../../core/errors/index.js';
1
+ import { lookupBudget } from '../../core/adapter/index.js';
2
+ import { InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
2
3
  import { zonedTime } from '../../core/time/index.js';
3
- import { clean, fetchBounded, parseJsonBytes, UpstreamHttpError } from '../../core/transport/index.js';
4
+ import { clean, fetchBounded, parseJsonBytes, UpstreamHttpError, userAgentOf } from '../../core/transport/index.js';
4
5
  import { isRecord } from '../../core/types.js';
5
6
  import { classifyCorreosSpainStatus } from './status.js';
6
7
  // Protocol provenance:
@@ -16,6 +17,8 @@ import { classifyCorreosSpainStatus } from './status.js';
16
17
  // attempt → office hold → collected); the map lives in status.ts.
17
18
  const TRACKING_ENDPOINT = 'https://localizador.correos.es/canonico/eventos_envio_servicio';
18
19
  const DEFAULT_TIMEOUT_MS = 15_000;
20
+ /** `fetchBounded` repeats a request that failed in transit once, after this pause. */
21
+ const TRANSIENT_RETRY_DELAY_MS = 1_000;
19
22
  const MAX_RESPONSE_BYTES = 1_000_000;
20
23
  const MAX_EVENTS_TO_RETURN = 20;
21
24
  function parsedTime(dateValue, timeValue) {
@@ -33,7 +36,7 @@ export function normalizeCorreosSpainTrackingNumber(raw) {
33
36
  // PR-prefixed); the codError envelope — not the shape — decides unknown.
34
37
  const value = raw.toLocaleUpperCase('en-US').replace(/[\s.-]/g, '');
35
38
  if (!/^[A-Z0-9]{4,40}$/.test(value) || !/\d/.test(value)) {
36
- throw new TypeError('Correos tracking requires a tracking code with letters, numbers and a digit');
39
+ throw new InvalidInputError('Correos', 'Correos tracking requires a tracking code with letters, numbers and a digit');
37
40
  }
38
41
  return value;
39
42
  }
@@ -151,26 +154,31 @@ export function parseCorreosSpainTrackingResponse(payload, trackingNumber) {
151
154
  export class CorreosSpainTracker {
152
155
  timeoutMs;
153
156
  fetcher;
157
+ userAgent;
154
158
  constructor(options = {}) {
155
159
  this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
156
160
  this.fetcher = options.fetcher;
161
+ this.userAgent = userAgentOf(options.userAgent);
157
162
  if (!Number.isFinite(this.timeoutMs) || this.timeoutMs <= 0) {
158
163
  throw new TypeError('Correos tracking timeout must be positive');
159
164
  }
160
165
  }
161
- async fetch(rawTrackingNumber) {
166
+ async fetch(rawTrackingNumber, context = {}) {
162
167
  const trackingNumber = normalizeCorreosSpainTrackingNumber(rawTrackingNumber);
168
+ // The default budget covers the request, the pause and the one transient retry.
169
+ const budget = lookupBudget(context, 2 * this.timeoutMs + TRANSIENT_RETRY_DELAY_MS);
163
170
  const url = `${TRACKING_ENDPOINT}/${encodeURIComponent(trackingNumber)}`
164
171
  + '?codAplicacion=60&codCanal=3&codIdioma=ES&indUltEvento=N';
165
172
  const { response, bytes } = await fetchBounded(url, {
173
+ signal: budget.signal,
166
174
  headers: {
167
175
  Accept: 'application/json, text/plain, */*',
168
176
  'Accept-Language': 'en-US,en;q=0.9',
169
- 'User-Agent': 'Mozilla/5.0 (compatible; DeliveryTracker/1.0)',
177
+ 'User-Agent': this.userAgent,
170
178
  },
171
179
  }, {
172
180
  provider: 'Correos tracking',
173
- timeoutMs: this.timeoutMs,
181
+ timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
174
182
  maxBytes: MAX_RESPONSE_BYTES,
175
183
  retryTransient: true,
176
184
  allowHttpError: true,
@@ -182,10 +190,10 @@ export class CorreosSpainTracker {
182
190
  }
183
191
  }
184
192
  export const adapter = (environment) => {
185
- const tracker = new CorreosSpainTracker({ fetcher: environment.fetcher });
193
+ const tracker = new CorreosSpainTracker({ fetcher: environment.fetcher, userAgent: environment.userAgent });
186
194
  return {
187
195
  id: 'correos-spain',
188
196
  steps: ['direct'],
189
- track: (input) => tracker.fetch(input.number),
197
+ track: (input, context) => tracker.fetch(input.number, context),
190
198
  };
191
199
  };
@@ -1,4 +1,4 @@
1
- import type { AdapterFactory } from '../../core/adapter/index.js';
1
+ import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
2
2
  import { IndeterminateError, MaintenanceError } from '../../core/errors/index.js';
3
3
  import type { CarrierResult } from '../../core/result/index.js';
4
4
  /** A CTT maintenance window: the backend is up but refuses to answer lookups. */
@@ -22,7 +22,7 @@ export declare class CttTracker {
22
22
  timeoutMs?: number;
23
23
  fetcher?: typeof fetch;
24
24
  });
25
- fetch(rawTrackingNumber: string): Promise<CarrierResult>;
25
+ fetch(rawTrackingNumber: string, context?: TrackingContext): Promise<CarrierResult>;
26
26
  private isMaintenance;
27
27
  private callAction;
28
28
  private ensureApiVersion;