shipflow 0.3.1 → 0.3.2

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.
@@ -16,6 +16,16 @@ export declare class SMSAExpressAdapter extends BaseCarrierAdapter {
16
16
  private http;
17
17
  constructor(config: SMSAExpressConfig);
18
18
  protected getBaseUrl(): string;
19
+ /**
20
+ * Ensure the input carries a `reference` so the OrderNumber the shipment is
21
+ * booked under is the exact value surfaced back on `Shipment.reference` (which
22
+ * the caller needs for {@link trackByReference}). When no reference is
23
+ * supplied we generate one here via the shared {@link generateOrderNumber} and
24
+ * pass this prepared input to BOTH the request mapper and the response mapper,
25
+ * so the request OrderNumber and the response reference always agree (otherwise
26
+ * each would call `Date.now()` independently and disagree).
27
+ */
28
+ private withOrderNumber;
19
29
  protected executeCreateShipment(input: CreateShipmentInput): Promise<Shipment>;
20
30
  private createC2BShipment;
21
31
  /**
@@ -24,10 +34,17 @@ export declare class SMSAExpressAdapter extends BaseCarrierAdapter {
24
34
  * **Important:** SMSA only supports cancellation for C2B/reverse-pickup
25
35
  * shipments, via the C2B endpoint. B2C shipments cannot be cancelled through
26
36
  * the SMSA API: the endpoint responds without a cancellation confirmation,
27
- * which is surfaced as an {@link APIError} rather than a misleading `false`.
28
- * That lets callers tell "the carrier could not cancel" apart from a genuine
29
- * refusal of a cancellable shipment and fall back accordingly. Consistent with
30
- * the other adapters, this method either returns `true` or throws.
37
+ * which is surfaced as an {@link APIError} (with `code: "CANCELLATION_UNCONFIRMED"`)
38
+ * rather than a misleading `false`. That `code` lets callers distinguish this
39
+ * "carrier did not confirm" case from a genuine request failure (network/4xx/5xx)
40
+ * and fall back accordingly. Consistent with the other adapters, this method
41
+ * either returns `true` or throws.
42
+ *
43
+ * Success is detected with a negation-aware check: a cancel token must be
44
+ * present AND no negation token may be. A bare `includes("cancelled")` would
45
+ * wrongly succeed on negatives like "Shipment cannot be cancelled",
46
+ * "not cancelled" or "cancellation failed". Note "already cancelled" carries
47
+ * no negation token, so it correctly reports success (idempotent cancel).
31
48
  */
32
49
  cancelShipment(trackingNumber: string): Promise<boolean>;
33
50
  track(trackingNumber: string): Promise<TrackingResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/carriers/smsaexpress/adapter.ts"],"names":[],"mappings":"AACA;;;GAGG;AAIH,OAAO,KAAK,EACV,aAAa,EACb,IAAI,EACJ,mBAAmB,EACnB,QAAQ,EACR,QAAQ,EACR,cAAc,EACd,aAAa,EACb,YAAY,EACb,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAY7C,OAAO,KAAK,EAGV,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,EAEtB,wBAAwB,EAEzB,MAAM,SAAS,CAAC;AAQjB,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,WAAW,EAAE;QACX,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,qBAAa,kBAAmB,SAAQ,kBAAkB;IACxD,QAAQ,CAAC,IAAI,iBAAiB;IAC9B,QAAQ,CAAC,kBAAkB,WASzB;IAEF,OAAO,CAAC,IAAI,CAAa;gBAEb,MAAM,EAAE,iBAAiB;IAWrC,SAAS,CAAC,UAAU,IAAI,MAAM;cAUd,qBAAqB,CACnC,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,QAAQ,CAAC;YAkBN,iBAAiB;IAY/B;;;;;;;;;;OAUG;IACG,cAAc,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAyBxD,KAAK,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAQtD,aAAa,CAAC,eAAe,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAUnE,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAY5D,QAAQ,CACZ,cAAc,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAC9B,OAAO,CAAC,MAAM,CAAC;IAsCZ,SAAS,CAAC,WAAW,SAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAQ9C,mBAAmB,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IAY1C,kBAAkB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAcjE,WAAW,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC;IAI7D,oBAAoB,CACxB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,wBAAwB,CAAC;IAM9B,aAAa,CACjB,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,yBAAyB,CAAC;IAWrC,YAAY,CACV,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACrC,MAAM,CAAC,EAAE,aAAa,CAAC;KACxB,GACA,YAAY;IAIf;;;;;;OAMG;IACH,iBAAiB,CACf,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACrC,MAAM,CAAC,EAAE,aAAa,CAAC;KACxB,GACA,YAAY,EAAE;CAGlB"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/carriers/smsaexpress/adapter.ts"],"names":[],"mappings":"AACA;;;GAGG;AAIH,OAAO,KAAK,EACV,aAAa,EACb,IAAI,EACJ,mBAAmB,EACnB,QAAQ,EACR,QAAQ,EACR,cAAc,EACd,aAAa,EACb,YAAY,EACb,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAa7C,OAAO,KAAK,EAGV,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,EAEtB,wBAAwB,EAEzB,MAAM,SAAS,CAAC;AAQjB,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,WAAW,EAAE;QACX,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,qBAAa,kBAAmB,SAAQ,kBAAkB;IACxD,QAAQ,CAAC,IAAI,iBAAiB;IAC9B,QAAQ,CAAC,kBAAkB,WASzB;IAEF,OAAO,CAAC,IAAI,CAAa;gBAEb,MAAM,EAAE,iBAAiB;IAWrC,SAAS,CAAC,UAAU,IAAI,MAAM;IAU9B;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe;cAOP,qBAAqB,CACnC,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,QAAQ,CAAC;YAmBN,iBAAiB;IAY/B;;;;;;;;;;;;;;;;;OAiBG;IACG,cAAc,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA0CxD,KAAK,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAQtD,aAAa,CAAC,eAAe,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAUnE,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAY5D,QAAQ,CACZ,cAAc,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAC9B,OAAO,CAAC,MAAM,CAAC;IA2CZ,SAAS,CAAC,WAAW,SAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAkB9C,mBAAmB,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IAmB1C,kBAAkB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAejE,WAAW,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC;IAI7D,oBAAoB,CACxB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,wBAAwB,CAAC;IAM9B,aAAa,CACjB,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,yBAAyB,CAAC;IAWrC,YAAY,CACV,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACrC,MAAM,CAAC,EAAE,aAAa,CAAC;KACxB,GACA,YAAY;IAIf;;;;;;OAMG;IACH,iBAAiB,CACf,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACrC,MAAM,CAAC,EAAE,aAAa,CAAC;KACxB,GACA,YAAY,EAAE;CAGlB"}
@@ -2,9 +2,10 @@ import {
2
2
  APIError,
3
3
  BaseCarrierAdapter,
4
4
  HttpClient,
5
+ RateLimitError,
5
6
  ValidationError,
6
7
  WebhookVerificationError
7
- } from "../../index-qnxj8bct.js";
8
+ } from "../../index-8cpdcbsf.js";
8
9
 
9
10
  // src/carriers/smsaexpress/services.ts
