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
@@ -16,8 +16,9 @@
16
16
  */
17
17
  import { Buffer } from 'node:buffer';
18
18
  import { DateTime } from 'luxon';
19
- import { InputRequiredError, NotFoundError, SchemaError } from '../../core/errors/index.js';
20
- import { cleanScalar, decodeText, fetchBounded, parseJsonBytes, UpstreamHttpError, } from '../../core/transport/index.js';
19
+ import { lookupBudget } from '../../core/adapter/index.js';
20
+ import { InputRequiredError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
21
+ import { cleanScalar, decodeText, fetchBounded, parseJsonBytes, UpstreamHttpError, userAgentOf, } from '../../core/transport/index.js';
21
22
  import { isRecord } from '../../core/types.js';
22
23
  import { classifyHeppnerEvent, heppnerCode } from './status.js';
23
24
  const SEARCH_ENDPOINT = 'https://myportal.heppner-group.com/api/recipient/search/expedition';
@@ -43,7 +44,7 @@ function eventTime(value) {
43
44
  export function normalizeHeppnerTrackingNumber(raw) {
44
45
  const trackingNumber = raw.replace(/\s/g, '');
45
46
  if (!/^\d{8}$/.test(trackingNumber)) {
46
- throw new TypeError('Heppner tracking numbers must contain exactly 8 digits');
47
+ throw new InvalidInputError('Heppner', 'Heppner tracking numbers must contain exactly 8 digits');
47
48
  }
48
49
  return trackingNumber;
49
50
  }
@@ -51,7 +52,7 @@ export function normalizeHeppnerCredential(rawTrackingNumber, rawPostcode) {
51
52
  const trackingNumber = normalizeHeppnerTrackingNumber(rawTrackingNumber);
52
53
  const postcode = rawPostcode.trim();
53
54
  if (!/^\d{4,5}$/.test(postcode)) {
54
- throw new TypeError('Heppner requires a four-digit Swiss or five-digit French delivery postcode');
55
+ throw new InvalidInputError('Heppner', 'Heppner requires a four-digit Swiss or five-digit French delivery postcode');
55
56
  }
56
57
  return {
57
58
  trackingNumber,
@@ -178,30 +179,33 @@ export function parseHeppnerTrackingResponse(payload, rawTrackingNumber) {
178
179
  events: parsed.map(({ event }) => event),
179
180
  };
180
181
  }
181
- function requestHeaders(accept, origin = 'https://www.heppner-group.com') {
182
+ function requestHeaders(userAgent, accept, origin = 'https://www.heppner-group.com') {
182
183
  return {
183
184
  Accept: accept,
184
185
  'Accept-Language': 'fr-FR,fr;q=0.9',
185
186
  Origin: origin,
186
187
  Referer: 'https://www.heppner-group.com/destinataire-suivez-votre-marchandise/',
187
- 'User-Agent': 'Mozilla/5.0 (compatible; DeliveryTracker/1.0)',
188
+ 'User-Agent': userAgent,
188
189
  };
189
190
  }
190
191
  export class HeppnerTracker {
191
192
  timeoutMs;
192
193
  fetcher;
194
+ userAgent;
193
195
  constructor(options = {}) {
194
196
  this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
195
197
  this.fetcher = options.fetcher;
196
198
  if (!Number.isFinite(this.timeoutMs) || this.timeoutMs <= 0) {
197
199
  throw new TypeError('Heppner timeout must be positive');
198
200
  }
201
+ this.userAgent = userAgentOf(options.userAgent);
199
202
  }
200
- async fetch(rawTrackingNumber, rawPostcode) {
203
+ async fetch(rawTrackingNumber, rawPostcode, context = {}) {
201
204
  const credential = normalizeHeppnerCredential(rawTrackingNumber, rawPostcode);
202
- const search = await fetchBounded(heppnerSearchUrl(credential.trackingNumber, credential.postcode), { headers: requestHeaders('text/plain,*/*;q=0.8') }, {
205
+ const budget = lookupBudget(context, 2 * this.timeoutMs);
206
+ const search = await fetchBounded(heppnerSearchUrl(credential.trackingNumber, credential.postcode), { signal: budget.signal, headers: requestHeaders(this.userAgent, 'text/plain,*/*;q=0.8') }, {
203
207
  provider: 'Heppner shipment search',
204
- timeoutMs: this.timeoutMs,
208
+ timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
205
209
  maxBytes: 2_048,
206
210
  fetcher: this.fetcher,
207
211
  allowHttpError: true,
@@ -213,13 +217,14 @@ export class HeppnerTracker {
213
217
  }
214
218
  const capability = parseHeppnerCapability(decodeText(search.bytes), credential.trackingNumber, credential.postcode);
215
219
  const detail = await fetchBounded(heppnerDetailUrl(capability), {
220
+ signal: budget.signal,
216
221
  headers: {
217
- ...requestHeaders('application/json', 'https://myportal.heppner-group.com'),
222
+ ...requestHeaders(this.userAgent, 'application/json', 'https://myportal.heppner-group.com'),
218
223
  Referer: heppnerTrackingPageUrl(capability),
219
224
  },
220
225
  }, {
221
226
  provider: 'Heppner tracking',
222
- timeoutMs: this.timeoutMs,
227
+ timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
223
228
  maxBytes: MAX_RESPONSE_BYTES,
224
229
  fetcher: this.fetcher,
225
230
  allowHttpError: true,
@@ -233,15 +238,15 @@ export class HeppnerTracker {
233
238
  }
234
239
  }
235
240
  export const adapter = (environment) => {
236
- const tracker = new HeppnerTracker({ fetcher: environment.fetcher });
241
+ const tracker = new HeppnerTracker({ fetcher: environment.fetcher, userAgent: environment.userAgent });
237
242
  return {
238
243
  id: 'heppner',
239
244
  steps: ['direct'],
240
- track: async (input) => {
245
+ track: async (input, context) => {
241
246
  const postcode = input.postcode?.trim() ?? '';
242
247
  if (!postcode)
243
248
  throw new InputRequiredError('Heppner', 'the delivery postcode');
244
- return tracker.fetch(input.number, postcode);
249
+ return tracker.fetch(input.number, postcode, context);
245
250
  },
246
251
  };
247
252
  };
@@ -20,7 +20,7 @@
20
20
  "adapter": "heppner",
21
21
  "requirements": [
22
22
  {
23
- "field": "dpdPostcode",
23
+ "field": "postcode",
24
24
  "validator": "swissOrFrancePostcode",
25
25
  "label": "Delivery postcode",
26
26
  "type": "text",
@@ -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
  import { hermesStatus } from './status.js';
4
4
  export { hermesStatus };
@@ -6,12 +6,13 @@ export interface HermesOptions {
6
6
  timeoutMs?: number;
7
7
  /** Test seam; production uses the global fetch. */
8
8
  fetcher?: typeof fetch;
9
+ userAgent?: string;
9
10
  }
10
11
  export declare function parseHermesTrackingResponse(payload: unknown, trackingNumber: string): CarrierResult;
11
12
  export declare class HermesTracker {
12
13
  #private;
13
14
  readonly timeoutMs: number;
14
15
  constructor(options?: number | HermesOptions);
15
- fetch(trackingNumber: string): Promise<CarrierResult>;
16
+ fetch(trackingNumber: string, context?: TrackingContext): Promise<CarrierResult>;
16
17
  }
17
18
  export declare const adapter: AdapterFactory;
@@ -1,5 +1,6 @@
1
+ import { lookupBudget } from '../../core/adapter/index.js';
1
2
  import { NotFoundError, SchemaError } from '../../core/errors/index.js';
2
- import { fetchBounded, parseJsonBytes } from '../../core/transport/index.js';
3
+ import { fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
3
4
  import { isRecord } from '../../core/types.js';
4
5
  import { hermesEventStage, hermesStatus } from './status.js';
5
6
  export { hermesStatus };
@@ -70,34 +71,38 @@ export function parseHermesTrackingResponse(payload, trackingNumber) {
70
71
  export class HermesTracker {
71
72
  timeoutMs;
72
73
  #fetcher;
74
+ #userAgent;
73
75
  constructor(options = {}) {
74
- const { timeoutMs = DEFAULT_TIMEOUT_MS, fetcher } = typeof options === 'number'
75
- ? { timeoutMs: options, fetcher: undefined }
76
+ const { timeoutMs = DEFAULT_TIMEOUT_MS, fetcher, userAgent } = typeof options === 'number'
77
+ ? { timeoutMs: options, fetcher: undefined, userAgent: undefined }
76
78
  : options;
77
79
  this.timeoutMs = timeoutMs;
78
80
  this.#fetcher = fetcher;
81
+ this.#userAgent = userAgentOf(userAgent);
79
82
  }
80
- async fetch(trackingNumber) {
83
+ async fetch(trackingNumber, context = {}) {
81
84
  const url = new URL(HERMES_API);
82
85
  url.searchParams.set('parcelNumber', trackingNumber);
86
+ const budget = lookupBudget(context, this.timeoutMs);
83
87
  const { bytes } = await fetchBounded(url, {
88
+ signal: budget.signal,
84
89
  headers: {
85
90
  Accept: 'application/json',
86
- 'User-Agent': 'Mozilla/5.0 (compatible; SwissDeliveryTracker/1.0)',
91
+ 'User-Agent': this.#userAgent,
87
92
  },
88
93
  }, {
89
94
  provider: 'Hermes tracking',
90
- timeoutMs: this.timeoutMs,
95
+ timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
91
96
  ...(this.#fetcher ? { fetcher: this.#fetcher } : {}),
92
97
  });
93
98
  return parseHermesTrackingResponse(parseJsonBytes(bytes, PROVIDER), trackingNumber);
94
99
  }
95
100
  }
96
101
  export const adapter = (environment) => {
97
- const tracker = new HermesTracker({ fetcher: environment.fetcher });
102
+ const tracker = new HermesTracker({ fetcher: environment.fetcher, userAgent: environment.userAgent });
98
103
  return {
99
104
  id: 'hermes',
100
105
  steps: ['direct'],
101
- track: (input) => tracker.fetch(input.number),
106
+ track: (input, context) => tracker.fetch(input.number, context),
102
107
  };
103
108
  };
@@ -1,10 +1,11 @@
1
- import { type AdapterFactory } from '../../core/adapter/index.js';
1
+ import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
2
2
  import { NotFoundError } from '../../core/errors/index.js';
3
3
  import type { CarrierResult } from '../../core/result/index.js';
4
4
  export interface HermesGermanyOptions {
5
5
  timeoutMs?: number;
6
6
  /** Test seam; production uses the global fetch. */
7
7
  fetcher?: typeof fetch;
8
+ userAgent?: string;
8
9
  }
9
10
  export { STATUSES } from './status.js';
10
11
  /** Kept as a named class: the grouped live suite asserts this error's name. */
@@ -17,6 +18,6 @@ export declare class HermesGermanyTracker {
17
18
  #private;
18
19
  readonly timeoutMs: number;
19
20
  constructor(options?: number | HermesGermanyOptions);
20
- fetch(trackingNumber: string): Promise<CarrierResult>;
21
+ fetch(trackingNumber: string, context?: TrackingContext): Promise<CarrierResult>;
21
22
  }
22
23
  export declare const adapter: AdapterFactory;
@@ -1,7 +1,7 @@
1
1
  import { DateTime } from 'luxon';
2
- import { recognizeFromLookup } from '../../core/adapter/index.js';
3
- import { InputRequiredError, NotFoundError, SchemaError, UpstreamHttpError } from '../../core/errors/index.js';
4
- import { clean, fetchBounded, parseJsonBytes } from '../../core/transport/index.js';
2
+ import { lookupBudget, recognizeFromLookup } from '../../core/adapter/index.js';
3
+ import { InvalidInputError, NotFoundError, SchemaError, UpstreamHttpError } from '../../core/errors/index.js';
4
+ import { clean, fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
5
5
  import { isRecord } from '../../core/types.js';
6
6
  import { hermesGermanyMilestone, IGNORED_BOOKING_STATUS } from './status.js';
7
7
  // Public recipient protocol inspected 2026-09-08:
@@ -36,7 +36,7 @@ export class HermesGermanyTrackingError extends NotFoundError {
36
36
  export function normalizeHermesGermanyNumber(raw) {
37
37
  const value = raw.toUpperCase().replace(/[\s.-]/g, '');
38
38
  if (!/^(?=.*\d)[A-Z0-9]{8,20}$/.test(value)) {
39
- throw new InputRequiredError(CARRIER, 'an 8-to-20-character tracking number', 'Hermes Germany requires an 8-to-20-character tracking number');
39
+ throw new InvalidInputError(CARRIER, 'Hermes Germany requires an 8-to-20-character tracking number');
40
40
  }
41
41
  return value;
42
42
  }
@@ -125,27 +125,31 @@ export function parseHermesGermanyResponse(payload, trackingNumber) {
125
125
  export class HermesGermanyTracker {
126
126
  timeoutMs;
127
127
  #fetcher;
128
+ #userAgent;
128
129
  constructor(options = {}) {
129
- const { timeoutMs = DEFAULT_TIMEOUT_MS, fetcher } = typeof options === 'number'
130
- ? { timeoutMs: options, fetcher: undefined }
130
+ const { timeoutMs = DEFAULT_TIMEOUT_MS, fetcher, userAgent } = typeof options === 'number'
131
+ ? { timeoutMs: options, fetcher: undefined, userAgent: undefined }
131
132
  : options;
132
133
  if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
133
134
  throw new TypeError('Hermes Germany timeout must be positive');
134
135
  this.timeoutMs = timeoutMs;
135
136
  this.#fetcher = fetcher;
137
+ this.#userAgent = userAgentOf(userAgent);
136
138
  }
137
- async fetch(trackingNumber) {
139
+ async fetch(trackingNumber, context = {}) {
138
140
  const number = normalizeHermesGermanyNumber(trackingNumber);
141
+ const budget = lookupBudget(context, this.timeoutMs);
139
142
  const { response, bytes } = await fetchBounded(`${API}${encodeURIComponent(number)}`, {
143
+ signal: budget.signal,
140
144
  headers: {
141
145
  Accept: 'application/json',
142
146
  'X-Language': 'de',
143
147
  Referer: 'https://www.myhermes.de/',
144
- 'User-Agent': 'Mozilla/5.0 (compatible; DeliveryTracker/1.0)',
148
+ 'User-Agent': this.#userAgent,
145
149
  },
146
150
  }, {
147
151
  provider: PROVIDER,
148
- timeoutMs: this.timeoutMs,
152
+ timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
149
153
  maxBytes: 750_000,
150
154
  allowHttpError: true,
151
155
  ...(this.#fetcher ? { fetcher: this.#fetcher } : {}),
@@ -158,11 +162,11 @@ export class HermesGermanyTracker {
158
162
  }
159
163
  }
160
164
  export const adapter = (environment) => {
161
- const tracker = new HermesGermanyTracker({ fetcher: environment.fetcher });
165
+ const tracker = new HermesGermanyTracker({ fetcher: environment.fetcher, userAgent: environment.userAgent });
162
166
  return {
163
167
  id: 'hermes-de',
164
168
  steps: ['direct'],
165
- track: (input) => tracker.fetch(input.number),
166
- recognize: (number) => recognizeFromLookup(() => tracker.fetch(number)),
169
+ track: (input, context) => tracker.fetch(input.number, context),
170
+ recognize: (number, context) => recognizeFromLookup(() => tracker.fetch(number, context)),
167
171
  };
168
172
  };
@@ -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 { ChallengeError } from '../../core/errors/index.js';
3
3
  import { type CarrierResult } from '../../core/result/index.js';
4
4
  import { classifyIndiaPostEvent } from './status.js';
@@ -26,7 +26,7 @@ export declare class IndiaPostTracker {
26
26
  constructor(options?: IndiaPostTrackerOptions);
27
27
  private request;
28
28
  private update;
29
- fetch(trackingNumber: string): Promise<CarrierResult>;
29
+ fetch(trackingNumber: string, context?: TrackingContext): Promise<CarrierResult>;
30
30
  /** Send the first calls, then poll `fetchStatus` until the component completes. */
31
31
  private complete;
32
32
  }
@@ -1,7 +1,8 @@
1
1
  import { load } from 'cheerio';
2
2
  import makeFetchCookie from 'fetch-cookie';
3
3
  import { CookieJar } from 'tough-cookie';
4
- import { ChallengeError, IndeterminateError, NotFoundError, SchemaError } from '../../core/errors/index.js';
4
+ import { lookupBudget } from '../../core/adapter/index.js';
5
+ import { ChallengeError, IndeterminateError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
5
6
  import { eventPoint } from '../../core/result/index.js';
6
7
  import { isValidS10TrackingNumber } from '../../core/detection/index.js';
7
8
  import { isoTime } from '../../core/time/index.js';
@@ -38,7 +39,7 @@ export class IndiaPostChallengeError extends ChallengeError {
38
39
  export function normalizeIndiaPostTrackingNumber(raw) {
39
40
  const value = raw.toLocaleUpperCase('en-US').replace(/[\s.-]/g, '');
40
41
  if (!/^[A-Z]{2}\d{9}IN$/.test(value) || !isValidS10TrackingNumber(value)) {
41
- throw new TypeError('India Post tracking requires a valid 13-character S10 number ending in IN');
42
+ throw new InvalidInputError('India Post', 'India Post tracking requires a valid 13-character S10 number ending in IN');
42
43
  }
43
44
  return value;
44
45
  }
@@ -261,10 +262,22 @@ function challengePage(status, html, headers) {
261
262
  || headers.get('cf-mitigated') === 'challenge'
262
263
  || /Just a moment|Enable JavaScript and cookies|cf-chl-|_cf_chl_opt/i.test(html);
263
264
  }
264
- function pause(milliseconds) {
265
- return milliseconds > 0
266
- ? new Promise((resolve) => setTimeout(resolve, milliseconds))
267
- : Promise.resolve();
265
+ /** The poll interval, cut short as soon as the lookup's signal aborts. */
266
+ async function pause(milliseconds, signal) {
267
+ signal.throwIfAborted();
268
+ if (milliseconds <= 0)
269
+ return;
270
+ await new Promise((resolve, reject) => {
271
+ const abort = () => {
272
+ clearTimeout(timer);
273
+ reject(signal.reason);
274
+ };
275
+ const timer = setTimeout(() => {
276
+ signal.removeEventListener('abort', abort);
277
+ resolve();
278
+ }, milliseconds);
279
+ signal.addEventListener('abort', abort, { once: true });
280
+ });
268
281
  }
269
282
  export class IndiaPostTracker {
270
283
  timeoutMs;
@@ -288,10 +301,10 @@ export class IndiaPostTracker {
288
301
  throw new TypeError('India Post poll attempts must be positive');
289
302
  }
290
303
  }
291
- async request(fetcher, url, init) {
292
- const result = await fetchBounded(url, init, {
304
+ async request(fetcher, budget, url, init) {
305
+ const result = await fetchBounded(url, { ...init, signal: budget.signal }, {
293
306
  provider: 'India Post tracking',
294
- timeoutMs: this.timeoutMs,
307
+ timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
295
308
  maxBytes: MAX_RESPONSE_BYTES,
296
309
  redirect: 'manual',
297
310
  fetcher,
@@ -306,8 +319,8 @@ export class IndiaPostTracker {
306
319
  }
307
320
  return { bytes: result.bytes, html };
308
321
  }
309
- async update(fetcher, trackingNumber, pageUrl, token, snapshot, calls, updates = {}) {
310
- const result = await this.request(fetcher, LIVEWIRE_UPDATE, {
322
+ async update(fetcher, budget, trackingNumber, pageUrl, token, snapshot, calls, updates = {}) {
323
+ const result = await this.request(fetcher, budget, LIVEWIRE_UPDATE, {
311
324
  method: 'POST',
312
325
  headers: {
313
326
  Accept: 'application/json',
@@ -325,13 +338,16 @@ export class IndiaPostTracker {
325
338
  });
326
339
  return parseLivewireUpdate(parseJsonBytes(result.bytes, 'India Post tracking'), trackingNumber);
327
340
  }
328
- async fetch(trackingNumber) {
341
+ async fetch(trackingNumber, context = {}) {
329
342
  const normalized = normalizeIndiaPostTrackingNumber(trackingNumber);
330
343
  const pageUrl = indiaPostTrackingUrl(normalized);
344
+ // The default budget covers the page, the first update, then every poll
345
+ // after its pause.
346
+ const budget = lookupBudget(context, (this.maxPollAttempts + 2) * this.timeoutMs + this.maxPollAttempts * this.pollIntervalMs);
331
347
  // The Livewire flow is stateful: the session cookie issued with the page
332
348
  // must travel with every /livewire/update call, so the jar is per lookup.
333
349
  const fetcher = makeFetchCookie(this.fetcher, new CookieJar());
334
- const page = await this.request(fetcher, pageUrl, {
350
+ const page = await this.request(fetcher, budget, pageUrl, {
335
351
  headers: {
336
352
  Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
337
353
  'Accept-Language': 'en-US,en;q=0.9',
@@ -347,13 +363,18 @@ export class IndiaPostTracker {
347
363
  // What the page's Refresh button sends. A failed refresh still leaves
348
364
  // the cached history, which is better than no answer.
349
365
  try {
350
- return await this.complete(fetcher, normalized, pageUrl, csrfToken(page.html), initial.snapshot, [{
366
+ return await this.complete(fetcher, budget, normalized, pageUrl, csrfToken(page.html), initial.snapshot, [{
351
367
  path: '',
352
368
  method: '__dispatch',
353
369
  params: ['refresh_consignment', { userTimezone: USER_TIMEZONE }],
354
370
  }], { userTimezone: USER_TIMEZONE });
355
371
  }
356
372
  catch {
373
+ // A caller that cancelled is not waiting for an answer; one whose
374
+ // deadline passed still takes the history already in hand.
375
+ const reason = context.signal?.reason;
376
+ if (context.signal?.aborted && !(reason instanceof DOMException && reason.name === 'TimeoutError'))
377
+ throw reason;
357
378
  return cached;
358
379
  }
359
380
  }
@@ -362,12 +383,12 @@ export class IndiaPostTracker {
362
383
  }
363
384
  const token = csrfToken(page.html);
364
385
  return initial.status === 'Processing'
365
- ? this.complete(fetcher, normalized, pageUrl, token, initial.snapshot, [{
386
+ ? this.complete(fetcher, budget, normalized, pageUrl, token, initial.snapshot, [{
366
387
  path: '',
367
388
  method: 'fetchStatus',
368
389
  params: [],
369
390
  }])
370
- : this.complete(fetcher, normalized, pageUrl, token, initial.snapshot, [{
391
+ : this.complete(fetcher, budget, normalized, pageUrl, token, initial.snapshot, [{
371
392
  path: '',
372
393
  method: '__dispatch',
373
394
  params: ['set_consignment_number', { consignment_number: normalized }],
@@ -378,8 +399,8 @@ export class IndiaPostTracker {
378
399
  }], { userTimezone: USER_TIMEZONE });
379
400
  }
380
401
  /** Send the first calls, then poll `fetchStatus` until the component completes. */
381
- async complete(fetcher, normalized, pageUrl, token, snapshot, calls, updates = {}) {
382
- let update = await this.update(fetcher, normalized, pageUrl, token, snapshot, calls, updates);
402
+ async complete(fetcher, budget, normalized, pageUrl, token, snapshot, calls, updates = {}) {
403
+ let update = await this.update(fetcher, budget, normalized, pageUrl, token, snapshot, calls, updates);
383
404
  for (let attempt = 0; attempt <= this.maxPollAttempts; attempt += 1) {
384
405
  const names = dispatchNames(update.effects);
385
406
  if (names.has('consignment_not_found'))
@@ -395,8 +416,8 @@ export class IndiaPostTracker {
395
416
  }
396
417
  if (attempt === this.maxPollAttempts)
397
418
  break;
398
- await pause(this.pollIntervalMs);
399
- update = await this.update(fetcher, normalized, pageUrl, token, update.component.snapshot, [{ path: '', method: 'fetchStatus', params: [] }]);
419
+ await pause(this.pollIntervalMs, budget.signal);
420
+ update = await this.update(fetcher, budget, normalized, pageUrl, token, update.component.snapshot, [{ path: '', method: 'fetchStatus', params: [] }]);
400
421
  }
401
422
  // The backend kept saying "Processing": it answered, but it proved nothing
402
423
  // about the shipment, so this must not become a not-found cooldown.
@@ -410,6 +431,6 @@ export const adapter = (environment) => {
410
431
  // The Livewire submit-and-poll cycle is part of the direct call: it is one
411
432
  // stateful conversation with one host, not a fallback tier.
412
433
  steps: ['direct'],
413
- track: (input) => tracker.fetch(input.number),
434
+ track: (input, context) => tracker.fetch(input.number, context),
414
435
  };
415
436
  };
@@ -1,14 +1,16 @@
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 normalizeInpostTrackingNumber(raw: string): string;
4
4
  export declare function parseInpostTrackingResponse(payload: unknown, trackingNumber: string): CarrierResult;
5
5
  export declare class InpostTracker {
6
6
  readonly timeoutMs: number;
7
7
  readonly fetcher: typeof fetch | undefined;
8
+ private readonly userAgent;
8
9
  constructor(options?: {
9
10
  timeoutMs?: number;
10
11
  fetcher?: typeof fetch;
12
+ userAgent?: string;
11
13
  });
12
- fetch(rawTrackingNumber: string): Promise<CarrierResult>;
14
+ fetch(rawTrackingNumber: string, context?: TrackingContext): Promise<CarrierResult>;
13
15
  }
14
16
  export declare const adapter: AdapterFactory;
@@ -1,7 +1,7 @@
1
- import { accepted, recognizeFromLookup } from '../../core/adapter/index.js';
2
- import { NotFoundError, SchemaError } from '../../core/errors/index.js';
1
+ import { accepted, lookupBudget, recognizeFromLookup } from '../../core/adapter/index.js';
2
+ import { InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
3
3
  import { explicitOffsetTime } from '../../core/time/index.js';
4
- import { clean, fetchBounded, parseJsonBytes, UpstreamHttpError } from '../../core/transport/index.js';
4
+ import { clean, fetchBounded, parseJsonBytes, UpstreamHttpError, userAgentOf } from '../../core/transport/index.js';
5
5
  import { isRecord } from '../../core/types.js';
6
6
  import { classifyInpostStatus } from './status.js';
7
7
  // Protocol provenance:
@@ -20,6 +20,8 @@ import { classifyInpostStatus } from './status.js';
20
20
  // consignments 2026-08-31 by the prior-art client; the map lives in status.ts.
21
21
  const TRACKING_ENDPOINT = 'https://inposteasy.com/api/tracking';
22
22
  const DEFAULT_TIMEOUT_MS = 15_000;
23
+ /** The pause `fetchBounded` takes before its one retry of a failed request. */
24
+ const TRANSIENT_RETRY_DELAY_MS = 1_000;
23
25
  const MAX_RESPONSE_BYTES = 1_000_000;
24
26
  const MAX_EVENTS_TO_RETURN = 20;
25
27
  function statusCode(value) {
@@ -28,7 +30,7 @@ function statusCode(value) {
28
30
  export function normalizeInpostTrackingNumber(raw) {
29
31
  const value = raw.toLocaleUpperCase('en-US').replace(/[\s.-]/g, '');
30
32
  if (!/^(?:\d{24}|JJD\d{16}|JD\d{16}|8YDR\d{9})$/.test(value)) {
31
- throw new TypeError('InPost tracking requires a 24-digit, JJD/JD legacy or 8YDR identifier');
33
+ throw new InvalidInputError('InPost', 'InPost tracking requires a 24-digit, JJD/JD legacy or 8YDR identifier');
32
34
  }
33
35
  return value;
34
36
  }
@@ -101,25 +103,30 @@ export function parseInpostTrackingResponse(payload, trackingNumber) {
101
103
  export class InpostTracker {
102
104
  timeoutMs;
103
105
  fetcher;
106
+ userAgent;
104
107
  constructor(options = {}) {
105
108
  this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
106
109
  this.fetcher = options.fetcher;
110
+ this.userAgent = userAgentOf(options.userAgent);
107
111
  if (!Number.isFinite(this.timeoutMs) || this.timeoutMs <= 0) {
108
112
  throw new TypeError('InPost timeout must be positive');
109
113
  }
110
114
  }
111
- async fetch(rawTrackingNumber) {
115
+ async fetch(rawTrackingNumber, context = {}) {
112
116
  const trackingNumber = normalizeInpostTrackingNumber(rawTrackingNumber);
117
+ // The default budget covers the request, the pause and the one transient retry.
118
+ const budget = lookupBudget(context, 2 * this.timeoutMs + TRANSIENT_RETRY_DELAY_MS);
113
119
  const url = `${TRACKING_ENDPOINT}/${encodeURIComponent(trackingNumber)}`;
114
120
  const { response, bytes } = await fetchBounded(url, {
121
+ signal: budget.signal,
115
122
  headers: {
116
123
  Accept: 'application/json, text/plain, */*',
117
124
  'Accept-Language': 'en-US,en;q=0.9',
118
- 'User-Agent': 'Mozilla/5.0 (compatible; DeliveryTracker/1.0)',
125
+ 'User-Agent': this.userAgent,
119
126
  },
120
127
  }, {
121
128
  provider: 'InPost tracking',
122
- timeoutMs: this.timeoutMs,
129
+ timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
123
130
  maxBytes: MAX_RESPONSE_BYTES,
124
131
  retryTransient: true,
125
132
  allowHttpError: true,
@@ -133,11 +140,11 @@ export class InpostTracker {
133
140
  }
134
141
  }
135
142
  export const adapter = (environment) => {
136
- const tracker = new InpostTracker({ fetcher: environment.fetcher });
143
+ const tracker = new InpostTracker({ fetcher: environment.fetcher, userAgent: environment.userAgent });
137
144
  return {
138
145
  id: 'inpost',
139
146
  steps: ['direct'],
140
- track: (input) => tracker.fetch(input.number),
141
- recognize: (number) => recognizeFromLookup(() => tracker.fetch(number), () => accepted(() => normalizeInpostTrackingNumber(number))),
147
+ track: (input, context) => tracker.fetch(input.number, context),
148
+ recognize: (number, context) => recognizeFromLookup(() => tracker.fetch(number, context), () => accepted(() => normalizeInpostTrackingNumber(number))),
142
149
  };
143
150
  };
@@ -1,7 +1,7 @@
1
1
  import { load } from 'cheerio';
2
2
  import { DateTime } from 'luxon';
3
3
  import { isValidS10TrackingNumber, normalizeTrackingNumber } from '../../core/detection/index.js';
4
- import { ChallengeError, InputRequiredError, NotFoundError, SchemaError, TransportError, UpstreamHttpError } from '../../core/errors/index.js';
4
+ import { ChallengeError, InvalidInputError, NotFoundError, SchemaError, TransportError, UpstreamHttpError } from '../../core/errors/index.js';
5
5
  import { clean, decodeText, fetchBounded } from '../../core/transport/index.js';
6
6
  import { japanPostStatus } from './status.js';
7
7
  const PROVIDER = 'Japan Post';
@@ -19,7 +19,7 @@ export function normalizeJapanPostNumber(raw) {
19
19
  // S10 references. U-prefixed customs labels are explicitly not trackable.
20
20
  if (!/^\d{11,13}$/.test(number)
21
21
  && (!/^[A-TV-Z][A-Z]\d{9}[A-Z]{2}$/.test(number) || !isValidS10TrackingNumber(number))) {
22
- throw new InputRequiredError(PROVIDER, 'number', 'Japan Post requires a domestic tracking number or a valid tracked postal reference');
22
+ throw new InvalidInputError(PROVIDER, 'Japan Post requires a domestic tracking number or a valid tracked postal reference');
23
23
  }
24
24
  return number;
25
25
  }
@@ -1,6 +1,6 @@
1
1
  import { load } from 'cheerio';
2
2
  import { DateTime } from 'luxon';
3
- import { IndeterminateError, InputRequiredError, NotFoundError, SchemaError, TransportError, UpstreamHttpError } from '../../core/errors/index.js';
3
+ import { IndeterminateError, InvalidInputError, NotFoundError, SchemaError, TransportError, UpstreamHttpError } from '../../core/errors/index.js';
4
4
  import { runSteps } from '../../core/runner/index.js';
5
5
  import { clean, decodeText, fetchBounded } from '../../core/transport/index.js';
6
6
  import { koreaPostStatus } from './status.js';
@@ -8,7 +8,7 @@ const ENDPOINT = 'https://trace.epost.go.kr/xtts/servlet/kpl.tts.common.svl.SttS
8
8
  export function normalizeKoreaPostNumber(raw) {
9
9
  const number = raw.toUpperCase().replace(/[\s.-]/g, '');
10
10
  if (!/^[A-Z]{2}\d{9}[A-Z]{2}$/.test(number))
11
- throw new InputRequiredError('Korea Post', 'number', 'Korea Post direct tracking supports international postal identifiers');
11
+ throw new InvalidInputError('Korea Post', 'Korea Post direct tracking supports international postal identifiers');
12
12
  return number;
13
13
  }
14
14
  function eventTime(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 { CarrierError } from '../../core/errors/index.js';
3
3
  import type { CarrierResult } from '../../core/result/index.js';
4
4
  import type { StepRecorder } from '../../core/telemetry/index.js';
@@ -18,12 +18,14 @@ export interface LaPosteTrackerOptions {
18
18
  timeoutMs?: number;
19
19
  fetcher?: typeof fetch;
20
20
  recorder?: StepRecorder;
21
+ userAgent?: string;
21
22
  }
22
23
  export declare class LaPosteTracker {
23
24
  readonly timeoutMs: number;
24
25
  private readonly fetcher?;
25
26
  private readonly recorder?;
27
+ private readonly userAgent;
26
28
  constructor(options?: LaPosteTrackerOptions);
27
- fetch(trackingNumber: string): Promise<CarrierResult>;
29
+ fetch(trackingNumber: string, context?: TrackingContext): Promise<CarrierResult>;
28
30
  }
29
31
  export declare const adapter: AdapterFactory;