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
@@ -1,9 +1,9 @@
1
1
  import { load } from 'cheerio';
2
- import { ChallengeError, InputRequiredError, RateLimitedError, SchemaError, TransportError, UpstreamHttpError, } from '../../core/errors/index.js';
2
+ import { ChallengeError, InputRequiredError, InvalidInputError, RateLimitedError, 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
5
  import { explicitOffsetTime } from '../../core/time/index.js';
6
- import { clean, cleanScalar, TrawlClient } from '../../core/transport/index.js';
6
+ import { clean, cleanScalar, TRAWL_TRANSPORT_ALLOWANCE_MS, TrawlClient } from '../../core/transport/index.js';
7
7
  import { isRecord } from '../../core/types.js';
8
8
  import { FEDEX_CODE_STAGE, fedexStage, fedexStatus } from './status.js';
9
9
  /**
@@ -35,7 +35,7 @@ const MAX_EVENTS_TO_RETURN = 100;
35
35
  export function normalizeFedExTrackingNumber(raw) {
36
36
  const value = raw.toLocaleUpperCase('en-US').replace(/[\s.-]/g, '');
37
37
  if (!/^\d{12}$/.test(value) && !/^\d{15}$/.test(value)) {
38
- throw new SchemaError('FedEx', 'FedEx tracking numbers must contain 12 or 15 digits');
38
+ throw new InvalidInputError('FedEx', 'FedEx tracking numbers must contain 12 or 15 digits');
39
39
  }
40
40
  return value;
41
41
  }
@@ -210,7 +210,7 @@ export class FedExTracker {
210
210
  return this.#trawl;
211
211
  return this.trawlUrl ? new TrawlClient(this.trawlUrl, this.#fetcher) : null;
212
212
  }
213
- async fetch(trackingNumber) {
213
+ async fetch(trackingNumber, context = {}) {
214
214
  const number = normalizeFedExTrackingNumber(trackingNumber);
215
215
  // Plain HTTP probes were rejected; the dedicated route uses the browser.
216
216
  const trawl = this.#browserService();
@@ -218,9 +218,12 @@ export class FedExTracker {
218
218
  throw new ChallengeError('FedEx', 'FedEx challenged direct tracking; configure FLARESOLVERR_URL for browser fallback');
219
219
  }
220
220
  return runSteps({
221
- carrier: 'fedex', budgetMs: this.timeoutMs, recorder: this.#recorder,
221
+ // Without a caller's budget the lookup leaves the service its own time and
222
+ // the request the allowance to bring the answer back.
223
+ carrier: 'fedex', budgetMs: context.budgetMs ?? this.timeoutMs + TRAWL_TRANSPORT_ALLOWANCE_MS, signal: context.signal,
224
+ recorder: this.#recorder,
222
225
  }, [
223
- { id: 'trawl', run: () => this.#trawlResult(trawl, number) },
226
+ { id: 'trawl', run: ({ remainingMs, signal }) => this.#trawlResult(trawl, number, Math.max(1, Math.floor(Math.min(this.timeoutMs, remainingMs))), signal) },
224
227
  ]);
225
228
  }
226
229
  /**
@@ -229,19 +232,20 @@ export class FedExTracker {
229
232
  * over plain HTTP: the edge accepts the call only from the session it
230
233
  * validated. The page the browser rendered only tells a challenge apart.
231
234
  */
232
- async #trawlResult(trawl, number) {
235
+ async #trawlResult(trawl, number, timeoutMs, signal) {
233
236
  const page = await trawl.scrape({
234
237
  url: fedexTrackingUrl(number),
235
238
  skipHttp: true,
236
239
  maxTier: 3,
237
- maxTimeout: this.timeoutMs,
240
+ maxTimeout: timeoutMs,
238
241
  captureResponses: [TRACK_API],
239
242
  settleTimeout: SETTLE_TIMEOUT_MS,
240
243
  }, {
241
244
  provider: 'TRAWL while fetching FedEx',
242
- timeoutMs: this.timeoutMs,
245
+ timeoutMs,
243
246
  maxBytes: MAX_BYTES,
244
247
  fetcher: this.#fetcher,
248
+ signal,
245
249
  });
246
250
  let captureError;
247
251
  // Newest first: a later reply is the page's final answer.
@@ -302,6 +306,6 @@ export const adapter = (environment) => {
302
306
  id: 'fedex',
303
307
  // Browser-backed direct tracking; universal recovery belongs to the caller.
304
308
  steps: ['trawl'],
305
- track: (input) => tracker.fetch(input.number),
309
+ track: (input, context) => tracker.fetch(input.number, context),
306
310
  };
307
311
  };
