@freighttech/shipment-tracking 0.6.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/LICENSE +21 -0
- package/NOTICE +25 -0
- package/dist/generated/entities/bic_config/index.js +21 -0
- package/dist/generated/entities/bic_config/index.js.map +7 -0
- package/dist/generated/entities/carrier_config/index.js +27 -0
- package/dist/generated/entities/carrier_config/index.js.map +7 -0
- package/dist/generated/entities/location_override/index.js +33 -0
- package/dist/generated/entities/location_override/index.js.map +7 -0
- package/dist/generated/entities/shipment/index.js +73 -0
- package/dist/generated/entities/shipment/index.js.map +7 -0
- package/dist/generated/entities/ships_go_config/index.js +31 -0
- package/dist/generated/entities/ships_go_config/index.js.map +7 -0
- package/dist/generated/entities/tracking_event/index.js +101 -0
- package/dist/generated/entities/tracking_event/index.js.map +7 -0
- package/dist/generated/entities/tracking_job/index.js +47 -0
- package/dist/generated/entities/tracking_job/index.js.map +7 -0
- package/dist/generated/entities/webhook/index.js +25 -0
- package/dist/generated/entities/webhook/index.js.map +7 -0
- package/dist/generated/entities/webhook_delivery/index.js +25 -0
- package/dist/generated/entities/webhook_delivery/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +21 -0
- package/dist/generated/entities.ids.generated.js.map +7 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +7 -0
- package/dist/modules/shipment_tracking/acl.js +22 -0
- package/dist/modules/shipment_tracking/acl.js.map +7 -0
- package/dist/modules/shipment_tracking/api/bic-configs/route.js +176 -0
- package/dist/modules/shipment_tracking/api/bic-configs/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/bic-configs/test/route.js +95 -0
- package/dist/modules/shipment_tracking/api/bic-configs/test/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/cargo-events/route.js +93 -0
- package/dist/modules/shipment_tracking/api/cargo-events/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/carrier-configs/route.js +139 -0
- package/dist/modules/shipment_tracking/api/carrier-configs/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/dashboard/ships-map/route.js +177 -0
- package/dist/modules/shipment_tracking/api/dashboard/ships-map/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/location-overrides/route.js +179 -0
- package/dist/modules/shipment_tracking/api/location-overrides/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/openapi.js +27 -0
- package/dist/modules/shipment_tracking/api/openapi.js.map +7 -0
- package/dist/modules/shipment_tracking/api/shipments/route.js +192 -0
- package/dist/modules/shipment_tracking/api/shipments/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/shipsgo-configs/route.js +185 -0
- package/dist/modules/shipment_tracking/api/shipsgo-configs/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/tracking-jobs/route.js +144 -0
- package/dist/modules/shipment_tracking/api/tracking-jobs/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/tracking-jobs/sync/route.js +107 -0
- package/dist/modules/shipment_tracking/api/tracking-jobs/sync/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/utils.js +12 -0
- package/dist/modules/shipment_tracking/api/utils.js.map +7 -0
- package/dist/modules/shipment_tracking/api/webhooks/deliveries/route.js +76 -0
- package/dist/modules/shipment_tracking/api/webhooks/deliveries/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/webhooks/route.js +135 -0
- package/dist/modules/shipment_tracking/api/webhooks/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/webhooks/test/route.js +71 -0
- package/dist/modules/shipment_tracking/api/webhooks/test/route.js.map +7 -0
- package/dist/modules/shipment_tracking/assets/carriers/cma_cgm.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/cosco.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/evergreen.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/hapag_lloyd.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/maersk.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/msc.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/one.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/oocl.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/yang_ming.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/zim.png +0 -0
- package/dist/modules/shipment_tracking/assets/index.js +52 -0
- package/dist/modules/shipment_tracking/assets/index.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/carrier-configs/page.js +621 -0
- package/dist/modules/shipment_tracking/backend/carrier-configs/page.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/carrier-configs/page.meta.js +19 -0
- package/dist/modules/shipment_tracking/backend/carrier-configs/page.meta.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/location-overrides/page.js +198 -0
- package/dist/modules/shipment_tracking/backend/location-overrides/page.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/location-overrides/page.meta.js +19 -0
- package/dist/modules/shipment_tracking/backend/location-overrides/page.meta.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/shipment-tracking/page.js +279 -0
- package/dist/modules/shipment_tracking/backend/shipment-tracking/page.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/shipment-tracking/page.meta.js +19 -0
- package/dist/modules/shipment_tracking/backend/shipment-tracking/page.meta.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/tracking-jobs/page.js +10 -0
- package/dist/modules/shipment_tracking/backend/tracking-jobs/page.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/tracking-jobs/page.meta.js +19 -0
- package/dist/modules/shipment_tracking/backend/tracking-jobs/page.meta.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/webhooks/page.js +198 -0
- package/dist/modules/shipment_tracking/backend/webhooks/page.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/webhooks/page.meta.js +19 -0
- package/dist/modules/shipment_tracking/backend/webhooks/page.meta.js.map +7 -0
- package/dist/modules/shipment_tracking/ce.js +52 -0
- package/dist/modules/shipment_tracking/ce.js.map +7 -0
- package/dist/modules/shipment_tracking/cli.js +229 -0
- package/dist/modules/shipment_tracking/cli.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/bic-configs.js +66 -0
- package/dist/modules/shipment_tracking/commands/bic-configs.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/carrier-configs.js +65 -0
- package/dist/modules/shipment_tracking/commands/carrier-configs.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/index.js +9 -0
- package/dist/modules/shipment_tracking/commands/index.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/location-overrides.js +93 -0
- package/dist/modules/shipment_tracking/commands/location-overrides.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/shipments.js +200 -0
- package/dist/modules/shipment_tracking/commands/shipments.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/shipsgo-configs.js +75 -0
- package/dist/modules/shipment_tracking/commands/shipsgo-configs.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/tracking-jobs.js +120 -0
- package/dist/modules/shipment_tracking/commands/tracking-jobs.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/tracking.js +140 -0
- package/dist/modules/shipment_tracking/commands/tracking.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/webhooks.js +115 -0
- package/dist/modules/shipment_tracking/commands/webhooks.js.map +7 -0
- package/dist/modules/shipment_tracking/components/CombinedTimestampCell.js +168 -0
- package/dist/modules/shipment_tracking/components/CombinedTimestampCell.js.map +7 -0
- package/dist/modules/shipment_tracking/components/FacilityLocationInput.js +202 -0
- package/dist/modules/shipment_tracking/components/FacilityLocationInput.js.map +7 -0
- package/dist/modules/shipment_tracking/components/LocationOverrideDrawer.js +501 -0
- package/dist/modules/shipment_tracking/components/LocationOverrideDrawer.js.map +7 -0
- package/dist/modules/shipment_tracking/components/ShipmentDetailsDrawer.js +818 -0
- package/dist/modules/shipment_tracking/components/ShipmentDetailsDrawer.js.map +7 -0
- package/dist/modules/shipment_tracking/components/ShipmentDrawer.js +404 -0
- package/dist/modules/shipment_tracking/components/ShipmentDrawer.js.map +7 -0
- package/dist/modules/shipment_tracking/components/ShipsGoConfigSection.js +200 -0
- package/dist/modules/shipment_tracking/components/ShipsGoConfigSection.js.map +7 -0
- package/dist/modules/shipment_tracking/components/TimestampCell.js +108 -0
- package/dist/modules/shipment_tracking/components/TimestampCell.js.map +7 -0
- package/dist/modules/shipment_tracking/components/TrackingJobDrawer.js +343 -0
- package/dist/modules/shipment_tracking/components/TrackingJobDrawer.js.map +7 -0
- package/dist/modules/shipment_tracking/components/VesselTrackingMap.js +496 -0
- package/dist/modules/shipment_tracking/components/VesselTrackingMap.js.map +7 -0
- package/dist/modules/shipment_tracking/components/WebhookDrawer.js +332 -0
- package/dist/modules/shipment_tracking/components/WebhookDrawer.js.map +7 -0
- package/dist/modules/shipment_tracking/data/entities.js +686 -0
- package/dist/modules/shipment_tracking/data/entities.js.map +7 -0
- package/dist/modules/shipment_tracking/data/validators.js +434 -0
- package/dist/modules/shipment_tracking/data/validators.js.map +7 -0
- package/dist/modules/shipment_tracking/di.js +60 -0
- package/dist/modules/shipment_tracking/di.js.map +7 -0
- package/dist/modules/shipment_tracking/encryption.js +12 -0
- package/dist/modules/shipment_tracking/encryption.js.map +7 -0
- package/dist/modules/shipment_tracking/events.js +87 -0
- package/dist/modules/shipment_tracking/events.js.map +7 -0
- package/dist/modules/shipment_tracking/hooks/useVesselTracking.js +137 -0
- package/dist/modules/shipment_tracking/hooks/useVesselTracking.js.map +7 -0
- package/dist/modules/shipment_tracking/i18n/de.json +327 -0
- package/dist/modules/shipment_tracking/i18n/en.json +327 -0
- package/dist/modules/shipment_tracking/i18n/es.json +327 -0
- package/dist/modules/shipment_tracking/i18n/pl.json +327 -0
- package/dist/modules/shipment_tracking/index.js +15 -0
- package/dist/modules/shipment_tracking/index.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/cma-cgm.js +66 -0
- package/dist/modules/shipment_tracking/lib/adapters/cma-cgm.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/cosco-types.js +1 -0
- package/dist/modules/shipment_tracking/lib/adapters/cosco-types.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/cosco.js +236 -0
- package/dist/modules/shipment_tracking/lib/adapters/cosco.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/evergreen.js +87 -0
- package/dist/modules/shipment_tracking/lib/adapters/evergreen.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/hapag-lloyd.js +68 -0
- package/dist/modules/shipment_tracking/lib/adapters/hapag-lloyd.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/index.js +20 -0
- package/dist/modules/shipment_tracking/lib/adapters/index.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/maersk.js +84 -0
- package/dist/modules/shipment_tracking/lib/adapters/maersk.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/msc.js +170 -0
- package/dist/modules/shipment_tracking/lib/adapters/msc.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/zim.js +85 -0
- package/dist/modules/shipment_tracking/lib/adapters/zim.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/audit-helpers.js +20 -0
- package/dist/modules/shipment_tracking/lib/audit-helpers.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/auth/base64url.js +8 -0
- package/dist/modules/shipment_tracking/lib/auth/base64url.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/auth/oauth-client.js +38 -0
- package/dist/modules/shipment_tracking/lib/auth/oauth-client.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/background.js +15 -0
- package/dist/modules/shipment_tracking/lib/background.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/bic-api-client.js +106 -0
- package/dist/modules/shipment_tracking/lib/bic-api-client.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/carrier-adapter.js +17 -0
- package/dist/modules/shipment_tracking/lib/carrier-adapter.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/current-vessel.js +149 -0
- package/dist/modules/shipment_tracking/lib/current-vessel.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/dcsa-event-mapping.js +124 -0
- package/dist/modules/shipment_tracking/lib/dcsa-event-mapping.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/dcsa-event-parser.js +112 -0
- package/dist/modules/shipment_tracking/lib/dcsa-event-parser.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/dcsa-params.js +16 -0
- package/dist/modules/shipment_tracking/lib/dcsa-params.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/empty-return.js +48 -0
- package/dist/modules/shipment_tracking/lib/empty-return.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/location-overrides.js +85 -0
- package/dist/modules/shipment_tracking/lib/location-overrides.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/location-types.js +59 -0
- package/dist/modules/shipment_tracking/lib/location-types.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/logger.js +113 -0
- package/dist/modules/shipment_tracking/lib/logger.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/poi-event-processor.js +183 -0
- package/dist/modules/shipment_tracking/lib/poi-event-processor.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/poi-nats-config.js +52 -0
- package/dist/modules/shipment_tracking/lib/poi-nats-config.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/poi-types.js +43 -0
- package/dist/modules/shipment_tracking/lib/poi-types.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/rate-limiter.js +31 -0
- package/dist/modules/shipment_tracking/lib/rate-limiter.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/route-extraction.js +187 -0
- package/dist/modules/shipment_tracking/lib/route-extraction.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/route-inference.js +106 -0
- package/dist/modules/shipment_tracking/lib/route-inference.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/schedule-generator.js +63 -0
- package/dist/modules/shipment_tracking/lib/schedule-generator.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/shipsgo-client.js +143 -0
- package/dist/modules/shipment_tracking/lib/shipsgo-client.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/shipsgo-mapper.js +204 -0
- package/dist/modules/shipment_tracking/lib/shipsgo-mapper.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/status-machine.js +131 -0
- package/dist/modules/shipment_tracking/lib/status-machine.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/time-extraction.js +41 -0
- package/dist/modules/shipment_tracking/lib/time-extraction.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/timestamp-utils.js +100 -0
- package/dist/modules/shipment_tracking/lib/timestamp-utils.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/vessel-api.js +111 -0
- package/dist/modules/shipment_tracking/lib/vessel-api.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/vessel-cache.js +50 -0
- package/dist/modules/shipment_tracking/lib/vessel-cache.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/vessel-extraction.js +13 -0
- package/dist/modules/shipment_tracking/lib/vessel-extraction.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/webhook-dispatcher.js +62 -0
- package/dist/modules/shipment_tracking/lib/webhook-dispatcher.js.map +7 -0
- package/dist/modules/shipment_tracking/migrations/Migration20260422161208_shipment_tracking.js +48 -0
- package/dist/modules/shipment_tracking/migrations/Migration20260422161208_shipment_tracking.js.map +7 -0
- package/dist/modules/shipment_tracking/migrations/Migration20260529120000_shipment_tracking_poi_region_names.js +15 -0
- package/dist/modules/shipment_tracking/migrations/Migration20260529120000_shipment_tracking_poi_region_names.js.map +7 -0
- package/dist/modules/shipment_tracking/migrations/Migration20260821120000_shipment_tracking_shipsgo.js +47 -0
- package/dist/modules/shipment_tracking/migrations/Migration20260821120000_shipment_tracking_shipsgo.js.map +7 -0
- package/dist/modules/shipment_tracking/notifications.js +145 -0
- package/dist/modules/shipment_tracking/notifications.js.map +7 -0
- package/dist/modules/shipment_tracking/services/carrierRegistry.js +24 -0
- package/dist/modules/shipment_tracking/services/carrierRegistry.js.map +7 -0
- package/dist/modules/shipment_tracking/services/poiNatsService.js +397 -0
- package/dist/modules/shipment_tracking/services/poiNatsService.js.map +7 -0
- package/dist/modules/shipment_tracking/services/shipsGoProvider.js +163 -0
- package/dist/modules/shipment_tracking/services/shipsGoProvider.js.map +7 -0
- package/dist/modules/shipment_tracking/services/trackingService.js +1283 -0
- package/dist/modules/shipment_tracking/services/trackingService.js.map +7 -0
- package/dist/modules/shipment_tracking/services/webhookService.js +263 -0
- package/dist/modules/shipment_tracking/services/webhookService.js.map +7 -0
- package/dist/modules/shipment_tracking/setup.js +72 -0
- package/dist/modules/shipment_tracking/setup.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/cargo-event-created.js +60 -0
- package/dist/modules/shipment_tracking/subscribers/cargo-event-created.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/dcsa-event-webhook-dispatch.js +58 -0
- package/dist/modules/shipment_tracking/subscribers/dcsa-event-webhook-dispatch.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/poi-event-notification.js +82 -0
- package/dist/modules/shipment_tracking/subscribers/poi-event-notification.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/poi-event-webhook-dispatch.js +53 -0
- package/dist/modules/shipment_tracking/subscribers/poi-event-webhook-dispatch.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/shipment-status-changed.js +28 -0
- package/dist/modules/shipment_tracking/subscribers/shipment-status-changed.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/terminal-event-ingest.js +67 -0
- package/dist/modules/shipment_tracking/subscribers/terminal-event-ingest.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/tracking-job-initial-poll.js +30 -0
- package/dist/modules/shipment_tracking/subscribers/tracking-job-initial-poll.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/tracking-job-webhook-dispatch.js +19 -0
- package/dist/modules/shipment_tracking/subscribers/tracking-job-webhook-dispatch.js.map +7 -0
- package/dist/modules/shipment_tracking/tables/index.js +6 -0
- package/dist/modules/shipment_tracking/tables/index.js.map +7 -0
- package/dist/modules/shipment_tracking/tables/tracking_job/table.client.js +284 -0
- package/dist/modules/shipment_tracking/tables/tracking_job/table.client.js.map +7 -0
- package/dist/modules/shipment_tracking/tables/tracking_job/table.js +36 -0
- package/dist/modules/shipment_tracking/tables/tracking_job/table.js.map +7 -0
- package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/config.js +25 -0
- package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/config.js.map +7 -0
- package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/widget.client.js +355 -0
- package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/widget.client.js.map +7 -0
- package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/widget.js +26 -0
- package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/widget.js.map +7 -0
- package/dist/types/assets.d.js +1 -0
- package/dist/types/assets.d.js.map +7 -0
- package/generated/entities/bic_config/index.ts +20 -0
- package/generated/entities/carrier_config/index.ts +23 -0
- package/generated/entities/location_override/index.ts +26 -0
- package/generated/entities/shipment/index.ts +46 -0
- package/generated/entities/ships_go_config/index.ts +25 -0
- package/generated/entities/tracking_event/index.ts +60 -0
- package/generated/entities/tracking_job/index.ts +33 -0
- package/generated/entities/webhook/index.ts +22 -0
- package/generated/entities/webhook_delivery/index.ts +22 -0
- package/generated/entities.ids.generated.ts +47 -0
- package/package.json +81 -0
- package/src/index.ts +39 -0
- package/src/modules/shipment_tracking/__integration__/TC-TRACK-410-shipsgo-config-api.spec.ts +133 -0
- package/src/modules/shipment_tracking/__integration__/meta.ts +4 -0
- package/src/modules/shipment_tracking/__tests__/empty-return-stop.test.ts +231 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/hapag-lloyd-transship-container.json +702 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/index.ts +434 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/maersk-multi-transship-completed.json +1518 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/maersk-transship-intransit.json +558 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/msc-direct.json +222 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/msc-multi-transship-completed.json +974 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/msc-multi.json +4846 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/msc-transship.json +409 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/poi-events.json +240 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/synthetic/index.ts +138 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/synthetic-eta-updates.json +288 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/synthetic-route-inference.json +201 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/synthetic-voyage-progression.json +410 -0
- package/src/modules/shipment_tracking/__tests__/shipsgo-air-mapper.test.ts +157 -0
- package/src/modules/shipment_tracking/__tests__/shipsgo-client.test.ts +155 -0
- package/src/modules/shipment_tracking/__tests__/shipsgo-mapper.test.ts +201 -0
- package/src/modules/shipment_tracking/__tests__/shipsgo-poll.test.ts +357 -0
- package/src/modules/shipment_tracking/__tests__/shipsgo-provider.test.ts +364 -0
- package/src/modules/shipment_tracking/__tests__/trackingService.integration.test.ts +2248 -0
- package/src/modules/shipment_tracking/__tests__/webhookPayload.test.ts +151 -0
- package/src/modules/shipment_tracking/acl.ts +18 -0
- package/src/modules/shipment_tracking/api/bic-configs/route.ts +206 -0
- package/src/modules/shipment_tracking/api/bic-configs/test/route.ts +108 -0
- package/src/modules/shipment_tracking/api/cargo-events/route.ts +101 -0
- package/src/modules/shipment_tracking/api/carrier-configs/route.ts +142 -0
- package/src/modules/shipment_tracking/api/dashboard/ships-map/route.ts +246 -0
- package/src/modules/shipment_tracking/api/location-overrides/route.ts +185 -0
- package/src/modules/shipment_tracking/api/openapi.ts +28 -0
- package/src/modules/shipment_tracking/api/shipments/route.ts +197 -0
- package/src/modules/shipment_tracking/api/shipsgo-configs/route.ts +215 -0
- package/src/modules/shipment_tracking/api/tracking-jobs/route.ts +149 -0
- package/src/modules/shipment_tracking/api/tracking-jobs/sync/route.ts +116 -0
- package/src/modules/shipment_tracking/api/utils.ts +10 -0
- package/src/modules/shipment_tracking/api/webhooks/deliveries/route.ts +82 -0
- package/src/modules/shipment_tracking/api/webhooks/route.ts +138 -0
- package/src/modules/shipment_tracking/api/webhooks/test/route.ts +84 -0
- package/src/modules/shipment_tracking/assets/carriers/cma_cgm.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/cosco.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/evergreen.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/hapag_lloyd.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/maersk.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/msc.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/one.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/oocl.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/yang_ming.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/zim.png +0 -0
- package/src/modules/shipment_tracking/assets/index.ts +66 -0
- package/src/modules/shipment_tracking/backend/carrier-configs/page.meta.ts +16 -0
- package/src/modules/shipment_tracking/backend/carrier-configs/page.tsx +782 -0
- package/src/modules/shipment_tracking/backend/location-overrides/page.meta.ts +16 -0
- package/src/modules/shipment_tracking/backend/location-overrides/page.tsx +241 -0
- package/src/modules/shipment_tracking/backend/shipment-tracking/page.meta.ts +16 -0
- package/src/modules/shipment_tracking/backend/shipment-tracking/page.tsx +340 -0
- package/src/modules/shipment_tracking/backend/tracking-jobs/page.meta.ts +16 -0
- package/src/modules/shipment_tracking/backend/tracking-jobs/page.tsx +16 -0
- package/src/modules/shipment_tracking/backend/webhooks/page.meta.ts +16 -0
- package/src/modules/shipment_tracking/backend/webhooks/page.tsx +228 -0
- package/src/modules/shipment_tracking/ce.ts +48 -0
- package/src/modules/shipment_tracking/cli.ts +321 -0
- package/src/modules/shipment_tracking/commands/bic-configs.ts +94 -0
- package/src/modules/shipment_tracking/commands/carrier-configs.ts +89 -0
- package/src/modules/shipment_tracking/commands/index.ts +8 -0
- package/src/modules/shipment_tracking/commands/location-overrides.ts +119 -0
- package/src/modules/shipment_tracking/commands/shipments.ts +247 -0
- package/src/modules/shipment_tracking/commands/shipsgo-configs.ts +101 -0
- package/src/modules/shipment_tracking/commands/tracking-jobs.ts +158 -0
- package/src/modules/shipment_tracking/commands/tracking.ts +230 -0
- package/src/modules/shipment_tracking/commands/webhooks.ts +159 -0
- package/src/modules/shipment_tracking/components/CombinedTimestampCell.tsx +236 -0
- package/src/modules/shipment_tracking/components/FacilityLocationInput.tsx +235 -0
- package/src/modules/shipment_tracking/components/LocationOverrideDrawer.tsx +586 -0
- package/src/modules/shipment_tracking/components/ShipmentDetailsDrawer.tsx +1364 -0
- package/src/modules/shipment_tracking/components/ShipmentDrawer.tsx +498 -0
- package/src/modules/shipment_tracking/components/ShipsGoConfigSection.tsx +266 -0
- package/src/modules/shipment_tracking/components/TimestampCell.tsx +146 -0
- package/src/modules/shipment_tracking/components/TrackingJobDrawer.tsx +467 -0
- package/src/modules/shipment_tracking/components/VesselTrackingMap.tsx +761 -0
- package/src/modules/shipment_tracking/components/WebhookDrawer.tsx +434 -0
- package/src/modules/shipment_tracking/data/entities.ts +748 -0
- package/src/modules/shipment_tracking/data/validators.ts +523 -0
- package/src/modules/shipment_tracking/di.ts +73 -0
- package/src/modules/shipment_tracking/encryption.ts +17 -0
- package/src/modules/shipment_tracking/events.ts +122 -0
- package/src/modules/shipment_tracking/hooks/useVesselTracking.ts +297 -0
- package/src/modules/shipment_tracking/i18n/de.json +327 -0
- package/src/modules/shipment_tracking/i18n/en.json +327 -0
- package/src/modules/shipment_tracking/i18n/es.json +327 -0
- package/src/modules/shipment_tracking/i18n/pl.json +327 -0
- package/src/modules/shipment_tracking/index.ts +13 -0
- package/src/modules/shipment_tracking/lib/__tests__/dcsa-event-mapping.test.ts +385 -0
- package/src/modules/shipment_tracking/lib/__tests__/dcsa-event-parser.test.ts +398 -0
- package/src/modules/shipment_tracking/lib/__tests__/empty-return.test.ts +174 -0
- package/src/modules/shipment_tracking/lib/__tests__/poi-event-processor.test.ts +699 -0
- package/src/modules/shipment_tracking/lib/__tests__/route-inference.test.ts +402 -0
- package/src/modules/shipment_tracking/lib/__tests__/status-machine.test.ts +447 -0
- package/src/modules/shipment_tracking/lib/__tests__/time-extraction.test.ts +331 -0
- package/src/modules/shipment_tracking/lib/__tests__/timestamp-utils.test.ts +260 -0
- package/src/modules/shipment_tracking/lib/__tests__/vessel-api.test.ts +125 -0
- package/src/modules/shipment_tracking/lib/__tests__/vessel-extraction.test.ts +108 -0
- package/src/modules/shipment_tracking/lib/adapters/cma-cgm.ts +84 -0
- package/src/modules/shipment_tracking/lib/adapters/cosco-types.ts +145 -0
- package/src/modules/shipment_tracking/lib/adapters/cosco.ts +314 -0
- package/src/modules/shipment_tracking/lib/adapters/evergreen.ts +107 -0
- package/src/modules/shipment_tracking/lib/adapters/hapag-lloyd.ts +88 -0
- package/src/modules/shipment_tracking/lib/adapters/index.ts +18 -0
- package/src/modules/shipment_tracking/lib/adapters/maersk.ts +104 -0
- package/src/modules/shipment_tracking/lib/adapters/msc.ts +223 -0
- package/src/modules/shipment_tracking/lib/adapters/zim.ts +106 -0
- package/src/modules/shipment_tracking/lib/audit-helpers.ts +29 -0
- package/src/modules/shipment_tracking/lib/auth/base64url.ts +4 -0
- package/src/modules/shipment_tracking/lib/auth/oauth-client.ts +53 -0
- package/src/modules/shipment_tracking/lib/background.ts +24 -0
- package/src/modules/shipment_tracking/lib/bic-api-client.ts +199 -0
- package/src/modules/shipment_tracking/lib/carrier-adapter.ts +136 -0
- package/src/modules/shipment_tracking/lib/current-vessel.ts +274 -0
- package/src/modules/shipment_tracking/lib/dcsa-event-mapping.ts +202 -0
- package/src/modules/shipment_tracking/lib/dcsa-event-parser.ts +163 -0
- package/src/modules/shipment_tracking/lib/dcsa-params.ts +14 -0
- package/src/modules/shipment_tracking/lib/empty-return.ts +122 -0
- package/src/modules/shipment_tracking/lib/location-overrides.ts +150 -0
- package/src/modules/shipment_tracking/lib/location-types.ts +154 -0
- package/src/modules/shipment_tracking/lib/logger.ts +195 -0
- package/src/modules/shipment_tracking/lib/poi-event-processor.ts +302 -0
- package/src/modules/shipment_tracking/lib/poi-nats-config.ts +134 -0
- package/src/modules/shipment_tracking/lib/poi-types.ts +198 -0
- package/src/modules/shipment_tracking/lib/rate-limiter.ts +58 -0
- package/src/modules/shipment_tracking/lib/route-extraction.ts +439 -0
- package/src/modules/shipment_tracking/lib/route-inference.ts +238 -0
- package/src/modules/shipment_tracking/lib/schedule-generator.ts +92 -0
- package/src/modules/shipment_tracking/lib/shipsgo-client.ts +350 -0
- package/src/modules/shipment_tracking/lib/shipsgo-mapper.ts +296 -0
- package/src/modules/shipment_tracking/lib/status-machine.ts +230 -0
- package/src/modules/shipment_tracking/lib/time-extraction.ts +85 -0
- package/src/modules/shipment_tracking/lib/timestamp-utils.ts +244 -0
- package/src/modules/shipment_tracking/lib/vessel-api.ts +299 -0
- package/src/modules/shipment_tracking/lib/vessel-cache.ts +102 -0
- package/src/modules/shipment_tracking/lib/vessel-extraction.ts +39 -0
- package/src/modules/shipment_tracking/lib/webhook-dispatcher.ts +93 -0
- package/src/modules/shipment_tracking/migrations/.snapshot-open-mercato.json +2914 -0
- package/src/modules/shipment_tracking/migrations/Migration20260422161208_shipment_tracking.ts +60 -0
- package/src/modules/shipment_tracking/migrations/Migration20260529120000_shipment_tracking_poi_region_names.ts +22 -0
- package/src/modules/shipment_tracking/migrations/Migration20260821120000_shipment_tracking_shipsgo.ts +65 -0
- package/src/modules/shipment_tracking/notifications.ts +151 -0
- package/src/modules/shipment_tracking/services/carrierRegistry.ts +29 -0
- package/src/modules/shipment_tracking/services/poiNatsService.ts +526 -0
- package/src/modules/shipment_tracking/services/shipsGoProvider.ts +231 -0
- package/src/modules/shipment_tracking/services/trackingService.ts +1777 -0
- package/src/modules/shipment_tracking/services/webhookService.ts +347 -0
- package/src/modules/shipment_tracking/setup.ts +99 -0
- package/src/modules/shipment_tracking/subscribers/__tests__/terminal-event-ingest.test.ts +141 -0
- package/src/modules/shipment_tracking/subscribers/cargo-event-created.ts +115 -0
- package/src/modules/shipment_tracking/subscribers/dcsa-event-webhook-dispatch.ts +115 -0
- package/src/modules/shipment_tracking/subscribers/poi-event-notification.ts +144 -0
- package/src/modules/shipment_tracking/subscribers/poi-event-webhook-dispatch.ts +94 -0
- package/src/modules/shipment_tracking/subscribers/shipment-status-changed.ts +41 -0
- package/src/modules/shipment_tracking/subscribers/terminal-event-ingest.ts +109 -0
- package/src/modules/shipment_tracking/subscribers/tracking-job-initial-poll.ts +53 -0
- package/src/modules/shipment_tracking/subscribers/tracking-job-webhook-dispatch.ts +50 -0
- package/src/modules/shipment_tracking/tables/index.ts +4 -0
- package/src/modules/shipment_tracking/tables/tracking_job/table.client.tsx +337 -0
- package/src/modules/shipment_tracking/tables/tracking_job/table.ts +34 -0
- package/src/modules/shipment_tracking/widgets/dashboard/ships-map/config.ts +32 -0
- package/src/modules/shipment_tracking/widgets/dashboard/ships-map/widget.client.tsx +448 -0
- package/src/modules/shipment_tracking/widgets/dashboard/ships-map/widget.ts +25 -0
- package/src/types/assets.d.ts +5 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
|
+
import { extractDocumentReferences } from "../carrier-adapter.js";
|
|
7
|
+
import { base64UrlEncode } from "../auth/base64url.js";
|
|
8
|
+
import { buildDcsaQueryParams } from "../dcsa-params.js";
|
|
9
|
+
import { parseDcsaEvents } from "../dcsa-event-parser.js";
|
|
10
|
+
import { withCarrierApiSpan } from "../logger.js";
|
|
11
|
+
const EVENTS_URL = "https://api.tech.msc.com/msc/trackandtrace/v2.2/events";
|
|
12
|
+
function getAuth(authConfig) {
|
|
13
|
+
const certBase64 = authConfig?.certificate_base64;
|
|
14
|
+
const clientId = authConfig?.client_id;
|
|
15
|
+
const scope = authConfig?.scope;
|
|
16
|
+
const tokenUrl = authConfig?.token_url;
|
|
17
|
+
const aud = authConfig?.aud || tokenUrl;
|
|
18
|
+
const missing = [
|
|
19
|
+
!certBase64 && "certificate_base64",
|
|
20
|
+
!clientId && "client_id",
|
|
21
|
+
!scope && "scope",
|
|
22
|
+
!tokenUrl && "token_url"
|
|
23
|
+
].filter(Boolean);
|
|
24
|
+
if (missing.length) {
|
|
25
|
+
throw new Error(`MSC authConfig requires: ${missing.join(", ")}`);
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
certificate_base64: certBase64,
|
|
29
|
+
clientId,
|
|
30
|
+
scope,
|
|
31
|
+
tokenUrl,
|
|
32
|
+
aud
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function extractPfxComponents(pfxBuffer) {
|
|
36
|
+
const tempDir = os.tmpdir();
|
|
37
|
+
const uid = crypto.randomUUID();
|
|
38
|
+
const pfxPath = path.join(tempDir, `msc_${uid}.pfx`);
|
|
39
|
+
const certPath = path.join(tempDir, `msc_cert_${uid}.pem`);
|
|
40
|
+
const keyPath = path.join(tempDir, `msc_key_${uid}.pem`);
|
|
41
|
+
try {
|
|
42
|
+
fs.writeFileSync(pfxPath, pfxBuffer);
|
|
43
|
+
execFileSync("openssl", ["pkcs12", "-in", pfxPath, "-nokeys", "-out", certPath, "-passin", "pass:"]);
|
|
44
|
+
execFileSync("openssl", ["pkcs12", "-in", pfxPath, "-nocerts", "-nodes", "-out", keyPath, "-passin", "pass:"]);
|
|
45
|
+
const certPem = fs.readFileSync(certPath, "utf8");
|
|
46
|
+
const privateKeyPem = fs.readFileSync(keyPath, "utf8");
|
|
47
|
+
return { privateKeyPem, certPem };
|
|
48
|
+
} finally {
|
|
49
|
+
for (const filePath of [pfxPath, certPath, keyPath]) {
|
|
50
|
+
try {
|
|
51
|
+
fs.unlinkSync(filePath);
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function createMscJwt(auth) {
|
|
58
|
+
const pfxBuffer = Buffer.from(auth.certificate_base64, "base64");
|
|
59
|
+
const { privateKeyPem, certPem } = extractPfxComponents(pfxBuffer);
|
|
60
|
+
const certMatches = certPem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
|
|
61
|
+
if (!certMatches?.length) {
|
|
62
|
+
throw new Error("No certificates found in PFX");
|
|
63
|
+
}
|
|
64
|
+
const lastCert = certMatches[certMatches.length - 1];
|
|
65
|
+
const certDerBase64 = lastCert.replace(/-----BEGIN CERTIFICATE-----/g, "").replace(/-----END CERTIFICATE-----/g, "").replace(/\r?\n/g, "").trim();
|
|
66
|
+
const certDer = Buffer.from(certDerBase64, "base64");
|
|
67
|
+
const sha1Hash = crypto.createHash("sha1").update(certDer).digest();
|
|
68
|
+
const x5t = base64UrlEncode(sha1Hash);
|
|
69
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
70
|
+
const header = { alg: "RS256", typ: "JWT", x5t };
|
|
71
|
+
const payload = {
|
|
72
|
+
aud: auth.aud,
|
|
73
|
+
exp: now + 120,
|
|
74
|
+
iss: auth.clientId,
|
|
75
|
+
jti: crypto.randomUUID(),
|
|
76
|
+
nbf: now,
|
|
77
|
+
sub: auth.clientId
|
|
78
|
+
};
|
|
79
|
+
const encodedHeader = base64UrlEncode(JSON.stringify(header));
|
|
80
|
+
const encodedPayload = base64UrlEncode(JSON.stringify(payload));
|
|
81
|
+
const unsignedToken = `${encodedHeader}.${encodedPayload}`;
|
|
82
|
+
const sign = crypto.createSign("RSA-SHA256");
|
|
83
|
+
sign.update(unsignedToken);
|
|
84
|
+
const signature = sign.sign(privateKeyPem);
|
|
85
|
+
const encodedSignature = base64UrlEncode(signature);
|
|
86
|
+
return `${unsignedToken}.${encodedSignature}`;
|
|
87
|
+
}
|
|
88
|
+
async function authenticate(auth) {
|
|
89
|
+
return withCarrierApiSpan(
|
|
90
|
+
{ carrierCode: "msc", operation: "authenticate" },
|
|
91
|
+
async (span) => {
|
|
92
|
+
const jwt = createMscJwt(auth);
|
|
93
|
+
const body = new URLSearchParams({
|
|
94
|
+
client_id: auth.clientId,
|
|
95
|
+
client_assertion: jwt,
|
|
96
|
+
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
|
97
|
+
scope: auth.scope,
|
|
98
|
+
grant_type: "client_credentials"
|
|
99
|
+
});
|
|
100
|
+
const response = await fetch(auth.tokenUrl, {
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers: {
|
|
103
|
+
"Authorization": `Bearer ${jwt}`,
|
|
104
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
105
|
+
},
|
|
106
|
+
body: body.toString()
|
|
107
|
+
});
|
|
108
|
+
span.setAttribute("http.status_code", response.status);
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
const errorText = await response.text().catch(() => "unknown");
|
|
111
|
+
throw new Error(`MSC token request failed (${response.status}): ${errorText}`);
|
|
112
|
+
}
|
|
113
|
+
const data = await response.json();
|
|
114
|
+
return data.access_token;
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
class MscAdapter {
|
|
119
|
+
constructor() {
|
|
120
|
+
this.carrierCode = "msc";
|
|
121
|
+
this.supportedReferenceTypes = ["container", "booking", "bol"];
|
|
122
|
+
}
|
|
123
|
+
async fetchEvents(input) {
|
|
124
|
+
return withCarrierApiSpan(
|
|
125
|
+
{
|
|
126
|
+
carrierCode: this.carrierCode,
|
|
127
|
+
operation: "fetchEvents",
|
|
128
|
+
referenceType: input.referenceType,
|
|
129
|
+
referenceValue: input.referenceValue
|
|
130
|
+
},
|
|
131
|
+
async (span) => {
|
|
132
|
+
const auth = getAuth(input.authConfig);
|
|
133
|
+
const token = await authenticate(auth);
|
|
134
|
+
const params = buildDcsaQueryParams(input.referenceValue, input.referenceType);
|
|
135
|
+
const url = `${input.apiEndpoint || EVENTS_URL}?${params}`;
|
|
136
|
+
const response = await fetch(url, {
|
|
137
|
+
headers: {
|
|
138
|
+
"Authorization": `Bearer ${token}`,
|
|
139
|
+
"Content-Type": "application/json"
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
span.setAttribute("http.status_code", response.status);
|
|
143
|
+
if (!response.ok) {
|
|
144
|
+
const errorText = await response.text().catch(() => "unknown");
|
|
145
|
+
throw new Error(`MSC API error (${response.status}): ${errorText}`);
|
|
146
|
+
}
|
|
147
|
+
const data = await response.json();
|
|
148
|
+
const events = parseDcsaEvents(data, "MSC");
|
|
149
|
+
const { bookingNumber, bolNumber } = extractDocumentReferences(events);
|
|
150
|
+
span.setAttribute("events.count", events.length);
|
|
151
|
+
return { events, bookingNumber, bolNumber };
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
async testConnection(input) {
|
|
156
|
+
const start = Date.now();
|
|
157
|
+
try {
|
|
158
|
+
const auth = getAuth(input.authConfig);
|
|
159
|
+
await authenticate(auth);
|
|
160
|
+
return { success: true, message: "MSC authentication successful", latencyMs: Date.now() - start };
|
|
161
|
+
} catch (error) {
|
|
162
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
163
|
+
return { success: false, message: `MSC connection failed: ${message}`, latencyMs: Date.now() - start };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
export {
|
|
168
|
+
MscAdapter
|
|
169
|
+
};
|
|
170
|
+
//# sourceMappingURL=msc.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/shipment_tracking/lib/adapters/msc.ts"],
|
|
4
|
+
"sourcesContent": ["import crypto from 'node:crypto'\nimport fs, { writeFileSync } from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { execFileSync } from 'node:child_process'\nimport type { CarrierAdapter, CarrierFetchResult, CarrierAdapterTestResult } from '../carrier-adapter'\nimport { extractDocumentReferences } from '../carrier-adapter'\nimport type { TrackingReferenceType } from '../../data/entities'\nimport { base64UrlEncode } from '../auth/base64url'\nimport { buildDcsaQueryParams } from '../dcsa-params'\nimport { parseDcsaEvents } from '../dcsa-event-parser'\nimport { withCarrierApiSpan } from '../logger'\n\n// MSC's public track-and-trace events endpoint (DCSA). Overridable per carrier\n// config via `apiEndpoint`.\nconst EVENTS_URL = 'https://api.tech.msc.com/msc/trackandtrace/v2.2/events'\n\n/**\n * MSC OAuth account identity. These values are specific to the API account MSC\n * issues to each integrator (Azure AD tenant, registered client_id and API\n * scope) and are NOT shipped as defaults \u2014 supply them, together with the PFX\n * certificate, through the carrier config's `authConfig`.\n */\ntype MscAuthConfig = {\n certificate_base64: string\n clientId: string\n scope: string\n tokenUrl: string\n aud: string\n}\n\nfunction getAuth(authConfig: Record<string, unknown> | null | undefined): MscAuthConfig {\n const certBase64 = authConfig?.certificate_base64 as string | undefined\n const clientId = authConfig?.client_id as string | undefined\n const scope = authConfig?.scope as string | undefined\n const tokenUrl = authConfig?.token_url as string | undefined\n const aud = (authConfig?.aud as string | undefined) || tokenUrl\n const missing = [\n !certBase64 && 'certificate_base64',\n !clientId && 'client_id',\n !scope && 'scope',\n !tokenUrl && 'token_url',\n ].filter(Boolean)\n if (missing.length) {\n throw new Error(`MSC authConfig requires: ${missing.join(', ')}`)\n }\n return {\n certificate_base64: certBase64!,\n clientId: clientId!,\n scope: scope!,\n tokenUrl: tokenUrl!,\n aud: aud!,\n }\n}\n\nfunction extractPfxComponents(pfxBuffer: Buffer): { privateKeyPem: string; certPem: string } {\n const tempDir = os.tmpdir()\n const uid = crypto.randomUUID()\n const pfxPath = path.join(tempDir, `msc_${uid}.pfx`)\n const certPath = path.join(tempDir, `msc_cert_${uid}.pem`)\n const keyPath = path.join(tempDir, `msc_key_${uid}.pem`)\n\n try {\n fs.writeFileSync(pfxPath, pfxBuffer)\n\n execFileSync('openssl', ['pkcs12', '-in', pfxPath, '-nokeys', '-out', certPath, '-passin', 'pass:'])\n execFileSync('openssl', ['pkcs12', '-in', pfxPath, '-nocerts', '-nodes', '-out', keyPath, '-passin', 'pass:'])\n\n const certPem = fs.readFileSync(certPath, 'utf8')\n const privateKeyPem = fs.readFileSync(keyPath, 'utf8')\n\n return { privateKeyPem, certPem }\n } finally {\n for (const filePath of [pfxPath, certPath, keyPath]) {\n try { fs.unlinkSync(filePath) } catch { /* ignore cleanup errors */ }\n }\n }\n}\n\nfunction createMscJwt(auth: MscAuthConfig): string {\n const pfxBuffer = Buffer.from(auth.certificate_base64, 'base64')\n const { privateKeyPem, certPem } = extractPfxComponents(pfxBuffer)\n\n // Extract the last certificate from the chain for x5t thumbprint\n const certMatches = certPem.match(/-----BEGIN CERTIFICATE-----[\\s\\S]*?-----END CERTIFICATE-----/g)\n if (!certMatches?.length) {\n throw new Error('No certificates found in PFX')\n }\n\n const lastCert = certMatches[certMatches.length - 1]\n const certDerBase64 = lastCert\n .replace(/-----BEGIN CERTIFICATE-----/g, '')\n .replace(/-----END CERTIFICATE-----/g, '')\n .replace(/\\r?\\n/g, '')\n .trim()\n\n const certDer = Buffer.from(certDerBase64, 'base64')\n const sha1Hash = crypto.createHash('sha1').update(certDer).digest()\n const x5t = base64UrlEncode(sha1Hash)\n\n const now = Math.floor(Date.now() / 1000)\n\n const header = { alg: 'RS256', typ: 'JWT', x5t }\n const payload = {\n aud: auth.aud,\n exp: now + 120,\n iss: auth.clientId,\n jti: crypto.randomUUID(),\n nbf: now,\n sub: auth.clientId,\n }\n\n const encodedHeader = base64UrlEncode(JSON.stringify(header))\n const encodedPayload = base64UrlEncode(JSON.stringify(payload))\n const unsignedToken = `${encodedHeader}.${encodedPayload}`\n\n const sign = crypto.createSign('RSA-SHA256')\n sign.update(unsignedToken)\n const signature = sign.sign(privateKeyPem)\n const encodedSignature = base64UrlEncode(signature)\n\n return `${unsignedToken}.${encodedSignature}`\n}\n\nasync function authenticate(auth: MscAuthConfig): Promise<string> {\n return withCarrierApiSpan(\n { carrierCode: 'msc', operation: 'authenticate' },\n async (span) => {\n const jwt = createMscJwt(auth)\n\n const body = new URLSearchParams({\n client_id: auth.clientId,\n client_assertion: jwt,\n client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',\n scope: auth.scope,\n grant_type: 'client_credentials',\n })\n\n const response = await fetch(auth.tokenUrl, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${jwt}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: body.toString(),\n })\n\n span.setAttribute('http.status_code', response.status)\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => 'unknown')\n throw new Error(`MSC token request failed (${response.status}): ${errorText}`)\n }\n\n const data = await response.json() as { access_token: string }\n return data.access_token\n },\n )\n}\n\nexport class MscAdapter implements CarrierAdapter {\n readonly carrierCode = 'msc'\n readonly supportedReferenceTypes: TrackingReferenceType[] = ['container', 'booking', 'bol']\n\n async fetchEvents(input: {\n referenceType: TrackingReferenceType\n referenceValue: string\n apiEndpoint?: string | null\n authConfig?: Record<string, unknown> | null\n }): Promise<CarrierFetchResult> {\n return withCarrierApiSpan(\n {\n carrierCode: this.carrierCode,\n operation: 'fetchEvents',\n referenceType: input.referenceType,\n referenceValue: input.referenceValue,\n },\n async (span) => {\n const auth = getAuth(input.authConfig)\n const token = await authenticate(auth)\n const params = buildDcsaQueryParams(input.referenceValue, input.referenceType)\n const url = `${input.apiEndpoint || EVENTS_URL}?${params}`\n\n const response = await fetch(url, {\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n })\n\n span.setAttribute('http.status_code', response.status)\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => 'unknown')\n throw new Error(`MSC API error (${response.status}): ${errorText}`)\n }\n\n const data = await response.json()\n const events = parseDcsaEvents(data, 'MSC')\n\n const { bookingNumber, bolNumber } = extractDocumentReferences(events)\n\n span.setAttribute('events.count', events.length)\n return { events, bookingNumber, bolNumber }\n },\n )\n }\n\n async testConnection(input: {\n apiEndpoint?: string | null\n authConfig?: Record<string, unknown> | null\n }): Promise<CarrierAdapterTestResult> {\n const start = Date.now()\n try {\n const auth = getAuth(input.authConfig)\n await authenticate(auth)\n return { success: true, message: 'MSC authentication successful', latencyMs: Date.now() - start }\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error'\n return { success: false, message: `MSC connection failed: ${message}`, latencyMs: Date.now() - start }\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { extractDocumentReferences } from "../carrier-adapter.js";
|
|
2
|
+
import { fetchOAuthToken } from "../auth/oauth-client.js";
|
|
3
|
+
import { buildDcsaQueryParams } from "../dcsa-params.js";
|
|
4
|
+
import { parseDcsaEvents } from "../dcsa-event-parser.js";
|
|
5
|
+
import { withCarrierApiSpan } from "../logger.js";
|
|
6
|
+
const TOKEN_URL = "https://apigw.zim.com/authorize/v1";
|
|
7
|
+
const EVENTS_URL = "https://apigw.zim.com/trackAndTrace/v1";
|
|
8
|
+
function getAuth(authConfig) {
|
|
9
|
+
const clientId = authConfig?.client_id;
|
|
10
|
+
const clientSecret = authConfig?.client_secret;
|
|
11
|
+
const subscriptionKey = authConfig?.subscription_key;
|
|
12
|
+
if (!clientId || !clientSecret || !subscriptionKey) {
|
|
13
|
+
throw new Error("ZIM authConfig requires client_id, client_secret, and subscription_key");
|
|
14
|
+
}
|
|
15
|
+
return { client_id: clientId, client_secret: clientSecret, subscription_key: subscriptionKey };
|
|
16
|
+
}
|
|
17
|
+
async function authenticate(auth) {
|
|
18
|
+
return withCarrierApiSpan(
|
|
19
|
+
{ carrierCode: "zim", operation: "authenticate" },
|
|
20
|
+
async (span) => {
|
|
21
|
+
const token = await fetchOAuthToken({
|
|
22
|
+
tokenUrl: TOKEN_URL,
|
|
23
|
+
clientId: auth.client_id,
|
|
24
|
+
clientSecret: auth.client_secret,
|
|
25
|
+
authMethod: "body",
|
|
26
|
+
extraBody: { scope: "tracing" }
|
|
27
|
+
});
|
|
28
|
+
return token;
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
class ZimAdapter {
|
|
33
|
+
constructor() {
|
|
34
|
+
this.carrierCode = "zim";
|
|
35
|
+
this.supportedReferenceTypes = ["container", "booking", "bol"];
|
|
36
|
+
}
|
|
37
|
+
async fetchEvents(input) {
|
|
38
|
+
return withCarrierApiSpan(
|
|
39
|
+
{
|
|
40
|
+
carrierCode: this.carrierCode,
|
|
41
|
+
operation: "fetchEvents",
|
|
42
|
+
referenceType: input.referenceType,
|
|
43
|
+
referenceValue: input.referenceValue
|
|
44
|
+
},
|
|
45
|
+
async (span) => {
|
|
46
|
+
const auth = getAuth(input.authConfig);
|
|
47
|
+
const token = await authenticate(auth);
|
|
48
|
+
const params = buildDcsaQueryParams(input.referenceValue, input.referenceType);
|
|
49
|
+
const url = `${input.apiEndpoint || EVENTS_URL}?${params}`;
|
|
50
|
+
const response = await fetch(url, {
|
|
51
|
+
headers: {
|
|
52
|
+
"Authorization": `Bearer ${token}`,
|
|
53
|
+
"Ocp-Apim-Subscription-Key": auth.subscription_key,
|
|
54
|
+
"Content-Type": "application/json"
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
span.setAttribute("http.status_code", response.status);
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
const errorText = await response.text().catch(() => "unknown");
|
|
60
|
+
throw new Error(`ZIM API error (${response.status}): ${errorText}`);
|
|
61
|
+
}
|
|
62
|
+
const data = await response.json();
|
|
63
|
+
const events = parseDcsaEvents(data, "ZIM");
|
|
64
|
+
const { bookingNumber, bolNumber } = extractDocumentReferences(events);
|
|
65
|
+
span.setAttribute("events.count", events.length);
|
|
66
|
+
return { events, bookingNumber, bolNumber };
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
async testConnection(input) {
|
|
71
|
+
const start = Date.now();
|
|
72
|
+
try {
|
|
73
|
+
const auth = getAuth(input.authConfig);
|
|
74
|
+
await authenticate(auth);
|
|
75
|
+
return { success: true, message: "ZIM authentication successful", latencyMs: Date.now() - start };
|
|
76
|
+
} catch (error) {
|
|
77
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
78
|
+
return { success: false, message: `ZIM connection failed: ${message}`, latencyMs: Date.now() - start };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export {
|
|
83
|
+
ZimAdapter
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=zim.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/shipment_tracking/lib/adapters/zim.ts"],
|
|
4
|
+
"sourcesContent": ["import type { CarrierAdapter, CarrierFetchResult, CarrierAdapterTestResult } from '../carrier-adapter'\nimport { extractDocumentReferences } from '../carrier-adapter'\nimport type { TrackingReferenceType } from '../../data/entities'\nimport { fetchOAuthToken } from '../auth/oauth-client'\nimport { buildDcsaQueryParams } from '../dcsa-params'\nimport { parseDcsaEvents } from '../dcsa-event-parser'\nimport { withCarrierApiSpan } from '../logger'\n\nconst TOKEN_URL = 'https://apigw.zim.com/authorize/v1'\nconst EVENTS_URL = 'https://apigw.zim.com/trackAndTrace/v1'\n\ntype ZimAuthConfig = {\n client_id: string\n client_secret: string\n subscription_key: string\n}\n\nfunction getAuth(authConfig: Record<string, unknown> | null | undefined): ZimAuthConfig {\n const clientId = authConfig?.client_id as string | undefined\n const clientSecret = authConfig?.client_secret as string | undefined\n const subscriptionKey = authConfig?.subscription_key as string | undefined\n if (!clientId || !clientSecret || !subscriptionKey) {\n throw new Error('ZIM authConfig requires client_id, client_secret, and subscription_key')\n }\n return { client_id: clientId, client_secret: clientSecret, subscription_key: subscriptionKey }\n}\n\nasync function authenticate(auth: ZimAuthConfig): Promise<string> {\n return withCarrierApiSpan(\n { carrierCode: 'zim', operation: 'authenticate' },\n async (span) => {\n const token = await fetchOAuthToken({\n tokenUrl: TOKEN_URL,\n clientId: auth.client_id,\n clientSecret: auth.client_secret,\n authMethod: 'body',\n extraBody: { scope: 'tracing' },\n })\n return token\n },\n )\n}\n\nexport class ZimAdapter implements CarrierAdapter {\n readonly carrierCode = 'zim'\n readonly supportedReferenceTypes: TrackingReferenceType[] = ['container', 'booking', 'bol']\n\n async fetchEvents(input: {\n referenceType: TrackingReferenceType\n referenceValue: string\n apiEndpoint?: string | null\n authConfig?: Record<string, unknown> | null\n }): Promise<CarrierFetchResult> {\n return withCarrierApiSpan(\n {\n carrierCode: this.carrierCode,\n operation: 'fetchEvents',\n referenceType: input.referenceType,\n referenceValue: input.referenceValue,\n },\n async (span) => {\n const auth = getAuth(input.authConfig)\n const token = await authenticate(auth)\n const params = buildDcsaQueryParams(input.referenceValue, input.referenceType)\n const url = `${input.apiEndpoint || EVENTS_URL}?${params}`\n\n const response = await fetch(url, {\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Ocp-Apim-Subscription-Key': auth.subscription_key,\n 'Content-Type': 'application/json',\n },\n })\n\n span.setAttribute('http.status_code', response.status)\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => 'unknown')\n throw new Error(`ZIM API error (${response.status}): ${errorText}`)\n }\n\n const data = await response.json()\n const events = parseDcsaEvents(data, 'ZIM')\n const { bookingNumber, bolNumber } = extractDocumentReferences(events)\n\n span.setAttribute('events.count', events.length)\n return { events, bookingNumber, bolNumber }\n },\n )\n }\n\n async testConnection(input: {\n apiEndpoint?: string | null\n authConfig?: Record<string, unknown> | null\n }): Promise<CarrierAdapterTestResult> {\n const start = Date.now()\n try {\n const auth = getAuth(input.authConfig)\n await authenticate(auth)\n return { success: true, message: 'ZIM authentication successful', latencyMs: Date.now() - start }\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error'\n return { success: false, message: `ZIM connection failed: ${message}`, latencyMs: Date.now() - start }\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,iCAAiC;AAE1C,SAAS,uBAAuB;AAChC,SAAS,4BAA4B;AACrC,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AAEnC,MAAM,YAAY;AAClB,MAAM,aAAa;AAQnB,SAAS,QAAQ,YAAuE;AACtF,QAAM,WAAW,YAAY;AAC7B,QAAM,eAAe,YAAY;AACjC,QAAM,kBAAkB,YAAY;AACpC,MAAI,CAAC,YAAY,CAAC,gBAAgB,CAAC,iBAAiB;AAClD,UAAM,IAAI,MAAM,wEAAwE;AAAA,EAC1F;AACA,SAAO,EAAE,WAAW,UAAU,eAAe,cAAc,kBAAkB,gBAAgB;AAC/F;AAEA,eAAe,aAAa,MAAsC;AAChE,SAAO;AAAA,IACL,EAAE,aAAa,OAAO,WAAW,eAAe;AAAA,IAChD,OAAO,SAAS;AACd,YAAM,QAAQ,MAAM,gBAAgB;AAAA,QAClC,UAAU;AAAA,QACV,UAAU,KAAK;AAAA,QACf,cAAc,KAAK;AAAA,QACnB,YAAY;AAAA,QACZ,WAAW,EAAE,OAAO,UAAU;AAAA,MAChC,CAAC;AACD,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEO,MAAM,WAAqC;AAAA,EAA3C;AACL,SAAS,cAAc;AACvB,SAAS,0BAAmD,CAAC,aAAa,WAAW,KAAK;AAAA;AAAA,EAE1F,MAAM,YAAY,OAKc;AAC9B,WAAO;AAAA,MACL;AAAA,QACE,aAAa,KAAK;AAAA,QAClB,WAAW;AAAA,QACX,eAAe,MAAM;AAAA,QACrB,gBAAgB,MAAM;AAAA,MACxB;AAAA,MACA,OAAO,SAAS;AACd,cAAM,OAAO,QAAQ,MAAM,UAAU;AACrC,cAAM,QAAQ,MAAM,aAAa,IAAI;AACrC,cAAM,SAAS,qBAAqB,MAAM,gBAAgB,MAAM,aAAa;AAC7E,cAAM,MAAM,GAAG,MAAM,eAAe,UAAU,IAAI,MAAM;AAExD,cAAM,WAAW,MAAM,MAAM,KAAK;AAAA,UAChC,SAAS;AAAA,YACP,iBAAiB,UAAU,KAAK;AAAA,YAChC,6BAA6B,KAAK;AAAA,YAClC,gBAAgB;AAAA,UAClB;AAAA,QACF,CAAC;AAED,aAAK,aAAa,oBAAoB,SAAS,MAAM;AAErD,YAAI,CAAC,SAAS,IAAI;AAChB,gBAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,SAAS;AAC7D,gBAAM,IAAI,MAAM,kBAAkB,SAAS,MAAM,MAAM,SAAS,EAAE;AAAA,QACpE;AAEA,cAAM,OAAO,MAAM,SAAS,KAAK;AACjC,cAAM,SAAS,gBAAgB,MAAM,KAAK;AAC1C,cAAM,EAAE,eAAe,UAAU,IAAI,0BAA0B,MAAM;AAErE,aAAK,aAAa,gBAAgB,OAAO,MAAM;AAC/C,eAAO,EAAE,QAAQ,eAAe,UAAU;AAAA,MAC5C;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,eAAe,OAGiB;AACpC,UAAM,QAAQ,KAAK,IAAI;AACvB,QAAI;AACF,YAAM,OAAO,QAAQ,MAAM,UAAU;AACrC,YAAM,aAAa,IAAI;AACvB,aAAO,EAAE,SAAS,MAAM,SAAS,iCAAiC,WAAW,KAAK,IAAI,IAAI,MAAM;AAAA,IAClG,SAAS,OAAO;AACd,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,aAAO,EAAE,SAAS,OAAO,SAAS,0BAA0B,OAAO,IAAI,WAAW,KAAK,IAAI,IAAI,MAAM;AAAA,IACvG;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ActionLog } from "@open-mercato/core/modules/audit_logs/data/entities";
|
|
2
|
+
async function getTrackingJobCreator(em, trackingJobId, tenantId) {
|
|
3
|
+
const createLog = await em.findOne(
|
|
4
|
+
ActionLog,
|
|
5
|
+
{
|
|
6
|
+
tenantId,
|
|
7
|
+
resourceKind: "shipment_tracking.tracking_job",
|
|
8
|
+
resourceId: trackingJobId,
|
|
9
|
+
commandId: "shipment_tracking.tracking_job.create",
|
|
10
|
+
executionState: "done",
|
|
11
|
+
deletedAt: null
|
|
12
|
+
},
|
|
13
|
+
{ orderBy: { createdAt: "asc" } }
|
|
14
|
+
);
|
|
15
|
+
return createLog?.actorUserId ?? null;
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
getTrackingJobCreator
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=audit-helpers.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/shipment_tracking/lib/audit-helpers.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'\n\n/**\n * Get the user ID of who created a tracking job by querying audit logs.\n *\n * This is used for notifications where we need to notify the user who created\n * the tracking job, but the TrackingJob entity doesn't store createdByUserId.\n */\nexport async function getTrackingJobCreator(\n em: EntityManager,\n trackingJobId: string,\n tenantId: string,\n): Promise<string | null> {\n const createLog = await em.findOne(\n ActionLog,\n {\n tenantId,\n resourceKind: 'shipment_tracking.tracking_job',\n resourceId: trackingJobId,\n commandId: 'shipment_tracking.tracking_job.create',\n executionState: 'done',\n deletedAt: null,\n },\n { orderBy: { createdAt: 'asc' } },\n )\n\n return createLog?.actorUserId ?? null\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,iBAAiB;AAQ1B,eAAsB,sBACpB,IACA,eACA,UACwB;AACxB,QAAM,YAAY,MAAM,GAAG;AAAA,IACzB;AAAA,IACA;AAAA,MACE;AAAA,MACA,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,gBAAgB;AAAA,MAChB,WAAW;AAAA,IACb;AAAA,IACA,EAAE,SAAS,EAAE,WAAW,MAAM,EAAE;AAAA,EAClC;AAEA,SAAO,WAAW,eAAe;AACnC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
function base64UrlEncode(data) {
|
|
2
|
+
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
3
|
+
return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
4
|
+
}
|
|
5
|
+
export {
|
|
6
|
+
base64UrlEncode
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=base64url.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/shipment_tracking/lib/auth/base64url.ts"],
|
|
4
|
+
"sourcesContent": ["export function base64UrlEncode(data: string | Buffer): string {\n const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data)\n return buffer.toString('base64').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '')\n}\n"],
|
|
5
|
+
"mappings": "AAAO,SAAS,gBAAgB,MAA+B;AAC7D,QAAM,SAAS,OAAO,SAAS,IAAI,IAAI,OAAO,OAAO,KAAK,IAAI;AAC9D,SAAO,OAAO,SAAS,QAAQ,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,MAAM,EAAE;AAC3F;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
async function fetchOAuthToken(config) {
|
|
2
|
+
const { tokenUrl, clientId, clientSecret, scope, extraHeaders, extraBody, authMethod = "body" } = config;
|
|
3
|
+
const body = new URLSearchParams({ grant_type: "client_credentials" });
|
|
4
|
+
if (authMethod === "body") {
|
|
5
|
+
body.set("client_id", clientId);
|
|
6
|
+
body.set("client_secret", clientSecret);
|
|
7
|
+
}
|
|
8
|
+
if (scope) {
|
|
9
|
+
body.set("scope", scope);
|
|
10
|
+
}
|
|
11
|
+
if (extraBody) {
|
|
12
|
+
for (const [key, value] of Object.entries(extraBody)) {
|
|
13
|
+
body.set(key, value);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const headers = {
|
|
17
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
18
|
+
...extraHeaders
|
|
19
|
+
};
|
|
20
|
+
if (authMethod === "basic") {
|
|
21
|
+
headers["Authorization"] = `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`;
|
|
22
|
+
}
|
|
23
|
+
const response = await fetch(tokenUrl, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers,
|
|
26
|
+
body: body.toString()
|
|
27
|
+
});
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
const errorText = await response.text().catch(() => "unknown");
|
|
30
|
+
throw new Error(`OAuth token request failed (${response.status}): ${errorText}`);
|
|
31
|
+
}
|
|
32
|
+
const data = await response.json();
|
|
33
|
+
return data.access_token;
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
fetchOAuthToken
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=oauth-client.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/shipment_tracking/lib/auth/oauth-client.ts"],
|
|
4
|
+
"sourcesContent": ["type OAuthConfig = {\n tokenUrl: string\n clientId: string\n clientSecret: string\n scope?: string\n extraHeaders?: Record<string, string>\n extraBody?: Record<string, string>\n authMethod?: 'body' | 'basic'\n}\n\nexport async function fetchOAuthToken(config: OAuthConfig): Promise<string> {\n const { tokenUrl, clientId, clientSecret, scope, extraHeaders, extraBody, authMethod = 'body' } = config\n\n const body = new URLSearchParams({ grant_type: 'client_credentials' })\n\n if (authMethod === 'body') {\n body.set('client_id', clientId)\n body.set('client_secret', clientSecret)\n }\n\n if (scope) {\n body.set('scope', scope)\n }\n\n if (extraBody) {\n for (const [key, value] of Object.entries(extraBody)) {\n body.set(key, value)\n }\n }\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/x-www-form-urlencoded',\n ...extraHeaders,\n }\n\n if (authMethod === 'basic') {\n headers['Authorization'] = `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`\n }\n\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers,\n body: body.toString(),\n })\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => 'unknown')\n throw new Error(`OAuth token request failed (${response.status}): ${errorText}`)\n }\n\n const data = await response.json() as { access_token: string }\n return data.access_token\n}\n"],
|
|
5
|
+
"mappings": "AAUA,eAAsB,gBAAgB,QAAsC;AAC1E,QAAM,EAAE,UAAU,UAAU,cAAc,OAAO,cAAc,WAAW,aAAa,OAAO,IAAI;AAElG,QAAM,OAAO,IAAI,gBAAgB,EAAE,YAAY,qBAAqB,CAAC;AAErE,MAAI,eAAe,QAAQ;AACzB,SAAK,IAAI,aAAa,QAAQ;AAC9B,SAAK,IAAI,iBAAiB,YAAY;AAAA,EACxC;AAEA,MAAI,OAAO;AACT,SAAK,IAAI,SAAS,KAAK;AAAA,EACzB;AAEA,MAAI,WAAW;AACb,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,SAAS,GAAG;AACpD,WAAK,IAAI,KAAK,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,QAAM,UAAkC;AAAA,IACtC,gBAAgB;AAAA,IAChB,GAAG;AAAA,EACL;AAEA,MAAI,eAAe,SAAS;AAC1B,YAAQ,eAAe,IAAI,SAAS,OAAO,KAAK,GAAG,QAAQ,IAAI,YAAY,EAAE,EAAE,SAAS,QAAQ,CAAC;AAAA,EACnG;AAEA,QAAM,WAAW,MAAM,MAAM,UAAU;AAAA,IACrC,QAAQ;AAAA,IACR;AAAA,IACA,MAAM,KAAK,SAAS;AAAA,EACtB,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,SAAS;AAC7D,UAAM,IAAI,MAAM,+BAA+B,SAAS,MAAM,MAAM,SAAS,EAAE;AAAA,EACjF;AAEA,QAAM,OAAO,MAAM,SAAS,KAAK;AACjC,SAAO,KAAK;AACd;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
function runInBackground(fn, label) {
|
|
2
|
+
setImmediate(() => {
|
|
3
|
+
fn().catch((err) => {
|
|
4
|
+
console.error(`[background${label ? `:${label}` : ""}] Error:`, err);
|
|
5
|
+
});
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
function sleep(ms) {
|
|
9
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
runInBackground,
|
|
13
|
+
sleep
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=background.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/shipment_tracking/lib/background.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Runs a function in the background without blocking the caller.\n * Errors are logged but not propagated.\n *\n * Uses setImmediate to defer execution to the next event loop iteration,\n * allowing the caller to return immediately (non-blocking).\n *\n * @param fn - Async function to run in background\n * @param label - Optional label for error logging\n */\nexport function runInBackground(fn: () => Promise<void>, label?: string): void {\n setImmediate(() => {\n fn().catch((err) => {\n console.error(`[background${label ? `:${label}` : ''}] Error:`, err)\n })\n })\n}\n\n/**\n * Sleep helper for retry delays\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n"],
|
|
5
|
+
"mappings": "AAUO,SAAS,gBAAgB,IAAyB,OAAsB;AAC7E,eAAa,MAAM;AACjB,OAAG,EAAE,MAAM,CAAC,QAAQ;AAClB,cAAQ,MAAM,cAAc,QAAQ,IAAI,KAAK,KAAK,EAAE,YAAY,GAAG;AAAA,IACrE,CAAC;AAAA,EACH,CAAC;AACH;AAKO,SAAS,MAAM,IAA2B;AAC/C,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
class BicApiClient {
|
|
2
|
+
constructor(config) {
|
|
3
|
+
this.accessToken = null;
|
|
4
|
+
this.tokenExpiresAt = null;
|
|
5
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
6
|
+
this.username = config.username;
|
|
7
|
+
this.password = config.password;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Get OAuth2 token via Basic Auth
|
|
11
|
+
*/
|
|
12
|
+
async getToken() {
|
|
13
|
+
const now = /* @__PURE__ */ new Date();
|
|
14
|
+
if (this.accessToken && this.tokenExpiresAt && this.tokenExpiresAt > new Date(now.getTime() + 6e4)) {
|
|
15
|
+
return this.accessToken;
|
|
16
|
+
}
|
|
17
|
+
const credentials = Buffer.from(`${this.username}:${this.password}`).toString("base64");
|
|
18
|
+
const response = await fetch(`${this.baseUrl}/v2/oauth/token`, {
|
|
19
|
+
method: "POST",
|
|
20
|
+
headers: {
|
|
21
|
+
"Authorization": `Basic ${credentials}`,
|
|
22
|
+
"Content-Type": "application/json"
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
const text = await response.text();
|
|
27
|
+
throw new Error(`BIC OAuth failed: ${response.status} - ${text}`);
|
|
28
|
+
}
|
|
29
|
+
const data = await response.json();
|
|
30
|
+
this.accessToken = data.accessToken || data.access_token;
|
|
31
|
+
this.tokenExpiresAt = new Date(data.accessTokenExpiresAt);
|
|
32
|
+
return this.accessToken;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Look up facility by code
|
|
36
|
+
*
|
|
37
|
+
* @param facilityCode - The facility code (BIC: 9 chars, SMDG: varies)
|
|
38
|
+
* @param codeProvider - 'BIC' or 'SMDG'
|
|
39
|
+
* @param unlocode - Required for SMDG codes to build the full lookup code
|
|
40
|
+
* @returns Facility data or null if not found
|
|
41
|
+
*/
|
|
42
|
+
async getFacility(facilityCode, codeProvider, unlocode) {
|
|
43
|
+
const token = await this.getToken();
|
|
44
|
+
const lookupCode = codeProvider === "SMDG" && unlocode ? `${unlocode}${facilityCode}` : facilityCode;
|
|
45
|
+
const url = `${this.baseUrl}/v2/facilities/${lookupCode}?codeProvider=${codeProvider}`;
|
|
46
|
+
const response = await fetch(url, {
|
|
47
|
+
headers: { "Authorization": `Bearer ${token}` }
|
|
48
|
+
});
|
|
49
|
+
if (response.status === 404) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
const text = await response.text();
|
|
54
|
+
throw new Error(`BIC API error: ${response.status} - ${text}`);
|
|
55
|
+
}
|
|
56
|
+
return response.json();
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Test connection by fetching a token
|
|
60
|
+
* @returns true if authentication succeeds
|
|
61
|
+
*/
|
|
62
|
+
async testConnection() {
|
|
63
|
+
await this.getToken();
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
// ─── Static Helpers ──────────────────────────────────────────
|
|
67
|
+
/**
|
|
68
|
+
* Parse coordinates from BIC response (strings to numbers)
|
|
69
|
+
*/
|
|
70
|
+
static parseCoordinates(facility) {
|
|
71
|
+
const coords = facility.facility?.geographicalCoordinate;
|
|
72
|
+
if (!coords?.latitude || !coords?.longitude) return null;
|
|
73
|
+
const lat = parseFloat(coords.latitude);
|
|
74
|
+
const lng = parseFloat(coords.longitude);
|
|
75
|
+
if (isNaN(lat) || isNaN(lng)) return null;
|
|
76
|
+
return { latitude: lat, longitude: lng };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Format address from BIC response
|
|
80
|
+
*/
|
|
81
|
+
static formatAddress(facility) {
|
|
82
|
+
if (facility.facility?.formattedAddress) {
|
|
83
|
+
return facility.facility.formattedAddress;
|
|
84
|
+
}
|
|
85
|
+
const addr = facility.facility?.address;
|
|
86
|
+
if (!addr) return null;
|
|
87
|
+
const parts = [addr.street, addr.city, addr.state, addr.postcode, addr.country];
|
|
88
|
+
return parts.filter(Boolean).join(", ") || null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get facility name from BIC response
|
|
92
|
+
*/
|
|
93
|
+
static getFacilityName(facility) {
|
|
94
|
+
return facility.facility?.name || null;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get operator name from BIC response
|
|
98
|
+
*/
|
|
99
|
+
static getOperatorName(facility) {
|
|
100
|
+
return facility.operator?.name || null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export {
|
|
104
|
+
BicApiClient
|
|
105
|
+
};
|
|
106
|
+
//# sourceMappingURL=bic-api-client.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/shipment_tracking/lib/bic-api-client.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * BIC Facility Code API v2 Client\n * \n * Provides facility enrichment data (coordinates, addresses, operator names)\n * from the BIC Facility Code Database.\n * \n * API Documentation: https://app.swaggerhub.com/apis/BIC-ORG/Facility-Codes/2.0.1\n */\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface BicTokenResponse {\n accessToken: string\n access_token: string // Also provided for compatibility\n accessTokenExpiresAt: string // ISO datetime\n}\n\nexport interface BicAddress {\n street?: string\n city?: string\n postcode?: string\n state?: string\n country?: string\n}\n\nexport interface BicCoordinates {\n latitude: string // Note: strings in BIC API, need to parse\n longitude: string\n}\n\nexport interface BicFacilityDetails {\n name: string\n address?: BicAddress\n formattedAddress?: string\n geographicalCoordinate?: BicCoordinates\n}\n\nexport interface BicOperator {\n name: string\n}\n\nexport interface BicFacility {\n code: string\n codeProvider: 'BIC' | 'SMDG'\n countryCode: string\n unLocode: string\n aliasCode?: string // If this code is deprecated\n codeListProviderCode?: string // Short code for SMDG\n operator?: BicOperator\n facility?: BicFacilityDetails\n}\n\nexport interface BicClientConfig {\n baseUrl: string\n username: string\n password: string\n}\n\n// \u2500\u2500\u2500 Client \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class BicApiClient {\n private baseUrl: string\n private username: string\n private password: string\n private accessToken: string | null = null\n private tokenExpiresAt: Date | null = null\n\n constructor(config: BicClientConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, '') // Remove trailing slash\n this.username = config.username\n this.password = config.password\n }\n\n /**\n * Get OAuth2 token via Basic Auth\n */\n private async getToken(): Promise<string> {\n const now = new Date()\n // Refresh if token expires in less than 60 seconds\n if (this.accessToken && this.tokenExpiresAt && this.tokenExpiresAt > new Date(now.getTime() + 60_000)) {\n return this.accessToken\n }\n\n const credentials = Buffer.from(`${this.username}:${this.password}`).toString('base64')\n\n const response = await fetch(`${this.baseUrl}/v2/oauth/token`, {\n method: 'POST',\n headers: {\n 'Authorization': `Basic ${credentials}`,\n 'Content-Type': 'application/json',\n },\n })\n\n if (!response.ok) {\n const text = await response.text()\n throw new Error(`BIC OAuth failed: ${response.status} - ${text}`)\n }\n\n const data: BicTokenResponse = await response.json()\n this.accessToken = data.accessToken || data.access_token\n this.tokenExpiresAt = new Date(data.accessTokenExpiresAt)\n return this.accessToken\n }\n\n /**\n * Look up facility by code\n * \n * @param facilityCode - The facility code (BIC: 9 chars, SMDG: varies)\n * @param codeProvider - 'BIC' or 'SMDG'\n * @param unlocode - Required for SMDG codes to build the full lookup code\n * @returns Facility data or null if not found\n */\n async getFacility(\n facilityCode: string,\n codeProvider: 'BIC' | 'SMDG',\n unlocode?: string\n ): Promise<BicFacility | null> {\n const token = await this.getToken()\n\n // Build lookup code:\n // - BIC codes are already complete (9 chars)\n // - SMDG codes need unlocode prefix (e.g., \"PLGDY\" + \"DCT\" = \"PLGDYDCT\")\n const lookupCode = codeProvider === 'SMDG' && unlocode\n ? `${unlocode}${facilityCode}`\n : facilityCode\n\n const url = `${this.baseUrl}/v2/facilities/${lookupCode}?codeProvider=${codeProvider}`\n\n const response = await fetch(url, {\n headers: { 'Authorization': `Bearer ${token}` },\n })\n\n if (response.status === 404) {\n return null\n }\n\n if (!response.ok) {\n const text = await response.text()\n throw new Error(`BIC API error: ${response.status} - ${text}`)\n }\n\n return response.json()\n }\n\n /**\n * Test connection by fetching a token\n * @returns true if authentication succeeds\n */\n async testConnection(): Promise<boolean> {\n await this.getToken()\n return true\n }\n\n // \u2500\u2500\u2500 Static Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Parse coordinates from BIC response (strings to numbers)\n */\n static parseCoordinates(facility: BicFacility): { latitude: number; longitude: number } | null {\n const coords = facility.facility?.geographicalCoordinate\n if (!coords?.latitude || !coords?.longitude) return null\n\n const lat = parseFloat(coords.latitude)\n const lng = parseFloat(coords.longitude)\n\n if (isNaN(lat) || isNaN(lng)) return null\n return { latitude: lat, longitude: lng }\n }\n\n /**\n * Format address from BIC response\n */\n static formatAddress(facility: BicFacility): string | null {\n // Prefer formatted address if available\n if (facility.facility?.formattedAddress) {\n return facility.facility.formattedAddress\n }\n\n const addr = facility.facility?.address\n if (!addr) return null\n\n const parts = [addr.street, addr.city, addr.state, addr.postcode, addr.country]\n return parts.filter(Boolean).join(', ') || null\n }\n\n /**\n * Get facility name from BIC response\n */\n static getFacilityName(facility: BicFacility): string | null {\n return facility.facility?.name || null\n }\n\n /**\n * Get operator name from BIC response\n */\n static getOperatorName(facility: BicFacility): string | null {\n return facility.operator?.name || null\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
function extractDocumentReferences(events) {
|
|
2
|
+
let bookingNumber = null;
|
|
3
|
+
let bolNumber = null;
|
|
4
|
+
for (const event of events) {
|
|
5
|
+
if (!event.relatedDocumentReferences) continue;
|
|
6
|
+
for (const ref of event.relatedDocumentReferences) {
|
|
7
|
+
if (!bookingNumber && ref.type === "BKG" && ref.value) bookingNumber = ref.value;
|
|
8
|
+
if (!bolNumber && (ref.type === "TRD" || ref.type === "SHI") && ref.value) bolNumber = ref.value;
|
|
9
|
+
if (bookingNumber && bolNumber) return { bookingNumber, bolNumber };
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return { bookingNumber, bolNumber };
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
extractDocumentReferences
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=carrier-adapter.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/shipment_tracking/lib/carrier-adapter.ts"],
|
|
4
|
+
"sourcesContent": ["import type { TrackingEventType, TrackingEventClassifierCode, TrackingEventSource, TrackingReferenceType } from '../data/entities'\nimport type { SealInfo } from './route-extraction'\n\nexport type { SealInfo } from './route-extraction'\n\nexport type DocumentReference = {\n type: string // BKG, TRD, SHI, CBR, ARN, VGM, etc.\n value: string\n}\n\nexport type CarrierFetchedEvent = {\n // \u2500\u2500\u2500 Event Source \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n source: TrackingEventSource\n sourceEventId: string // Original ID from the source system (e.g., DCSA eventId)\n\n // \u2500\u2500\u2500 Core Event Fields \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n eventType: TrackingEventType\n eventCode: string\n eventClassifierCode?: TrackingEventClassifierCode | null\n eventDateTime: Date\n eventDateTimeOffset?: string | null\n description?: string | null\n rawData?: Record<string, unknown> | null\n\n // \u2500\u2500\u2500 Equipment Fields (DCSA EQUIPMENT events) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n equipmentReference?: string | null // Container number (BIC ISO)\n isoEquipmentCode?: string | null // Container type (22G1, 45R1, etc.)\n emptyIndicatorCode?: 'EMPTY' | 'LADEN' | null\n isTransshipmentMove?: boolean | null\n\n // \u2500\u2500\u2500 Location Fields \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n locationName?: string | null\n locationUnlocode?: string | null\n locationCountry?: string | null\n facilityCode?: string | null // Terminal/depot code (SMDG/BIC)\n facilityCodeListProvider?: 'SMDG' | 'BIC' | null\n facilityTypeCode?: string | null // POTE, DEPO, CLOC, COFS, etc.\n facilityAddress?: string | null // Full address string (from DCSA otherFacility)\n latitude?: number | null\n longitude?: number | null\n\n // \u2500\u2500\u2500 Transport Call Fields \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n transportCallReference?: string | null\n modeOfTransport?: 'VESSEL' | 'RAIL' | 'TRUCK' | 'BARGE' | 'AIR' | null\n vesselName?: string | null\n vesselImo?: string | null\n voyageNumber?: string | null\n carrierServiceCode?: string | null\n carrierExportVoyageNumber?: string | null\n carrierImportVoyageNumber?: string | null\n universalServiceReference?: string | null\n universalExportVoyageReference?: string | null\n universalImportVoyageReference?: string | null\n portVisitReference?: string | null\n\n // \u2500\u2500\u2500 Document References \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n relatedDocumentReferences?: DocumentReference[] | null\n\n // \u2500\u2500\u2500 Metadata Fields \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n eventCreatedDateTime?: Date | null\n retractedEventId?: string | null\n publisherName?: string | null\n publisherRole?: string | null // CA, AG, VSL, TR, etc.\n\n // \u2500\u2500\u2500 Additional Event Fields \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n delayReasonCode?: string | null // SMDG delay reason code\n changeRemark?: string | null\n seals?: SealInfo[] | null\n}\n\nexport type CarrierAdapterTestResult = {\n success: boolean\n message: string\n latencyMs?: number\n}\n\nexport type CarrierFetchResult = {\n events: CarrierFetchedEvent[]\n containerNumber?: string | null\n bookingNumber?: string | null\n bolNumber?: string | null\n vesselName?: string | null\n vesselImo?: string | null\n // \u2500\u2500\u2500 Air (mode='air') \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Air shipments have no container/vessel; identity is the AWB + flight/airline.\n awbNumber?: string | null\n flightNumber?: string | null\n airlineCode?: string | null\n airlineName?: string | null\n /** ShipsGo top-level air status (NEW/BOOKED/EN_ROUTE/LANDED/DELIVERED/\u2026). */\n airStatus?: string | null\n /**\n * Provider shipment-level metadata that has no dedicated column \u2014 merged into\n * `Shipment.extra`. Ocean: ShipsGo route summary (POL/POD + dates, transit\n * time, CO2, transshipments). Air: cargo (pieces/weight/volume) + status_extended.\n */\n extra?: Record<string, unknown> | null\n}\n\n/**\n * Extracts booking and BOL numbers from DCSA document references across all events.\n * BKG = Booking reference, TRD/SHI = Transport Document / Shipping Instruction (BOL).\n */\nexport function extractDocumentReferences(events: CarrierFetchedEvent[]): {\n bookingNumber: string | null\n bolNumber: string | null\n} {\n let bookingNumber: string | null = null\n let bolNumber: string | null = null\n for (const event of events) {\n if (!event.relatedDocumentReferences) continue\n for (const ref of event.relatedDocumentReferences) {\n if (!bookingNumber && ref.type === 'BKG' && ref.value) bookingNumber = ref.value\n if (!bolNumber && (ref.type === 'TRD' || ref.type === 'SHI') && ref.value) bolNumber = ref.value\n if (bookingNumber && bolNumber) return { bookingNumber, bolNumber }\n }\n }\n return { bookingNumber, bolNumber }\n}\n\nexport interface CarrierAdapter {\n readonly carrierCode: string\n readonly supportedReferenceTypes: TrackingReferenceType[]\n\n fetchEvents(input: {\n referenceType: TrackingReferenceType\n referenceValue: string\n apiEndpoint?: string | null\n authConfig?: Record<string, unknown> | null\n }): Promise<CarrierFetchResult>\n\n testConnection(input: {\n apiEndpoint?: string | null\n authConfig?: Record<string, unknown> | null\n }): Promise<CarrierAdapterTestResult>\n}\n"],
|
|
5
|
+
"mappings": "AAuGO,SAAS,0BAA0B,QAGxC;AACA,MAAI,gBAA+B;AACnC,MAAI,YAA2B;AAC/B,aAAW,SAAS,QAAQ;AAC1B,QAAI,CAAC,MAAM,0BAA2B;AACtC,eAAW,OAAO,MAAM,2BAA2B;AACjD,UAAI,CAAC,iBAAiB,IAAI,SAAS,SAAS,IAAI,MAAO,iBAAgB,IAAI;AAC3E,UAAI,CAAC,cAAc,IAAI,SAAS,SAAS,IAAI,SAAS,UAAU,IAAI,MAAO,aAAY,IAAI;AAC3F,UAAI,iBAAiB,UAAW,QAAO,EAAE,eAAe,UAAU;AAAA,IACpE;AAAA,EACF;AACA,SAAO,EAAE,eAAe,UAAU;AACpC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|