@shopickup/adapters-mpl 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +43 -0
  2. package/dist/capabilities/auth.d.ts +39 -0
  3. package/dist/capabilities/auth.d.ts.map +1 -0
  4. package/dist/capabilities/auth.js +130 -0
  5. package/dist/capabilities/close.d.ts +8 -0
  6. package/dist/capabilities/close.d.ts.map +1 -0
  7. package/dist/capabilities/close.js +70 -0
  8. package/dist/capabilities/get-shipment-details.d.ts +63 -0
  9. package/dist/capabilities/get-shipment-details.d.ts.map +1 -0
  10. package/dist/capabilities/get-shipment-details.js +97 -0
  11. package/dist/capabilities/index.d.ts +10 -0
  12. package/dist/capabilities/index.d.ts.map +1 -0
  13. package/dist/capabilities/index.js +9 -0
  14. package/dist/capabilities/label.d.ts +33 -0
  15. package/dist/capabilities/label.d.ts.map +1 -0
  16. package/dist/capabilities/label.js +328 -0
  17. package/dist/capabilities/parcels.d.ts +33 -0
  18. package/dist/capabilities/parcels.d.ts.map +1 -0
  19. package/dist/capabilities/parcels.js +284 -0
  20. package/dist/capabilities/pickup-points.d.ts +41 -0
  21. package/dist/capabilities/pickup-points.d.ts.map +1 -0
  22. package/dist/capabilities/pickup-points.js +294 -0
  23. package/dist/capabilities/track.d.ts +72 -0
  24. package/dist/capabilities/track.d.ts.map +1 -0
  25. package/dist/capabilities/track.js +331 -0
  26. package/dist/index.d.ts +83 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +142 -0
  29. package/dist/mappers/label.d.ts +67 -0
  30. package/dist/mappers/label.d.ts.map +1 -0
  31. package/dist/mappers/label.js +83 -0
  32. package/dist/mappers/shipment.d.ts +110 -0
  33. package/dist/mappers/shipment.d.ts.map +1 -0
  34. package/dist/mappers/shipment.js +258 -0
  35. package/dist/mappers/tracking.d.ts +60 -0
  36. package/dist/mappers/tracking.d.ts.map +1 -0
  37. package/dist/mappers/tracking.js +187 -0
  38. package/dist/utils/httpUtils.d.ts +36 -0
  39. package/dist/utils/httpUtils.d.ts.map +1 -0
  40. package/dist/utils/httpUtils.js +76 -0
  41. package/dist/utils/oauthFallback.d.ts +47 -0
  42. package/dist/utils/oauthFallback.d.ts.map +1 -0
  43. package/dist/utils/oauthFallback.js +250 -0
  44. package/dist/utils/resolveBaseUrl.d.ts +75 -0
  45. package/dist/utils/resolveBaseUrl.d.ts.map +1 -0
  46. package/dist/utils/resolveBaseUrl.js +65 -0
  47. package/dist/validation.d.ts +1890 -0
  48. package/dist/validation.d.ts.map +1 -0
  49. package/dist/validation.js +726 -0
  50. package/package.json +69 -0