@@ -1,5 +1,5 @@
1
1
  import { DateTime } from 'luxon';
2
- import { IndeterminateError, NotFoundError, SchemaError, TransportError, UpstreamHttpError } from '../../core/errors/index.js';
2
+ import { IndeterminateError, InvalidInputError, NotFoundError, SchemaError, TransportError, UpstreamHttpError } from '../../core/errors/index.js';
3
3
  import { runSteps } from '../../core/runner/index.js';
4
4
  import { clean, fetchBounded, parseJsonBytes } from '../../core/transport/index.js';
5
5
  import { isRecord } from '../../core/types.js';
@@ -8,7 +8,7 @@ const ENDPOINT = 'https://track.4px.com/track/v2/front/listTrackV3';
8
8
  export function normalizeFourPxNumber(raw) {
9
9
  const number = raw.toUpperCase().replace(/[\s.-]/g, '');
10
10
  if (!/^[A-Z0-9]{8,40}$/.test(number))
11
- throw new TypeError('4PX requires an alphanumeric parcel reference');
11
+ throw new InvalidInputError('4PX', '4PX requires an alphanumeric parcel reference');
12
12
  return number;
13
13
  }
14
14
  function eventTime(raw) {
@@ -7,7 +7,7 @@
7
7
  * its result from an allowlist of status, timeline and operational-location
8
8
  * fields, so none of that reaches the result or the logs.
9
9
  */
10
- import type { AdapterFactory } from '../../core/adapter/index.js';
10
+ import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
11
11
  import { NotFoundError } from '../../core/errors/index.js';
12
12
  import type { CarrierResult } from '../../core/result/index.js';
13
13
  export { classifyStatus } from './status.js';
@@ -27,11 +27,12 @@ export interface GeodisTrackerOptions {
27
27
  timeoutMs?: number;
28
28
  /** Test seam; production uses the global fetch. */
29
29
  fetcher?: typeof fetch;
30
+ userAgent?: string;
30
31
  }
31
32
  export declare class GeodisTracker {
32
33
  #private;
33
34
  readonly timeoutMs: number;
34
35
  constructor(options?: GeodisTrackerOptions);
35
- fetch(rawTrackingNumber: string): Promise<CarrierResult>;
36
+ fetch(rawTrackingNumber: string, context?: TrackingContext): Promise<CarrierResult>;
36
37
  }
37
38
  export declare const adapter: AdapterFactory;
@@ -8,8 +8,9 @@
8
8
  * fields, so none of that reaches the result or the logs.
9
9
  */
10
10
  import { createHash } from 'node:crypto';
11
- import { IndeterminateError, NotFoundError, SchemaError } from '../../core/errors/index.js';
12
- import { clean, 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 { clean, fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
13
14
  import { isRecord } from '../../core/types.js';
14
15
  import { classifyStatus, comparableText, includesAny } from './status.js';
15
16
  export { classifyStatus } from './status.js';
@@ -152,7 +153,7 @@ function isNotFoundCode(value) {
152
153
  export function normalizeGeodisTrackingNumber(raw) {
153
154
  const trackingNumber = raw.trim().toLocaleUpperCase('en-US');
154
155
  if (!/^1G[A-Z0-9]{10}$/.test(trackingNumber)) {
155
- throw new TypeError('GEODIS tracking numbers must start with 1G and contain 12 letters and digits');
156
+ throw new InvalidInputError(PROVIDER, 'GEODIS tracking numbers must start with 1G and contain 12 letters and digits');
156
157
  }
157
158
  return trackingNumber;
158
159
  }
@@ -233,32 +234,36 @@ export function parseGeodisTrackingResponse(payload, rawTrackingNumber) {
233
234
  export class GeodisTracker {
234
235
  timeoutMs;
235
236
  #fetcher;
237
+ #userAgent;
236
238
  constructor(options = {}) {
237
239
  this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
238
240
  if (!Number.isFinite(this.timeoutMs) || this.timeoutMs <= 0) {
239
241
  throw new TypeError('GEODIS timeout must be positive');
240
242
  }
241
243
  this.#fetcher = options.fetcher;
244
+ this.#userAgent = userAgentOf(options.userAgent);
242
245
  }
243
- async fetch(rawTrackingNumber) {
246
+ async fetch(rawTrackingNumber, context = {}) {
244
247
  const trackingNumber = normalizeGeodisTrackingNumber(rawTrackingNumber);
248
+ const budget = lookupBudget(context, this.timeoutMs);
245
249
  const body = geodisRequestBody(trackingNumber);
246
250
  const timestamp = Date.now();
247
251
  const { bytes } = await fetchBounded(geodisTrackingUrl(), {
248
252
  method: 'POST',
253
+ signal: budget.signal,
249
254
  headers: {
250
255
  Accept: 'application/json',
251
256
  'Accept-Language': 'fr-FR,fr;q=0.9',
252
257
  'Content-Type': 'application/json',
253
258
  Origin: 'https://espace-client.geodis.com',
254
259
  Referer: TRACKING_PAGE,
255
- 'User-Agent': 'Mozilla/5.0 (compatible; SwissDeliveryTracker/1.0)',
260
+ 'User-Agent': this.#userAgent,
256
261
  'X-GEODIS-Service': geodisServiceHeader(trackingNumber, timestamp),
257
262
  },
258
263
  body,
259
264
  }, {
260
265
  provider: 'GEODIS tracking',
261
- timeoutMs: this.timeoutMs,
266
+ timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
262
267
  maxBytes: MAX_RESPONSE_BYTES,
263
268
  fetcher: this.#fetcher,
264
269
  });
@@ -266,10 +271,10 @@ export class GeodisTracker {
266
271
  }
267
272
  }
268
273
  export const adapter = (environment) => {
269
- const tracker = new GeodisTracker({ fetcher: environment.fetcher });
274
+ const tracker = new GeodisTracker({ fetcher: environment.fetcher, userAgent: environment.userAgent });
270
275
  return {
271
276
  id: 'geodis',
272
277
  steps: ['direct'],
273
- track: (input) => tracker.fetch(input.number),
278
+ track: (input, context) => tracker.fetch(input.number, context),
274
279
  };
275
280
  };
@@ -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 { NotFoundError } from '../../core/errors/index.js';
3
3
  import type { CarrierResult } from '../../core/result/index.js';
4
4
  import { type JsonObject } from '../../core/types.js';
@@ -8,6 +8,7 @@ export interface GLSSwitzerlandOptions {
8
8
  now?: () => number;
9
9
  /** Test seam; production uses the global fetch. */
10
10
  fetcher?: typeof fetch;
11
+ userAgent?: string;
11
12
  }
12
13
  /** Kept as a named class: the German folder narrows on it with `instanceof`. */
13
14
  export declare class GLSSwitzerlandTrackingError extends NotFoundError {
@@ -25,13 +26,13 @@ export declare class GLSSwitzerlandTracker {
25
26
  readonly timeoutMs: number;
26
27
  readonly now: () => number;
27
28
  constructor(options?: number | GLSSwitzerlandOptions);
28
- fetch(rawTrackingNumber: string, rawPostcode?: string): Promise<CarrierResult>;
29
+ fetch(rawTrackingNumber: string, rawPostcode?: string, context?: TrackingContext): Promise<CarrierResult>;
29
30
  /**
30
31
  * Whether GLS knows a number, from the overview alone: no postcode is sent.
31
32
  * Only a 404 whose body carries GLS's `lastError: E000` is a not-found here;
32
33
  * a 400 or 403 can be a challenge, so it stays a failure.
33
34
  */
34
- recognizes(rawTrackingNumber: string): Promise<boolean>;
35
+ recognizes(rawTrackingNumber: string, context?: TrackingContext): Promise<boolean>;
35
36
  private request;
36
37
  }
37
38
  /** Keep the verified overview's delivery owner when the postcode detail omits it. */
@@ -1,7 +1,8 @@
1
1
  import { load } from 'cheerio';
2
2
  import { DateTime } from 'luxon';
3
- import { InputRequiredError, NotFoundError, SchemaError, UpstreamHttpError } from '../../core/errors/index.js';
4
- import { cleanScalar, fetchBounded, parseJsonBytes } from '../../core/transport/index.js';
3
+ import { lookupBudget } from '../../core/adapter/index.js';
4
+ import { InputRequiredError, InvalidInputError, NotFoundError, SchemaError, UpstreamHttpError } from '../../core/errors/index.js';
5
+ import { cleanScalar, fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
5
6
  import { isRecord } from '../../core/types.js';
6
7
  import { classifyDescription, GLS_STATUSES, statusCode } from './status.js';
7
8
  // Protocol provenance (inspected 2026-08-30):
@@ -44,7 +45,7 @@ function isReturnParcel(parcel) {
44
45
  export function normalizeGLSSwitzerlandTrackingNumber(raw) {
45
46
  const value = raw.toLocaleUpperCase('en-US').replace(/[\s.-]/g, '');
46
47
  if (!/^(?:(?=[A-Z0-9]{8}$)(?=.*[A-Z])(?=.*\d)[A-Z0-9]{8}|\d{11,14})$/.test(value)) {
47
- throw new InputRequiredError('GLS', 'an 8-character Track ID or an 11-to-14-digit parcel number', 'GLS tracking requires an 8-character Track ID or an 11-to-14-digit parcel number');
48
+ throw new InvalidInputError('GLS', 'GLS tracking requires an 8-character Track ID or an 11-to-14-digit parcel number');
48
49
  }
49
50
  return value;
50
51
  }
@@ -302,22 +303,23 @@ export function parseGLSSwitzerlandTrackingResponse(payload, rawTrackingNumber)
302
303
  function ownerCode(parcel) {
303
304
  return records(parcel.owners).reduce((found, owner) => (found || (statusCode(owner.type) === 'REQUEST' ? text(owner.code, 32) : '')), '');
304
305
  }
305
- function pageHeaders() {
306
+ function pageHeaders(userAgent) {
306
307
  return {
307
308
  Accept: 'application/json',
308
309
  'Accept-Language': 'en-CH,en;q=0.9',
309
310
  Origin: 'https://gls-group.eu',
310
311
  Referer: `${TRACKING_PAGE}/`,
311
- 'User-Agent': 'Mozilla/5.0 (compatible; DeliveryTracker/1.0)',
312
+ 'User-Agent': userAgent,
312
313
  };
313
314
  }
314
315
  export class GLSSwitzerlandTracker {
315
316
  timeoutMs;
316
317
  now;
317
318
  #fetcher;
319
+ #userAgent;
318
320
  constructor(options = {}) {
319
- const { timeoutMs = DEFAULT_TIMEOUT_MS, now = Date.now, fetcher } = typeof options === 'number'
320
- ? { timeoutMs: options, now: Date.now, fetcher: undefined }
321
+ const { timeoutMs = DEFAULT_TIMEOUT_MS, now = Date.now, fetcher, userAgent } = typeof options === 'number'
322
+ ? { timeoutMs: options, now: Date.now, fetcher: undefined, userAgent: undefined }
321
323
  : options;
322
324
  if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
323
325
  throw new TypeError('GLS Switzerland timeout must be positive');
@@ -325,10 +327,13 @@ export class GLSSwitzerlandTracker {
325
327
  this.timeoutMs = timeoutMs;
326
328
  this.now = now;
327
329
  this.#fetcher = fetcher;
330
+ this.#userAgent = userAgentOf(userAgent);
328
331
  }
329
- async fetch(rawTrackingNumber, rawPostcode = '') {
332
+ async fetch(rawTrackingNumber, rawPostcode = '', context = {}) {
330
333
  const trackingNumber = normalizeGLSSwitzerlandTrackingNumber(rawTrackingNumber);
331
- const overview = await this.request(glsSwitzerlandOverviewApiUrl(trackingNumber, this.now()));
334
+ // Up to two requests, each with the full request timeout.
335
+ const budget = lookupBudget(context, 2 * this.timeoutMs);
336
+ const overview = await this.request(glsSwitzerlandOverviewApiUrl(trackingNumber, this.now()), budget);
332
337
  const parcel = selectGLSParcel(overview, trackingNumber);
333
338
  if (!rawPostcode.trim())
334
339
  return parseGLSSwitzerlandTrackingResponse(overview, trackingNumber);
@@ -336,7 +341,7 @@ export class GLSSwitzerlandTracker {
336
341
  if (!/^\d{11,14}$/.test(parcelNumber)) {
337
342
  throw new SchemaError(PROVIDER, 'GLS did not return a numeric parcel number');
338
343
  }
339
- const detail = await this.request(glsSwitzerlandDetailApiUrl(parcelNumber, rawPostcode, this.now(), ownerCode(parcel)));
344
+ const detail = await this.request(glsSwitzerlandDetailApiUrl(parcelNumber, rawPostcode, this.now(), ownerCode(parcel)), budget);
340
345
  const result = parseGLSSwitzerlandTrackingResponse(detail, parcelNumber);
341
346
  const overviewResult = parseGLSSwitzerlandTrackingResponse(overview, trackingNumber);
342
347
  return { ...result, ...glsDeliveryReference(overviewResult) };
@@ -346,13 +351,15 @@ export class GLSSwitzerlandTracker {
346
351
  * Only a 404 whose body carries GLS's `lastError: E000` is a not-found here;
347
352
  * a 400 or 403 can be a challenge, so it stays a failure.
348
353
  */
349
- async recognizes(rawTrackingNumber) {
354
+ async recognizes(rawTrackingNumber, context = {}) {
350
355
  const trackingNumber = normalizeGLSSwitzerlandTrackingNumber(rawTrackingNumber);
356
+ const budget = lookupBudget(context, this.timeoutMs);
351
357
  const { response, bytes } = await fetchBounded(glsSwitzerlandOverviewApiUrl(trackingNumber, this.now()), {
352
- headers: pageHeaders(),
358
+ signal: budget.signal,
359
+ headers: pageHeaders(this.#userAgent),
353
360
  }, {
354
361
  provider: 'GLS Switzerland tracking',
355
- timeoutMs: this.timeoutMs,
362
+ timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
356
363
  maxBytes: MAX_RESPONSE_BYTES,
357
364
  allowHttpError: true,
358
365
  ...(this.#fetcher ? { fetcher: this.#fetcher } : {}),
@@ -373,12 +380,13 @@ export class GLSSwitzerlandTracker {
373
380
  throw error;
374
381
  }
375
382
  }
376
- async request(url) {
383
+ async request(url, budget) {
377
384
  const { response, bytes } = await fetchBounded(url, {
378
- headers: pageHeaders(),
385
+ signal: budget.signal,
386
+ headers: pageHeaders(this.#userAgent),
379
387
  }, {
380
388
  provider: 'GLS Switzerland tracking',
381
- timeoutMs: this.timeoutMs,
389
+ timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
382
390
  maxBytes: MAX_RESPONSE_BYTES,
383
391
  allowHttpError: true,
384
392
  ...(this.#fetcher ? { fetcher: this.#fetcher } : {}),
@@ -395,11 +403,11 @@ export function glsDeliveryReference(result) {
395
403
  return result.delivery_carrier ? { delivery_carrier: result.delivery_carrier, delivery_tracking_number: result.delivery_tracking_number } : {};
396
404
  }
397
405
  export const adapter = (environment) => {
398
- const tracker = new GLSSwitzerlandTracker({ fetcher: environment.fetcher });
406
+ const tracker = new GLSSwitzerlandTracker({ fetcher: environment.fetcher, userAgent: environment.userAgent });
399
407
  return {
400
408
  id: 'gls-ch',
401
409
  steps: ['direct'],
402
- track: (input) => tracker.fetch(input.number, input.postcode ?? ''),
403
- recognize: async (number) => ({ known: await tracker.recognizes(number) }),
410
+ track: (input, context) => tracker.fetch(input.number, input.postcode ?? '', context),
411
+ recognize: async (number, context) => ({ known: await tracker.recognizes(number, context) }),
404
412
  };
405
413
  };
@@ -21,7 +21,7 @@
21
21
  "adapter": "gls-ch",
22
22
  "requirements": [
23
23
  {
24
- "field": "dpdPostcode",
24
+ "field": "postcode",
25
25
  "validator": "swissPostcode",
26
26
  "label": "Delivery postcode",
27
27
  "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 { NotFoundError } from '../../core/errors/index.js';
3
3
  import type { CarrierResult } from '../../core/result/index.js';
4
4
  export interface GLSGermanyOptions {
@@ -16,13 +16,13 @@ export declare class GLSGermanyTracker {
16
16
  readonly timeoutMs: number;
17
17
  readonly now: () => number;
18
18
  constructor(options?: number | GLSGermanyOptions);
19
- fetch(rawTrackingNumber: string, rawPostcode: string): Promise<CarrierResult>;
19
+ fetch(rawTrackingNumber: string, rawPostcode: string, context?: TrackingContext): Promise<CarrierResult>;
20
20
  /**
21
21
  * Whether GLS Germany itself recognizes an ambiguous numeric shape. Used by
22
22
  * the host's carrier-detection route, which promotes a number to `gls-de`
23
23
  * only after this returns true; a provider failure must stay a failure.
24
24
  */
25
- recognizes(rawTrackingNumber: string): Promise<boolean>;
25
+ recognizes(rawTrackingNumber: string, context?: TrackingContext): Promise<boolean>;
26
26
  private request;
27
27
  }
28
28
  export declare const adapter: AdapterFactory;
@@ -1,3 +1,4 @@
1
+ import { lookupBudget } from '../../core/adapter/index.js';
1
2
  import { NotFoundError, SchemaError, UpstreamHttpError } from '../../core/errors/index.js';
2
3
  import { fetchBounded, parseJsonBytes } from '../../core/transport/index.js';
3
4
  import { isRecord } from '../../core/types.js';
@@ -28,10 +29,12 @@ export class GLSGermanyTracker {
28
29
  this.now = now;
29
30
  this.#fetcher = fetcher;
30
31
  }
31
- async fetch(rawTrackingNumber, rawPostcode) {
32
+ async fetch(rawTrackingNumber, rawPostcode, context = {}) {
32
33
  const number = normalizeGLSSwitzerlandTrackingNumber(rawTrackingNumber);
33
34
  const postcode = normalizeGLSSwitzerlandPostcode(rawPostcode, '4,5');
34
- const overview = await this.request(glsSwitzerlandOverviewApiUrl(number, this.now()));
35
+ // Two requests, each with the full request timeout.
36
+ const budget = lookupBudget(context, 2 * this.timeoutMs);
37
+ const overview = await this.request(glsSwitzerlandOverviewApiUrl(number, this.now()), budget);
35
38
  // Validate the overview identity before resolving a Track ID to a parcel number.
36
39
  try {
37
40
  parseGLSSwitzerlandTrackingResponse(overview, number);
@@ -50,7 +53,7 @@ export class GLSGermanyTracker {
50
53
  }
51
54
  const owners = Array.isArray(parcel.owners) ? parcel.owners.filter(isRecord) : [];
52
55
  const owner = owners.find((row) => row.type === 'REQUEST');
53
- const detail = await this.request(glsSwitzerlandDetailApiUrl(String(parcel.tuNo), postcode, this.now(), String(owner?.code ?? ''), '4,5'));
56
+ const detail = await this.request(glsSwitzerlandDetailApiUrl(String(parcel.tuNo), postcode, this.now(), String(owner?.code ?? ''), '4,5'), budget);
54
57
  try {
55
58
  const result = parseGLSSwitzerlandTrackingResponse(detail, String(parcel.tuNo));
56
59
  // Both services use CET/CEST; expose the regional timezone to clients.
@@ -67,11 +70,12 @@ export class GLSGermanyTracker {
67
70
  * the host's carrier-detection route, which promotes a number to `gls-de`
68
71
  * only after this returns true; a provider failure must stay a failure.
69
72
  */
70
- async recognizes(rawTrackingNumber) {
73
+ async recognizes(rawTrackingNumber, context = {}) {
71
74
  const number = normalizeGLSSwitzerlandTrackingNumber(rawTrackingNumber);
75
+ const budget = lookupBudget(context, this.timeoutMs);
72
76
  try {
73
77
  const url = glsSwitzerlandOverviewApiUrl(number, this.now()).replace('/GROUP/en/', '/DE/en/');
74
- const overview = await this.request(url);
78
+ const overview = await this.request(url, budget);
75
79
  const result = parseGLSSwitzerlandTrackingResponse(overview, number);
76
80
  return result.status !== 'unknown';
77
81
  }
@@ -81,12 +85,13 @@ export class GLSGermanyTracker {
81
85
  throw error;
82
86
  }
83
87
  }
84
- async request(url) {
88
+ async request(url, budget) {
85
89
  const { response, bytes } = await fetchBounded(url, {
90
+ signal: budget.signal,
86
91
  headers: { Accept: 'application/json', Referer: 'https://gls-group.eu/EU/en/parcel-tracking' },
87
92
  }, {
88
93
  provider: PROVIDER,
89
- timeoutMs: this.timeoutMs,
94
+ timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
90
95
  maxBytes: 1_000_000,
91
96
  allowHttpError: true,
92
97
  ...(this.#fetcher ? { fetcher: this.#fetcher } : {}),
@@ -107,7 +112,7 @@ export const adapter = (environment) => {
107
112
  return {
108
113
  id: 'gls-de',
109
114
  steps: ['direct'],
110
- track: (input) => tracker.fetch(input.number, input.postcode ?? ''),
111
- recognize: async (number) => ({ known: await tracker.recognizes(number) }),
115
+ track: (input, context) => tracker.fetch(input.number, input.postcode ?? '', context),
116
+ recognize: async (number, context) => ({ known: await tracker.recognizes(number, context) }),
112
117
  };
113
118
  };
@@ -20,7 +20,7 @@
20
20
  "adapter": "gls-de",
21
21
  "requirements": [
22
22
  {
23
- "field": "dpdPostcode",
23
+ "field": "postcode",
24
24
  "validator": "swissOrFrancePostcode",
25
25
  "label": "Delivery postcode",
26
26
  "type": "text",
@@ -7,7 +7,7 @@
7
7
  * operational-location fields, so recipient names, street addresses, contacts,
8
8
  * signatures and delivery instructions never leave this module.
9
9
  */
10
- import type { AdapterFactory } from '../../core/adapter/index.js';
10
+ import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
11
11
  import type { CarrierResult } from '../../core/result/index.js';
12
12
  export { glsFranceStatus } from './status.js';
13
13
  export declare function normalizeGLSFranceTrackingNumber(raw: string): string;
@@ -18,12 +18,13 @@ export interface GLSFranceTrackerOptions {
18
18
  timeoutMs?: number;
19
19
  /** Test seam; production uses the global fetch. */
20
20
  fetcher?: typeof fetch;
21
+ userAgent?: string;
21
22
  }
22
23
  export declare class GLSFranceTracker {
23
24
  #private;
24
25
  readonly timeoutMs: number;
25
26
  constructor(options?: GLSFranceTrackerOptions);
26
- fetch(trackingNumber: string): Promise<CarrierResult>;
27
+ fetch(trackingNumber: string, context?: TrackingContext): Promise<CarrierResult>;
27
28
  private lookup;
28
29
  }
29
30
  export declare const adapter: AdapterFactory;
@@ -8,10 +8,11 @@
8
8
  * signatures and delivery instructions never leave this module.
9
9
  */
10
10
  import { DateTime } from 'luxon';
11
+ import { lookupBudget } from '../../core/adapter/index.js';
11
12
  import { isValidGlsParcelNumber } from '../../core/detection/index.js';
12
- import { SchemaError, UpstreamHttpError } from '../../core/errors/index.js';
13
+ import { InvalidInputError, SchemaError, UpstreamHttpError } from '../../core/errors/index.js';
13
14
  import { EXPLICIT_OFFSET_PATTERN } from '../../core/time/index.js';
14
- import { cleanScalar, fetchBounded, parseJsonBytes } from '../../core/transport/index.js';
15
+ import { cleanScalar, fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
15
16
  import { isRecord } from '../../core/types.js';
16
17
  import { FAILED_DELAYED_DELIVERY, glsFranceStatusCode, glsFranceStatusMetadata, } from './status.js';
17
18
  export { glsFranceStatus } from './status.js';
@@ -72,7 +73,7 @@ function printedNumber(raw) {
72
73
  export function normalizeGLSFranceTrackingNumber(raw) {
73
74
  const value = parcelNumber(printedNumber(raw));
74
75
  if (!value) {
75
- throw new TypeError('GLS France tracking numbers must contain 8 letters or digits, or 11 digits (12 with a valid check digit)');
76
+ throw new InvalidInputError(PROVIDER, 'GLS France tracking numbers must contain 8 letters or digits, or 11 digits (12 with a valid check digit)');
76
77
  }
77
78
  return value;
78
79
  }
@@ -171,39 +172,43 @@ export function parseGLSFranceTrackingResponse(payload, trackingNumber) {
171
172
  export class GLSFranceTracker {
172
173
  timeoutMs;
173
174
  #fetcher;
175
+ #userAgent;
174
176
  constructor(options = {}) {
175
177
  this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
176
178
  if (!Number.isFinite(this.timeoutMs) || this.timeoutMs <= 0) {
177
179
  throw new TypeError('GLS France timeout must be positive');
178
180
  }
179
181
  this.#fetcher = options.fetcher;
182
+ this.#userAgent = userAgentOf(options.userAgent);
180
183
  }
181
- async fetch(trackingNumber) {
184
+ async fetch(trackingNumber, context = {}) {
182
185
  const normalized = normalizeGLSFranceTrackingNumber(trackingNumber);
183
186
  const printed = printedNumber(trackingNumber);
187
+ const budget = lookupBudget(context, this.timeoutMs);
184
188
  try {
185
- return await this.lookup(normalized, normalized);
189
+ return await this.lookup(normalized, normalized, budget);
186
190
  }
187
191
  catch (error) {
188
192
  // The 11-digit parcel number is what GLS keys a parcel by. Should the
189
193
  // French backend only know the number as printed, ask for it once too.
190
194
  if (printed === normalized || !(error instanceof UpstreamHttpError) || error.status !== 404)
191
195
  throw error;
192
- return await this.lookup(printed, normalized);
196
+ return await this.lookup(printed, normalized, budget);
193
197
  }
194
198
  }
195
- async lookup(code, normalized) {
199
+ async lookup(code, normalized, budget) {
196
200
  const { bytes } = await fetchBounded(`${TRACKING_API}/${encodeURIComponent(code)}`, {
201
+ signal: budget.signal,
197
202
  headers: {
198
203
  Accept: 'application/json',
199
204
  'Accept-Language': 'fr-FR,fr;q=0.9',
200
205
  Origin: 'https://moncolis.gls-france.com',
201
206
  Referer: `${TRACKING_PAGE}/`,
202
- 'User-Agent': 'Mozilla/5.0 (compatible; SwissDeliveryTracker/1.0)',
207
+ 'User-Agent': this.#userAgent,
203
208
  },
204
209
  }, {
205
210
  provider: 'GLS France tracking',
206
- timeoutMs: this.timeoutMs,
211
+ timeoutMs: Math.min(this.timeoutMs, budget.remainingMs()),
207
212
  maxBytes: MAX_RESPONSE_BYTES,
208
213
  fetcher: this.#fetcher,
209
214
  });
@@ -211,10 +216,10 @@ export class GLSFranceTracker {
211
216
  }
212
217
  }
213
218
  export const adapter = (environment) => {
214
- const tracker = new GLSFranceTracker({ fetcher: environment.fetcher });
219
+ const tracker = new GLSFranceTracker({ fetcher: environment.fetcher, userAgent: environment.userAgent });
215
220
  return {
216
221
  id: 'gls-fr',
217
222
  steps: ['direct'],
218
- track: (input) => tracker.fetch(input.number),
223
+ track: (input, context) => tracker.fetch(input.number, context),
219
224
  };
220
225
  };
@@ -1,6 +1,6 @@
1
1
  import { DateTime, IANAZone } from 'luxon';
2
2
  import { normalizeTrackingNumber } from '../../core/detection/index.js';
3
- import { IndeterminateError, NotFoundError, SchemaError } from '../../core/errors/index.js';
3
+ import { IndeterminateError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
4
4
  import { explicitOffsetTime } from '../../core/time/index.js';
5
5
  import { clean } from '../../core/transport/index.js';
6
6
  import { isRecord } from '../../core/types.js';
@@ -15,7 +15,7 @@ const SUPPORT_LINE = new RegExp(String.raw `\s*(?:For delivery issues (?:&|and)
15
15
  export function normalizeGofoNumber(raw) {
16
16
  const number = normalizeTrackingNumber(raw);
17
17
  if (!/^GFUS\d{14}$/.test(number))
18
- throw new TypeError('GOFO US requires a GFUS parcel reference');
18
+ throw new InvalidInputError('GOFO', 'GOFO US requires a GFUS parcel reference');
19
19
  return number;
20
20
  }
21
21
  function scanClock(value, scanZone) {
@@ -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
  interface HeppnerCredential {
4
4
  trackingNumber: string;
@@ -16,12 +16,14 @@ export interface HeppnerTrackerOptions {
16
16
  timeoutMs?: number;
17
17
  /** Test seam; production uses the global fetch. */
18
18
  fetcher?: typeof fetch;
19
+ userAgent?: string;
19
20
  }
20
21
  export declare class HeppnerTracker {
21
22
  readonly timeoutMs: number;
22
23
  readonly fetcher?: typeof fetch;
24
+ private readonly userAgent;
23
25
  constructor(options?: HeppnerTrackerOptions);
24
- fetch(rawTrackingNumber: string, rawPostcode: string): Promise<CarrierResult>;
26
+ fetch(rawTrackingNumber: string, rawPostcode: string, context?: TrackingContext): Promise<CarrierResult>;
25
27
  }
26
28
  export declare const adapter: AdapterFactory;
27
29
  export {};