shipflow 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/dist/carriers/aramex/adapter.d.ts.map +1 -1
- package/dist/carriers/aramex/index.js +12 -11
- package/dist/carriers/aramex/index.js.map +4 -4
- package/dist/carriers/aramex/mappers.d.ts +13 -5
- package/dist/carriers/aramex/mappers.d.ts.map +1 -1
- package/dist/carriers/aymakan/adapter.d.ts +17 -21
- package/dist/carriers/aymakan/adapter.d.ts.map +1 -1
- package/dist/carriers/aymakan/index.js +67 -53
- package/dist/carriers/aymakan/index.js.map +4 -4
- package/dist/carriers/aymakan/mappers.d.ts +2 -0
- package/dist/carriers/aymakan/mappers.d.ts.map +1 -1
- package/dist/carriers/aymakan/types.d.ts +1 -1
- package/dist/carriers/aymakan/types.d.ts.map +1 -1
- package/dist/carriers/smsaexpress/adapter.d.ts +43 -4
- package/dist/carriers/smsaexpress/adapter.d.ts.map +1 -1
- package/dist/carriers/smsaexpress/index.js +141 -25
- package/dist/carriers/smsaexpress/index.js.map +5 -5
- package/dist/carriers/smsaexpress/mappers.d.ts +12 -3
- package/dist/carriers/smsaexpress/mappers.d.ts.map +1 -1
- package/dist/carriers/smsaexpress/services.d.ts +3 -1
- package/dist/carriers/smsaexpress/services.d.ts.map +1 -1
- package/dist/core/city-resolver.d.ts +44 -0
- package/dist/core/city-resolver.d.ts.map +1 -0
- package/dist/core/http.d.ts +8 -0
- package/dist/core/http.d.ts.map +1 -1
- package/dist/core/retry.d.ts +8 -3
- package/dist/core/retry.d.ts.map +1 -1
- package/dist/{index-qnxj8bct.js → index-8cpdcbsf.js} +32 -6
- package/dist/{index-qnxj8bct.js.map → index-8cpdcbsf.js.map} +5 -5
- package/dist/index-dfx6rbm2.js +36 -0
- package/dist/index-dfx6rbm2.js.map +10 -0
- package/dist/index.js +3 -3
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
|
@@ -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 \"
|
|
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 {\n CITIES_CACHE_TTL,\n CITIES_FAILURE_COOLDOWN,\n findCityMatch,\n} from \"../../core/city-resolver\";\nimport { APIError, RateLimitError, ValidationError } 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 /** Cached SMSA Saudi cities list for city validation on booking */\n private citiesCache: City[] | null = null;\n private citiesCacheTime = 0;\n /** Timestamp of the last failed cities-lookup attempt, if any */\n private citiesFetchFailedAt = 0;\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 /**\n * Load the SMSA Saudi cities list and cache it, mirroring the Aymakan\n * adapter's TTL + failure-cooldown behavior: a lookup outage falls back to\n * an empty cache (pass-through resolution) so it never blocks bookings.\n */\n private async ensureCitiesLoaded(): Promise<void> {\n const now = Date.now();\n if (this.citiesCache && now - this.citiesCacheTime < CITIES_CACHE_TTL) {\n return;\n }\n if (now - this.citiesFetchFailedAt < CITIES_FAILURE_COOLDOWN) {\n if (!this.citiesCache) this.citiesCache = [];\n return;\n }\n try {\n this.citiesCache = await this.getCities();\n this.citiesCacheTime = now;\n } catch {\n this.citiesFetchFailedAt = now;\n if (!this.citiesCache) this.citiesCache = [];\n }\n }\n\n /**\n * Validate/normalize a Saudi address city against SMSA's own city list\n * before booking (SMSA's City field is list-validated server-side, so an\n * unknown value previously produced an opaque carrier rejection). Strict:\n * when the list is loaded and nothing matches, fail fast with a clear,\n * field-scoped error. Lenient when the list is unavailable (outage) and for\n * non-SA addresses, whose cities are not in the SA lookup.\n */\n private resolveCityStrict(\n address: CreateShipmentInput[\"shipper\"] | CreateShipmentInput[\"consignee\"],\n field: \"shipper.city\" | \"consignee.city\",\n ): string {\n if (address.countryCode !== \"SA\") return address.city;\n if (!this.citiesCache || this.citiesCache.length === 0) return address.city;\n const match = findCityMatch(address.city, this.citiesCache);\n if (!match) {\n throw new ValidationError(\n `Unknown ${field.replace(\".city\", \"\")} city \"${address.city}\": not in SMSA's supported city list`,\n { field },\n );\n }\n return match.nameEn;\n }\n\n /** Resolve shipper + consignee cities for a booking (strict — see above). */\n private async resolveCitiesInInput(\n input: CreateShipmentInput,\n ): Promise<CreateShipmentInput> {\n await this.ensureCitiesLoaded();\n return {\n ...input,\n shipper: {\n ...input.shipper,\n city: this.resolveCityStrict(input.shipper, \"shipper.city\"),\n },\n consignee: {\n ...input.consignee,\n city: this.resolveCityStrict(input.consignee, \"consignee.city\"),\n },\n };\n }\n\n protected async executeCreateShipment(\n input: CreateShipmentInput,\n ): Promise<Shipment> {\n const prepared = await this.resolveCitiesInInput(\n this.withOrderNumber(input),\n );\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 = await this.resolveCitiesInInput(\n this.withOrderNumber(input),\n );\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": "
|
|
9
|
+
"mappings": "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",
|
|
10
|
+
"debugId": "87215A69EE27862E64756E2164756E21",
|
|
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
|
-
*
|
|
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;
|
|
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 "
|
|
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
|
|
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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared city matching for carrier adapters that validate the shipper /
|
|
3
|
+
* consignee city against the carrier's own city list before booking.
|
|
4
|
+
*
|
|
5
|
+
* Extracted verbatim from the Aymakan adapter's proven matching strategy so
|
|
6
|
+
* SMSA (and future carriers) enforce identical semantics. The strategy never
|
|
7
|
+
* silently reroutes a distinct longer city to a shorter candidate (see the
|
|
8
|
+
* step-5 direction guard).
|
|
9
|
+
*/
|
|
10
|
+
import type { City } from "./types.js";
|
|
11
|
+
/** How long a fetched carrier city list stays fresh (1 hour). */
|
|
12
|
+
export declare const CITIES_CACHE_TTL: number;
|
|
13
|
+
/**
|
|
14
|
+
* After a failed city-list fetch, don't retry for this long (1 minute) —
|
|
15
|
+
* adapters fall back to pass-through resolution so a carrier /cities outage
|
|
16
|
+
* never hard-blocks shipment creation.
|
|
17
|
+
*/
|
|
18
|
+
export declare const CITIES_FAILURE_COOLDOWN: number;
|
|
19
|
+
/**
|
|
20
|
+
* Normalize Arabic text for comparison:
|
|
21
|
+
* - Strip diacritics (tashkeel)
|
|
22
|
+
* - Normalize alef variants (أ إ آ → ا)
|
|
23
|
+
* - Normalize taa marbuta (ة → ه)
|
|
24
|
+
* - Trim whitespace
|
|
25
|
+
*/
|
|
26
|
+
export declare function normalizeArabicCity(text: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Match a user-input city name against a carrier city list.
|
|
29
|
+
*
|
|
30
|
+
* Matching strategy (first match wins):
|
|
31
|
+
* 1. Exact match on English name (case-insensitive)
|
|
32
|
+
* 2. Exact match on Arabic name
|
|
33
|
+
* 3. Normalized Arabic match (handles tashkeel, alef variants, taa marbuta)
|
|
34
|
+
* 4. Match after toggling the "ال" prefix on the Arabic input
|
|
35
|
+
* 5. Candidate-contains-input match on English name (candidate name contains
|
|
36
|
+
* the input, e.g. "Riyadh" -> "Riyadh City"; requires input length >= 3).
|
|
37
|
+
* The reverse direction is intentionally excluded so a distinct longer
|
|
38
|
+
* city like "Riyadh Al Khabra" is never rerouted to "Riyadh".
|
|
39
|
+
*
|
|
40
|
+
* Returns null when nothing matches — the caller decides whether that means
|
|
41
|
+
* pass-through (lenient) or a validation error (strict).
|
|
42
|
+
*/
|
|
43
|
+
export declare function findCityMatch(inputCity: string, cities: City[]): City | null;
|
|
44
|
+
//# sourceMappingURL=city-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"city-resolver.d.ts","sourceRoot":"","sources":["../../src/core/city-resolver.ts"],"names":[],"mappings":"AACA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAEpC,iEAAiE;AACjE,eAAO,MAAM,gBAAgB,QAAiB,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,QAAY,CAAC;AAEjD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMxD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,GAAG,IAAI,CA4C5E"}
|
package/dist/core/http.d.ts
CHANGED
|
@@ -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 */
|
package/dist/core/http.d.ts.map
CHANGED
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/core/http.ts"],"names":[],"mappings":"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"}
|
package/dist/core/retry.d.ts
CHANGED
|
@@ -8,9 +8,14 @@
|
|
|
8
8
|
* request-bound contexts, and Retry-After is a contract — don't retry early)
|
|
9
9
|
* - otherwise → full-jitter exponential backoff
|
|
10
10
|
*
|
|
11
|
-
* The inter-attempt sleep uses
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* The inter-attempt sleep uses a Web-standard abortable `sleep` helper (defined
|
|
12
|
+
* below) rather than `node:timers/promises`, so ShipFlow stays runtime-agnostic:
|
|
13
|
+
* `node:timers/promises` requires `nodejs_compat` on Cloudflare Workers and fails
|
|
14
|
+
* at module load on other edge runtimes — even for callers who never retry. The
|
|
15
|
+
* helper is built deliberately on globals available on Node 18+, Deno, Bun, and
|
|
16
|
+
* edge/workers (setTimeout/clearTimeout, DOMException, AbortSignal), and removes
|
|
17
|
+
* its `abort` listener on normal resolution so a caller's signal never
|
|
18
|
+
* accumulates leaked listeners across attempts.
|
|
14
19
|
*/
|
|
15
20
|
export interface RetryOptions {
|
|
16
21
|
/** Max number of retries after the first attempt. */
|
package/dist/core/retry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/core/retry.ts"],"names":[],"mappings":"AACA
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/core/retry.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;GAkBG;AA0BH,MAAM,WAAW,YAAY;IAC3B,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IACzC,0EAA0E;IAC1E,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;IACtD,sFAAsF;IACtF,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,wBAAsB,SAAS,CAAC,CAAC,EAC/B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,CAAC,CAAC,CAuCZ;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACrC,MAAM,GAAG,SAAS,CAUpB"}
|
|
@@ -801,7 +801,23 @@ class BaseCarrierAdapter {
|
|
|
801
801
|
}
|
|
802
802
|
|
|
803
803
|
// src/core/retry.ts
|
|
804
|
-
|
|
804
|
+
function sleep(ms, signal) {
|
|
805
|
+
return new Promise((resolve, reject) => {
|
|
806
|
+
if (signal?.aborted) {
|
|
807
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
const onAbort = () => {
|
|
811
|
+
clearTimeout(timer);
|
|
812
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
813
|
+
};
|
|
814
|
+
const timer = setTimeout(() => {
|
|
815
|
+
signal?.removeEventListener("abort", onAbort);
|
|
816
|
+
resolve();
|
|
817
|
+
}, ms);
|
|
818
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
819
|
+
});
|
|
820
|
+
}
|
|
805
821
|
async function withRetry(fn, options) {
|
|
806
822
|
const {
|
|
807
823
|
retries,
|
|
@@ -828,7 +844,7 @@ async function withRetry(fn, options) {
|
|
|
828
844
|
delay = Math.random() * Math.min(maxMs, baseMs * 2 ** attempt);
|
|
829
845
|
}
|
|
830
846
|
try {
|
|
831
|
-
await sleep(delay,
|
|
847
|
+
await sleep(delay, signal);
|
|
832
848
|
} catch {
|
|
833
849
|
throw error;
|
|
834
850
|
}
|
|
@@ -935,7 +951,7 @@ class HttpClient {
|
|
|
935
951
|
...this.config.headers,
|
|
936
952
|
...headers
|
|
937
953
|
},
|
|
938
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
954
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
939
955
|
signal
|
|
940
956
|
});
|
|
941
957
|
let json;
|
|
@@ -1023,9 +1039,19 @@ class HttpClient {
|
|
|
1023
1039
|
raw: json
|
|
1024
1040
|
});
|
|
1025
1041
|
}
|
|
1042
|
+
static MAX_ERROR_TEXT_LENGTH = 500;
|
|
1043
|
+
sanitizeErrorText(text) {
|
|
1044
|
+
const stripped = text.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
|
|
1045
|
+
if (stripped.length > HttpClient.MAX_ERROR_TEXT_LENGTH) {
|
|
1046
|
+
return `${stripped.slice(0, HttpClient.MAX_ERROR_TEXT_LENGTH)}... [truncated]`;
|
|
1047
|
+
}
|
|
1048
|
+
return stripped;
|
|
1049
|
+
}
|
|
1026
1050
|
extractErrorMessage(json) {
|
|
1027
|
-
if (typeof json === "string")
|
|
1028
|
-
|
|
1051
|
+
if (typeof json === "string") {
|
|
1052
|
+
const trimmed = json.trim();
|
|
1053
|
+
return trimmed ? this.sanitizeErrorText(trimmed) : undefined;
|
|
1054
|
+
}
|
|
1029
1055
|
if (!json || typeof json !== "object")
|
|
1030
1056
|
return;
|
|
1031
1057
|
const obj = json;
|
|
@@ -1064,4 +1090,4 @@ class HttpClient {
|
|
|
1064
1090
|
|
|
1065
1091
|
export { ShipFlowError, NetworkError, APIError, RateLimitError, ValidationError, AuthenticationError, WebhookVerificationError, UnsupportedOperationError, AddressSchema, WeightSchema, DimensionsSchema, ParcelSchema, CODDetailsSchema, DeclaredValueSchema, CreateShipmentInputSchema, PickupRequestSchema, WebhookConfigSchema, validateCreateShipmentInput, validatePickupRequest, BaseCarrierAdapter, HttpClient };
|
|
1066
1092
|
|
|
1067
|
-
//# debugId=
|
|
1093
|
+
//# debugId=2E558E460F11A10664756E2164756E21
|