@@ -0,0 +1,331 @@
1
+ /**
2
+ * MPL Tracking Implementation
3
+ *
4
+ * Implements the TRACK capability using MPL's Pull-1 endpoints:
5
+ * - /v2/nyomkovetes/guest - Guest endpoint (public, no financial data)
6
+ * - /v2/nyomkovetes/registered - Registered endpoint (authenticated, includes financial data)
7
+ *
8
+ * Both endpoints return C-code tracking records that are normalized to canonical TrackingUpdate format.
9
+ */
10
+ import { CarrierError, serializeForLog } from '@shopickup/core';
11
+ import { safeValidateTrackingRequest, safeValidateTrackingResponse, safeValidatePull500StartRequest, safeValidatePull500StartResponse, safeValidatePull500CheckRequest, safeValidatePull500CheckResponse, } from '../validation.js';
12
+ import { mapMPLTrackingToCanonical, } from '../mappers/tracking.js';
13
+ import { buildMPLHeaders } from '../utils/httpUtils.js';
14
+ import { randomUUID } from 'crypto';
15
+ /**
16
+ * Submit batch tracking request using Pull-500 endpoint
17
+ *
18
+ * Submits up to 500 tracking numbers in a single request.
19
+ * Returns trackingGUID for polling results.
20
+ * Results are processed asynchronously by MPL (takes 1+ minutes).
21
+ *
22
+ * @param request - Pull-500 request with tracking numbers
23
+ * @param ctx - Adapter context with HTTP client
24
+ * @param resolveTrackingUrl - Function to resolve tracking API base URL
25
+ * @returns trackingGUID for use with trackPull500Check()
26
+ * @throws CarrierError for validation, auth, or network errors
27
+ */
28
+ export async function trackPull500Start(request, ctx, resolveTrackingUrl) {
29
+ // Validate request
30
+ const validation = safeValidatePull500StartRequest(request);
31
+ if (!validation.success) {
32
+ throw new CarrierError(`Invalid Pull-500 start request: ${validation.error.message}`, 'Validation', { raw: serializeForLog(validation.error) });
33
+ }
34
+ if (!ctx.http) {
35
+ throw new CarrierError('HTTP client not provided in context', 'Permanent');
36
+ }
37
+ const validRequest = validation.data;
38
+ // Resolve base URL and extract accounting code
39
+ const baseUrl = resolveTrackingUrl(validRequest.options);
40
+ const accountingCode = validRequest.credentials?.accountingCode;
41
+ const isTestApi = validRequest.options?.useTestApi ?? false;
42
+ ctx.logger?.debug('MPL: Pull-500 start', {
43
+ count: validRequest.trackingNumbers.length,
44
+ language: validRequest.language,
45
+ testMode: isTestApi,
46
+ });
47
+ // Build request payload
48
+ const payload = {
49
+ trackingNumbers: validRequest.trackingNumbers,
50
+ language: validRequest.language || 'hu',
51
+ };
52
+ // Build authentication headers with request ID and correlation ID
53
+ const headers = {
54
+ ...buildMPLHeaders(validRequest.credentials, accountingCode),
55
+ 'X-Request-Id': randomUUID(),
56
+ 'Content-Type': 'application/json',
57
+ };
58
+ // Add correlation ID if needed (optional)
59
+ if (validRequest.options?.useTestApi) {
60
+ headers['X-Correlation-Id'] = `test-${Date.now()}`;
61
+ }
62
+ // Make request to Pull-500 start endpoint
63
+ let httpResponse;
64
+ try {
65
+ const url = new URL('/v2/mplapi-tracking/tracking', baseUrl);
66
+ httpResponse = await ctx.http.post(url.toString(), payload, { headers });
67
+ }
68
+ catch (error) {
69
+ throw translateTrackingError(error, `Pull-500 start for ${validRequest.trackingNumbers.length} items`);
70
+ }
71
+ // Extract body from normalized HttpResponse
72
+ const responseData = httpResponse.body;
73
+ // Validate response
74
+ const responseValidation = safeValidatePull500StartResponse(responseData);
75
+ if (!responseValidation.success) {
76
+ throw new CarrierError(`Invalid Pull-500 start response: ${responseValidation.error.message}`, 'Transient', { raw: serializeForLog(responseValidation.error) });
77
+ }
78
+ const validResponse = responseValidation.data;
79
+ if (!validResponse.trackingGUID) {
80
+ throw new CarrierError('Pull-500 start response missing trackingGUID', 'Transient', { raw: responseData });
81
+ }
82
+ ctx.logger?.info('MPL: Pull-500 start completed', {
83
+ trackingGUID: validResponse.trackingGUID,
84
+ submitted: validRequest.trackingNumbers.length,
85
+ });
86
+ return validResponse;
87
+ }
88
+ /**
89
+ * Poll for Pull-500 batch tracking results
90
+ *
91
+ * Checks status of previously submitted batch request.
92
+ * Status progression: NEW -> INPROGRESS -> READY (or ERROR)
93
+ * When READY, response includes CSV report with tracking data.
94
+ *
95
+ * Recommendation: Wait 1+ minute before first poll, then poll every 30-60 seconds.
96
+ *
97
+ * @param request - Pull-500 check request with trackingGUID
98
+ * @param ctx - Adapter context with HTTP client
99
+ * @param resolveTrackingUrl - Function to resolve tracking API base URL
100
+ * @returns Pull-500 response with status and report (when ready)
101
+ * @throws CarrierError for validation, auth, or network errors
102
+ */
103
+ export async function trackPull500Check(request, ctx, resolveTrackingUrl) {
104
+ // Validate request
105
+ const validation = safeValidatePull500CheckRequest(request);
106
+ if (!validation.success) {
107
+ throw new CarrierError(`Invalid Pull-500 check request: ${validation.error.message}`, 'Validation', { raw: serializeForLog(validation.error) });
108
+ }
109
+ if (!ctx.http) {
110
+ throw new CarrierError('HTTP client not provided in context', 'Permanent');
111
+ }
112
+ const validRequest = validation.data;
113
+ // Resolve base URL and extract accounting code
114
+ const baseUrl = resolveTrackingUrl(validRequest.options);
115
+ const accountingCode = validRequest.credentials?.accountingCode;
116
+ const isTestApi = validRequest.options?.useTestApi ?? false;
117
+ ctx.logger?.debug('MPL: Pull-500 check', {
118
+ trackingGUID: validRequest.trackingGUID,
119
+ testMode: isTestApi,
120
+ });
121
+ // Build authentication headers with request ID
122
+ const headers = {
123
+ ...buildMPLHeaders(validRequest.credentials, accountingCode),
124
+ 'X-Request-Id': randomUUID(),
125
+ };
126
+ // Make request to Pull-500 check endpoint
127
+ let httpResponse;
128
+ try {
129
+ const url = new URL(`/v2/mplapi-tracking/tracking/${validRequest.trackingGUID}`, baseUrl);
130
+ httpResponse = await ctx.http.get(url.toString(), { headers });
131
+ }
132
+ catch (error) {
133
+ throw translateTrackingError(error, `Pull-500 check for GUID ${validRequest.trackingGUID}`);
134
+ }
135
+ // Extract body from normalized HttpResponse
136
+ const responseData = httpResponse.body;
137
+ // Validate response
138
+ const responseValidation = safeValidatePull500CheckResponse(responseData);
139
+ if (!responseValidation.success) {
140
+ throw new CarrierError(`Invalid Pull-500 check response: ${responseValidation.error.message}`, 'Transient', { raw: serializeForLog(responseValidation.error) });
141
+ }
142
+ const validResponse = responseValidation.data;
143
+ ctx.logger?.info('MPL: Pull-500 check completed', {
144
+ status: validResponse.status,
145
+ hasReport: !!validResponse.report,
146
+ });
147
+ return validResponse;
148
+ }
149
+ /**
150
+ * Track parcels using the registered endpoint (with financial data)
151
+ *
152
+ * Extends the core track() function to use /v2/nyomkovetes/registered
153
+ * instead of /v2/nyomkovetes/guest. Includes financial data:
154
+ * - Weight (C5)
155
+ * - Service Code (C2)
156
+ * - Dimensions (C41, C42, C43)
157
+ * - Declared value (C58)
158
+ *
159
+ * Requires authentication and is intended for power users / internal use.
160
+ *
161
+ * @param request - Tracking request with tracking numbers
162
+ * @param ctx - Adapter context with HTTP client
163
+ * @param resolveTrackingUrl - Function to resolve tracking API base URL
164
+ * @returns Array of TrackingUpdate objects with financial data included
165
+ * @throws CarrierError for validation, auth, or network errors
166
+ */
167
+ export async function trackRegistered(request, ctx, resolveTrackingUrl) {
168
+ // Use core track() logic but force registered endpoint
169
+ return track({
170
+ ...request,
171
+ useRegisteredEndpoint: true,
172
+ }, ctx, resolveTrackingUrl);
173
+ }
174
+ /**
175
+ * Track one or more parcels using the Pull-1 endpoint
176
+ *
177
+ * @param request - Tracking request with tracking numbers and credentials
178
+ * @param ctx - Adapter context with HTTP client
179
+ * @param resolveTrackingUrl - Function to resolve tracking API base URL
180
+ * @returns Array of TrackingUpdate objects (one per tracking number)
181
+ * @throws CarrierError for various error scenarios
182
+ */
183
+ export async function track(request, ctx, resolveTrackingUrl) {
184
+ // Validate request
185
+ const validation = safeValidateTrackingRequest(request);
186
+ if (!validation.success) {
187
+ throw new CarrierError(`Invalid tracking request: ${validation.error.message}`, 'Validation', { raw: serializeForLog(validation.error) });
188
+ }
189
+ if (!ctx.http) {
190
+ throw new CarrierError('HTTP client not provided in context', 'Permanent');
191
+ }
192
+ const validRequest = validation.data;
193
+ // Determine endpoint (guest vs registered)
194
+ const isRegistered = validRequest.useRegisteredEndpoint ?? false;
195
+ const endpoint = isRegistered ? 'registered' : 'guest';
196
+ // Build request payload.
197
+ // MPL Pull-1 API uses POST body fields:
198
+ // - ids: comma-separated string of tracking numbers
199
+ // - state: 'last' or 'all'
200
+ // - language: 'hu' | 'en' | 'de'
201
+ const idsParam = validRequest.trackingNumbers.join(',');
202
+ const stateParam = validRequest.state ?? 'last';
203
+ // Extract accountingCode from credentials
204
+ const accountingCode = validRequest.credentials?.accountingCode;
205
+ // Resolve base URL
206
+ const baseUrl = resolveTrackingUrl(validRequest.options);
207
+ const isTestApi = validRequest.options?.useTestApi ?? false;
208
+ ctx.logger?.debug('MPL: Tracking parcels', {
209
+ endpoint,
210
+ trackingNumbers: validRequest.trackingNumbers,
211
+ state: stateParam,
212
+ testMode: isTestApi,
213
+ registered: isRegistered,
214
+ });
215
+ // Build URL and JSON body payload.
216
+ // Ensure base URL is treated as a path prefix, not as a file segment.
217
+ const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
218
+ const url = new URL(endpoint, normalizedBaseUrl);
219
+ const payload = {
220
+ ids: idsParam,
221
+ state: stateParam,
222
+ language: 'hu',
223
+ };
224
+ // Build authentication headers
225
+ const headers = buildMPLHeaders(validRequest.credentials, accountingCode);
226
+ // Make request
227
+ let httpResponse;
228
+ try {
229
+ httpResponse = await ctx.http.post(url.toString(), payload, { headers });
230
+ }
231
+ catch (error) {
232
+ throw translateTrackingError(error, idsParam);
233
+ }
234
+ // Extract body from normalized HttpResponse
235
+ const responseData = httpResponse.body;
236
+ // Validate response
237
+ const responseValidation = safeValidateTrackingResponse(responseData);
238
+ if (!responseValidation.success) {
239
+ throw new CarrierError(`Invalid tracking response: ${responseValidation.error.message}`, 'Transient', { raw: serializeForLog(responseValidation.error) });
240
+ }
241
+ const validResponse = responseValidation.data;
242
+ // Check for empty response (tracking not found)
243
+ const records = validResponse.trackAndTrace;
244
+ if (!records || records.length === 0) {
245
+ throw new CarrierError(`No tracking information found for: ${idsParam}`, 'NotFound');
246
+ }
247
+ // Convert records to canonical format
248
+ // If state='all', each record is a separate event in history
249
+ // If state='last', each record is just the latest event
250
+ const trackingUpdates = records.map((record, index) => {
251
+ try {
252
+ // For 'all' state, this would be more complex (multiple records per tracking number)
253
+ // For now, assume one record per tracking number
254
+ return mapMPLTrackingToCanonical(record, isRegistered);
255
+ }
256
+ catch (error) {
257
+ throw new CarrierError(`Failed to map tracking record ${index}: ${error instanceof Error ? error.message : String(error)}`, 'Transient', { raw: record });
258
+ }
259
+ });
260
+ ctx.logger?.info('MPL: Tracking completed', {
261
+ found: trackingUpdates.length,
262
+ requested: validRequest.trackingNumbers.length,
263
+ });
264
+ return trackingUpdates;
265
+ }
266
+ /**
267
+ * Translate MPL tracking errors to CarrierError
268
+ *
269
+ * @param error - Original error from HTTP client
270
+ * @param trackingIds - Tracking numbers being queried (for context)
271
+ * @returns CarrierError with appropriate category
272
+ */
273
+ function translateTrackingError(error, trackingIds) {
274
+ // Check if it's an axios or fetch error
275
+ if (error && typeof error === 'object') {
276
+ const err = error;
277
+ // Handle HTTP status codes
278
+ if (typeof err.status === 'number' || typeof err.response?.status === 'number') {
279
+ const status = err.status ?? err.response?.status;
280
+ const statusText = err.statusText ?? err.response?.statusText ?? '';
281
+ const data = err.data ?? err.response?.data ?? {};
282
+ if (status === 400) {
283
+ // Bad request - validation error
284
+ return new CarrierError(`Bad request (400): ${statusText || getErrorMessage(data)}`, 'Validation');
285
+ }
286
+ else if (status === 401 || status === 403) {
287
+ // Authentication/authorization error
288
+ return new CarrierError(`${status === 401 ? 'Unauthorized (401)' : 'Forbidden (403)'}: Invalid credentials`, 'Auth');
289
+ }
290
+ else if (status === 404) {
291
+ // Not found
292
+ return new CarrierError(`Not found (404): Tracking information not available`, 'NotFound');
293
+ }
294
+ else if (status === 429) {
295
+ // Rate limited
296
+ const retryAfter = err.headers?.['retry-after'] ?? err.response?.headers?.['retry-after'];
297
+ const retryAfterMs = retryAfter ? parseInt(retryAfter) * 1000 : 60000;
298
+ return new CarrierError(`Rate limited (429): Too many requests`, 'RateLimit', { retryAfterMs });
299
+ }
300
+ else if (status >= 500) {
301
+ // Server error - transient
302
+ return new CarrierError(`Server error (${status}): ${statusText}`, 'Transient');
303
+ }
304
+ }
305
+ // Network/timeout errors
306
+ if (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND' || err.code === 'ETIMEDOUT') {
307
+ return new CarrierError(`Network error: ${err.message}`, 'Transient');
308
+ }
309
+ }
310
+ // Generic error
311
+ return new CarrierError(`Tracking error: ${error instanceof Error ? error.message : String(error)}`, 'Transient');
312
+ }
313
+ /**
314
+ * Extract error message from MPL API error response
315
+ */
316
+ function getErrorMessage(data) {
317
+ if (data && typeof data === 'object') {
318
+ // Check for gateway error format
319
+ if (data.fault?.faultstring) {
320
+ return data.fault.faultstring;
321
+ }
322
+ // Check for other error formats
323
+ if (data.message) {
324
+ return data.message;
325
+ }
326
+ if (data.error) {
327
+ return typeof data.error === 'string' ? data.error : data.error.message || 'Unknown error';
328
+ }
329
+ }
330
+ return 'Unknown error';
331
+ }
@@ -0,0 +1,83 @@
1
+ import { AdapterContext, Capability, CarrierAdapter, CarrierResource, CreateLabelRequest, CreateLabelResponse, CreateLabelsRequest, CreateLabelsResponse, CreateParcelRequest, CreateParcelsRequest, CreateParcelsResponse, TrackingRequest, TrackingUpdate, ShipmentDetailsRequest, ShipmentDetailsResponse, FetchPickupPointsRequest, FetchPickupPointsResponse } from '@shopickup/core';
2
+ import { createResolveBaseUrl, createResolveOAuthUrl, ResolveBaseUrl, ResolveOAuthUrl } from './utils/resolveBaseUrl.js';
3
+ import type { ExchangeAuthTokenRequest, ExchangeAuthTokenResponse } from './validation.js';
4
+ /**
5
+ * MPLAdapter
6
+ *
7
+ * MPL (hu-mpl) is a major Hungarian logistics carrier.
8
+ *
9
+ * Capabilities supported:
10
+ * - CREATE_PARCEL: Create parcels directly
11
+ * - CREATE_PARCELS: Batch create multiple parcels
12
+ * - CREATE_LABEL: Generate PDF labels for parcels
13
+ * - CLOSE_SHIPMENTS: Closes shipments to finalize them before sendoff.
14
+ * - TRACK: Track parcels by barcode
15
+ * - EXCHANGE_AUTH_TOKEN: Exchange API credentials for OAuth2 Bearer token
16
+ * - TEST_MODE_SUPPORTED: Can switch to test API for sandbox testing
17
+ *
18
+ * Test API:
19
+ * - Production: https://core.api.posta.hu/v2/mplapi
20
+ * - Test/Sandbox: https://sandbox.api.posta.hu/v2/mplapi
21
+ * - Pass options.useTestApi = true in request to switch to test endpoint for that call
22
+ * - Test API requires separate test credentials
23
+ *
24
+ * OAuth Token Exchange:
25
+ * - Call exchangeAuthToken() to exchange API credentials for a Bearer token
26
+ * - Cached internally within the adapter; TTL is ~1 hour
27
+ * - Returns access_token, expires_in, and token_type
28
+ * - Useful when Basic auth is disabled at account level
29
+ *
30
+ * OAuth Fallback:
31
+ * - Wrap HTTP client with withOAuthFallback() to automatically exchange credentials
32
+ * when receiving 401 "Basic auth not enabled" error
33
+ * - No explicit calls needed; fallback is transparent
34
+ *
35
+ * Notes:
36
+ * - MPL does NOT have a shipment concept; parcels are created directly
37
+ * - Labels are generated per parcel
38
+ * - Tracking available via barcode (FoxWeb barcode format: CLFOX...)
39
+ * - createLabel does not support per-call test mode (no request object in interface)
40
+ */
41
+ export declare class MPLAdapter implements CarrierAdapter {
42
+ readonly id = "hu-mpl";
43
+ readonly displayName = "MPL Hungary";
44
+ readonly capabilities: Capability[];
45
+ readonly requires: {
46
+ createLabel: ("CREATE_PARCEL" | "CLOSE_SHIPMENT")[];
47
+ };
48
+ private prodBaseUrl;
49
+ private testBaseUrl;
50
+ private prodOAuthUrl;
51
+ private testOAuthUrl;
52
+ private prodTrackingUrl;
53
+ private testTrackingUrl;
54
+ private resolveBaseUrl;
55
+ private resolveOAuthUrl;
56
+ private resolveTrackingUrl;
57
+ private accountingCode;
58
+ constructor(baseUrl?: string, accountingCode?: string);
59
+ /**
60
+ * Create a single parcel in MPL
61
+ */
62
+ createParcel(req: CreateParcelRequest, ctx: AdapterContext): Promise<CarrierResource>;
63
+ /**
64
+ * Create multiple parcels in MPL (batch operation)
65
+ */
66
+ createParcels(req: CreateParcelsRequest, ctx: AdapterContext): Promise<CreateParcelsResponse>;
67
+ createLabel(req: CreateLabelRequest, ctx: AdapterContext): Promise<CreateLabelResponse>;
68
+ createLabels(req: CreateLabelsRequest, ctx: AdapterContext): Promise<CreateLabelsResponse>;
69
+ track(req: TrackingRequest, ctx: AdapterContext): Promise<TrackingUpdate>;
70
+ getShipmentDetails(req: ShipmentDetailsRequest, ctx: AdapterContext): Promise<ShipmentDetailsResponse>;
71
+ exchangeAuthToken(req: ExchangeAuthTokenRequest, ctx: AdapterContext): Promise<ExchangeAuthTokenResponse>;
72
+ fetchPickupPoints(req: FetchPickupPointsRequest, ctx: AdapterContext): Promise<FetchPickupPointsResponse>;
73
+ /**
74
+ * Close shipments (batch) - delegates to capability implementation
75
+ */
76
+ closeShipments(req: import('@shopickup/core').CloseShipmentsRequest, ctx: AdapterContext): Promise<import('@shopickup/core').CloseShipmentsResponse>;
77
+ }
78
+ export { withOAuthFallback } from './utils/oauthFallback.js';
79
+ export { createResolveBaseUrl, createResolveOAuthUrl };
80
+ export type { ResolveBaseUrl, ResolveOAuthUrl };
81
+ export { track, trackPull500Start, trackPull500Check, trackRegistered } from './capabilities/track.js';
82
+ export * from './validation.js';
83
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAgB,UAAU,EAAE,cAAc,EAAgB,eAAe,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,eAAe,EAAE,cAAc,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AACvZ,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAA4B,cAAc,EAAE,eAAe,EAAsB,MAAM,2BAA2B,CAAC;AAOvK,OAAO,KAAK,EAAkG,wBAAwB,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAE3L;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,qBAAa,UAAW,YAAW,cAAc;IAC7C,QAAQ,CAAC,EAAE,YAAY;IACvB,QAAQ,CAAC,WAAW,iBAAiB;IAErC,QAAQ,CAAC,YAAY,EAAE,UAAU,EAAE,CASjC;IAGF,QAAQ,CAAC,QAAQ;;MAEhB;IAED,OAAO,CAAC,WAAW,CAAyC;IAC5D,OAAO,CAAC,WAAW,CAA4C;IAC/D,OAAO,CAAC,YAAY,CAA4C;IAChE,OAAO,CAAC,YAAY,CAA+C;IACnE,OAAO,CAAC,eAAe,CAA8C;IACrE,OAAO,CAAC,eAAe,CAAiD;IACxE,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,cAAc,CAAc;gBAExB,OAAO,GAAE,MAA8C,EAAE,cAAc,GAAE,MAAW;IAahG;;OAEG;IACG,YAAY,CACd,GAAG,EAAE,mBAAmB,EACxB,GAAG,EAAE,cAAc,GACpB,OAAO,CAAC,eAAe,CAAC;IAQ3B;;OAEG;IACG,aAAa,CACf,GAAG,EAAE,oBAAoB,EACzB,GAAG,EAAE,cAAc,GACpB,OAAO,CAAC,qBAAqB,CAAC;IAI3B,WAAW,CACb,GAAG,EAAE,kBAAkB,EACvB,GAAG,EAAE,cAAc,GACpB,OAAO,CAAC,mBAAmB,CAAC;IAIzB,YAAY,CACd,GAAG,EAAE,mBAAmB,EACxB,GAAG,EAAE,cAAc,GACpB,OAAO,CAAC,oBAAoB,CAAC;IAI1B,KAAK,CACP,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,GACpB,OAAO,CAAC,cAAc,CAAC;IAuBpB,kBAAkB,CACpB,GAAG,EAAE,sBAAsB,EAC3B,GAAG,EAAE,cAAc,GACpB,OAAO,CAAC,uBAAuB,CAAC;IAI7B,iBAAiB,CACnB,GAAG,EAAE,wBAAwB,EAC7B,GAAG,EAAE,cAAc,GACpB,OAAO,CAAC,yBAAyB,CAAC;IAI/B,iBAAiB,CACnB,GAAG,EAAE,wBAAwB,EAC7B,GAAG,EAAE,cAAc,GACpB,OAAO,CAAC,yBAAyB,CAAC;IAIrC;;OAEG;IACG,cAAc,CAChB,GAAG,EAAE,OAAO,iBAAiB,EAAE,qBAAqB,EACpD,GAAG,EAAE,cAAc,GACpB,OAAO,CAAC,OAAO,iBAAiB,EAAE,sBAAsB,CAAC;CAK/D;AAGD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,CAAC;AACvD,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAEvG,cAAc,iBAAiB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,142 @@
1
+ import { Capabilities, CarrierError } from '@shopickup/core';
2
+ import { createResolveBaseUrl, createResolveOAuthUrl, createResolveTrackingUrl } from './utils/resolveBaseUrl.js';
3
+ import { fetchPickupPoints as fetchPickupPointsImpl } from './capabilities/index.js';
4
+ import { getShipmentDetails as getShipmentDetailsImpl } from './capabilities/get-shipment-details.js';
5
+ import { track as trackImpl } from './capabilities/track.js';
6
+ import { exchangeAuthToken as exchangeAuthTokenImpl } from './capabilities/auth.js';
7
+ import { createParcel as createParcelImpl, createParcels as createParcelsImpl } from './capabilities/parcels.js';
8
+ import { createLabel as createLabelImpl, createLabels as createLabelsImpl } from './capabilities/label.js';
9
+ /**
10
+ * MPLAdapter
11
+ *
12
+ * MPL (hu-mpl) is a major Hungarian logistics carrier.
13
+ *
14
+ * Capabilities supported:
15
+ * - CREATE_PARCEL: Create parcels directly
16
+ * - CREATE_PARCELS: Batch create multiple parcels
17
+ * - CREATE_LABEL: Generate PDF labels for parcels
18
+ * - CLOSE_SHIPMENTS: Closes shipments to finalize them before sendoff.
19
+ * - TRACK: Track parcels by barcode
20
+ * - EXCHANGE_AUTH_TOKEN: Exchange API credentials for OAuth2 Bearer token
21
+ * - TEST_MODE_SUPPORTED: Can switch to test API for sandbox testing
22
+ *
23
+ * Test API:
24
+ * - Production: https://core.api.posta.hu/v2/mplapi
25
+ * - Test/Sandbox: https://sandbox.api.posta.hu/v2/mplapi
26
+ * - Pass options.useTestApi = true in request to switch to test endpoint for that call
27
+ * - Test API requires separate test credentials
28
+ *
29
+ * OAuth Token Exchange:
30
+ * - Call exchangeAuthToken() to exchange API credentials for a Bearer token
31
+ * - Cached internally within the adapter; TTL is ~1 hour
32
+ * - Returns access_token, expires_in, and token_type
33
+ * - Useful when Basic auth is disabled at account level
34
+ *
35
+ * OAuth Fallback:
36
+ * - Wrap HTTP client with withOAuthFallback() to automatically exchange credentials
37
+ * when receiving 401 "Basic auth not enabled" error
38
+ * - No explicit calls needed; fallback is transparent
39
+ *
40
+ * Notes:
41
+ * - MPL does NOT have a shipment concept; parcels are created directly
42
+ * - Labels are generated per parcel
43
+ * - Tracking available via barcode (FoxWeb barcode format: CLFOX...)
44
+ * - createLabel does not support per-call test mode (no request object in interface)
45
+ */
46
+ export class MPLAdapter {
47
+ id = "hu-mpl";
48
+ displayName = "MPL Hungary";
49
+ capabilities = [
50
+ Capabilities.CREATE_PARCEL,
51
+ Capabilities.CREATE_PARCELS,
52
+ Capabilities.CREATE_LABEL,
53
+ Capabilities.TRACK,
54
+ Capabilities.GET_SHIPMENT_DETAILS,
55
+ Capabilities.CLOSE_SHIPMENT,
56
+ Capabilities.TEST_MODE_SUPPORTED,
57
+ Capabilities.EXCHANGE_AUTH_TOKEN,
58
+ ];
59
+ // MPL requires close before label generation
60
+ requires = {
61
+ createLabel: [Capabilities.CREATE_PARCEL, Capabilities.CLOSE_SHIPMENT],
62
+ };
63
+ prodBaseUrl = "https://core.api.posta.hu/v2/mplapi";
64
+ testBaseUrl = "https://sandbox.api.posta.hu/v2/mplapi";
65
+ prodOAuthUrl = "https://core.api.posta.hu/oauth2/token";
66
+ testOAuthUrl = "https://sandbox.api.posta.hu/oauth2/token";
67
+ prodTrackingUrl = "https://core.api.posta.hu/v2/nyomkovetes";
68
+ testTrackingUrl = "https://sandbox.api.posta.hu/v2/nyomkovetes";
69
+ resolveBaseUrl;
70
+ resolveOAuthUrl;
71
+ resolveTrackingUrl;
72
+ accountingCode = "";
73
+ constructor(baseUrl = "https://core.api.posta.hu/v2/mplapi", accountingCode = "") {
74
+ this.prodBaseUrl = "https://core.api.posta.hu/v2/mplapi";
75
+ this.testBaseUrl = "https://sandbox.api.posta.hu/v2/mplapi";
76
+ this.prodOAuthUrl = "https://core.api.posta.hu/oauth2/token";
77
+ this.testOAuthUrl = "https://sandbox.api.posta.hu/oauth2/token";
78
+ this.prodTrackingUrl = "https://core.api.posta.hu/v2/nyomkovetes";
79
+ this.testTrackingUrl = "https://sandbox.api.posta.hu/v2/nyomkovetes";
80
+ this.resolveBaseUrl = createResolveBaseUrl(this.prodBaseUrl, this.testBaseUrl);
81
+ this.resolveOAuthUrl = createResolveOAuthUrl(this.prodOAuthUrl, this.testOAuthUrl);
82
+ this.resolveTrackingUrl = createResolveTrackingUrl(this.prodTrackingUrl, this.testTrackingUrl);
83
+ this.accountingCode = accountingCode;
84
+ }
85
+ /**
86
+ * Create a single parcel in MPL
87
+ */
88
+ async createParcel(req, ctx) {
89
+ return createParcelImpl(req, ctx, (batchReq, batchCtx) => this.createParcels(batchReq, batchCtx));
90
+ }
91
+ /**
92
+ * Create multiple parcels in MPL (batch operation)
93
+ */
94
+ async createParcels(req, ctx) {
95
+ return createParcelsImpl(req, ctx, this.resolveBaseUrl);
96
+ }
97
+ async createLabel(req, ctx) {
98
+ return createLabelImpl(req, ctx, this.resolveBaseUrl);
99
+ }
100
+ async createLabels(req, ctx) {
101
+ return createLabelsImpl(req, ctx, this.resolveBaseUrl);
102
+ }
103
+ async track(req, ctx) {
104
+ // MPL supports batch tracking, but core interface expects single TrackingUpdate
105
+ // Convert single TrackingRequest to internal batch format and return first result
106
+ const batchRequest = {
107
+ trackingNumbers: [req.trackingNumber],
108
+ credentials: (req.credentials || {}),
109
+ state: 'last',
110
+ useRegisteredEndpoint: false,
111
+ options: req.options,
112
+ };
113
+ const results = await trackImpl(batchRequest, ctx, this.resolveTrackingUrl);
114
+ if (results.length === 0) {
115
+ throw new CarrierError(`No tracking information found for: ${req.trackingNumber}`, 'NotFound');
116
+ }
117
+ return results[0];
118
+ }
119
+ async getShipmentDetails(req, ctx) {
120
+ return getShipmentDetailsImpl(req, ctx, this.resolveBaseUrl);
121
+ }
122
+ async exchangeAuthToken(req, ctx) {
123
+ return exchangeAuthTokenImpl(req, ctx, this.resolveOAuthUrl, this.accountingCode);
124
+ }
125
+ async fetchPickupPoints(req, ctx) {
126
+ return fetchPickupPointsImpl(req, ctx, this.resolveBaseUrl);
127
+ }
128
+ /**
129
+ * Close shipments (batch) - delegates to capability implementation
130
+ */
131
+ async closeShipments(req, ctx) {
132
+ // Lazy import to avoid circular deps in some test environments
133
+ const { closeShipments: closeImpl } = await import('./capabilities/index.js');
134
+ return closeImpl(req, ctx, this.resolveBaseUrl);
135
+ }
136
+ }
137
+ // Export utilities for use with dev server or other integrations
138
+ export { withOAuthFallback } from './utils/oauthFallback.js';
139
+ export { createResolveBaseUrl, createResolveOAuthUrl };
140
+ export { track, trackPull500Start, trackPull500Check, trackRegistered } from './capabilities/track.js';
141
+ // Re-export validation helpers for consumers (dev-server uses these)
142
+ export * from './validation.js';
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Label mapping utilities for MPL adapter
3
+ *
4
+ * Handles conversion from canonical label requests to MPL API query parameters
5
+ * and normalization of API responses back to canonical format.
6
+ *
7
+ * The MPL label endpoint uses GET with query parameters:
8
+ * GET /shipments/label?trackingNumbers=X&trackingNumbers=Y&labelType=A5&labelFormat=PDF&orderBy=SENDING&singleFile=true
9
+ *
10
+ * Response is JSON array of LabelQueryResult objects with base64-encoded label data.
11
+ */
12
+ import type { CreateLabelsMPLRequest, CreateLabelsMPLOptions, LabelOrderBy, LabelFormat, LabelType } from '../validation.js';
13
+ type CreateLabelsMPLRequestWithOptions = Omit<CreateLabelsMPLRequest, 'options'> & {
14
+ options: CreateLabelsMPLOptions;
15
+ };
16
+ /**
17
+ * Query parameters for the MPL label API GET request
18
+ */
19
+ export interface LabelQueryParams {
20
+ trackingNumbers: string[];
21
+ labelType?: LabelType;
22
+ labelFormat?: LabelFormat;
23
+ orderBy?: LabelOrderBy;
24
+ singleFile?: boolean;
25
+ }
26
+ /**
27
+ * Build query parameters from canonical label request
28
+ *
29
+ * Transforms CreateLabelsMPLRequest into query parameters suitable for
30
+ * the MPL GET /shipments/label endpoint.
31
+ *
32
+ * @param req - Canonical label request
33
+ * @returns Query parameters object
34
+ */
35
+ export declare function buildLabelQueryParams(req: CreateLabelsMPLRequestWithOptions): LabelQueryParams;
36
+ /**
37
+ * Serialize query parameters to URL search params string
38
+ *
39
+ * Handles array parameters (trackingNumbers) as multiple query params:
40
+ * ?trackingNumbers=X&trackingNumbers=Y&trackingNumbers=Z&labelType=A5&labelFormat=PDF
41
+ *
42
+ * @param params - Query parameters
43
+ * @returns URLSearchParams string (without leading ?)
44
+ */
45
+ export declare function serializeQueryParams(params: LabelQueryParams): string;
46
+ /**
47
+ * Build complete query string for logging/debugging
48
+ *
49
+ * Shows the actual query string that will be sent to the API
50
+ *
51
+ * @param params - Query parameters
52
+ * @returns Complete query string with leading ?
53
+ */
54
+ export declare function buildQueryString(params: LabelQueryParams): string;
55
+ /**
56
+ * Get default values for optional label parameters
57
+ *
58
+ * These match MPL server defaults for when parameters are not specified
59
+ */
60
+ export declare const LABEL_DEFAULTS: {
61
+ labelType: "A5";
62
+ labelFormat: "PDF";
63
+ orderBy: undefined;
64
+ singleFile: boolean;
65
+ };
66
+ export {};
67
+ //# sourceMappingURL=label.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"label.d.ts","sourceRoot":"","sources":["../../src/mappers/label.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,sBAAsB,EACtB,sBAAsB,EACtB,YAAY,EACZ,WAAW,EACX,SAAS,EACV,MAAM,kBAAkB,CAAC;AAE1B,KAAK,iCAAiC,GAAG,IAAI,CAAC,sBAAsB,EAAE,SAAS,CAAC,GAAG;IACjF,OAAO,EAAE,sBAAsB,CAAC;CACjC,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,iCAAiC,GAAG,gBAAgB,CAS9F;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAuBrE;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAGjE;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;;;CAK1B,CAAC"}