10
11
  var SMSAService = {
@@ -37,7 +38,13 @@ var SMSAStatusCodes = {
37
38
 
38
39
  // src/carriers/smsaexpress/mappers.ts
39
40
  function mapSMSAStatus(scanType) {
40
- return SMSAStatusCodes[scanType] ?? "in_transit";
41
+ return SMSAStatusCodes[scanType] ?? "unknown";
42
+ }
43
+ function scanDate(scan) {
44
+ return new Date(scan.ScanTimeZone ? `${scan.ScanDateTime}${scan.ScanTimeZone}` : scan.ScanDateTime);
45
+ }
46
+ function scanTimestampMs(scan) {
47
+ return scanDate(scan).getTime();
41
48
  }
42
49
  function deriveStatusFromScans(scans, isDelivered) {
43
50
  if (isDelivered) {
@@ -47,7 +54,7 @@ function deriveStatusFromScans(scans, isDelivered) {
47
54
  if (safeScans.length === 0) {
48
55
  return { status: "unknown", statusLabel: "Unknown" };
49
56
  }
50
- const sorted = [...safeScans].sort((a, b) => new Date(b.ScanDateTime).getTime() - new Date(a.ScanDateTime).getTime());
57
+ const sorted = [...safeScans].sort((a, b) => scanTimestampMs(b) - scanTimestampMs(a));
51
58
  const latest = sorted[0];
52
59
  return {
53
60
  status: mapSMSAStatus(latest.ScanType),
@@ -57,10 +64,19 @@ function deriveStatusFromScans(scans, isDelivered) {
57
64
  function roundWeight(weight) {
58
65
  return Math.round(weight * 1000) / 1000;
59
66
  }
67
+ function sanitizePhone(phone) {
68
+ return phone.replace(/\D/g, "");
69
+ }
70
+ function generateOrderNumber() {
71
+ return `ORD-${Date.now()}`;
72
+ }
73
+ function smsaShipDate() {
74
+ return new Date(Date.now() + 3 * 60 * 60 * 1000).toISOString().slice(0, 19);
75
+ }
60
76
  function mapAddress(addr, opts) {
61
77
  const result = {
62
78
  ContactName: addr.name,
63
- ContactPhoneNumber: addr.phone,
79
+ ContactPhoneNumber: sanitizePhone(addr.phone),
64
80
  Country: addr.countryCode,
65
81
  City: addr.city,
66
82
  AddressLine1: addr.line1,
@@ -88,12 +104,12 @@ function mapCreateB2CRequest(input) {
88
104
  return {
89
105
  ConsigneeAddress: consigneeAddress,
90
106
  ShipperAddress: mapAddress(input.shipper),
91
- OrderNumber: input.reference ?? `ORD-${Date.now()}`,
107
+ OrderNumber: input.reference ?? generateOrderNumber(),
92
108
  CODAmount: input.cod?.enabled ? input.cod.amount : 0,
93
109
  DeclaredValue: input.declaredValue?.amount ?? 0,
94
110
  ContentDescription: input.parcels[0]?.description ?? "Shipment contents",
95
111
  Parcels: totalPieces,
96
- ShipDate: new Date().toISOString().slice(0, 19),
112
+ ShipDate: smsaShipDate(),
97
113
  ShipmentCurrency: (input.cod?.enabled ? input.cod.currency : undefined) ?? input.declaredValue?.currency ?? input.cod?.currency ?? "SAR",
98
114
  Weight: roundWeight(totalWeight),
99
115
  WeightUnit: "KG",
@@ -110,11 +126,11 @@ function mapCreateC2BRequest(input) {
110
126
  return {
111
127
  PickupAddress: mapAddress(input.consignee, { includeShortCode: true }),
112
128
  ReturnToAddress: mapAddress(input.shipper),
113
- OrderNumber: input.reference ?? `ORD-${Date.now()}`,
129
+ OrderNumber: input.reference ?? generateOrderNumber(),
114
130
  DeclaredValue: input.declaredValue?.amount ?? 0.1,
115
131
  ContentDescription: input.parcels[0]?.description ?? "Return shipment contents",
116
132
  Parcels: totalPieces,
117
- ShipDate: new Date().toISOString().slice(0, 19),
133
+ ShipDate: smsaShipDate(),
118
134
  ShipmentCurrency: (input.cod?.enabled ? input.cod.currency : undefined) ?? input.declaredValue?.currency ?? input.cod?.currency ?? "SAR",
119
135
  Weight: roundWeight(totalWeight),
120
136
  WeightUnit: "KG",
@@ -202,11 +218,11 @@ function mapCreate2WayRequest(input) {
202
218
  return {
203
219
  ConsigneeAddress: consigneeAddress,
204
220
  ShipperAddress: mapAddress(input.shipper),
205
- OrderNumber: input.reference ?? `ORD-${Date.now()}`,
221
+ OrderNumber: input.reference ?? generateOrderNumber(),
206
222
  DeclaredValue: input.declaredValue?.amount ?? 0,
207
223
  ContentDescription: input.parcels[0]?.description ?? "Shipment contents",
208
224
  Parcels: totalPieces,
209
- ShipDate: new Date().toISOString().slice(0, 19),
225
+ ShipDate: smsaShipDate(),
210
226
  ShipmentCurrency: input.declaredValue?.currency ?? "SAR",
211
227
  Weight: roundWeight(totalWeight),
212
228
  WeightUnit: "KG",
@@ -251,9 +267,9 @@ function verifyWebhookAuth(options) {
251
267
  function mapWebhookShipmentToEvent(shipment) {
252
268
  const scans = shipment.Scans ?? [];
253
269
  const { status, statusLabel } = deriveStatusFromScans(scans, shipment.isDelivered);
254
- const sorted = [...scans].sort((a, b) => new Date(b.ScanDateTime).getTime() - new Date(a.ScanDateTime).getTime());
270
+ const sorted = [...scans].sort((a, b) => scanTimestampMs(b) - scanTimestampMs(a));
255
271
  const latestScan = sorted[0];
256
- const timestamp = latestScan ? new Date(latestScan.ScanTimeZone ? `${latestScan.ScanDateTime}${latestScan.ScanTimeZone}` : latestScan.ScanDateTime) : new Date;
272
+ const timestamp = latestScan ? scanDate(latestScan) : new Date;
257
273
  return {
258
274
  carrier: "smsaexpress",
259
275
  eventType: "status_update",
@@ -266,6 +282,28 @@ function mapWebhookShipmentToEvent(shipment) {
266
282
  raw: shipment
267
283
  };
268
284
  }
285
+ function mapWebhookShipmentToEvents(shipment) {
286
+ const scans = shipment.Scans ?? [];
287
+ if (scans.length === 0) {
288
+ return [mapWebhookShipmentToEvent(shipment)];
289
+ }
290
+ const sorted = [...scans].sort((a, b) => scanTimestampMs(a) - scanTimestampMs(b));
291
+ return sorted.map((scan, index) => {
292
+ const isLatest = index === sorted.length - 1;
293
+ const status = isLatest && shipment.isDelivered ? "delivered" : mapSMSAStatus(scan.ScanType);
294
+ return {
295
+ carrier: "smsaexpress",
296
+ eventType: "status_update",
297
+ trackingNumber: shipment.AWB,
298
+ reference: shipment.Reference || undefined,
299
+ status,
300
+ statusCode: scan.ScanType,
301
+ statusLabel: scan.ScanDescription,
302
+ timestamp: scanDate(scan),
303
+ raw: shipment
304
+ };
305
+ });
306
+ }
269
307
  function validateWebhookPayload(payload) {
270
308
  if (!Array.isArray(payload)) {
271
309
  throw new ValidationError("Invalid SMSA webhook payload: expected an array of shipments", { raw: payload });
@@ -276,8 +314,8 @@ function validateWebhookPayload(payload) {
276
314
  });
277
315
  }
278
316
  for (const item of payload) {
279
- if (!item || typeof item !== "object" || !("AWB" in item) || !("Scans" in item)) {
280
- throw new ValidationError("Invalid SMSA webhook payload: shipment missing required fields (AWB, Scans)", { raw: item });
317
+ if (!item || typeof item !== "object" || !("AWB" in item)) {
318
+ throw new ValidationError("Invalid SMSA webhook payload: shipment missing required field (AWB)", { raw: item });
281
319
  }
282
320
  }
283
321
  return payload;
@@ -290,7 +328,7 @@ function parseSMSAWebhook(payload, options) {
290
328
  function parseSMSAWebhookBatch(payload, options) {
291
329
  verifyWebhookAuth(options);
292
330
  const shipments = validateWebhookPayload(payload);
293
- return shipments.map(mapWebhookShipmentToEvent);
331
+ return shipments.flatMap(mapWebhookShipmentToEvents);
294
332
  }
295
333
 
296
334
  // src/carriers/smsaexpress/adapter.ts
@@ -324,14 +362,21 @@ class SMSAExpressAdapter extends BaseCarrierAdapter {
324
362
  getBaseUrl() {
325
363
  return this.config.mode === "production" ? SMSA_PRODUCTION_URL : SMSA_SANDBOX_URL;
326
364
  }
365
+ withOrderNumber(input) {
366
+ if (input.reference) {
367
+ return input;
368
+ }
369
+ return { ...input, reference: generateOrderNumber() };
370
+ }
327
371
  async executeCreateShipment(input) {
328
- const isC2B = input.serviceType !== undefined && C2B_SERVICE_CODES.has(input.serviceType);
372
+ const prepared = this.withOrderNumber(input);
373
+ const isC2B = prepared.serviceType !== undefined && C2B_SERVICE_CODES.has(prepared.serviceType.trim().toUpperCase());
329
374
  if (isC2B) {
330
- return this.createC2BShipment(input);
375
+ return this.createC2BShipment(prepared);
331
376
  }
332
- const request = mapCreateB2CRequest(input);
377
+ const request = mapCreateB2CRequest(prepared);
333
378
  const response = await this.http.post("/api/shipment/b2c/new", request);
334
- return mapShipmentResponse(response, input);
379
+ return mapShipmentResponse(response, prepared);
335
380
  }
336
381
  async createC2BShipment(input) {
337
382
  const request = mapCreateC2BRequest(input);
@@ -343,10 +388,16 @@ class SMSAExpressAdapter extends BaseCarrierAdapter {
343
388
  if (typeof response !== "string") {
344
389
  return true;
345
390
  }
346
- if (response.toLowerCase().includes("cancelled")) {
391
+ const hasCancelToken = /cancel/i.test(response);
392
+ const hasNegation = /\b(?:not|can\s?not|can'?t|cant|unable|fail(?:ed|ure)?|invalid|reject(?:ed)?|error)\b/i.test(response);
393
+ if (hasCancelToken && !hasNegation) {
347
394
  return true;
348
395
  }
349
- throw new APIError(response.trim() || "SMSA did not confirm cancellation (B2C shipments cannot be cancelled via the SMSA API)", { carrier: "smsaexpress", raw: response });
396
+ throw new APIError(response.trim() || "SMSA did not confirm cancellation", {
397
+ carrier: "smsaexpress",
398
+ code: "CANCELLATION_UNCONFIRMED",
399
+ raw: response
400
+ });
350
401
  }
351
402
  async track(trackingNumber) {
352
403
  const response = await this.http.get(`/api/track/single/${encodeURIComponent(trackingNumber)}`);
@@ -373,7 +424,7 @@ class SMSAExpressAdapter extends BaseCarrierAdapter {
373
424
  return `data:application/pdf;base64,${waybill.awbFile}`;
374
425
  }
375
426
  } catch (error) {
376
- if (!(error instanceof APIError) || error.statusCode === 401 || error.statusCode !== undefined && error.statusCode >= 500) {
427
+ if (!(error instanceof APIError) || error instanceof RateLimitError || error.statusCode !== undefined && error.statusCode >= 500) {
377
428
  throw error;
378
429
  }
379
430
  }
@@ -385,16 +436,29 @@ class SMSAExpressAdapter extends BaseCarrierAdapter {
385
436
  }
386
437
  async getCities(countryCode = "SA") {
387
438
  const response = await this.http.get(`/api/lookup/cities/${encodeURIComponent(countryCode)}`);
439
+ if (!Array.isArray(response)) {
440
+ throw new APIError("SMSA cities lookup returned an unexpected shape", {
441
+ carrier: "smsaexpress",
442
+ raw: response
443
+ });
444
+ }
388
445
  return response.map(mapCity);
389
446
  }
390
447
  async getDropoffLocations() {
391
448
  const response = await this.http.get("/api/lookup/smsaoffices");
449
+ if (!Array.isArray(response)) {
450
+ throw new APIError("SMSA offices lookup returned an unexpected shape", {
451
+ carrier: "smsaexpress",
452
+ raw: response
453
+ });
454
+ }
392
455
  return response.map(mapOffice);
393
456
  }
394
457
  async create2WayShipment(input) {
395
- const request = mapCreate2WayRequest(input);
458
+ const prepared = this.withOrderNumber(input);
459
+ const request = mapCreate2WayRequest(prepared);
396
460
  const response = await this.http.post("/api/TwoWayShipment/new", request);
397
- return mapShipmentResponse(response, input);
461
+ return mapShipmentResponse(response, prepared);
398
462
  }
399
463
  async sendInvoice(request) {
400
464
  return this.http.post("/api/invoice", request);
@@ -421,4 +485,4 @@ export {
421
485
  SMSAExpressAdapter
422
486
  };
423
487
 
424
- //# debugId=E494CE182FFD4CD964756E2164756E21
488
+ //# debugId=AF4035876BEB4A1F64756E2164756E21
@@ -2,11 +2,11 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/carriers/smsaexpress/services.ts", "../src/carriers/smsaexpress/mappers.ts", "../src/carriers/smsaexpress/adapter.ts"],
4
4
  "sourcesContent": [
5
- "// file: src/carriers/smsaexpress/services.ts\n/**\n * SMSA Express Service Type Codes\n * Use these constants for type-safe service selection.\n */\n\nexport const SMSAService = {\n /** E-commerce delivery */\n ECOMMERCE_DELIVERY: \"EDDL\",\n /** Express delivery */\n EXPRESS_DELIVERY: \"EDEL\",\n /** C2B / Reverse pickup */\n C2B_REVERSE: \"EDCR\",\n} as const;\n\nexport type SMSAServiceType = (typeof SMSAService)[keyof typeof SMSAService];\n\n/**\n * SMSA Express Scan Type Codes → Unified ShipmentStatus mapping.\n *\n * Based on /api/track/statuslookup response.\n * Scan types not explicitly listed default to \"in_transit\".\n */\nexport const SMSAStatusCodes: Record<string, string> = {\n // Delivery\n DL: \"delivered\",\n\n // Out for delivery\n OD: \"out_for_delivery\",\n\n // Arrived at facility\n AF: \"at_warehouse\",\n\n // Hub / sorting\n HOP: \"in_transit\",\n HOR: \"in_transit\",\n HOT: \"in_transit\",\n\n // Picked up / collected\n PU: \"picked_up\",\n PKD: \"picked_up\",\n\n // Customs\n CR: \"in_transit\",\n CH: \"in_transit\",\n\n // Exceptions\n DE: \"exception\",\n DMG: \"exception\",\n MISS: \"exception\",\n\n // Cancelled / returned\n CAN: \"cancelled\",\n RTO: \"returned\",\n RTN: \"returned\",\n\n // Processing\n CC: \"at_warehouse\",\n PP: \"pending\",\n\n // Created / booked\n BK: \"created\",\n NEW: \"created\",\n};\n",
6
- "// file: src/carriers/smsaexpress/mappers.ts\n/**\n * SMSA Express Data Mappers\n * Transform between unified ShipFlow types and SMSA Express API formats.\n */\n\nimport {\n APIError,\n ValidationError,\n WebhookVerificationError,\n} from \"../../core/errors\";\nimport type {\n City,\n CreateShipmentInput,\n Location,\n Shipment,\n ShipmentStatus,\n TrackingEvent,\n TrackingResult,\n WebhookConfig,\n WebhookEvent,\n} from \"../../core/types\";\nimport { SMSAStatusCodes } from \"./services\";\nimport type {\n SMSACityLookupItem,\n SMSACreate2WayShipmentRequest,\n SMSACreateB2CShipmentRequest,\n SMSACreateC2BShipmentRequest,\n SMSAOfficeLookupItem,\n SMSAShipmentAddress,\n SMSAShipmentResponse,\n SMSATrackingResponse,\n SMSATrackingScan,\n SMSAWebhookShipment,\n} from \"./types\";\n\n// ============================================================================\n// STATUS MAPPING\n// ============================================================================\n\nexport function mapSMSAStatus(scanType: string): ShipmentStatus {\n return (SMSAStatusCodes[scanType] as ShipmentStatus) ?? \"in_transit\";\n}\n\n/**\n * Derive the current shipment status from tracking scans.\n * Defensively sorts by ScanDateTime descending before taking the latest.\n */\nfunction deriveStatusFromScans(\n scans: SMSATrackingScan[] | undefined,\n isDelivered?: boolean,\n): { status: ShipmentStatus; statusLabel: string } {\n if (isDelivered) {\n return { status: \"delivered\", statusLabel: \"Delivered\" };\n }\n // Records may omit Scans entirely (freshly-booked AWB, partial bulk record).\n const safeScans = scans ?? [];\n if (safeScans.length === 0) {\n return { status: \"unknown\", statusLabel: \"Unknown\" };\n }\n const sorted = [...safeScans].sort(\n (a, b) =>\n new Date(b.ScanDateTime).getTime() -\n new Date(a.ScanDateTime).getTime(),\n );\n const latest = sorted[0]!;\n return {\n status: mapSMSAStatus(latest.ScanType),\n statusLabel: latest.ScanDescription,\n };\n}\n\n// ============================================================================\n// REQUEST MAPPERS\n// ============================================================================\n\n/**\n * Round a weight to 3 decimal places to avoid float-arithmetic noise\n * (e.g. 0.1 + 0.2 = 0.30000000000000004) leaking into the API payload.\n */\nfunction roundWeight(weight: number): number {\n return Math.round(weight * 1000) / 1000;\n}\n\nfunction mapAddress(\n addr: CreateShipmentInput[\"shipper\"] | CreateShipmentInput[\"consignee\"],\n opts?: { includeShortCode?: boolean },\n): SMSAShipmentAddress {\n const result: SMSAShipmentAddress = {\n ContactName: addr.name,\n ContactPhoneNumber: addr.phone,\n Country: addr.countryCode,\n City: addr.city,\n AddressLine1: addr.line1,\n AddressLine2: addr.line2,\n District: addr.neighbourhood ?? addr.state,\n PostalCode: addr.postalCode,\n };\n\n if (addr.coordinates) {\n result.Coordinates = `${addr.coordinates.latitude},${addr.coordinates.longitude}`;\n }\n\n // ShortCode is valid only on the consignee address per the SMSA spec.\n if (opts?.includeShortCode && addr.nationalAddress?.shortCode) {\n result.ShortCode = addr.nationalAddress.shortCode;\n }\n\n return result;\n}\n\nexport function mapCreateB2CRequest(\n input: CreateShipmentInput,\n): SMSACreateB2CShipmentRequest {\n const totalPieces = input.parcels.reduce((sum, p) => sum + p.pieces, 0);\n const totalWeight = input.parcels.reduce(\n (sum, p) =>\n sum +\n (p.weight.unit === \"lb\" ? p.weight.value * 0.453592 : p.weight.value),\n 0,\n );\n\n const consigneeAddress = mapAddress(input.consignee, {\n includeShortCode: true,\n });\n if (input.options?.metadata?.consigneeId) {\n consigneeAddress.ConsigneeID = input.options.metadata.consigneeId as string;\n }\n\n return {\n ConsigneeAddress: consigneeAddress,\n ShipperAddress: mapAddress(input.shipper),\n OrderNumber: input.reference ?? `ORD-${Date.now()}`,\n CODAmount: input.cod?.enabled ? input.cod.amount : 0,\n DeclaredValue: input.declaredValue?.amount ?? 0,\n ContentDescription: input.parcels[0]?.description ?? \"Shipment contents\",\n Parcels: totalPieces,\n ShipDate: new Date().toISOString().slice(0, 19),\n // When COD is enabled, its currency is the amount physically collected\n // and must win over the declared-value currency.\n ShipmentCurrency:\n (input.cod?.enabled ? input.cod.currency : undefined) ??\n input.declaredValue?.currency ??\n input.cod?.currency ??\n \"SAR\",\n Weight: roundWeight(totalWeight),\n WeightUnit: \"KG\",\n WaybillType: input.labelFormat === \"ZPL\" ? \"ZPL\" : \"PDF\",\n ServiceCode: input.serviceType,\n SMSARetailID: (input.options?.metadata?.smsaRetailId as string) ?? \"0\",\n VatPaid: (input.options?.metadata?.vatPaid as boolean) ?? true,\n DutyPaid: (input.options?.metadata?.dutyPaid as boolean) ?? false,\n };\n}\n\n/**\n * Map to C2B (reverse pickup) request.\n * In C2B flow, the consignee is the pickup point (customer returning),\n * and the shipper is the return-to address (merchant warehouse).\n */\nexport function mapCreateC2BRequest(\n input: CreateShipmentInput,\n): SMSACreateC2BShipmentRequest {\n const totalPieces = input.parcels.reduce((sum, p) => sum + p.pieces, 0);\n const totalWeight = input.parcels.reduce(\n (sum, p) =>\n sum +\n (p.weight.unit === \"lb\" ? p.weight.value * 0.453592 : p.weight.value),\n 0,\n );\n\n return {\n PickupAddress: mapAddress(input.consignee, { includeShortCode: true }),\n ReturnToAddress: mapAddress(input.shipper),\n OrderNumber: input.reference ?? `ORD-${Date.now()}`,\n DeclaredValue: input.declaredValue?.amount ?? 0.1,\n ContentDescription:\n input.parcels[0]?.description ?? \"Return shipment contents\",\n Parcels: totalPieces,\n ShipDate: new Date().toISOString().slice(0, 19),\n // When COD is enabled, its currency is the amount physically collected\n // and must win over the declared-value currency.\n ShipmentCurrency:\n (input.cod?.enabled ? input.cod.currency : undefined) ??\n input.declaredValue?.currency ??\n input.cod?.currency ??\n \"SAR\",\n Weight: roundWeight(totalWeight),\n WeightUnit: \"KG\",\n WaybillType: input.labelFormat === \"ZPL\" ? \"ZPL\" : \"PDF\",\n ServiceCode: input.serviceType ?? \"EDCR\",\n SMSARetailID: (input.options?.metadata?.smsaRetailId as string) ?? \"0\",\n };\n}\n\n// ============================================================================\n// RESPONSE MAPPERS\n// ============================================================================\n\nexport function mapShipmentResponse(\n data: SMSAShipmentResponse,\n input: CreateShipmentInput,\n): Shipment {\n const firstWaybill = data.waybills?.[0];\n const trackingNumber = firstWaybill?.awb ?? data.sawb;\n\n if (!trackingNumber) {\n throw new APIError(\"No tracking number in shipment response\", {\n carrier: \"smsaexpress\",\n raw: data,\n });\n }\n\n return {\n carrier: \"smsaexpress\",\n trackingNumber,\n reference: input.reference,\n status: \"created\",\n statusLabel: \"Created\",\n codAmount: input.cod?.enabled ? input.cod.amount : undefined,\n declaredValue: input.declaredValue?.amount,\n // When COD is enabled, its currency is the amount physically collected\n // and must win over the declared-value currency.\n currency:\n (input.cod?.enabled ? input.cod.currency : undefined) ??\n input.declaredValue?.currency ??\n input.cod?.currency ??\n \"SAR\",\n returnLabel: firstWaybill?.returnBarcode\n ? `data:application/pdf;base64,${firstWaybill.returnBarcode}`\n : undefined,\n // Guard a missing createDate so we never build `new Date(\"undefined+03:00\")`\n // (Invalid Date). Append the SMSA timezone (+03:00) only to bare timestamps.\n createdAt: data.createDate\n ? new Date(\n /[Z]$|[+-]\\d{2}(:\\d{2})?$/.test(data.createDate)\n ? data.createDate\n : `${data.createDate}+03:00`,\n )\n : new Date(),\n raw: data,\n };\n}\n\nexport function mapTrackingEvent(scan: SMSATrackingScan): TrackingEvent {\n return {\n timestamp: new Date(\n scan.ScanTimeZone\n ? `${scan.ScanDateTime}${scan.ScanTimeZone}`\n : scan.ScanDateTime,\n ),\n statusCode: scan.ScanType,\n status: mapSMSAStatus(scan.ScanType),\n description: scan.ScanDescription,\n location: scan.City,\n };\n}\n\nexport function mapTrackingResult(data: SMSATrackingResponse): TrackingResult {\n // Real API records (freshly-booked AWB, partial bulk record) may omit Scans\n // entirely; default to [] so a scan-less record yields \"unknown\" instead of\n // throwing (and failing the whole trackMultiple batch).\n const scans = data.Scans ?? [];\n const { status, statusLabel } = deriveStatusFromScans(scans, data.isDelivered);\n\n const deliveredScan = scans.find((s) => s.ScanType === \"DL\");\n const deliveredTimestamp = deliveredScan\n ? new Date(\n deliveredScan.ScanTimeZone\n ? `${deliveredScan.ScanDateTime}${deliveredScan.ScanTimeZone}`\n : deliveredScan.ScanDateTime,\n )\n : undefined;\n\n return {\n trackingNumber: data.AWB,\n carrier: \"smsaexpress\",\n reference: data.Reference || undefined,\n status,\n statusLabel,\n events: scans.map(mapTrackingEvent),\n deliveryDate: deliveredTimestamp,\n codAmount: data.CODAmount > 0 ? data.CODAmount : undefined,\n pieces: data.Pieces,\n raw: data,\n };\n}\n\nexport function mapCity(city: SMSACityLookupItem): City {\n return {\n nameEn: city.cityName,\n code: city.cityCode,\n };\n}\n\nexport function mapOffice(office: SMSAOfficeLookupItem): Location {\n const [lat, lng] = (office.coordinates || \"\")\n .split(\",\")\n .map((s) => parseFloat(s.trim()));\n\n return {\n id: office.code,\n name: office.address,\n nameAr: office.addressAR,\n city: office.cityName,\n latitude: Number.isNaN(lat) ? undefined : lat,\n longitude: Number.isNaN(lng) ? undefined : lng,\n };\n}\n\nexport function mapCreate2WayRequest(\n input: CreateShipmentInput,\n): SMSACreate2WayShipmentRequest {\n const totalPieces = input.parcels.reduce((sum, p) => sum + p.pieces, 0);\n const totalWeight = input.parcels.reduce(\n (sum, p) =>\n sum +\n (p.weight.unit === \"lb\" ? p.weight.value * 0.453592 : p.weight.value),\n 0,\n );\n\n const consigneeAddress = mapAddress(input.consignee, {\n includeShortCode: true,\n });\n if (input.options?.metadata?.consigneeId) {\n consigneeAddress.ConsigneeID = input.options.metadata.consigneeId as string;\n }\n\n return {\n ConsigneeAddress: consigneeAddress,\n ShipperAddress: mapAddress(input.shipper),\n OrderNumber: input.reference ?? `ORD-${Date.now()}`,\n DeclaredValue: input.declaredValue?.amount ?? 0,\n ContentDescription: input.parcels[0]?.description ?? \"Shipment contents\",\n Parcels: totalPieces,\n ShipDate: new Date().toISOString().slice(0, 19),\n ShipmentCurrency: input.declaredValue?.currency ?? \"SAR\",\n Weight: roundWeight(totalWeight),\n WeightUnit: \"KG\",\n WaybillType: input.labelFormat === \"ZPL\" ? \"ZPL\" : \"PDF\",\n SMSARetailID: (input.options?.metadata?.smsaRetailId as string) ?? \"0\",\n VatPaid: (input.options?.metadata?.vatPaid as boolean) ?? true,\n DutyPaid: (input.options?.metadata?.dutyPaid as boolean) ?? false,\n };\n}\n\n// ============================================================================\n// WEBHOOK MAPPERS\n// ============================================================================\n\n/**\n * Timing-safe string comparison to prevent timing attacks on auth tokens.\n */\nfunction timingSafeEqual(a: string, b: string): boolean {\n const encoder = new TextEncoder();\n const bufA = encoder.encode(a);\n const bufB = encoder.encode(b);\n if (bufA.byteLength !== bufB.byteLength) return false;\n let mismatch = 0;\n for (let i = 0; i < bufA.byteLength; i++) {\n mismatch |= bufA[i]! ^ bufB[i]!;\n }\n return mismatch === 0;\n}\n\nfunction verifyWebhookAuth(options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n}): void {\n const { headers = {}, queryParams = {}, config } = options ?? {};\n\n // Verify auth via header (case-insensitive lookup, timing-safe comparison)\n if (config?.authHeader && config?.authValue) {\n const lowerKey = config.authHeader.toLowerCase();\n const headerValue = Object.entries(headers).find(\n ([k]) => k.toLowerCase() === lowerKey,\n )?.[1];\n if (!headerValue || !timingSafeEqual(headerValue, config.authValue)) {\n throw new WebhookVerificationError(\"Invalid webhook auth header\", {\n carrier: \"smsaexpress\",\n });\n }\n }\n\n // Verify auth via query param (SMSA uses API key as query param, timing-safe)\n if (config?.authQueryParam && config?.authQueryValue) {\n const paramValue = queryParams[config.authQueryParam];\n if (!paramValue || !timingSafeEqual(paramValue, config.authQueryValue)) {\n throw new WebhookVerificationError(\"Invalid webhook auth query param\", {\n carrier: \"smsaexpress\",\n });\n }\n }\n}\n\nfunction mapWebhookShipmentToEvent(\n shipment: SMSAWebhookShipment,\n): WebhookEvent {\n // A webhook record may arrive without Scans; default to [] so a scan-less\n // shipment yields \"unknown\" instead of throwing.\n const scans = shipment.Scans ?? [];\n const { status, statusLabel } = deriveStatusFromScans(\n scans,\n shipment.isDelivered,\n );\n\n // Sort scans descending to get the latest one for timestamp/statusCode\n const sorted = [...scans].sort(\n (a, b) =>\n new Date(b.ScanDateTime).getTime() -\n new Date(a.ScanDateTime).getTime(),\n );\n const latestScan = sorted[0];\n const timestamp = latestScan\n ? new Date(\n latestScan.ScanTimeZone\n ? `${latestScan.ScanDateTime}${latestScan.ScanTimeZone}`\n : latestScan.ScanDateTime,\n )\n : new Date();\n\n return {\n carrier: \"smsaexpress\",\n eventType: \"status_update\",\n trackingNumber: shipment.AWB,\n reference: shipment.Reference || undefined,\n status,\n statusCode: latestScan?.ScanType ?? \"unknown\",\n statusLabel,\n timestamp,\n raw: shipment,\n };\n}\n\nfunction validateWebhookPayload(payload: unknown): SMSAWebhookShipment[] {\n if (!Array.isArray(payload)) {\n throw new ValidationError(\n \"Invalid SMSA webhook payload: expected an array of shipments\",\n { raw: payload },\n );\n }\n\n if (payload.length === 0) {\n throw new ValidationError(\"Invalid SMSA webhook payload: empty array\", {\n raw: payload,\n });\n }\n\n for (const item of payload) {\n if (\n !item ||\n typeof item !== \"object\" ||\n !(\"AWB\" in item) ||\n !(\"Scans\" in item)\n ) {\n throw new ValidationError(\n \"Invalid SMSA webhook payload: shipment missing required fields (AWB, Scans)\",\n { raw: item },\n );\n }\n }\n\n return payload as SMSAWebhookShipment[];\n}\n\n/**\n * Parse an SMSA webhook payload and return a single WebhookEvent\n * for the first shipment in the batch.\n *\n * SMSA sends webhooks as an array of shipments. This function returns\n * only the first item — use `parseSMSAWebhookBatch()` for full batch parsing.\n */\nexport function parseSMSAWebhook(\n payload: unknown,\n options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n },\n): WebhookEvent {\n verifyWebhookAuth(options);\n const shipments = validateWebhookPayload(payload);\n return mapWebhookShipmentToEvent(shipments[0]!);\n}\n\n/**\n * Parse an SMSA webhook payload and return a WebhookEvent for every\n * shipment in the batch.\n *\n * SMSA sends webhook payloads as an array of shipments, each with\n * their own tracking scans.\n */\nexport function parseSMSAWebhookBatch(\n payload: unknown,\n options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n },\n): WebhookEvent[] {\n verifyWebhookAuth(options);\n const shipments = validateWebhookPayload(payload);\n return shipments.map(mapWebhookShipmentToEvent);\n}\n",
7
- "// file: src/carriers/smsaexpress/adapter.ts\n/**\n * SMSA Express Carrier Adapter\n * Full implementation of the CarrierAdapter interface for SMSA Express API.\n */\n\nimport { APIError } from \"../../core/errors\";\nimport { HttpClient } from \"../../core/http\";\nimport type {\n CarrierConfig,\n City,\n CreateShipmentInput,\n Location,\n Shipment,\n TrackingResult,\n WebhookConfig,\n WebhookEvent,\n} from \"../../core/types\";\nimport { BaseCarrierAdapter } from \"../base\";\nimport {\n mapCity,\n mapCreate2WayRequest,\n mapCreateB2CRequest,\n mapCreateC2BRequest,\n mapOffice,\n mapShipmentResponse,\n mapTrackingResult,\n parseSMSAWebhook,\n parseSMSAWebhookBatch,\n} from \"./mappers\";\nimport type {\n SMSACityLookupItem,\n SMSAOfficeLookupItem,\n SMSAPushIdDetailsRequest,\n SMSAPushIdDetailsResponse,\n SMSASendInvoiceRequest,\n SMSAShipmentResponse,\n SMSAShortAddressResponse,\n SMSATrackingResponse,\n} from \"./types\";\n\nconst SMSA_SANDBOX_URL = \"https://ecomapis-sandbox.azurewebsites.net\";\nconst SMSA_PRODUCTION_URL = \"https://ecomapis.smsaexpress.com\";\n\n/** Reverse-pickup / C2B service codes */\nconst C2B_SERVICE_CODES = new Set([\"EDCR\"]);\n\nexport interface SMSAExpressConfig extends CarrierConfig {\n credentials: {\n apiKey: string;\n };\n}\n\nexport class SMSAExpressAdapter extends BaseCarrierAdapter {\n readonly name = \"smsaexpress\";\n readonly supportedCountries = [\n \"SA\",\n \"AE\",\n \"BH\",\n \"EG\",\n \"KW\",\n \"OM\",\n \"QA\",\n \"JO\",\n ];\n\n private http: HttpClient;\n\n constructor(config: SMSAExpressConfig) {\n super(config);\n this.http = new HttpClient({\n baseUrl: this.getBaseUrl(),\n carrier: \"smsaexpress\",\n headers: {\n apikey: config.credentials.apiKey,\n },\n });\n }\n\n protected getBaseUrl(): string {\n return this.config.mode === \"production\"\n ? SMSA_PRODUCTION_URL\n : SMSA_SANDBOX_URL;\n }\n\n // =========================================================================\n // SHIPPING\n // =========================================================================\n\n protected async executeCreateShipment(\n input: CreateShipmentInput,\n ): Promise<Shipment> {\n const isC2B =\n input.serviceType !== undefined &&\n C2B_SERVICE_CODES.has(input.serviceType);\n\n if (isC2B) {\n return this.createC2BShipment(input);\n }\n\n const request = mapCreateB2CRequest(input);\n const response = await this.http.post<SMSAShipmentResponse>(\n \"/api/shipment/b2c/new\",\n request,\n );\n\n return mapShipmentResponse(response, input);\n }\n\n private async createC2BShipment(\n input: CreateShipmentInput,\n ): Promise<Shipment> {\n const request = mapCreateC2BRequest(input);\n const response = await this.http.post<SMSAShipmentResponse>(\n \"/api/c2b/new\",\n request,\n );\n\n return mapShipmentResponse(response, input);\n }\n\n /**\n * Cancel a reverse-pickup (C2B) shipment.\n *\n * **Important:** SMSA only supports cancellation for C2B/reverse-pickup\n * shipments, via the C2B endpoint. B2C shipments cannot be cancelled through\n * the SMSA API: the endpoint responds without a cancellation confirmation,\n * which is surfaced as an {@link APIError} rather than a misleading `false`.\n * That lets callers tell \"the carrier could not cancel\" apart from a genuine\n * refusal of a cancellable shipment and fall back accordingly. Consistent with\n * the other adapters, this method either returns `true` or throws.\n */\n async cancelShipment(trackingNumber: string): Promise<boolean> {\n const response = await this.http.post<string>(\n `/api/c2b/cancel/${encodeURIComponent(trackingNumber)}`,\n );\n // A non-string (JSON) success response is treated as success.\n if (typeof response !== \"string\") {\n return true;\n }\n // Success messages contain \"cancelled\" (e.g. \"Shipment Cancelled\n // Successfully!\"). Anything else means the carrier did NOT cancel — most\n // commonly because this is a B2C shipment, which the SMSA API cannot cancel.\n if (response.toLowerCase().includes(\"cancelled\")) {\n return true;\n }\n throw new APIError(\n response.trim() ||\n \"SMSA did not confirm cancellation (B2C shipments cannot be cancelled via the SMSA API)\",\n { carrier: \"smsaexpress\", raw: response },\n );\n }\n\n // =========================================================================\n // TRACKING\n // =========================================================================\n\n async track(trackingNumber: string): Promise<TrackingResult> {\n const response = await this.http.get<SMSATrackingResponse>(\n `/api/track/single/${encodeURIComponent(trackingNumber)}`,\n );\n\n return mapTrackingResult(response);\n }\n\n async trackMultiple(trackingNumbers: string[]): Promise<TrackingResult[]> {\n const response = await this.http.post<SMSATrackingResponse[]>(\n \"/api/track/bulk/\",\n trackingNumbers,\n { retry: true },\n );\n\n return response.map(mapTrackingResult);\n }\n\n async trackByReference(reference: string): Promise<TrackingResult> {\n const response = await this.http.get<SMSATrackingResponse>(\n `/api/track/reference/${encodeURIComponent(reference)}`,\n );\n\n return mapTrackingResult(response);\n }\n\n // =========================================================================\n // LABELS\n // =========================================================================\n\n async getLabel(\n trackingNumber: string,\n _format?: \"PDF\" | \"ZPL\" | \"PNG\",\n ): Promise<string> {\n // SMSA returns the waybill file (base64 PDF) as part of the shipment query.\n // Try B2C first (most common), then C2B as fallback.\n const encoded = encodeURIComponent(trackingNumber);\n\n for (const path of [\n `/api/shipment/b2c/query/${encoded}`,\n `/api/c2b/query/${encoded}`,\n ]) {\n try {\n const response = await this.http.get<SMSAShipmentResponse>(path);\n const waybill = response.waybills?.[0];\n if (waybill?.awbFile) {\n return `data:application/pdf;base64,${waybill.awbFile}`;\n }\n } catch (error) {\n // Only swallow 4xx API errors (shipment type mismatch / not found).\n // Re-throw auth, network, server, and unexpected errors immediately.\n if (\n !(error instanceof APIError) ||\n error.statusCode === 401 ||\n (error.statusCode !== undefined && error.statusCode >= 500)\n ) {\n throw error;\n }\n }\n }\n\n throw new APIError(\"No label found for shipment\", {\n carrier: \"smsaexpress\",\n raw: { trackingNumber },\n });\n }\n\n // =========================================================================\n // CITIES & LOCATIONS\n // =========================================================================\n\n async getCities(countryCode = \"SA\"): Promise<City[]> {\n const response = await this.http.get<SMSACityLookupItem[]>(\n `/api/lookup/cities/${encodeURIComponent(countryCode)}`,\n );\n\n return response.map(mapCity);\n }\n\n async getDropoffLocations(): Promise<Location[]> {\n const response = await this.http.get<SMSAOfficeLookupItem[]>(\n \"/api/lookup/smsaoffices\",\n );\n\n return response.map(mapOffice);\n }\n\n // =========================================================================\n // 2-WAY SHIPMENT\n // =========================================================================\n\n async create2WayShipment(input: CreateShipmentInput): Promise<Shipment> {\n const request = mapCreate2WayRequest(input);\n const response = await this.http.post<SMSAShipmentResponse>(\n \"/api/TwoWayShipment/new\",\n request,\n );\n\n return mapShipmentResponse(response, input);\n }\n\n // =========================================================================\n // INVOICE & ID OPERATIONS (SMSA-specific)\n // =========================================================================\n\n async sendInvoice(request: SMSASendInvoiceRequest): Promise<string> {\n return this.http.post<string>(\"/api/invoice\", request);\n }\n\n async validateShortAddress(\n shortCode: string,\n ): Promise<SMSAShortAddressResponse> {\n return this.http.get<SMSAShortAddressResponse>(\n `/api/Lookup/FullAddressByShortCode/${encodeURIComponent(shortCode)}`,\n );\n }\n\n async pushIdDetails(\n request: SMSAPushIdDetailsRequest,\n ): Promise<SMSAPushIdDetailsResponse> {\n return this.http.post<SMSAPushIdDetailsResponse>(\n \"/api/shipment/identity-details\",\n request,\n );\n }\n\n // =========================================================================\n // WEBHOOKS\n // =========================================================================\n\n parseWebhook(\n payload: unknown,\n options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n },\n ): WebhookEvent {\n return parseSMSAWebhook(payload, options);\n }\n\n /**\n * Parse a batch SMSA webhook payload into multiple events.\n *\n * SMSA sends webhook payloads as an array of shipments, each with their own\n * tracking scans. This method returns a WebhookEvent for every shipment\n * in the payload.\n */\n parseWebhookBatch(\n payload: unknown,\n options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n },\n ): WebhookEvent[] {\n return parseSMSAWebhookBatch(payload, options);\n }\n}\n"
5
+ "// file: src/carriers/smsaexpress/services.ts\n/**\n * SMSA Express Service Type Codes\n * Use these constants for type-safe service selection.\n */\n\nexport const SMSAService = {\n /** E-commerce delivery */\n ECOMMERCE_DELIVERY: \"EDDL\",\n /** Express delivery */\n EXPRESS_DELIVERY: \"EDEL\",\n /** C2B / Reverse pickup */\n C2B_REVERSE: \"EDCR\",\n} as const;\n\nexport type SMSAServiceType = (typeof SMSAService)[keyof typeof SMSAService];\n\n/**\n * SMSA Express Scan Type Codes → Unified ShipmentStatus mapping.\n *\n * Based on /api/track/statuslookup response.\n * Scan types not explicitly listed default to \"unknown\" (see mapSMSAStatus)\n * so operators can detect unmapped codes instead of silently treating them\n * as in-transit.\n */\nexport const SMSAStatusCodes: Record<string, string> = {\n // Delivery\n DL: \"delivered\",\n\n // Out for delivery\n OD: \"out_for_delivery\",\n\n // Arrived at facility\n AF: \"at_warehouse\",\n\n // Hub / sorting\n HOP: \"in_transit\",\n HOR: \"in_transit\",\n HOT: \"in_transit\",\n\n // Picked up / collected\n PU: \"picked_up\",\n PKD: \"picked_up\",\n\n // Customs\n CR: \"in_transit\",\n CH: \"in_transit\",\n\n // Exceptions\n DE: \"exception\",\n DMG: \"exception\",\n MISS: \"exception\",\n\n // Cancelled / returned\n CAN: \"cancelled\",\n RTO: \"returned\",\n RTN: \"returned\",\n\n // Processing\n CC: \"at_warehouse\",\n PP: \"pending\",\n\n // Created / booked\n BK: \"created\",\n NEW: \"created\",\n};\n",
6
+ "// file: src/carriers/smsaexpress/mappers.ts\n/**\n * SMSA Express Data Mappers\n * Transform between unified ShipFlow types and SMSA Express API formats.\n */\n\nimport {\n APIError,\n ValidationError,\n WebhookVerificationError,\n} from \"../../core/errors\";\nimport type {\n City,\n CreateShipmentInput,\n Location,\n Shipment,\n ShipmentStatus,\n TrackingEvent,\n TrackingResult,\n WebhookConfig,\n WebhookEvent,\n} from \"../../core/types\";\nimport { SMSAStatusCodes } from \"./services\";\nimport type {\n SMSACityLookupItem,\n SMSACreate2WayShipmentRequest,\n SMSACreateB2CShipmentRequest,\n SMSACreateC2BShipmentRequest,\n SMSAOfficeLookupItem,\n SMSAShipmentAddress,\n SMSAShipmentResponse,\n SMSATrackingResponse,\n SMSATrackingScan,\n SMSAWebhookShipment,\n} from \"./types\";\n\n// ============================================================================\n// STATUS MAPPING\n// ============================================================================\n\nexport function mapSMSAStatus(scanType: string): ShipmentStatus {\n return (SMSAStatusCodes[scanType] as ShipmentStatus) ?? \"unknown\";\n}\n\n/**\n * Resolve a scan's Date, applying its ScanTimeZone offset when present\n * (consistent with mapTrackingEvent / mapShipmentResponse timestamp handling).\n */\nfunction scanDate(scan: SMSATrackingScan): Date {\n return new Date(\n scan.ScanTimeZone\n ? `${scan.ScanDateTime}${scan.ScanTimeZone}`\n : scan.ScanDateTime,\n );\n}\n\nfunction scanTimestampMs(scan: SMSATrackingScan): number {\n return scanDate(scan).getTime();\n}\n\n/**\n * Derive the current shipment status from tracking scans.\n * Defensively sorts by (zone-adjusted) ScanDateTime descending before taking\n * the latest.\n */\nfunction deriveStatusFromScans(\n scans: SMSATrackingScan[] | undefined,\n isDelivered?: boolean,\n): { status: ShipmentStatus; statusLabel: string } {\n if (isDelivered) {\n return { status: \"delivered\", statusLabel: \"Delivered\" };\n }\n // Records may omit Scans entirely (freshly-booked AWB, partial bulk record).\n const safeScans = scans ?? [];\n if (safeScans.length === 0) {\n return { status: \"unknown\", statusLabel: \"Unknown\" };\n }\n const sorted = [...safeScans].sort(\n (a, b) => scanTimestampMs(b) - scanTimestampMs(a),\n );\n const latest = sorted[0]!;\n return {\n status: mapSMSAStatus(latest.ScanType),\n statusLabel: latest.ScanDescription,\n };\n}\n\n// ============================================================================\n// REQUEST MAPPERS\n// ============================================================================\n\n/**\n * Round a weight to 3 decimal places to avoid float-arithmetic noise\n * (e.g. 0.1 + 0.2 = 0.30000000000000004) leaking into the API payload.\n */\nfunction roundWeight(weight: number): number {\n return Math.round(weight * 1000) / 1000;\n}\n\n/** Strip non-digit characters from phone numbers (SMSA requires digits only). */\nfunction sanitizePhone(phone: string): string {\n return phone.replace(/\\D/g, \"\");\n}\n\n/**\n * Generate a fallback OrderNumber when the caller supplies no reference. Single\n * source of the `ORD-<epoch-ms>` format — the adapter uses it to fix a reference\n * on the input so the booked OrderNumber is surfaced back on the Shipment, and\n * the request mappers reference it as their type-required default.\n */\nexport function generateOrderNumber(): string {\n return `ORD-${Date.now()}`;\n}\n\n/**\n * Format the ShipDate as the Saudi (UTC+3) wall-clock, with no zone suffix.\n *\n * SMSA treats a bare (zone-less) timestamp as Saudi local time (UTC+3) — its\n * own responses come back bare and the response mapper appends `+03:00` to them.\n * Sending `new Date().toISOString().slice(0, 19)` (zero-offset UTC wall-clock)\n * therefore lands a day early between ~21:00–24:00 Saudi time, so shift by +3h\n * before formatting.\n */\nfunction smsaShipDate(): string {\n return new Date(Date.now() + 3 * 60 * 60 * 1000)\n .toISOString()\n .slice(0, 19);\n}\n\nfunction mapAddress(\n addr: CreateShipmentInput[\"shipper\"] | CreateShipmentInput[\"consignee\"],\n opts?: { includeShortCode?: boolean },\n): SMSAShipmentAddress {\n const result: SMSAShipmentAddress = {\n ContactName: addr.name,\n ContactPhoneNumber: sanitizePhone(addr.phone),\n Country: addr.countryCode,\n City: addr.city,\n AddressLine1: addr.line1,\n AddressLine2: addr.line2,\n District: addr.neighbourhood ?? addr.state,\n PostalCode: addr.postalCode,\n };\n\n if (addr.coordinates) {\n result.Coordinates = `${addr.coordinates.latitude},${addr.coordinates.longitude}`;\n }\n\n // ShortCode is valid only on the consignee address per the SMSA spec.\n if (opts?.includeShortCode && addr.nationalAddress?.shortCode) {\n result.ShortCode = addr.nationalAddress.shortCode;\n }\n\n return result;\n}\n\nexport function mapCreateB2CRequest(\n input: CreateShipmentInput,\n): SMSACreateB2CShipmentRequest {\n const totalPieces = input.parcels.reduce((sum, p) => sum + p.pieces, 0);\n const totalWeight = input.parcels.reduce(\n (sum, p) =>\n sum +\n (p.weight.unit === \"lb\" ? p.weight.value * 0.453592 : p.weight.value),\n 0,\n );\n\n const consigneeAddress = mapAddress(input.consignee, {\n includeShortCode: true,\n });\n if (input.options?.metadata?.consigneeId) {\n consigneeAddress.ConsigneeID = input.options.metadata.consigneeId as string;\n }\n\n return {\n ConsigneeAddress: consigneeAddress,\n ShipperAddress: mapAddress(input.shipper),\n OrderNumber: input.reference ?? generateOrderNumber(),\n CODAmount: input.cod?.enabled ? input.cod.amount : 0,\n DeclaredValue: input.declaredValue?.amount ?? 0,\n ContentDescription: input.parcels[0]?.description ?? \"Shipment contents\",\n Parcels: totalPieces,\n // SMSA reads bare timestamps as Saudi local time (UTC+3), so send the\n // UTC+3 wall-clock rather than zero-offset UTC.\n ShipDate: smsaShipDate(),\n // When COD is enabled, its currency is the amount physically collected\n // and must win over the declared-value currency.\n ShipmentCurrency:\n (input.cod?.enabled ? input.cod.currency : undefined) ??\n input.declaredValue?.currency ??\n input.cod?.currency ??\n \"SAR\",\n Weight: roundWeight(totalWeight),\n WeightUnit: \"KG\",\n WaybillType: input.labelFormat === \"ZPL\" ? \"ZPL\" : \"PDF\",\n ServiceCode: input.serviceType,\n SMSARetailID: (input.options?.metadata?.smsaRetailId as string) ?? \"0\",\n VatPaid: (input.options?.metadata?.vatPaid as boolean) ?? true,\n DutyPaid: (input.options?.metadata?.dutyPaid as boolean) ?? false,\n };\n}\n\n/**\n * Map to C2B (reverse pickup) request.\n * In C2B flow, the consignee is the pickup point (customer returning),\n * and the shipper is the return-to address (merchant warehouse).\n */\nexport function mapCreateC2BRequest(\n input: CreateShipmentInput,\n): SMSACreateC2BShipmentRequest {\n const totalPieces = input.parcels.reduce((sum, p) => sum + p.pieces, 0);\n const totalWeight = input.parcels.reduce(\n (sum, p) =>\n sum +\n (p.weight.unit === \"lb\" ? p.weight.value * 0.453592 : p.weight.value),\n 0,\n );\n\n return {\n PickupAddress: mapAddress(input.consignee, { includeShortCode: true }),\n ReturnToAddress: mapAddress(input.shipper),\n OrderNumber: input.reference ?? generateOrderNumber(),\n DeclaredValue: input.declaredValue?.amount ?? 0.1,\n ContentDescription:\n input.parcels[0]?.description ?? \"Return shipment contents\",\n Parcels: totalPieces,\n // SMSA reads bare timestamps as Saudi local time (UTC+3), so send the\n // UTC+3 wall-clock rather than zero-offset UTC.\n ShipDate: smsaShipDate(),\n // When COD is enabled, its currency is the amount physically collected\n // and must win over the declared-value currency.\n ShipmentCurrency:\n (input.cod?.enabled ? input.cod.currency : undefined) ??\n input.declaredValue?.currency ??\n input.cod?.currency ??\n \"SAR\",\n Weight: roundWeight(totalWeight),\n WeightUnit: \"KG\",\n WaybillType: input.labelFormat === \"ZPL\" ? \"ZPL\" : \"PDF\",\n ServiceCode: input.serviceType ?? \"EDCR\",\n SMSARetailID: (input.options?.metadata?.smsaRetailId as string) ?? \"0\",\n };\n}\n\n// ============================================================================\n// RESPONSE MAPPERS\n// ============================================================================\n\nexport function mapShipmentResponse(\n data: SMSAShipmentResponse,\n input: CreateShipmentInput,\n): Shipment {\n const firstWaybill = data.waybills?.[0];\n const trackingNumber = firstWaybill?.awb ?? data.sawb;\n\n if (!trackingNumber) {\n throw new APIError(\"No tracking number in shipment response\", {\n carrier: \"smsaexpress\",\n raw: data,\n });\n }\n\n return {\n carrier: \"smsaexpress\",\n trackingNumber,\n reference: input.reference,\n status: \"created\",\n statusLabel: \"Created\",\n codAmount: input.cod?.enabled ? input.cod.amount : undefined,\n declaredValue: input.declaredValue?.amount,\n // When COD is enabled, its currency is the amount physically collected\n // and must win over the declared-value currency.\n currency:\n (input.cod?.enabled ? input.cod.currency : undefined) ??\n input.declaredValue?.currency ??\n input.cod?.currency ??\n \"SAR\",\n returnLabel: firstWaybill?.returnBarcode\n ? `data:application/pdf;base64,${firstWaybill.returnBarcode}`\n : undefined,\n // Guard a missing createDate so we never build `new Date(\"undefined+03:00\")`\n // (Invalid Date). Append the SMSA timezone (+03:00) only to bare timestamps.\n createdAt: data.createDate\n ? new Date(\n /[Z]$|[+-]\\d{2}(:\\d{2})?$/.test(data.createDate)\n ? data.createDate\n : `${data.createDate}+03:00`,\n )\n : new Date(),\n raw: data,\n };\n}\n\nexport function mapTrackingEvent(scan: SMSATrackingScan): TrackingEvent {\n return {\n timestamp: new Date(\n scan.ScanTimeZone\n ? `${scan.ScanDateTime}${scan.ScanTimeZone}`\n : scan.ScanDateTime,\n ),\n statusCode: scan.ScanType,\n status: mapSMSAStatus(scan.ScanType),\n description: scan.ScanDescription,\n location: scan.City,\n };\n}\n\nexport function mapTrackingResult(data: SMSATrackingResponse): TrackingResult {\n // Real API records (freshly-booked AWB, partial bulk record) may omit Scans\n // entirely; default to [] so a scan-less record yields \"unknown\" instead of\n // throwing (and failing the whole trackMultiple batch).\n const scans = data.Scans ?? [];\n const { status, statusLabel } = deriveStatusFromScans(scans, data.isDelivered);\n\n const deliveredScan = scans.find((s) => s.ScanType === \"DL\");\n const deliveredTimestamp = deliveredScan\n ? new Date(\n deliveredScan.ScanTimeZone\n ? `${deliveredScan.ScanDateTime}${deliveredScan.ScanTimeZone}`\n : deliveredScan.ScanDateTime,\n )\n : undefined;\n\n return {\n trackingNumber: data.AWB,\n carrier: \"smsaexpress\",\n reference: data.Reference || undefined,\n status,\n statusLabel,\n events: scans.map(mapTrackingEvent),\n deliveryDate: deliveredTimestamp,\n codAmount: data.CODAmount > 0 ? data.CODAmount : undefined,\n pieces: data.Pieces,\n raw: data,\n };\n}\n\nexport function mapCity(city: SMSACityLookupItem): City {\n return {\n nameEn: city.cityName,\n code: city.cityCode,\n };\n}\n\nexport function mapOffice(office: SMSAOfficeLookupItem): Location {\n const [lat, lng] = (office.coordinates || \"\")\n .split(\",\")\n .map((s) => parseFloat(s.trim()));\n\n return {\n id: office.code,\n name: office.address,\n nameAr: office.addressAR,\n city: office.cityName,\n latitude: Number.isNaN(lat) ? undefined : lat,\n longitude: Number.isNaN(lng) ? undefined : lng,\n };\n}\n\nexport function mapCreate2WayRequest(\n input: CreateShipmentInput,\n): SMSACreate2WayShipmentRequest {\n const totalPieces = input.parcels.reduce((sum, p) => sum + p.pieces, 0);\n const totalWeight = input.parcels.reduce(\n (sum, p) =>\n sum +\n (p.weight.unit === \"lb\" ? p.weight.value * 0.453592 : p.weight.value),\n 0,\n );\n\n const consigneeAddress = mapAddress(input.consignee, {\n includeShortCode: true,\n });\n if (input.options?.metadata?.consigneeId) {\n consigneeAddress.ConsigneeID = input.options.metadata.consigneeId as string;\n }\n\n return {\n ConsigneeAddress: consigneeAddress,\n ShipperAddress: mapAddress(input.shipper),\n OrderNumber: input.reference ?? generateOrderNumber(),\n DeclaredValue: input.declaredValue?.amount ?? 0,\n ContentDescription: input.parcels[0]?.description ?? \"Shipment contents\",\n Parcels: totalPieces,\n // SMSA reads bare timestamps as Saudi local time (UTC+3), so send the\n // UTC+3 wall-clock rather than zero-offset UTC.\n ShipDate: smsaShipDate(),\n ShipmentCurrency: input.declaredValue?.currency ?? \"SAR\",\n Weight: roundWeight(totalWeight),\n WeightUnit: \"KG\",\n WaybillType: input.labelFormat === \"ZPL\" ? \"ZPL\" : \"PDF\",\n SMSARetailID: (input.options?.metadata?.smsaRetailId as string) ?? \"0\",\n VatPaid: (input.options?.metadata?.vatPaid as boolean) ?? true,\n DutyPaid: (input.options?.metadata?.dutyPaid as boolean) ?? false,\n };\n}\n\n// ============================================================================\n// WEBHOOK MAPPERS\n// ============================================================================\n\n/**\n * Timing-safe string comparison to prevent timing attacks on auth tokens.\n */\nfunction timingSafeEqual(a: string, b: string): boolean {\n const encoder = new TextEncoder();\n const bufA = encoder.encode(a);\n const bufB = encoder.encode(b);\n if (bufA.byteLength !== bufB.byteLength) return false;\n let mismatch = 0;\n for (let i = 0; i < bufA.byteLength; i++) {\n mismatch |= bufA[i]! ^ bufB[i]!;\n }\n return mismatch === 0;\n}\n\nfunction verifyWebhookAuth(options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n}): void {\n const { headers = {}, queryParams = {}, config } = options ?? {};\n\n // Verify auth via header (case-insensitive lookup, timing-safe comparison)\n if (config?.authHeader && config?.authValue) {\n const lowerKey = config.authHeader.toLowerCase();\n const headerValue = Object.entries(headers).find(\n ([k]) => k.toLowerCase() === lowerKey,\n )?.[1];\n if (!headerValue || !timingSafeEqual(headerValue, config.authValue)) {\n throw new WebhookVerificationError(\"Invalid webhook auth header\", {\n carrier: \"smsaexpress\",\n });\n }\n }\n\n // Verify auth via query param (SMSA uses API key as query param, timing-safe)\n if (config?.authQueryParam && config?.authQueryValue) {\n const paramValue = queryParams[config.authQueryParam];\n if (!paramValue || !timingSafeEqual(paramValue, config.authQueryValue)) {\n throw new WebhookVerificationError(\"Invalid webhook auth query param\", {\n carrier: \"smsaexpress\",\n });\n }\n }\n}\n\nfunction mapWebhookShipmentToEvent(\n shipment: SMSAWebhookShipment,\n): WebhookEvent {\n // A webhook record may arrive without Scans; default to [] so a scan-less\n // shipment yields \"unknown\" instead of throwing.\n const scans = shipment.Scans ?? [];\n const { status, statusLabel } = deriveStatusFromScans(\n scans,\n shipment.isDelivered,\n );\n\n // Sort scans descending (zone-adjusted) to get the latest one for\n // timestamp/statusCode.\n const sorted = [...scans].sort(\n (a, b) => scanTimestampMs(b) - scanTimestampMs(a),\n );\n const latestScan = sorted[0];\n const timestamp = latestScan ? scanDate(latestScan) : new Date();\n\n return {\n carrier: \"smsaexpress\",\n eventType: \"status_update\",\n trackingNumber: shipment.AWB,\n reference: shipment.Reference || undefined,\n status,\n statusCode: latestScan?.ScanType ?? \"unknown\",\n statusLabel,\n timestamp,\n raw: shipment,\n };\n}\n\n/**\n * Map a webhook shipment to one WebhookEvent PER scan, in chronological\n * (oldest → newest) order.\n *\n * SMSA can report several new scans for the same shipment in a single\n * webhook call (e.g. AF, OD, DL all at once). Collapsing that into only the\n * latest scan silently drops intermediate transitions/history, so this\n * expands every scan into its own event.\n */\nfunction mapWebhookShipmentToEvents(\n shipment: SMSAWebhookShipment,\n): WebhookEvent[] {\n const scans = shipment.Scans ?? [];\n\n if (scans.length === 0) {\n // No scans to expand — fall back to the single derived (possibly\n // \"unknown\"/isDelivered-driven) event.\n return [mapWebhookShipmentToEvent(shipment)];\n }\n\n const sorted = [...scans].sort(\n (a, b) => scanTimestampMs(a) - scanTimestampMs(b),\n );\n\n return sorted.map((scan, index) => {\n // Only the chronologically-last scan can be elevated to \"delivered\" by\n // the isDelivered flag; earlier scans reflect their own scan type.\n const isLatest = index === sorted.length - 1;\n const status: ShipmentStatus =\n isLatest && shipment.isDelivered\n ? \"delivered\"\n : mapSMSAStatus(scan.ScanType);\n\n return {\n carrier: \"smsaexpress\",\n eventType: \"status_update\",\n trackingNumber: shipment.AWB,\n reference: shipment.Reference || undefined,\n status,\n statusCode: scan.ScanType,\n statusLabel: scan.ScanDescription,\n timestamp: scanDate(scan),\n raw: shipment,\n };\n });\n}\n\nfunction validateWebhookPayload(payload: unknown): SMSAWebhookShipment[] {\n if (!Array.isArray(payload)) {\n throw new ValidationError(\n \"Invalid SMSA webhook payload: expected an array of shipments\",\n { raw: payload },\n );\n }\n\n if (payload.length === 0) {\n throw new ValidationError(\"Invalid SMSA webhook payload: empty array\", {\n raw: payload,\n });\n }\n\n for (const item of payload) {\n // Only AWB is truly required (it becomes the event's trackingNumber). Scans\n // may be absent on a freshly-booked record — every mapper defends with\n // `Scans ?? []` and yields an \"unknown\" status — so requiring it here would\n // wrongly reject valid scan-less webhooks.\n if (!item || typeof item !== \"object\" || !(\"AWB\" in item)) {\n throw new ValidationError(\n \"Invalid SMSA webhook payload: shipment missing required field (AWB)\",\n { raw: item },\n );\n }\n }\n\n return payload as SMSAWebhookShipment[];\n}\n\n/**\n * Parse an SMSA webhook payload and return a single WebhookEvent\n * for the first shipment in the batch.\n *\n * SMSA sends webhooks as an array of shipments. This function returns\n * only the first item — use `parseSMSAWebhookBatch()` for full batch parsing.\n */\nexport function parseSMSAWebhook(\n payload: unknown,\n options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n },\n): WebhookEvent {\n verifyWebhookAuth(options);\n const shipments = validateWebhookPayload(payload);\n return mapWebhookShipmentToEvent(shipments[0]!);\n}\n\n/**\n * Parse an SMSA webhook payload and return a WebhookEvent for every\n * scan of every shipment in the batch (in chronological order per shipment).\n *\n * SMSA sends webhook payloads as an array of shipments, each with their own\n * tracking scans, and a single call can carry multiple new scans for the\n * same shipment (e.g. AF, OD, DL together) — each becomes its own event so\n * no intermediate transition/history is dropped.\n */\nexport function parseSMSAWebhookBatch(\n payload: unknown,\n options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n },\n): WebhookEvent[] {\n verifyWebhookAuth(options);\n const shipments = validateWebhookPayload(payload);\n return shipments.flatMap(mapWebhookShipmentToEvents);\n}\n",
7
+ "// file: src/carriers/smsaexpress/adapter.ts\n/**\n * SMSA Express Carrier Adapter\n * Full implementation of the CarrierAdapter interface for SMSA Express API.\n */\n\nimport { APIError, RateLimitError } from \"../../core/errors\";\nimport { HttpClient } from \"../../core/http\";\nimport type {\n CarrierConfig,\n City,\n CreateShipmentInput,\n Location,\n Shipment,\n TrackingResult,\n WebhookConfig,\n WebhookEvent,\n} from \"../../core/types\";\nimport { BaseCarrierAdapter } from \"../base\";\nimport {\n generateOrderNumber,\n mapCity,\n mapCreate2WayRequest,\n mapCreateB2CRequest,\n mapCreateC2BRequest,\n mapOffice,\n mapShipmentResponse,\n mapTrackingResult,\n parseSMSAWebhook,\n parseSMSAWebhookBatch,\n} from \"./mappers\";\nimport type {\n SMSACityLookupItem,\n SMSAOfficeLookupItem,\n SMSAPushIdDetailsRequest,\n SMSAPushIdDetailsResponse,\n SMSASendInvoiceRequest,\n SMSAShipmentResponse,\n SMSAShortAddressResponse,\n SMSATrackingResponse,\n} from \"./types\";\n\nconst SMSA_SANDBOX_URL = \"https://ecomapis-sandbox.azurewebsites.net\";\nconst SMSA_PRODUCTION_URL = \"https://ecomapis.smsaexpress.com\";\n\n/** Reverse-pickup / C2B service codes */\nconst C2B_SERVICE_CODES = new Set([\"EDCR\"]);\n\nexport interface SMSAExpressConfig extends CarrierConfig {\n credentials: {\n apiKey: string;\n };\n}\n\nexport class SMSAExpressAdapter extends BaseCarrierAdapter {\n readonly name = \"smsaexpress\";\n readonly supportedCountries = [\n \"SA\",\n \"AE\",\n \"BH\",\n \"EG\",\n \"KW\",\n \"OM\",\n \"QA\",\n \"JO\",\n ];\n\n private http: HttpClient;\n\n constructor(config: SMSAExpressConfig) {\n super(config);\n this.http = new HttpClient({\n baseUrl: this.getBaseUrl(),\n carrier: \"smsaexpress\",\n headers: {\n apikey: config.credentials.apiKey,\n },\n });\n }\n\n protected getBaseUrl(): string {\n return this.config.mode === \"production\"\n ? SMSA_PRODUCTION_URL\n : SMSA_SANDBOX_URL;\n }\n\n // =========================================================================\n // SHIPPING\n // =========================================================================\n\n /**\n * Ensure the input carries a `reference` so the OrderNumber the shipment is\n * booked under is the exact value surfaced back on `Shipment.reference` (which\n * the caller needs for {@link trackByReference}). When no reference is\n * supplied we generate one here via the shared {@link generateOrderNumber} and\n * pass this prepared input to BOTH the request mapper and the response mapper,\n * so the request OrderNumber and the response reference always agree (otherwise\n * each would call `Date.now()` independently and disagree).\n */\n private withOrderNumber(input: CreateShipmentInput): CreateShipmentInput {\n if (input.reference) {\n return input;\n }\n return { ...input, reference: generateOrderNumber() };\n }\n\n protected async executeCreateShipment(\n input: CreateShipmentInput,\n ): Promise<Shipment> {\n const prepared = this.withOrderNumber(input);\n const isC2B =\n prepared.serviceType !== undefined &&\n C2B_SERVICE_CODES.has(prepared.serviceType.trim().toUpperCase());\n\n if (isC2B) {\n return this.createC2BShipment(prepared);\n }\n\n const request = mapCreateB2CRequest(prepared);\n const response = await this.http.post<SMSAShipmentResponse>(\n \"/api/shipment/b2c/new\",\n request,\n );\n\n return mapShipmentResponse(response, prepared);\n }\n\n private async createC2BShipment(\n input: CreateShipmentInput,\n ): Promise<Shipment> {\n const request = mapCreateC2BRequest(input);\n const response = await this.http.post<SMSAShipmentResponse>(\n \"/api/c2b/new\",\n request,\n );\n\n return mapShipmentResponse(response, input);\n }\n\n /**\n * Cancel a reverse-pickup (C2B) shipment.\n *\n * **Important:** SMSA only supports cancellation for C2B/reverse-pickup\n * shipments, via the C2B endpoint. B2C shipments cannot be cancelled through\n * the SMSA API: the endpoint responds without a cancellation confirmation,\n * which is surfaced as an {@link APIError} (with `code: \"CANCELLATION_UNCONFIRMED\"`)\n * rather than a misleading `false`. That `code` lets callers distinguish this\n * \"carrier did not confirm\" case from a genuine request failure (network/4xx/5xx)\n * and fall back accordingly. Consistent with the other adapters, this method\n * either returns `true` or throws.\n *\n * Success is detected with a negation-aware check: a cancel token must be\n * present AND no negation token may be. A bare `includes(\"cancelled\")` would\n * wrongly succeed on negatives like \"Shipment cannot be cancelled\",\n * \"not cancelled\" or \"cancellation failed\". Note \"already cancelled\" carries\n * no negation token, so it correctly reports success (idempotent cancel).\n */\n async cancelShipment(trackingNumber: string): Promise<boolean> {\n const response = await this.http.post<string>(\n `/api/c2b/cancel/${encodeURIComponent(trackingNumber)}`,\n );\n // A non-string (JSON) success response is treated as success.\n if (typeof response !== \"string\") {\n return true;\n }\n // Success messages contain a cancel token (e.g. \"Shipment Cancelled\n // Successfully!\"), but so do negative ones (\"Shipment cannot be cancelled\",\n // \"cancellation failed\"). Treat as success only when a cancel token is\n // present AND no negation token is — so \"already cancelled\" (idempotent\n // success) still passes while \"not cancelled\" does not. Anything else means\n // the carrier did NOT cancel — most commonly because this is a B2C shipment,\n // which the SMSA API cannot cancel.\n const hasCancelToken = /cancel/i.test(response);\n const hasNegation =\n /\\b(?:not|can\\s?not|can'?t|cant|unable|fail(?:ed|ure)?|invalid|reject(?:ed)?|error)\\b/i.test(\n response,\n );\n if (hasCancelToken && !hasNegation) {\n return true;\n }\n // Distinguish \"carrier responded but did not confirm cancellation\" from a\n // genuine request failure (network/4xx/5xx, which throws separately via\n // HttpClient) so callers can tell \"unconfirmed\" apart from \"failed\" and\n // fall back accordingly (e.g. B2C shipments cannot be cancelled via the\n // SMSA API at all).\n throw new APIError(\n response.trim() || \"SMSA did not confirm cancellation\",\n {\n carrier: \"smsaexpress\",\n code: \"CANCELLATION_UNCONFIRMED\",\n raw: response,\n },\n );\n }\n\n // =========================================================================\n // TRACKING\n // =========================================================================\n\n async track(trackingNumber: string): Promise<TrackingResult> {\n const response = await this.http.get<SMSATrackingResponse>(\n `/api/track/single/${encodeURIComponent(trackingNumber)}`,\n );\n\n return mapTrackingResult(response);\n }\n\n async trackMultiple(trackingNumbers: string[]): Promise<TrackingResult[]> {\n const response = await this.http.post<SMSATrackingResponse[]>(\n \"/api/track/bulk/\",\n trackingNumbers,\n { retry: true },\n );\n\n return response.map(mapTrackingResult);\n }\n\n async trackByReference(reference: string): Promise<TrackingResult> {\n const response = await this.http.get<SMSATrackingResponse>(\n `/api/track/reference/${encodeURIComponent(reference)}`,\n );\n\n return mapTrackingResult(response);\n }\n\n // =========================================================================\n // LABELS\n // =========================================================================\n\n async getLabel(\n trackingNumber: string,\n _format?: \"PDF\" | \"ZPL\" | \"PNG\",\n ): Promise<string> {\n // SMSA returns the waybill file (base64 PDF) as part of the shipment query.\n // Try B2C first (most common), then C2B as fallback.\n const encoded = encodeURIComponent(trackingNumber);\n\n for (const path of [\n `/api/shipment/b2c/query/${encoded}`,\n `/api/c2b/query/${encoded}`,\n ]) {\n try {\n const response = await this.http.get<SMSAShipmentResponse>(path);\n const waybill = response.waybills?.[0];\n if (waybill?.awbFile) {\n return `data:application/pdf;base64,${waybill.awbFile}`;\n }\n } catch (error) {\n // Only swallow genuine 4xx (non-429) API errors (shipment type mismatch\n // / not found) so we can try the next path. Re-throw everything else:\n // - non-APIError → auth (AuthenticationError) and network (NetworkError)\n // are not APIErrors, so this clause re-throws them;\n // - RateLimitError → a 429 that must propagate, not masquerade as\n // \"No label found\" (it extends APIError, so guard it explicitly);\n // - 5xx server errors.\n if (\n !(error instanceof APIError) ||\n error instanceof RateLimitError ||\n (error.statusCode !== undefined && error.statusCode >= 500)\n ) {\n throw error;\n }\n }\n }\n\n throw new APIError(\"No label found for shipment\", {\n carrier: \"smsaexpress\",\n raw: { trackingNumber },\n });\n }\n\n // =========================================================================\n // CITIES & LOCATIONS\n // =========================================================================\n\n async getCities(countryCode = \"SA\"): Promise<City[]> {\n const response = await this.http.get<SMSACityLookupItem[]>(\n `/api/lookup/cities/${encodeURIComponent(countryCode)}`,\n );\n\n // Guard the shape before mapping: a non-array (e.g. an error object at\n // HTTP 200) would otherwise throw an opaque \"response.map is not a function\"\n // far from the cause.\n if (!Array.isArray(response)) {\n throw new APIError(\"SMSA cities lookup returned an unexpected shape\", {\n carrier: \"smsaexpress\",\n raw: response,\n });\n }\n\n return response.map(mapCity);\n }\n\n async getDropoffLocations(): Promise<Location[]> {\n const response = await this.http.get<SMSAOfficeLookupItem[]>(\n \"/api/lookup/smsaoffices\",\n );\n\n if (!Array.isArray(response)) {\n throw new APIError(\"SMSA offices lookup returned an unexpected shape\", {\n carrier: \"smsaexpress\",\n raw: response,\n });\n }\n\n return response.map(mapOffice);\n }\n\n // =========================================================================\n // 2-WAY SHIPMENT\n // =========================================================================\n\n async create2WayShipment(input: CreateShipmentInput): Promise<Shipment> {\n const prepared = this.withOrderNumber(input);\n const request = mapCreate2WayRequest(prepared);\n const response = await this.http.post<SMSAShipmentResponse>(\n \"/api/TwoWayShipment/new\",\n request,\n );\n\n return mapShipmentResponse(response, prepared);\n }\n\n // =========================================================================\n // INVOICE & ID OPERATIONS (SMSA-specific)\n // =========================================================================\n\n async sendInvoice(request: SMSASendInvoiceRequest): Promise<string> {\n return this.http.post<string>(\"/api/invoice\", request);\n }\n\n async validateShortAddress(\n shortCode: string,\n ): Promise<SMSAShortAddressResponse> {\n return this.http.get<SMSAShortAddressResponse>(\n `/api/Lookup/FullAddressByShortCode/${encodeURIComponent(shortCode)}`,\n );\n }\n\n async pushIdDetails(\n request: SMSAPushIdDetailsRequest,\n ): Promise<SMSAPushIdDetailsResponse> {\n return this.http.post<SMSAPushIdDetailsResponse>(\n \"/api/shipment/identity-details\",\n request,\n );\n }\n\n // =========================================================================\n // WEBHOOKS\n // =========================================================================\n\n parseWebhook(\n payload: unknown,\n options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n },\n ): WebhookEvent {\n return parseSMSAWebhook(payload, options);\n }\n\n /**\n * Parse a batch SMSA webhook payload into multiple events.\n *\n * SMSA sends webhook payloads as an array of shipments, each with their own\n * tracking scans. This method returns a WebhookEvent for every shipment\n * in the payload.\n */\n parseWebhookBatch(\n payload: unknown,\n options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n },\n ): WebhookEvent[] {\n return parseSMSAWebhookBatch(payload, options);\n }\n}\n"
8
8
  ],
9
- "mappings": "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",
10
- "debugId": "E494CE182FFD4CD964756E2164756E21",
9
+ "mappings": "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",
10
+ "debugId": "AF4035876BEB4A1F64756E2164756E21",
11
11
  "names": []
12
12
  }
@@ -5,6 +5,13 @@
5
5
  import type { City, CreateShipmentInput, Location, Shipment, ShipmentStatus, TrackingEvent, TrackingResult, WebhookConfig, WebhookEvent } from "../../core/types.js";
6
6
  import type { SMSACityLookupItem, SMSACreate2WayShipmentRequest, SMSACreateB2CShipmentRequest, SMSACreateC2BShipmentRequest, SMSAOfficeLookupItem, SMSAShipmentResponse, SMSATrackingResponse, SMSATrackingScan } from "./types.js";
7
7
  export declare function mapSMSAStatus(scanType: string): ShipmentStatus;
8
+ /**
9
+ * Generate a fallback OrderNumber when the caller supplies no reference. Single
10
+ * source of the `ORD-<epoch-ms>` format — the adapter uses it to fix a reference
11
+ * on the input so the booked OrderNumber is surfaced back on the Shipment, and
12
+ * the request mappers reference it as their type-required default.
13
+ */
14
+ export declare function generateOrderNumber(): string;
8
15
  export declare function mapCreateB2CRequest(input: CreateShipmentInput): SMSACreateB2CShipmentRequest;
9
16
  /**
10
17
  * Map to C2B (reverse pickup) request.
@@ -32,10 +39,12 @@ export declare function parseSMSAWebhook(payload: unknown, options?: {
32
39
  }): WebhookEvent;
33
40
  /**
34
41
  * Parse an SMSA webhook payload and return a WebhookEvent for every
35
- * shipment in the batch.
42
+ * scan of every shipment in the batch (in chronological order per shipment).
36
43
  *
37
- * SMSA sends webhook payloads as an array of shipments, each with
38
- * their own tracking scans.
44
+ * SMSA sends webhook payloads as an array of shipments, each with their own
45
+ * tracking scans, and a single call can carry multiple new scans for the
46
+ * same shipment (e.g. AF, OD, DL together) — each becomes its own event so
47
+ * no intermediate transition/history is dropped.
39
48
  */
40
49
  export declare function parseSMSAWebhookBatch(payload: unknown, options?: {
41
50
  headers?: Record<string, string>;
@@ -1 +1 @@
1
- {"version":3,"file":"mappers.d.ts","sourceRoot":"","sources":["../../../src/carriers/smsaexpress/mappers.ts"],"names":[],"mappings":"AACA;;;GAGG;AAOH,OAAO,KAAK,EACV,IAAI,EACJ,mBAAmB,EACnB,QAAQ,EACR,QAAQ,EACR,cAAc,EACd,aAAa,EACb,cAAc,EACd,aAAa,EACb,YAAY,EACb,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EACV,kBAAkB,EAClB,6BAA6B,EAC7B,4BAA4B,EAC5B,4BAA4B,EAC5B,oBAAoB,EAEpB,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAEjB,MAAM,SAAS,CAAC;AAMjB,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAE9D;AAqED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,mBAAmB,GACzB,4BAA4B,CAwC9B;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,mBAAmB,GACzB,4BAA4B,CA+B9B;AAMD,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,oBAAoB,EAC1B,KAAK,EAAE,mBAAmB,GACzB,QAAQ,CAwCV;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,aAAa,CAYtE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,oBAAoB,GAAG,cAAc,CA4B5E;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI,CAKtD;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,oBAAoB,GAAG,QAAQ,CAahE;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,mBAAmB,GACzB,6BAA6B,CAgC/B;AA0HD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE;IACR,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB,GACA,YAAY,CAId;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE;IACR,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB,GACA,YAAY,EAAE,CAIhB"}
1
+ {"version":3,"file":"mappers.d.ts","sourceRoot":"","sources":["../../../src/carriers/smsaexpress/mappers.ts"],"names":[],"mappings":"AACA;;;GAGG;AAOH,OAAO,KAAK,EACV,IAAI,EACJ,mBAAmB,EACnB,QAAQ,EACR,QAAQ,EACR,cAAc,EACd,aAAa,EACb,cAAc,EACd,aAAa,EACb,YAAY,EACb,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EACV,kBAAkB,EAClB,6BAA6B,EAC7B,4BAA4B,EAC5B,4BAA4B,EAC5B,oBAAoB,EAEpB,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAEjB,MAAM,SAAS,CAAC;AAMjB,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAE9D;AA8DD;;;;;GAKG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AA4CD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,mBAAmB,GACzB,4BAA4B,CA0C9B;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,mBAAmB,GACzB,4BAA4B,CAiC9B;AAMD,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,oBAAoB,EAC1B,KAAK,EAAE,mBAAmB,GACzB,QAAQ,CAwCV;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,aAAa,CAYtE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,oBAAoB,GAAG,cAAc,CA4B5E;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI,CAKtD;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,oBAAoB,GAAG,QAAQ,CAahE;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,mBAAmB,GACzB,6BAA6B,CAkC/B;AAiKD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE;IACR,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB,GACA,YAAY,CAId;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE;IACR,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB,GACA,YAAY,EAAE,CAIhB"}
@@ -15,7 +15,9 @@ export type SMSAServiceType = (typeof SMSAService)[keyof typeof SMSAService];
15
15
  * SMSA Express Scan Type Codes → Unified ShipmentStatus mapping.
16
16
  *
17
17
  * Based on /api/track/statuslookup response.
18
- * Scan types not explicitly listed default to "in_transit".
18
+ * Scan types not explicitly listed default to "unknown" (see mapSMSAStatus)
19
+ * so operators can detect unmapped codes instead of silently treating them
20
+ * as in-transit.
19
21
  */
20
22
  export declare const SMSAStatusCodes: Record<string, string>;
21
23
  //# sourceMappingURL=services.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../../../src/carriers/smsaexpress/services.ts"],"names":[],"mappings":"AACA;;;GAGG;AAEH,eAAO,MAAM,WAAW;IACtB,0BAA0B;;IAE1B,uBAAuB;;IAEvB,2BAA2B;;CAEnB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,CAAC;AAE7E;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAwClD,CAAC"}
1
+ {"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../../../src/carriers/smsaexpress/services.ts"],"names":[],"mappings":"AACA;;;GAGG;AAEH,eAAO,MAAM,WAAW;IACtB,0BAA0B;;IAE1B,uBAAuB;;IAEvB,2BAA2B;;CAEnB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,CAAC;AAE7E;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAwClD,CAAC"}
@@ -79,6 +79,14 @@ export declare class HttpClient {
79
79
  private isRetryable;
80
80
  private executeRequest;
81
81
  private handleHttpError;
82
+ /** Cap on sanitized non-JSON error bodies (e.g. HTML gateway/proxy pages). */
83
+ private static readonly MAX_ERROR_TEXT_LENGTH;
84
+ /**
85
+ * Strip markup and cap the length of a raw (non-JSON) error body so a
86
+ * carrier's HTML gateway/proxy error page doesn't get dumped verbatim into
87
+ * the thrown error's message.
88
+ */
89
+ private sanitizeErrorText;
82
90
  private extractErrorMessage;
83
91
  private extractValidationErrors;
84
92
  /** Create GET request */
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/core/http.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;GAYG;AAWH,MAAM,WAAW,WAAW;IAC1B,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;IAC/D;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,0EAA0E;IAC1E,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK;QAClC,QAAQ,EAAE,OAAO,CAAC;QAClB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAClC,oEAAoE;QACpE,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,+DAA+D;QAC/D,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;CACH;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,WAAW,CAMjB;IAEF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAA6B;IACtE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAuC;gBAE/D,MAAM,EAAE,gBAAgB;IAmB9B,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,CAAC,CAAC;IAoB5E;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,WAAW;YAYL,cAAc;IA8I5B,OAAO,CAAC,eAAe;IAgCvB,OAAO,CAAC,mBAAmB;IAe3B,OAAO,CAAC,uBAAuB;IAc/B,yBAAyB;IACzB,GAAG,CAAC,CAAC,EACH,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,MAAM,CAAC,GAChD,OAAO,CAAC,CAAC,CAAC;IAIb,0BAA0B;IAC1B,IAAI,CAAC,CAAC,EACJ,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,MAAM,CAAC,GAChD,OAAO,CAAC,CAAC,CAAC;IAIb,yBAAyB;IACzB,GAAG,CAAC,CAAC,EACH,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,MAAM,CAAC,GAChD,OAAO,CAAC,CAAC,CAAC;IAIb,4BAA4B;IAC5B,MAAM,CAAC,CAAC,EACN,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,GACvC,OAAO,CAAC,CAAC,CAAC;CAGd"}
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/core/http.ts"],"names":[],"mappings":"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"}