sabre-client 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.es.md +207 -15
- package/README.md +207 -15
- package/dist/index.d.ts +2762 -690
- package/dist/index.js +508 -119
- package/dist/index.js.map +1 -1
- package/dist/reservation-history-bKDqkf8T.d.ts +788 -0
- package/dist/testing.d.ts +129 -7
- package/dist/testing.js +475 -57
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
- package/dist/queue-ref-BDGh8FrV.d.ts +0 -11
package/dist/index.js
CHANGED
|
@@ -223,6 +223,22 @@ function isObject(value) {
|
|
|
223
223
|
return typeof value === "object" && value !== null;
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
// src/http/raw-request.ts
|
|
227
|
+
function rawRequestHeaders(headers) {
|
|
228
|
+
const copy = new Headers(headers);
|
|
229
|
+
copy.delete("Authorization");
|
|
230
|
+
return copy;
|
|
231
|
+
}
|
|
232
|
+
function rawHooks(options, operation) {
|
|
233
|
+
const { onRawResponse, onRawRequest } = options;
|
|
234
|
+
if (onRawResponse === void 0 && onRawRequest === void 0) return {};
|
|
235
|
+
return {
|
|
236
|
+
operation,
|
|
237
|
+
...onRawResponse === void 0 ? {} : { onRawResponse },
|
|
238
|
+
...onRawRequest === void 0 ? {} : { onRawRequest }
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
226
242
|
// src/air/bargain-finder-max.ts
|
|
227
243
|
var BARGAIN_FINDER_MAX_SERVICE = "bargainFinderMax";
|
|
228
244
|
var PATH = "/v5/offers/shop";
|
|
@@ -248,7 +264,7 @@ async function bargainFinderMax(transport, request, options = {}) {
|
|
|
248
264
|
classify: classifyBargainFinderMax,
|
|
249
265
|
...options.retry === void 0 ? {} : { retry: options.retry },
|
|
250
266
|
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
251
|
-
...options
|
|
267
|
+
...rawHooks(options, "air.bargainFinderMax")
|
|
252
268
|
});
|
|
253
269
|
return denormalizeGroupedItineraryResponse(body.groupedItineraryResponse, BARGAIN_FINDER_MAX_SERVICE);
|
|
254
270
|
}
|
|
@@ -327,7 +343,7 @@ async function send(transport, path, service, request, options) {
|
|
|
327
343
|
classify: (raw) => classifyGetSeats(raw, service),
|
|
328
344
|
...options.retry === void 0 ? {} : { retry: options.retry },
|
|
329
345
|
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
330
|
-
...options
|
|
346
|
+
...rawHooks(options, `air.${service}`)
|
|
331
347
|
});
|
|
332
348
|
return {
|
|
333
349
|
response: body.response,
|
|
@@ -418,7 +434,7 @@ async function revalidateItinerary(transport, request, options = {}) {
|
|
|
418
434
|
classify: classifyRevalidateItinerary,
|
|
419
435
|
...options.retry === void 0 ? {} : { retry: options.retry },
|
|
420
436
|
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
421
|
-
...options
|
|
437
|
+
...rawHooks(options, "air.revalidateItinerary")
|
|
422
438
|
});
|
|
423
439
|
return denormalizeGroupedItineraryResponse(body.groupedItineraryResponse, REVALIDATE_ITINERARY_SERVICE);
|
|
424
440
|
}
|
|
@@ -797,7 +813,7 @@ async function cancelBooking(transport, request, options = {}) {
|
|
|
797
813
|
body: request,
|
|
798
814
|
classify: classifyCancelBooking,
|
|
799
815
|
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
800
|
-
...options
|
|
816
|
+
...rawHooks(options, "booking.cancel")
|
|
801
817
|
});
|
|
802
818
|
return toCancelBookingResponse(body);
|
|
803
819
|
}
|
|
@@ -974,7 +990,7 @@ async function createBooking(transport, request, options = {}) {
|
|
|
974
990
|
body: request,
|
|
975
991
|
classify: classifyCreateBooking,
|
|
976
992
|
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
977
|
-
...options
|
|
993
|
+
...rawHooks(options, "booking.create")
|
|
978
994
|
});
|
|
979
995
|
return toCreateBookingResponse(body);
|
|
980
996
|
}
|
|
@@ -1066,7 +1082,7 @@ async function getBooking(transport, request, options = {}) {
|
|
|
1066
1082
|
body: request,
|
|
1067
1083
|
classify: classifyGetBooking,
|
|
1068
1084
|
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
1069
|
-
...options
|
|
1085
|
+
...rawHooks(options, "booking.get")
|
|
1070
1086
|
});
|
|
1071
1087
|
return toGetBookingResponse(body);
|
|
1072
1088
|
}
|
|
@@ -1207,19 +1223,26 @@ function resolveConfig(config) {
|
|
|
1207
1223
|
if (environment !== "sandbox" && environment !== "production") {
|
|
1208
1224
|
fail(`environment debe ser 'sandbox' o 'production' (recibido: ${JSON.stringify(environment)})`);
|
|
1209
1225
|
}
|
|
1210
|
-
const
|
|
1211
|
-
if (
|
|
1212
|
-
fail(`authVersion debe ser 'v3' o 'v2' (recibido: ${JSON.stringify(
|
|
1226
|
+
const configVersion = config.authVersion ?? void 0;
|
|
1227
|
+
if (configVersion !== void 0 && configVersion !== "v3" && configVersion !== "v2") {
|
|
1228
|
+
fail(`authVersion debe ser 'v3' o 'v2' (recibido: ${JSON.stringify(configVersion)})`);
|
|
1213
1229
|
}
|
|
1214
1230
|
const { credentials } = config;
|
|
1215
1231
|
if (!isObject3(credentials)) fail("credentials es requerido");
|
|
1232
|
+
const credentialsVersion = credentials.authVersion ?? void 0;
|
|
1233
|
+
if (credentialsVersion !== void 0 && credentialsVersion !== "v3" && credentialsVersion !== "v2") {
|
|
1234
|
+
fail(`credentials.authVersion debe ser 'v3' o 'v2' (recibido: ${JSON.stringify(credentialsVersion)})`);
|
|
1235
|
+
}
|
|
1236
|
+
if (configVersion !== void 0 && credentialsVersion !== void 0 && configVersion !== credentialsVersion) {
|
|
1237
|
+
fail(`authVersion '${configVersion}' no coincide con credentials.authVersion '${credentialsVersion}'`);
|
|
1238
|
+
}
|
|
1239
|
+
const authVersion = configVersion ?? credentialsVersion ?? "v3";
|
|
1216
1240
|
requireString(credentials.username, "credentials.username");
|
|
1217
1241
|
requireString(credentials.password, "credentials.password");
|
|
1218
1242
|
if (authVersion === "v3") {
|
|
1219
1243
|
requireString(credentials.clientId, "credentials.clientId (requerido con authVersion v3)");
|
|
1220
1244
|
requireString(credentials.clientSecret, "credentials.clientSecret (requerido con authVersion v3)");
|
|
1221
1245
|
}
|
|
1222
|
-
if (config.pcc !== void 0) requireString(config.pcc, "pcc");
|
|
1223
1246
|
const { logger } = config;
|
|
1224
1247
|
if (logger !== void 0) {
|
|
1225
1248
|
if (!isObject3(logger)) fail("logger debe ser un objeto con debug/info/warn/error");
|
|
@@ -1240,12 +1263,12 @@ function resolveConfig(config) {
|
|
|
1240
1263
|
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
1241
1264
|
if (typeof fetchImpl !== "function") fail("no hay fetch disponible: pasa config.fetch");
|
|
1242
1265
|
return {
|
|
1266
|
+
// Ya validado arriba: con v3 trae clientId/clientSecret; la marca de versión ya se resolvió.
|
|
1243
1267
|
credentials: Object.freeze({ ...credentials }),
|
|
1244
1268
|
environment,
|
|
1245
1269
|
baseUrl: BASE_URLS[environment],
|
|
1246
1270
|
soapBaseUrl: SOAP_BASE_URLS[environment],
|
|
1247
1271
|
authVersion,
|
|
1248
|
-
pcc: config.pcc,
|
|
1249
1272
|
logger,
|
|
1250
1273
|
retry: Object.freeze({ ...DEFAULT_RETRY, ...definedOnly(config.retry ?? {}) }),
|
|
1251
1274
|
// Enlazado al global para evitar "Illegal invocation" en algunos runtimes.
|
|
@@ -1282,10 +1305,12 @@ var RestTransport = class {
|
|
|
1282
1305
|
}
|
|
1283
1306
|
async request(request) {
|
|
1284
1307
|
if (request.retry !== void 0) validateRetryOptions(request.retry, "retry");
|
|
1285
|
-
const
|
|
1308
|
+
const wantsRequest = request.onRawRequest !== void 0 && request.auth !== false;
|
|
1309
|
+
const capture = request.onRawResponse === void 0 && !wantsRequest ? void 0 : { attempts: 0 };
|
|
1286
1310
|
try {
|
|
1287
1311
|
return await this.#attempts(request, capture);
|
|
1288
1312
|
} finally {
|
|
1313
|
+
if (wantsRequest && capture?.request !== void 0) this.#deliverRawRequest(request, capture.request, capture.attempts);
|
|
1289
1314
|
if (capture?.last !== void 0) this.#deliverRaw(request, { ...capture.last, attempts: capture.attempts });
|
|
1290
1315
|
}
|
|
1291
1316
|
}
|
|
@@ -1339,6 +1364,19 @@ var RestTransport = class {
|
|
|
1339
1364
|
});
|
|
1340
1365
|
}
|
|
1341
1366
|
}
|
|
1367
|
+
/** Igual que `#deliverRaw`: un callback que lanza no rompe la operación. */
|
|
1368
|
+
#deliverRawRequest(request, raw, attempts) {
|
|
1369
|
+
const operation = request.operation ?? request.service;
|
|
1370
|
+
try {
|
|
1371
|
+
request.onRawRequest?.({ operation, protocol: "rest", ...raw, attempts });
|
|
1372
|
+
} catch (error) {
|
|
1373
|
+
this.#log.emit("warn", "sabre.request.rawRequestHookFailed", {
|
|
1374
|
+
service: request.service,
|
|
1375
|
+
operation,
|
|
1376
|
+
errorName: error instanceof Error ? error.name : typeof error
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1342
1380
|
async #authorizationFor(request) {
|
|
1343
1381
|
if (request.auth === false || this.#authorization === void 0) return void 0;
|
|
1344
1382
|
return this.#authorization.get();
|
|
@@ -1355,12 +1393,18 @@ var RestTransport = class {
|
|
|
1355
1393
|
this.#log.emit("debug", "sabre.request.start", logContext);
|
|
1356
1394
|
const startedAt = Date.now();
|
|
1357
1395
|
let response;
|
|
1358
|
-
|
|
1396
|
+
const body = request.body === void 0 ? void 0 : form ? String(request.body) : JSON.stringify(request.body);
|
|
1397
|
+
if (capture !== void 0) {
|
|
1398
|
+
capture.attempts++;
|
|
1399
|
+
if (request.onRawRequest !== void 0 && request.auth !== false) {
|
|
1400
|
+
capture.request = { method: request.method, url, headers: rawRequestHeaders(headers), body };
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1359
1403
|
try {
|
|
1360
1404
|
response = await this.#config.fetch(url, {
|
|
1361
1405
|
method: request.method,
|
|
1362
1406
|
headers,
|
|
1363
|
-
...
|
|
1407
|
+
...body === void 0 ? {} : { body },
|
|
1364
1408
|
...request.signal === void 0 ? {} : { signal: request.signal }
|
|
1365
1409
|
});
|
|
1366
1410
|
} catch (cause) {
|
|
@@ -1375,20 +1419,20 @@ var RestTransport = class {
|
|
|
1375
1419
|
}
|
|
1376
1420
|
const text = await response.text();
|
|
1377
1421
|
if (capture !== void 0) capture.last = { status: response.status, headers: response.headers, body: text };
|
|
1378
|
-
const
|
|
1422
|
+
const parsed = parseBody(text);
|
|
1379
1423
|
const endContext = { ...logContext, status: response.status, durationMs: Date.now() - startedAt };
|
|
1380
1424
|
if (!response.ok) {
|
|
1381
|
-
const error = gatewayError(request.service, response.status,
|
|
1425
|
+
const error = gatewayError(request.service, response.status, parsed);
|
|
1382
1426
|
this.#log.emit("error", "sabre.request.end", { ...endContext, error });
|
|
1383
1427
|
throw error;
|
|
1384
1428
|
}
|
|
1385
|
-
const serviceError = request.classify?.(
|
|
1429
|
+
const serviceError = request.classify?.(parsed, response);
|
|
1386
1430
|
if (serviceError !== void 0) {
|
|
1387
1431
|
this.#log.emit("warn", "sabre.request.end", { ...endContext, error: serviceError });
|
|
1388
1432
|
throw serviceError;
|
|
1389
1433
|
}
|
|
1390
1434
|
this.#log.emit("info", "sabre.request.end", endContext);
|
|
1391
|
-
return
|
|
1435
|
+
return parsed;
|
|
1392
1436
|
}
|
|
1393
1437
|
#url(request) {
|
|
1394
1438
|
const path = request.path.startsWith("/") ? request.path : `/${request.path}`;
|
|
@@ -1491,6 +1535,7 @@ var TRANSIENT_CODES = /* @__PURE__ */ new Set([
|
|
|
1491
1535
|
var PREVIOUS_ENTRY_ACTIVE_RETRIES = 3;
|
|
1492
1536
|
var PREVIOUS_ENTRY_ACTIVE_DELAY_MS = 1e3;
|
|
1493
1537
|
var PREVIOUS_ENTRY_ACTIVE = /PREVIOUS ENTRY ACTIVE/i;
|
|
1538
|
+
var SOAP_HEADERS = { "Content-Type": "text/xml; charset=utf-8", Accept: "text/xml" };
|
|
1494
1539
|
var PARSER_OPTIONS = {
|
|
1495
1540
|
ignoreAttributes: false,
|
|
1496
1541
|
attributeNamePrefix: "@",
|
|
@@ -1526,6 +1571,7 @@ var SoapTransport = class {
|
|
|
1526
1571
|
try {
|
|
1527
1572
|
return await this.#attempts(request, capture);
|
|
1528
1573
|
} finally {
|
|
1574
|
+
if (capture.request !== void 0) this.#deliverRawRequest(request, capture.request, capture.attempts);
|
|
1529
1575
|
if (capture.last !== void 0) this.#deliverRaw(request, { ...capture.last, attempts: capture.attempts });
|
|
1530
1576
|
}
|
|
1531
1577
|
}
|
|
@@ -1572,10 +1618,18 @@ var SoapTransport = class {
|
|
|
1572
1618
|
const startedAt = Date.now();
|
|
1573
1619
|
let response;
|
|
1574
1620
|
capture.attempts++;
|
|
1621
|
+
if (wantsRawRequest(request)) {
|
|
1622
|
+
capture.request = {
|
|
1623
|
+
method: "POST",
|
|
1624
|
+
url: this.#config.soapBaseUrl,
|
|
1625
|
+
headers: rawRequestHeaders(SOAP_HEADERS),
|
|
1626
|
+
body: redactSoapEnvelope(scrubToken(envelope, request.security))
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1575
1629
|
try {
|
|
1576
1630
|
response = await this.#config.fetch(this.#config.soapBaseUrl, {
|
|
1577
1631
|
method: "POST",
|
|
1578
|
-
headers:
|
|
1632
|
+
headers: SOAP_HEADERS,
|
|
1579
1633
|
body: envelope,
|
|
1580
1634
|
...request.signal === void 0 ? {} : { signal: request.signal }
|
|
1581
1635
|
});
|
|
@@ -1606,6 +1660,7 @@ var SoapTransport = class {
|
|
|
1606
1660
|
return {
|
|
1607
1661
|
...result,
|
|
1608
1662
|
messageId,
|
|
1663
|
+
respondedAt: respondedAtOf(text),
|
|
1609
1664
|
raw: text,
|
|
1610
1665
|
securityToken: request.readSecurityToken === true ? readSecurityToken(original) : void 0,
|
|
1611
1666
|
status: response.status
|
|
@@ -1648,12 +1703,12 @@ var SoapTransport = class {
|
|
|
1648
1703
|
});
|
|
1649
1704
|
}
|
|
1650
1705
|
const results = asNode(payload.ApplicationResults);
|
|
1651
|
-
const warnings = asArray(results?.Warning).map((
|
|
1652
|
-
const entry = resultEntry(
|
|
1706
|
+
const warnings = asArray(results?.Warning).map((node3) => {
|
|
1707
|
+
const entry = resultEntry(node3);
|
|
1653
1708
|
return new SabreWarning(`${service}: ${entry.description}`, { service, code: entry.code, raw: text });
|
|
1654
1709
|
});
|
|
1655
|
-
const errors = asArray(results?.Error).map((
|
|
1656
|
-
const entry = resultEntry(
|
|
1710
|
+
const errors = asArray(results?.Error).map((node3) => {
|
|
1711
|
+
const entry = resultEntry(node3);
|
|
1657
1712
|
return new SabreBusinessError(`${service}: ${entry.description}`, {
|
|
1658
1713
|
service,
|
|
1659
1714
|
code: entry.code,
|
|
@@ -1699,6 +1754,19 @@ var SoapTransport = class {
|
|
|
1699
1754
|
].join("");
|
|
1700
1755
|
}
|
|
1701
1756
|
/** Un callback que lanza no rompe la operación (mismo criterio que REST). */
|
|
1757
|
+
#deliverRawRequest(request, raw, attempts) {
|
|
1758
|
+
const operation = request.operation ?? request.service;
|
|
1759
|
+
try {
|
|
1760
|
+
request.onRawRequest?.({ operation, protocol: "soap", ...raw, attempts });
|
|
1761
|
+
} catch (error) {
|
|
1762
|
+
this.#log.emit("warn", "sabre.request.rawRequestHookFailed", {
|
|
1763
|
+
service: request.service,
|
|
1764
|
+
operation,
|
|
1765
|
+
errorName: error instanceof Error ? error.name : typeof error
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
/** Un callback que lanza no rompe la operación (mismo criterio que REST). */
|
|
1702
1770
|
#deliverRaw(request, raw) {
|
|
1703
1771
|
if (request.onRawResponse === void 0) return;
|
|
1704
1772
|
const operation = request.operation ?? request.service;
|
|
@@ -1713,6 +1781,18 @@ var SoapTransport = class {
|
|
|
1713
1781
|
}
|
|
1714
1782
|
}
|
|
1715
1783
|
};
|
|
1784
|
+
function respondedAtOf(raw) {
|
|
1785
|
+
if (typeof raw !== "string") return void 0;
|
|
1786
|
+
const header = /<(?:[\w-]+:)?Header\b[\s\S]*?<\/(?:[\w-]+:)?Header>/.exec(raw)?.[0];
|
|
1787
|
+
const value = header === void 0 ? void 0 : /<(?:[\w-]+:)?Timestamp>\s*([^<\s]+)\s*</.exec(header)?.[1];
|
|
1788
|
+
const match = value === void 0 ? null : /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?)(Z|[+-]\d{2}:\d{2})?$/.exec(value);
|
|
1789
|
+
if (match === null) return void 0;
|
|
1790
|
+
return `${match[1]}${match[2] ?? "Z"}`;
|
|
1791
|
+
}
|
|
1792
|
+
function respondedAtField(raw) {
|
|
1793
|
+
const respondedAt = respondedAtOf(raw);
|
|
1794
|
+
return respondedAt === void 0 ? {} : { respondedAt };
|
|
1795
|
+
}
|
|
1716
1796
|
function xmlEscape(value) {
|
|
1717
1797
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1718
1798
|
}
|
|
@@ -1735,6 +1815,9 @@ function securityHeader(security) {
|
|
|
1735
1815
|
"</wsse:Security>"
|
|
1736
1816
|
].join("");
|
|
1737
1817
|
}
|
|
1818
|
+
function wantsRawRequest(request) {
|
|
1819
|
+
return request.onRawRequest !== void 0 && "binarySecurityToken" in request.security;
|
|
1820
|
+
}
|
|
1738
1821
|
function scrubToken(text, security) {
|
|
1739
1822
|
if (!("binarySecurityToken" in security) || security.binarySecurityToken === "") return text;
|
|
1740
1823
|
const token = security.binarySecurityToken;
|
|
@@ -1774,8 +1857,8 @@ function faultError(service, status, fault, text) {
|
|
|
1774
1857
|
...code === void 0 || code === "" ? {} : { code }
|
|
1775
1858
|
});
|
|
1776
1859
|
}
|
|
1777
|
-
function resultEntry(
|
|
1778
|
-
const entry = asNode(
|
|
1860
|
+
function resultEntry(node3) {
|
|
1861
|
+
const entry = asNode(node3) ?? {};
|
|
1779
1862
|
const messages = [];
|
|
1780
1863
|
const shortTexts = [];
|
|
1781
1864
|
let code;
|
|
@@ -1813,8 +1896,8 @@ function untrimmedPayload(raw) {
|
|
|
1813
1896
|
const document = parseEnvelope(raw, untrimmedParser);
|
|
1814
1897
|
return document === void 0 ? void 0 : asNode(firstElement(document.Body)[1]);
|
|
1815
1898
|
}
|
|
1816
|
-
function firstElement(
|
|
1817
|
-
for (const [key, value] of Object.entries(
|
|
1899
|
+
function firstElement(node3) {
|
|
1900
|
+
for (const [key, value] of Object.entries(node3)) {
|
|
1818
1901
|
if (!key.startsWith("@") && key !== "#text") return [key, Array.isArray(value) ? value[0] : value];
|
|
1819
1902
|
}
|
|
1820
1903
|
return [void 0, void 0];
|
|
@@ -1829,14 +1912,14 @@ function asArray(value) {
|
|
|
1829
1912
|
}
|
|
1830
1913
|
function textOf(value) {
|
|
1831
1914
|
if (typeof value === "string") return value;
|
|
1832
|
-
const
|
|
1833
|
-
const text =
|
|
1834
|
-
return typeof text === "string" ? text :
|
|
1915
|
+
const node3 = asNode(value);
|
|
1916
|
+
const text = node3?.["#text"];
|
|
1917
|
+
return typeof text === "string" ? text : node3 === void 0 ? void 0 : "";
|
|
1835
1918
|
}
|
|
1836
1919
|
function stringAt(root2, ...path) {
|
|
1837
|
-
let
|
|
1838
|
-
for (const key of path)
|
|
1839
|
-
return nonEmpty2(textOf(
|
|
1920
|
+
let node3 = root2;
|
|
1921
|
+
for (const key of path) node3 = asNode(node3)?.[key];
|
|
1922
|
+
return nonEmpty2(textOf(node3));
|
|
1840
1923
|
}
|
|
1841
1924
|
function nonEmpty2(value) {
|
|
1842
1925
|
return typeof value === "string" && value !== "" ? value : void 0;
|
|
@@ -1893,27 +1976,41 @@ import { XMLParser as XMLParser2 } from "fast-xml-parser";
|
|
|
1893
1976
|
// src/queue/queue-ref.ts
|
|
1894
1977
|
var MIN_QUEUE_NUMBER = 0;
|
|
1895
1978
|
var MAX_QUEUE_NUMBER = 511;
|
|
1979
|
+
var MESSAGE_QUEUE = "S";
|
|
1980
|
+
var ALPHA_QUEUE = /^[A-Z]{1,2}$/;
|
|
1981
|
+
function fail3(message) {
|
|
1982
|
+
throw new TypeError(`sabre-client: ${message}`);
|
|
1983
|
+
}
|
|
1896
1984
|
function validateQueueRef(value, label) {
|
|
1897
1985
|
if (typeof value === "number") {
|
|
1898
1986
|
if (Number.isInteger(value) && value >= MIN_QUEUE_NUMBER && value <= MAX_QUEUE_NUMBER) return;
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1987
|
+
fail3(`${label} debe ser un n\xFAmero de cola entero de ${MIN_QUEUE_NUMBER} a ${MAX_QUEUE_NUMBER}, una cola alfab\xE9tica ('S') o { name }`);
|
|
1988
|
+
}
|
|
1989
|
+
if (typeof value === "string") {
|
|
1990
|
+
if (ALPHA_QUEUE.test(value)) return;
|
|
1991
|
+
if (/^\d+$/.test(value.trim())) fail3(`${label}: una cola num\xE9rica va como n\xFAmero (${Number(value)}), no como string`);
|
|
1992
|
+
fail3(`${label} debe ser una cola alfab\xE9tica de una o dos letras may\xFAsculas (p. ej. 'S')`);
|
|
1902
1993
|
}
|
|
1903
1994
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1904
1995
|
const { name } = value;
|
|
1905
|
-
if (typeof name
|
|
1906
|
-
|
|
1996
|
+
if (typeof name !== "string" || name.trim() === "") fail3(`${label}.name debe ser un string no vac\xEDo`);
|
|
1997
|
+
if (ALPHA_QUEUE.test(name)) fail3(`${label}: '${name}' es una cola alfab\xE9tica: pasa '${name}', no { name: '${name}' }`);
|
|
1998
|
+
return;
|
|
1907
1999
|
}
|
|
1908
|
-
|
|
2000
|
+
fail3(`${label} debe ser un n\xFAmero de cola (0\u2013511), una cola alfab\xE9tica ('S') o { name }`);
|
|
1909
2001
|
}
|
|
1910
2002
|
function queueRefAttribute(queue) {
|
|
1911
|
-
|
|
2003
|
+
if (typeof queue === "number") return `Number="${queue}"`;
|
|
2004
|
+
if (typeof queue === "string") return `Number="${xmlEscape(queue)}"`;
|
|
2005
|
+
return `Name="${xmlEscape(queue.name)}"`;
|
|
2006
|
+
}
|
|
2007
|
+
function isMessageQueue(queue) {
|
|
2008
|
+
return queue === MESSAGE_QUEUE;
|
|
1912
2009
|
}
|
|
1913
2010
|
function toQueueRef(value) {
|
|
1914
2011
|
const text = value?.trim();
|
|
1915
2012
|
if (text === void 0 || text === "") return void 0;
|
|
1916
|
-
return /^\d+$/.test(text) ? Number(text) :
|
|
2013
|
+
return /^\d+$/.test(text) ? Number(text) : text;
|
|
1917
2014
|
}
|
|
1918
2015
|
|
|
1919
2016
|
// src/queue/queue-access.ts
|
|
@@ -1931,9 +2028,11 @@ function root(body) {
|
|
|
1931
2028
|
function identifier(queue, pcc) {
|
|
1932
2029
|
return `<QueueIdentifier ${queueRefAttribute(queue)} PseudoCityCode="${xmlEscape(pcc)}"/>`;
|
|
1933
2030
|
}
|
|
2031
|
+
function selectionXml(selection) {
|
|
2032
|
+
return selection.map((filter) => `<Selection Function="${xmlEscape(filter.function)}" Criteria="${xmlEscape(filter.criteria)}"/>`).join("");
|
|
2033
|
+
}
|
|
1934
2034
|
function queueAccessPayload(queue, pcc, selection = []) {
|
|
1935
|
-
|
|
1936
|
-
return root(`${identifier(queue, pcc)}${filters}`);
|
|
2035
|
+
return root(`${identifier(queue, pcc)}${selectionXml(selection)}`);
|
|
1937
2036
|
}
|
|
1938
2037
|
function queueNavigationPayload(action, from = []) {
|
|
1939
2038
|
return root(`<Navigation Action="${action}"/>${from.map((target) => identifier(target.queue, target.pcc)).join("")}`);
|
|
@@ -1999,17 +2098,21 @@ function queueEndError(error, service) {
|
|
|
1999
2098
|
function isOffQueueError(error) {
|
|
2000
2099
|
return error instanceof SabreBusinessError && [error, ...error.errors].some((result) => resultHasText(result, OFF_QUEUE));
|
|
2001
2100
|
}
|
|
2002
|
-
function endStep(warnings, reason = "end-of-display") {
|
|
2003
|
-
return { done: true, reason, message: reason === "empty" ? QUEUE_EMPTY : END_OF_DISPLAY, warnings };
|
|
2101
|
+
function endStep(warnings, reason = "end-of-display", raw) {
|
|
2102
|
+
return { done: true, reason, message: reason === "empty" ? QUEUE_EMPTY : END_OF_DISPLAY, warnings, ...respondedAtField(raw) };
|
|
2004
2103
|
}
|
|
2005
|
-
function parseQueueStep(response) {
|
|
2104
|
+
function parseQueueStep(response, { messages = false } = {}) {
|
|
2006
2105
|
const { warnings } = response;
|
|
2007
2106
|
const paragraph = paragraphLines(response.raw);
|
|
2008
2107
|
const firstLine = paragraph.find((line) => line.trim() !== "");
|
|
2009
|
-
if (isEndOfDisplay(firstLine) || warnings.some(isEndOfDisplayResult)) return endStep(warnings);
|
|
2010
|
-
|
|
2108
|
+
if (isEndOfDisplay(firstLine) || warnings.some(isEndOfDisplayResult)) return endStep(warnings, "end-of-display", response.raw);
|
|
2109
|
+
if (messages && response.payload.Line === void 0 && firstLine !== void 0) {
|
|
2110
|
+
return { done: false, kind: "message", message: { text: paragraph }, warnings, ...respondedAtField(response.raw) };
|
|
2111
|
+
}
|
|
2112
|
+
const fields = messages ? void 0 : lineFields(response.payload.Line);
|
|
2011
2113
|
if (fields === void 0) {
|
|
2012
|
-
|
|
2114
|
+
const expected = messages ? "sin Paragraph de mensaje ni fin de cola en la cola S" : "sin Line/UniqueID ni fin de cola";
|
|
2115
|
+
throw new SabreGatewayError(`${QUEUE_ACCESS_SERVICE}: respuesta no reconocida (${expected})`, {
|
|
2013
2116
|
protocol: "soap",
|
|
2014
2117
|
status: response.status,
|
|
2015
2118
|
retryable: false,
|
|
@@ -2017,7 +2120,7 @@ function parseQueueStep(response) {
|
|
|
2017
2120
|
});
|
|
2018
2121
|
}
|
|
2019
2122
|
const item = { ...fields, paragraph };
|
|
2020
|
-
return { done: false, item, warnings };
|
|
2123
|
+
return { done: false, kind: "pnr", item, warnings, ...respondedAtField(response.raw) };
|
|
2021
2124
|
}
|
|
2022
2125
|
function lineFields(value) {
|
|
2023
2126
|
const line = node(value);
|
|
@@ -2064,8 +2167,8 @@ function parseHostCount(value) {
|
|
|
2064
2167
|
const match = /^[.\s]*(\d+)\s*$/.exec(value ?? "");
|
|
2065
2168
|
return match === null ? void 0 : Number(match[1]);
|
|
2066
2169
|
}
|
|
2067
|
-
function attribute2(
|
|
2068
|
-
const value =
|
|
2170
|
+
function attribute2(node3, name) {
|
|
2171
|
+
const value = node3?.[`@${name}`];
|
|
2069
2172
|
return typeof value === "string" ? value : void 0;
|
|
2070
2173
|
}
|
|
2071
2174
|
function parseQueueCountResponse(response) {
|
|
@@ -2075,7 +2178,8 @@ function parseQueueCountResponse(response) {
|
|
|
2075
2178
|
countedAt ??= nonEmpty3(attribute2(info, "DateTime"));
|
|
2076
2179
|
for (const identifier2 of asArray(info.QueueIdentifier)) {
|
|
2077
2180
|
const number = attribute2(identifier2, "Number");
|
|
2078
|
-
const
|
|
2181
|
+
const name = nonEmpty3(attribute2(identifier2, "Name"));
|
|
2182
|
+
const queue = number === void 0 && name !== void 0 ? { name } : toQueueRef(number);
|
|
2079
2183
|
const count = parseHostCount(attribute2(identifier2, "Count"));
|
|
2080
2184
|
if (queue === void 0 || count === void 0) {
|
|
2081
2185
|
throw malformed(`QueueIdentifier con Number="${number ?? ""}" y Count="${attribute2(identifier2, "Count") ?? ""}"`, response);
|
|
@@ -2091,7 +2195,13 @@ function parseQueueCountResponse(response) {
|
|
|
2091
2195
|
if (count === void 0) throw malformed(`Totals ${key} con Count="${attribute2(total, "Count") ?? ""}"`, response);
|
|
2092
2196
|
totals[key] = count;
|
|
2093
2197
|
}
|
|
2094
|
-
return {
|
|
2198
|
+
return {
|
|
2199
|
+
queues,
|
|
2200
|
+
totals,
|
|
2201
|
+
...countedAt === void 0 ? {} : { countedAt },
|
|
2202
|
+
warnings: response.warnings,
|
|
2203
|
+
...respondedAtField(response.raw)
|
|
2204
|
+
};
|
|
2095
2205
|
}
|
|
2096
2206
|
function malformed(detail, response) {
|
|
2097
2207
|
return new SabreGatewayError(`${QUEUE_COUNT_SERVICE}: respuesta no reconocida (${detail})`, {
|
|
@@ -2108,9 +2218,10 @@ function nonEmpty3(value) {
|
|
|
2108
2218
|
|
|
2109
2219
|
// src/queue/queue-list.ts
|
|
2110
2220
|
var NAMESPACE3 = "http://webservices.sabre.com/sabreXML/2011/10";
|
|
2111
|
-
|
|
2221
|
+
var LIST_SELECTION_FUNCTIONS = ["QD", "AK"];
|
|
2222
|
+
function queueListPayload(queue, pcc, primaryPassenger, selection = []) {
|
|
2112
2223
|
const list = primaryPassenger ? '<List Ind="true" PrimaryPassenger="true"/>' : '<List Ind="true"/>';
|
|
2113
|
-
return `<QueueAccessRQ Version="2.1.1" xmlns="${NAMESPACE3}"><QueueIdentifier ${queueRefAttribute(queue)} PseudoCityCode="${xmlEscape(pcc)}">${list}</QueueIdentifier
|
|
2224
|
+
return `<QueueAccessRQ Version="2.1.1" xmlns="${NAMESPACE3}"><QueueIdentifier ${queueRefAttribute(queue)} PseudoCityCode="${xmlEscape(pcc)}">${list}</QueueIdentifier>${selectionXml(selection)}</QueueAccessRQ>`;
|
|
2114
2225
|
}
|
|
2115
2226
|
function queueListRequest(payload, options) {
|
|
2116
2227
|
return {
|
|
@@ -2122,14 +2233,14 @@ function queueListRequest(payload, options) {
|
|
|
2122
2233
|
...options
|
|
2123
2234
|
};
|
|
2124
2235
|
}
|
|
2125
|
-
function emptyList(warnings, paragraph = []) {
|
|
2126
|
-
return { locators: [], entries: [], paragraph, warnings };
|
|
2236
|
+
function emptyList(warnings, paragraph = [], raw) {
|
|
2237
|
+
return { locators: [], entries: [], paragraph, warnings, ...respondedAtField(raw) };
|
|
2127
2238
|
}
|
|
2128
2239
|
function parseQueueList(response) {
|
|
2129
2240
|
const { warnings } = response;
|
|
2130
2241
|
const paragraph = paragraphLines(response.raw);
|
|
2131
2242
|
const firstLine = paragraph.find((line) => line.trim() !== "");
|
|
2132
|
-
if (isEndOfDisplay(firstLine) || warnings.some(isEndOfDisplayResult)) return emptyList(warnings, paragraph);
|
|
2243
|
+
if (isEndOfDisplay(firstLine) || warnings.some(isEndOfDisplayResult)) return emptyList(warnings, paragraph, response.raw);
|
|
2133
2244
|
const entries = asArray(response.payload.Line).map((line) => {
|
|
2134
2245
|
const fields = lineFields(line);
|
|
2135
2246
|
if (fields === void 0) {
|
|
@@ -2144,12 +2255,88 @@ function parseQueueList(response) {
|
|
|
2144
2255
|
const position = number !== void 0 && /^\d+$/.test(number) ? Number(number) : void 0;
|
|
2145
2256
|
return { ...position === void 0 ? {} : { position }, ...fields };
|
|
2146
2257
|
});
|
|
2147
|
-
return { locators: entries.map((entry) => entry.locator), entries, paragraph, warnings };
|
|
2258
|
+
return { locators: entries.map((entry) => entry.locator), entries, paragraph, warnings, ...respondedAtField(response.raw) };
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
// src/queue/queue-activity.ts
|
|
2262
|
+
var QUEUE_ACTIVITY_SERVICE = "queueAnalysis";
|
|
2263
|
+
var NAMESPACE4 = "http://webservices.sabre.com/sabreXML/2011/10";
|
|
2264
|
+
var COUNTERS = [
|
|
2265
|
+
["NumMessages", "numMessages"],
|
|
2266
|
+
["Ended", "ended"],
|
|
2267
|
+
["Ignored", "ignored"],
|
|
2268
|
+
["Moved", "moved"],
|
|
2269
|
+
["Removed", "removed"],
|
|
2270
|
+
["Transferred", "transferred"],
|
|
2271
|
+
["LeftMessageToContact", "leftMessageToContact"],
|
|
2272
|
+
["UnableToReach", "unableToReach"]
|
|
2273
|
+
];
|
|
2274
|
+
function queueActivityPayload(pcc) {
|
|
2275
|
+
return `<QueueAnalysisRQ Version="2.0.0" xmlns="${NAMESPACE4}"><QueueInfo><QueueIdentifier PseudoCityCode="${xmlEscape(pcc)}"/></QueueInfo></QueueAnalysisRQ>`;
|
|
2276
|
+
}
|
|
2277
|
+
function queueActivityRequest(pcc, options) {
|
|
2278
|
+
return {
|
|
2279
|
+
service: QUEUE_ACTIVITY_SERVICE,
|
|
2280
|
+
action: "QueueAnalysisLLSRQ",
|
|
2281
|
+
payload: queueActivityPayload(pcc),
|
|
2282
|
+
idempotent: true,
|
|
2283
|
+
operation: "queue.activity",
|
|
2284
|
+
...options
|
|
2285
|
+
};
|
|
2286
|
+
}
|
|
2287
|
+
function attribute3(node3, name) {
|
|
2288
|
+
const value = (Array.isArray(node3) ? node3[0] : node3)?.[`@${name}`];
|
|
2289
|
+
return typeof value === "string" ? value : void 0;
|
|
2290
|
+
}
|
|
2291
|
+
function nonEmpty4(value) {
|
|
2292
|
+
const text = value?.trim();
|
|
2293
|
+
return text === void 0 || text === "" ? void 0 : text;
|
|
2294
|
+
}
|
|
2295
|
+
function parseQueueActivityResponse(response) {
|
|
2296
|
+
const queues = [];
|
|
2297
|
+
let analyzedAt;
|
|
2298
|
+
for (const info of asArray(response.payload.QueueInfo)) {
|
|
2299
|
+
analyzedAt ??= nonEmpty4(attribute3(info, "DateTime"));
|
|
2300
|
+
for (const identifier2 of asArray(info.QueueIdentifier)) {
|
|
2301
|
+
const number = attribute3(identifier2, "Number");
|
|
2302
|
+
const name = nonEmpty4(attribute3(identifier2, "Name"));
|
|
2303
|
+
const queue = number === void 0 && name !== void 0 ? { name } : toQueueRef(number);
|
|
2304
|
+
if (queue === void 0) throw malformed2(`QueueIdentifier con Number="${number ?? ""}"`, response);
|
|
2305
|
+
queues.push({ queue, ...counters(identifier2, `la cola ${number ?? name}`, response) });
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
const totalsNode = asArray(response.payload.Totals)[0];
|
|
2309
|
+
const totals = totalsNode === void 0 ? void 0 : counters(totalsNode, "Totals", response);
|
|
2310
|
+
return {
|
|
2311
|
+
queues,
|
|
2312
|
+
...totals === void 0 ? {} : { totals },
|
|
2313
|
+
...analyzedAt === void 0 ? {} : { analyzedAt },
|
|
2314
|
+
warnings: response.warnings,
|
|
2315
|
+
...respondedAtField(response.raw)
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2318
|
+
function counters(node3, label, response) {
|
|
2319
|
+
const result = {};
|
|
2320
|
+
for (const [element2, key] of COUNTERS) {
|
|
2321
|
+
const raw = attribute3(node3[element2], "Count");
|
|
2322
|
+
const count = parseHostCount(raw);
|
|
2323
|
+
if (count === void 0) throw malformed2(`${label} con ${element2} Count="${raw ?? ""}"`, response);
|
|
2324
|
+
result[key] = count;
|
|
2325
|
+
}
|
|
2326
|
+
return result;
|
|
2327
|
+
}
|
|
2328
|
+
function malformed2(detail, response) {
|
|
2329
|
+
return new SabreGatewayError(`${QUEUE_ACTIVITY_SERVICE}: respuesta no reconocida (${detail})`, {
|
|
2330
|
+
protocol: "soap",
|
|
2331
|
+
status: response.status,
|
|
2332
|
+
retryable: false,
|
|
2333
|
+
raw: response.raw
|
|
2334
|
+
});
|
|
2148
2335
|
}
|
|
2149
2336
|
|
|
2150
2337
|
// src/queue/queue-place.ts
|
|
2151
2338
|
var QUEUE_PLACE_SERVICE = "queuePlace";
|
|
2152
|
-
var
|
|
2339
|
+
var NAMESPACE5 = "http://webservices.sabre.com/sabreXML/2011/10";
|
|
2153
2340
|
var MAX_PLACE_TO = 10;
|
|
2154
2341
|
var MAX_PLACE_LOCATORS = 6;
|
|
2155
2342
|
var INSTRUCTION_CODE_RANGES = [
|
|
@@ -2165,7 +2352,7 @@ function queuePlacePayload(to, locators = []) {
|
|
|
2165
2352
|
return `<QueueIdentifier ${queueRefAttribute(target.queue)}${pic} PseudoCityCode="${xmlEscape(target.pcc)}"/>`;
|
|
2166
2353
|
}).join("");
|
|
2167
2354
|
const ids = locators.map((locator) => `<UniqueID ID="${xmlEscape(locator)}"/>`).join("");
|
|
2168
|
-
return `<QueuePlaceRQ Version="2.0.4" xmlns="${
|
|
2355
|
+
return `<QueuePlaceRQ Version="2.0.4" xmlns="${NAMESPACE5}"><QueueInfo>${identifiers}${ids}</QueueInfo></QueuePlaceRQ>`;
|
|
2169
2356
|
}
|
|
2170
2357
|
function queuePlaceRequest(payload, options) {
|
|
2171
2358
|
return {
|
|
@@ -2190,7 +2377,7 @@ function isEnd(response, text) {
|
|
|
2190
2377
|
}
|
|
2191
2378
|
function parsePlaceStep(response) {
|
|
2192
2379
|
const text = textLines(response.raw, "QueuePlaceRS");
|
|
2193
|
-
if (isEnd(response, text)) return endStep(response.warnings);
|
|
2380
|
+
if (isEnd(response, text)) return endStep(response.warnings, "end-of-display", response.raw);
|
|
2194
2381
|
const locator = signatureLocator(text);
|
|
2195
2382
|
if (locator === void 0) {
|
|
2196
2383
|
throw new SabreGatewayError(`${QUEUE_PLACE_SERVICE}: respuesta no reconocida (sin l\xEDnea de firma del PNR ni fin de cola)`, {
|
|
@@ -2200,7 +2387,7 @@ function parsePlaceStep(response) {
|
|
|
2200
2387
|
raw: response.raw
|
|
2201
2388
|
});
|
|
2202
2389
|
}
|
|
2203
|
-
return { done: false, item: { locator, paragraph: text }, warnings: response.warnings };
|
|
2390
|
+
return { done: false, kind: "pnr", item: { locator, paragraph: text }, warnings: response.warnings, ...respondedAtField(response.raw) };
|
|
2204
2391
|
}
|
|
2205
2392
|
function parsePlaceUnknown(response) {
|
|
2206
2393
|
const text = textLines(response.raw, "QueuePlaceRS");
|
|
@@ -2223,11 +2410,16 @@ function parsePlaceResult(response) {
|
|
|
2223
2410
|
...placedAt === void 0 ? {} : { placedAt }
|
|
2224
2411
|
};
|
|
2225
2412
|
});
|
|
2226
|
-
return {
|
|
2413
|
+
return {
|
|
2414
|
+
placements,
|
|
2415
|
+
text: textLines(response.raw, "QueuePlaceRS"),
|
|
2416
|
+
warnings: response.warnings,
|
|
2417
|
+
...respondedAtField(response.raw)
|
|
2418
|
+
};
|
|
2227
2419
|
}
|
|
2228
2420
|
|
|
2229
2421
|
// src/queue/queue-management.ts
|
|
2230
|
-
function
|
|
2422
|
+
function fail4(message) {
|
|
2231
2423
|
throw new TypeError(`sabre-client: ${message}`);
|
|
2232
2424
|
}
|
|
2233
2425
|
function rejectedBySabre(error) {
|
|
@@ -2236,6 +2428,8 @@ function rejectedBySabre(error) {
|
|
|
2236
2428
|
var QueueManagement = class {
|
|
2237
2429
|
#context;
|
|
2238
2430
|
#state = "outside";
|
|
2431
|
+
/** Si la última cola a la que se entró es la `S`: ahí cada paso es un mensaje, no un PNR. */
|
|
2432
|
+
#messages = false;
|
|
2239
2433
|
/** @internal Lo construye `SabreSession`. */
|
|
2240
2434
|
constructor(context) {
|
|
2241
2435
|
this.#context = context;
|
|
@@ -2261,31 +2455,61 @@ var QueueManagement = class {
|
|
|
2261
2455
|
const response = await this.#context.send(queueCountRequest(pcc, options));
|
|
2262
2456
|
return parseQueueCountResponse(response);
|
|
2263
2457
|
}
|
|
2458
|
+
/**
|
|
2459
|
+
* Get Queue Activity (`QueueAnalysisLLSRQ` 2.0.0, el `QA` del host): por cada cola del PCC, los
|
|
2460
|
+
* ocho contadores de actividad (`numMessages`, `ended`, `ignored`, `moved`, `removed`,
|
|
2461
|
+
* `transferred`, `leftMessageToContact` y `unableToReach`), más los totales. Cada `queue` se puede
|
|
2462
|
+
* pasar tal cual a las demás operaciones de cola. Ninguna fuente dice desde cuándo cuentan
|
|
2463
|
+
* (Q-097): el SDK los devuelve sin interpretar.
|
|
2464
|
+
*
|
|
2465
|
+
* Se puede llamar dentro o fuera de una cola, y no cambia `state`. Es una lectura: se reintenta
|
|
2466
|
+
* ante fallos transitorios. No pide duty code 9 (CERT y producción, Q-098). Un PCC que no tiene 3
|
|
2467
|
+
* o 4 caracteres alfanuméricos lanza `TypeError` sin red.
|
|
2468
|
+
*/
|
|
2469
|
+
async activity(request = {}, options = {}) {
|
|
2470
|
+
if (typeof request !== "object" || request === null) fail4("queue.activity recibe { pcc? }");
|
|
2471
|
+
const pcc = this.#pcc(request.pcc, "pcc");
|
|
2472
|
+
const response = await this.#context.send(queueActivityRequest(pcc, options));
|
|
2473
|
+
return parseQueueActivityResponse(response);
|
|
2474
|
+
}
|
|
2264
2475
|
/**
|
|
2265
2476
|
* La lista de localizadores de una cola (`QueueAccessLLSRQ` con `List`), sin recorrerla. Con
|
|
2266
2477
|
* `primaryPassenger: true`, cada entrada trae también el pasajero principal. Una cola vacía da
|
|
2267
2478
|
* una lista vacía.
|
|
2268
2479
|
*
|
|
2480
|
+
* No pagina: Sabre devuelve la cola entera en una respuesta, también con más de 1000 PNR. Con
|
|
2481
|
+
* `selection`, solo los PNR que cumplen el filtro; Sabre acepta solo `QD` o `AK` en la lista.
|
|
2482
|
+
*
|
|
2269
2483
|
* No cambia `state`. Es una lectura: se reintenta ante fallos transitorios. Lanza `TypeError`
|
|
2270
|
-
* sin red si `queue` o `pcc` no son válidos,
|
|
2484
|
+
* sin red si `queue` o `pcc` no son válidos, si `primaryPassenger` no es un boolean, o si
|
|
2485
|
+
* `selection` no es válida o trae un código que no es `QD` ni `AK`.
|
|
2271
2486
|
*/
|
|
2272
2487
|
async list(request, options = {}) {
|
|
2273
|
-
if (typeof request !== "object" || request === null)
|
|
2488
|
+
if (typeof request !== "object" || request === null) fail4("queue.list recibe { queue, pcc?, primaryPassenger?, selection? }");
|
|
2274
2489
|
validateQueueRef(request.queue, "queue");
|
|
2490
|
+
if (isMessageQueue(request.queue)) fail4("la cola 'S' es la de mensajes del PCC: no tiene PNR y Sabre no la lista");
|
|
2275
2491
|
const pcc = this.#pcc(request.pcc, "pcc");
|
|
2276
2492
|
const { primaryPassenger = false } = request;
|
|
2277
|
-
if (typeof primaryPassenger !== "boolean")
|
|
2278
|
-
const
|
|
2493
|
+
if (typeof primaryPassenger !== "boolean") fail4("primaryPassenger debe ser un boolean");
|
|
2494
|
+
const selection = validateSelection(request.selection);
|
|
2495
|
+
selection.forEach((filter, index) => {
|
|
2496
|
+
if (!LIST_SELECTION_FUNCTIONS.includes(filter.function)) {
|
|
2497
|
+
fail4(`selection[${index}].function en list solo puede ser ${LIST_SELECTION_FUNCTIONS.join(" o ")}: Sabre no acepta otro c\xF3digo en la lista`);
|
|
2498
|
+
}
|
|
2499
|
+
});
|
|
2500
|
+
const send2 = queueListRequest(queueListPayload(request.queue, pcc, primaryPassenger, selection), options);
|
|
2279
2501
|
try {
|
|
2280
2502
|
return parseQueueList(await this.#context.send(send2));
|
|
2281
2503
|
} catch (error) {
|
|
2282
2504
|
const end = queueEndError(error, send2.service);
|
|
2283
|
-
if (end !== void 0) return emptyList([...end.error.warnings]);
|
|
2505
|
+
if (end !== void 0) return emptyList([...end.error.warnings], [], end.error.raw);
|
|
2284
2506
|
throw error;
|
|
2285
2507
|
}
|
|
2286
2508
|
}
|
|
2287
2509
|
/**
|
|
2288
|
-
* Access Queue (`QueueAccessLLSRQ` 2.1.1): entra a una cola y devuelve su primer PNR
|
|
2510
|
+
* Access Queue (`QueueAccessLLSRQ` 2.1.1): entra a una cola y devuelve su primer PNR
|
|
2511
|
+
* (`kind: 'pnr'`). En la cola `S`, la de mensajes, devuelve el primer mensaje
|
|
2512
|
+
* (`kind: 'message'`), y `ignore` pasa al siguiente sin borrarlo (CERT, 2026-11-06). Una cola
|
|
2289
2513
|
* vacía, o sin PNR que cumplan `selection`, devuelve `{ done: true }` y la sesión sigue afuera.
|
|
2290
2514
|
*
|
|
2291
2515
|
* Lanza `TypeError` sin red si la sesión ya está dentro de una cola (o en `'unknown'`: hay que
|
|
@@ -2294,12 +2518,13 @@ var QueueManagement = class {
|
|
|
2294
2518
|
* `'unknown'`.
|
|
2295
2519
|
*/
|
|
2296
2520
|
async access(request, options = {}) {
|
|
2297
|
-
if (this.#state === "inside")
|
|
2298
|
-
if (this.#state === "unknown")
|
|
2299
|
-
if (typeof request !== "object" || request === null)
|
|
2521
|
+
if (this.#state === "inside") fail4("la sesi\xF3n ya est\xE1 dentro de una cola: sal con queue.exit() antes de entrar a otra");
|
|
2522
|
+
if (this.#state === "unknown") fail4("no se sabe si la sesi\xF3n sigue dentro de una cola: llama a queue.exit() antes de entrar");
|
|
2523
|
+
if (typeof request !== "object" || request === null) fail4("queue.access recibe { queue, pcc?, selection? }");
|
|
2300
2524
|
validateQueueRef(request.queue, "queue");
|
|
2301
2525
|
const pcc = this.#pcc(request.pcc, "pcc");
|
|
2302
2526
|
const selection = validateSelection(request.selection);
|
|
2527
|
+
this.#messages = isMessageQueue(request.queue);
|
|
2303
2528
|
return this.#step(queueAccessRequest(queueAccessPayload(request.queue, pcc, selection), "queue.access", options));
|
|
2304
2529
|
}
|
|
2305
2530
|
/**
|
|
@@ -2312,7 +2537,8 @@ var QueueManagement = class {
|
|
|
2312
2537
|
}
|
|
2313
2538
|
/**
|
|
2314
2539
|
* Saca el PNR actual de la cola y pasa al siguiente (`QR`). Con `from` (de 1 a 3 colas), lo saca
|
|
2315
|
-
* de esas colas a la vez. Fuera de una cola lanza `TypeError` sin red.
|
|
2540
|
+
* de esas colas a la vez. Fuera de una cola lanza `TypeError` sin red. En la cola `S` manda `QR`
|
|
2541
|
+
* igual, pero no hay evidencia de qué hace Sabre con el mensaje.
|
|
2316
2542
|
*/
|
|
2317
2543
|
async remove(request = {}, options = {}) {
|
|
2318
2544
|
this.#requireQueue("remove");
|
|
@@ -2359,10 +2585,11 @@ var QueueManagement = class {
|
|
|
2359
2585
|
* `options` se aplica a cada llamada a Sabre del recorrido.
|
|
2360
2586
|
*/
|
|
2361
2587
|
async process(request, callback, options = {}) {
|
|
2362
|
-
if (typeof request !== "object" || request === null)
|
|
2363
|
-
if (typeof callback !== "function")
|
|
2588
|
+
if (typeof request !== "object" || request === null) fail4("queue.process recibe { queue, pcc?, selection?, limit? }");
|
|
2589
|
+
if (typeof callback !== "function") fail4("queue.process necesita un callback");
|
|
2364
2590
|
const { limit, ...access } = request;
|
|
2365
|
-
if (
|
|
2591
|
+
if (isMessageQueue(access.queue)) fail4("queue.process no recorre la cola 'S', la de mensajes: usa queue.access() e queue.ignore()");
|
|
2592
|
+
if (limit !== void 0 && !(Number.isInteger(limit) && limit >= 1)) fail4("limit debe ser un entero mayor o igual a 1");
|
|
2366
2593
|
const startedOutside = this.#state === "outside";
|
|
2367
2594
|
let visited = 0;
|
|
2368
2595
|
try {
|
|
@@ -2372,6 +2599,7 @@ var QueueManagement = class {
|
|
|
2372
2599
|
await this.exit(options);
|
|
2373
2600
|
return { visited, ended: "limit" };
|
|
2374
2601
|
}
|
|
2602
|
+
if (step.kind !== "pnr") throw new TypeError("sabre-client: queue.process recibi\xF3 un mensaje en vez de un PNR");
|
|
2375
2603
|
const action = await callback(step.item);
|
|
2376
2604
|
visited++;
|
|
2377
2605
|
step = await this.#processAction(action, options);
|
|
@@ -2403,17 +2631,20 @@ var QueueManagement = class {
|
|
|
2403
2631
|
}
|
|
2404
2632
|
}
|
|
2405
2633
|
async place(request, options = {}) {
|
|
2406
|
-
if (typeof request !== "object" || request === null)
|
|
2634
|
+
if (typeof request !== "object" || request === null) fail4("queue.place recibe { to, locators? }");
|
|
2407
2635
|
const to = this.#placeTo(request.to);
|
|
2408
2636
|
const locators = validateLocators(request.locators);
|
|
2637
|
+
if (this.#messages && this.#state !== "outside") {
|
|
2638
|
+
fail4("queue.place no se puede dentro de la cola 'S': lo que hay en pantalla es un mensaje, no un PNR");
|
|
2639
|
+
}
|
|
2409
2640
|
if (locators.length > 0 && this.#state === "inside") {
|
|
2410
|
-
|
|
2641
|
+
fail4("queue.place con locators no se puede dentro de una cola, donde coloca el PNR actual: sal con queue.exit() antes");
|
|
2411
2642
|
}
|
|
2412
2643
|
if (locators.length > 0 && this.#state === "unknown") {
|
|
2413
|
-
|
|
2644
|
+
fail4("no se sabe si la sesi\xF3n sigue dentro de una cola: llama a queue.exit() antes de colocar por localizador");
|
|
2414
2645
|
}
|
|
2415
2646
|
if (to.some((target) => target.instructionCode === void 0)) {
|
|
2416
|
-
|
|
2647
|
+
fail4("queue.place necesita instructionCode en cada destino: Sabre lo exige, dentro y fuera de una cola");
|
|
2417
2648
|
}
|
|
2418
2649
|
const send2 = queuePlaceRequest(queuePlacePayload(to, locators), options);
|
|
2419
2650
|
if (this.#state === "outside") return parsePlaceResult(await this.#context.send(send2));
|
|
@@ -2421,7 +2652,8 @@ var QueueManagement = class {
|
|
|
2421
2652
|
}
|
|
2422
2653
|
/** Manda una operación que avanza y actualiza `state` con el resultado. */
|
|
2423
2654
|
#step(request) {
|
|
2424
|
-
|
|
2655
|
+
const messages = this.#messages;
|
|
2656
|
+
return this.#advance(request, (response) => parseQueueStep(response, { messages }));
|
|
2425
2657
|
}
|
|
2426
2658
|
/**
|
|
2427
2659
|
* Manda una operación que puede mover el cursor. Un `QueueStep` fija el estado: `'outside'` con
|
|
@@ -2435,7 +2667,7 @@ var QueueManagement = class {
|
|
|
2435
2667
|
const end = queueEndError(error, request.service);
|
|
2436
2668
|
if (end !== void 0) {
|
|
2437
2669
|
this.#state = "outside";
|
|
2438
|
-
return endStep([...end.error.warnings], end.reason);
|
|
2670
|
+
return endStep([...end.error.warnings], end.reason, end.error.raw);
|
|
2439
2671
|
}
|
|
2440
2672
|
this.#onError(error);
|
|
2441
2673
|
throw error;
|
|
@@ -2456,14 +2688,14 @@ var QueueManagement = class {
|
|
|
2456
2688
|
}
|
|
2457
2689
|
#placeTo(to) {
|
|
2458
2690
|
if (!Array.isArray(to) || to.length === 0 || to.length > MAX_PLACE_TO) {
|
|
2459
|
-
|
|
2691
|
+
fail4(`to debe ser una lista de 1 a ${MAX_PLACE_TO} colas`);
|
|
2460
2692
|
}
|
|
2461
2693
|
return to.map((target, index) => {
|
|
2462
|
-
if (typeof target !== "object" || target === null)
|
|
2694
|
+
if (typeof target !== "object" || target === null) fail4(`to[${index}] debe ser { queue, pcc?, instructionCode }`);
|
|
2463
2695
|
const { queue, pcc, instructionCode } = target;
|
|
2464
2696
|
validateQueueRef(queue, `to[${index}].queue`);
|
|
2465
2697
|
if (instructionCode !== void 0 && !isInstructionCode(instructionCode)) {
|
|
2466
|
-
|
|
2698
|
+
fail4(`to[${index}].instructionCode debe ser un entero de 0 a 191 o de 200 a 254`);
|
|
2467
2699
|
}
|
|
2468
2700
|
return {
|
|
2469
2701
|
queue,
|
|
@@ -2473,20 +2705,20 @@ var QueueManagement = class {
|
|
|
2473
2705
|
});
|
|
2474
2706
|
}
|
|
2475
2707
|
#requireQueue(operation) {
|
|
2476
|
-
if (this.#state === "outside")
|
|
2708
|
+
if (this.#state === "outside") fail4(`queue.${operation} necesita estar dentro de una cola: entra con queue.access()`);
|
|
2477
2709
|
}
|
|
2478
2710
|
#pcc(value, label) {
|
|
2479
2711
|
if (value === void 0) return this.#context.pcc;
|
|
2480
|
-
if (!isPseudoCityCode(value))
|
|
2712
|
+
if (!isPseudoCityCode(value)) fail4(`${label} debe tener 3 o 4 caracteres alfanum\xE9ricos`);
|
|
2481
2713
|
return value;
|
|
2482
2714
|
}
|
|
2483
2715
|
#removeFrom(from) {
|
|
2484
2716
|
if (from === void 0) return [];
|
|
2485
2717
|
if (!Array.isArray(from) || from.length === 0 || from.length > MAX_REMOVE_FROM) {
|
|
2486
|
-
|
|
2718
|
+
fail4(`from debe ser una lista de 1 a ${MAX_REMOVE_FROM} colas`);
|
|
2487
2719
|
}
|
|
2488
2720
|
return from.map((target, index) => {
|
|
2489
|
-
if (typeof target !== "object" || target === null)
|
|
2721
|
+
if (typeof target !== "object" || target === null) fail4(`from[${index}] debe ser { queue, pcc? }`);
|
|
2490
2722
|
const { queue, pcc } = target;
|
|
2491
2723
|
validateQueueRef(queue, `from[${index}].queue`);
|
|
2492
2724
|
return { queue, pcc: this.#pcc(pcc, `from[${index}].pcc`) };
|
|
@@ -2496,37 +2728,171 @@ var QueueManagement = class {
|
|
|
2496
2728
|
function validateLocators(locators) {
|
|
2497
2729
|
if (locators === void 0) return [];
|
|
2498
2730
|
if (!Array.isArray(locators) || locators.length === 0 || locators.length > MAX_PLACE_LOCATORS) {
|
|
2499
|
-
|
|
2731
|
+
fail4(`locators debe ser una lista de 1 a ${MAX_PLACE_LOCATORS} localizadores`);
|
|
2500
2732
|
}
|
|
2501
2733
|
return locators.map((locator, index) => {
|
|
2502
|
-
if (typeof locator !== "string" || locator.trim() === "")
|
|
2734
|
+
if (typeof locator !== "string" || locator.trim() === "") fail4(`locators[${index}] debe ser un string no vac\xEDo`);
|
|
2503
2735
|
return locator;
|
|
2504
2736
|
});
|
|
2505
2737
|
}
|
|
2506
2738
|
function validateSelection(selection) {
|
|
2507
2739
|
if (selection === void 0) return [];
|
|
2508
|
-
if (!Array.isArray(selection))
|
|
2509
|
-
if (selection.length > MAX_SELECTIONS)
|
|
2740
|
+
if (!Array.isArray(selection)) fail4("selection debe ser una lista de { function, criteria }");
|
|
2741
|
+
if (selection.length > MAX_SELECTIONS) fail4(`selection admite hasta ${MAX_SELECTIONS} filtros`);
|
|
2510
2742
|
return selection.map((filter, index) => {
|
|
2511
2743
|
const { function: fn, criteria } = typeof filter === "object" && filter !== null ? filter : {};
|
|
2512
|
-
if (typeof fn !== "string" || fn.trim() === "")
|
|
2744
|
+
if (typeof fn !== "string" || fn.trim() === "") fail4(`selection[${index}].function debe ser un string no vac\xEDo`);
|
|
2513
2745
|
if (typeof criteria !== "string" || criteria.trim() === "") {
|
|
2514
|
-
|
|
2746
|
+
fail4(`selection[${index}].criteria debe ser un string no vac\xEDo`);
|
|
2515
2747
|
}
|
|
2516
2748
|
return { function: fn, criteria };
|
|
2517
2749
|
});
|
|
2518
2750
|
}
|
|
2519
2751
|
|
|
2752
|
+
// src/reservation/reservation-history.ts
|
|
2753
|
+
var RESERVATION_HISTORY_SERVICE = "itineraryHistory";
|
|
2754
|
+
var NAMESPACE6 = "http://webservices.sabre.com/sabreXML/2011/10";
|
|
2755
|
+
var LOCATOR = /^[A-Z0-9]{6}$/;
|
|
2756
|
+
function isRecordLocator(value) {
|
|
2757
|
+
return typeof value === "string" && LOCATOR.test(value);
|
|
2758
|
+
}
|
|
2759
|
+
function reservationHistoryPayload(locator) {
|
|
2760
|
+
return `<TravelItineraryHistoryRQ Version="2.4.0" xmlns="${NAMESPACE6}"><UniqueID ID="${xmlEscape(locator)}"/></TravelItineraryHistoryRQ>`;
|
|
2761
|
+
}
|
|
2762
|
+
function reservationHistoryRequest(locator, options) {
|
|
2763
|
+
return {
|
|
2764
|
+
service: RESERVATION_HISTORY_SERVICE,
|
|
2765
|
+
action: "TravelItineraryHistoryLLSRQ",
|
|
2766
|
+
payload: reservationHistoryPayload(locator),
|
|
2767
|
+
idempotent: true,
|
|
2768
|
+
operation: "reservation.history",
|
|
2769
|
+
...options
|
|
2770
|
+
};
|
|
2771
|
+
}
|
|
2772
|
+
function node2(value) {
|
|
2773
|
+
const first = Array.isArray(value) ? value[0] : value;
|
|
2774
|
+
return typeof first === "object" && first !== null ? first : void 0;
|
|
2775
|
+
}
|
|
2776
|
+
function attribute4(value, name) {
|
|
2777
|
+
const text = node2(value)?.[`@${name}`];
|
|
2778
|
+
if (typeof text !== "string") return void 0;
|
|
2779
|
+
const trimmed = text.trim();
|
|
2780
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
2781
|
+
}
|
|
2782
|
+
function defined(fields) {
|
|
2783
|
+
return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== void 0));
|
|
2784
|
+
}
|
|
2785
|
+
function integer(value) {
|
|
2786
|
+
return value !== void 0 && /^\d+$/.test(value) ? Number(value) : void 0;
|
|
2787
|
+
}
|
|
2788
|
+
function parseReservationHistoryResponse(response) {
|
|
2789
|
+
const transactions = asArray(response.payload.HistoricalInfo).map((info) => transaction(node2(info) ?? {}, response));
|
|
2790
|
+
return { transactions, warnings: response.warnings, ...respondedAtField(response.raw) };
|
|
2791
|
+
}
|
|
2792
|
+
function transaction(info, response) {
|
|
2793
|
+
const source = node2(node2(info.ItineraryRef)?.Source);
|
|
2794
|
+
const flightSegments = asArray(node2(node2(info.ItineraryInfo)?.ReservationItems)?.Item).flatMap(
|
|
2795
|
+
(item) => asArray(node2(item)?.FlightSegment).map((segment) => flightSegment(node2(segment) ?? {}, response))
|
|
2796
|
+
);
|
|
2797
|
+
return {
|
|
2798
|
+
...defined({
|
|
2799
|
+
rph: integer(attribute4(info, "RPH")),
|
|
2800
|
+
createdAt: attribute4(source, "CreateDateTime"),
|
|
2801
|
+
agent: attribute4(source, "CreationAgent"),
|
|
2802
|
+
agentDutyCode: attribute4(source, "CreationAgentDutyCode"),
|
|
2803
|
+
agentFunctionCode: attribute4(source, "CreationAgentFunctionCode"),
|
|
2804
|
+
pcc: attribute4(source, "PseudoCityCode"),
|
|
2805
|
+
aaaPcc: attribute4(source, "AAA_PseudoCityCode") ?? attribute4(source, "AAAPseudoCityCode"),
|
|
2806
|
+
receivedFrom: attribute4(source, "ReceivedFrom")
|
|
2807
|
+
}),
|
|
2808
|
+
flightSegments,
|
|
2809
|
+
generalInfo: asArray(info.GeneralInfo).map((entry) => textEntry(entry, "GeneralInfo", response)),
|
|
2810
|
+
remarks: asArray(node2(info.RemarkInfo)?.Remark).map((entry) => textEntry(entry, "Remark", response)),
|
|
2811
|
+
accounting: asArray(info.AccountingInfo).map(
|
|
2812
|
+
(entry) => defined({ action: attribute4(entry, "Action"), type: attribute4(entry, "Type"), linkCode: attribute4(entry, "LinkCode") })
|
|
2813
|
+
)
|
|
2814
|
+
};
|
|
2815
|
+
}
|
|
2816
|
+
function textEntry(entry, label, response) {
|
|
2817
|
+
const action = attribute4(entry, "Action");
|
|
2818
|
+
if (action === void 0) throw malformed3(`${label} sin Action`, response);
|
|
2819
|
+
const texts = asArray(node2(entry)?.Text).map((text) => textOf(text) ?? "");
|
|
2820
|
+
return { action, text: texts.join("\n") };
|
|
2821
|
+
}
|
|
2822
|
+
function flightSegment(segment, response) {
|
|
2823
|
+
const action = attribute4(segment, "Action");
|
|
2824
|
+
if (action === void 0) throw malformed3("FlightSegment sin Action", response);
|
|
2825
|
+
return {
|
|
2826
|
+
action,
|
|
2827
|
+
...defined({
|
|
2828
|
+
status: attribute4(segment, "Status"),
|
|
2829
|
+
marketingAirline: attribute4(segment.MarketingAirline, "Code"),
|
|
2830
|
+
flightNumber: attribute4(segment, "FlightNumber") ?? attribute4(segment.MarketingAirline, "FlightNumber"),
|
|
2831
|
+
bookingClass: attribute4(segment, "ResBookDesigCode"),
|
|
2832
|
+
origin: attribute4(segment.OriginLocation, "LocationCode"),
|
|
2833
|
+
destination: attribute4(segment.DestinationLocation, "LocationCode"),
|
|
2834
|
+
departureDateTime: attribute4(segment, "DepartureDateTime"),
|
|
2835
|
+
arrivalDateTime: attribute4(segment, "ArrivalDateTime"),
|
|
2836
|
+
numberInParty: integer(attribute4(segment, "NumberInParty")),
|
|
2837
|
+
supplierRef: attribute4(segment.SupplierRef, "ID")
|
|
2838
|
+
})
|
|
2839
|
+
};
|
|
2840
|
+
}
|
|
2841
|
+
function malformed3(detail, response) {
|
|
2842
|
+
return new SabreGatewayError(`${RESERVATION_HISTORY_SERVICE}: respuesta no reconocida (${detail})`, {
|
|
2843
|
+
protocol: "soap",
|
|
2844
|
+
status: response.status,
|
|
2845
|
+
retryable: false,
|
|
2846
|
+
raw: response.raw
|
|
2847
|
+
});
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
// src/reservation/session-reservation.ts
|
|
2851
|
+
var SessionReservation = class {
|
|
2852
|
+
#context;
|
|
2853
|
+
/** @internal Lo construye `SabreSession`. */
|
|
2854
|
+
constructor(context) {
|
|
2855
|
+
this.#context = context;
|
|
2856
|
+
}
|
|
2857
|
+
/**
|
|
2858
|
+
* Get Itinerary History (`TravelItineraryHistoryLLSRQ` 2.4.0, el `*H` del host): el historial
|
|
2859
|
+
* del PNR, una entrada por transacción. Cada una trae quién la firmó, cuándo y desde qué PCC, y
|
|
2860
|
+
* los elementos que cambió con su `action`: los segmentos de vuelo con su estado en ese momento,
|
|
2861
|
+
* y SSR, ticketing, teléfonos, nombres y emails como texto en `generalInfo`. Lo que no se tipa
|
|
2862
|
+
* queda en la respuesta cruda (`onRawResponse`). **Trae datos del pasajero.**
|
|
2863
|
+
*
|
|
2864
|
+
* El historial no registra las colocaciones en cola, ni las del sistema ni un `place` manual
|
|
2865
|
+
* (CERT, 2026-11-06, Q-097): para eso están `queue.list`/`access` o los remarks `QUEUE_PLACE` de
|
|
2866
|
+
* Get Booking.
|
|
2867
|
+
*
|
|
2868
|
+
* **Dentro de una cola, Sabre lo rechaza** para otro PNR (CERT, 2026-11-06, Q-102): lanza
|
|
2869
|
+
* `SabreBusinessError` con `NAK6 -NOT ALLOWED- FINISH OR IGNORE OTHER ACTIVITY`. El PNR del AAA
|
|
2870
|
+
* no cambia, `queue.state` sigue en `'inside'` y el recorrido sigue con `ignore`. Conviene
|
|
2871
|
+
* llamarlo fuera de una cola.
|
|
2872
|
+
*
|
|
2873
|
+
* Es una lectura: se reintenta ante fallos transitorios. Un `locator` que no es de 6 caracteres
|
|
2874
|
+
* alfanuméricos en mayúscula lanza `TypeError` sin red. Un PNR que no existe sale como
|
|
2875
|
+
* `SabreBusinessError` (`service: 'itineraryHistory'`).
|
|
2876
|
+
*/
|
|
2877
|
+
async history(locator, options = {}) {
|
|
2878
|
+
if (!isRecordLocator(locator)) {
|
|
2879
|
+
throw new TypeError("sabre-client: reservation.history necesita un localizador de 6 caracteres alfanum\xE9ricos en may\xFAscula");
|
|
2880
|
+
}
|
|
2881
|
+
const response = await this.#context.send(reservationHistoryRequest(locator, options));
|
|
2882
|
+
return parseReservationHistoryResponse(response);
|
|
2883
|
+
}
|
|
2884
|
+
};
|
|
2885
|
+
|
|
2520
2886
|
// src/session/command.ts
|
|
2521
2887
|
var SABRE_COMMAND_SERVICE = "sabreCommand";
|
|
2522
|
-
var
|
|
2888
|
+
var NAMESPACE7 = "http://webservices.sabre.com/sabreXML/2011/10";
|
|
2523
2889
|
var MAX_COMMAND_LENGTH = 1023;
|
|
2524
2890
|
var DOUBLE_WIDTH = /* @__PURE__ */ new Set(["!", "%", "$", "&", "\xA5", "\xA7"]);
|
|
2525
|
-
function
|
|
2891
|
+
function fail5(message) {
|
|
2526
2892
|
throw new TypeError(`sabre-client: ${message}`);
|
|
2527
2893
|
}
|
|
2528
2894
|
function sabreCommandPayload(command) {
|
|
2529
|
-
return `<SabreCommandLLSRQ xmlns="${
|
|
2895
|
+
return `<SabreCommandLLSRQ xmlns="${NAMESPACE7}" Version="2.0.0" ReturnHostCommand="true"><Request Output="SCREEN"><HostCommand>${xmlEscape(command)}</HostCommand></Request></SabreCommandLLSRQ>`;
|
|
2530
2896
|
}
|
|
2531
2897
|
function commandToken(command) {
|
|
2532
2898
|
return (command.trim().split(/[¥‡\s]/u)[0] ?? "").slice(0, 4);
|
|
@@ -2537,13 +2903,13 @@ function sabreCommandLength(command) {
|
|
|
2537
2903
|
return length;
|
|
2538
2904
|
}
|
|
2539
2905
|
function sabreCommandRequest(command, options) {
|
|
2540
|
-
if (typeof command !== "string" || command.trim() === "")
|
|
2906
|
+
if (typeof command !== "string" || command.trim() === "") fail5("command es requerido y debe ser un string no vac\xEDo");
|
|
2541
2907
|
const length = sabreCommandLength(command);
|
|
2542
2908
|
if (length > MAX_COMMAND_LENGTH) {
|
|
2543
|
-
|
|
2909
|
+
fail5(`command mide ${length} y el m\xE1ximo es ${MAX_COMMAND_LENGTH} (!, %, $, &, \xA5 y \xA7 cuentan 2); Sabre responder\xEDa REQUEST EXCEEDED MAXIMUM LENGTH`);
|
|
2544
2910
|
}
|
|
2545
2911
|
const { idempotent = false, ...requestOptions } = options;
|
|
2546
|
-
if (typeof idempotent !== "boolean")
|
|
2912
|
+
if (typeof idempotent !== "boolean") fail5("idempotent debe ser un boolean");
|
|
2547
2913
|
return {
|
|
2548
2914
|
service: SABRE_COMMAND_SERVICE,
|
|
2549
2915
|
action: "SabreCommandLLSRQ",
|
|
@@ -2565,7 +2931,8 @@ function parseSabreCommandResponse(response) {
|
|
|
2565
2931
|
return {
|
|
2566
2932
|
text: textOf((untrimmedPayload(response.raw) ?? response.payload).Response) ?? "",
|
|
2567
2933
|
warnings: response.warnings,
|
|
2568
|
-
messageId: response.messageId
|
|
2934
|
+
messageId: response.messageId,
|
|
2935
|
+
...respondedAtField(response.raw)
|
|
2569
2936
|
};
|
|
2570
2937
|
}
|
|
2571
2938
|
|
|
@@ -2573,7 +2940,7 @@ function parseSabreCommandResponse(response) {
|
|
|
2573
2940
|
var OTA_NAMESPACE = "http://www.opentravel.org/OTA/2002/11";
|
|
2574
2941
|
var SWS_NAMESPACE = "http://webservices.sabre.com";
|
|
2575
2942
|
var sessionSend = /* @__PURE__ */ Symbol("sabre-client.session.send");
|
|
2576
|
-
function
|
|
2943
|
+
function fail6(message) {
|
|
2577
2944
|
throw new TypeError(`sabre-client: ${message}`);
|
|
2578
2945
|
}
|
|
2579
2946
|
var SESSION_ERROR_KINDS = /* @__PURE__ */ new Map([
|
|
@@ -2602,6 +2969,8 @@ var KEEP_ALIVE_IDLE_MS = 13 * 6e4;
|
|
|
2602
2969
|
var SabreSession = class {
|
|
2603
2970
|
/** Queue Management: contar, recorrer y colocar en colas, dentro de esta sesión. */
|
|
2604
2971
|
queue;
|
|
2972
|
+
/** La reserva: el historial de un PNR, dentro de esta sesión. */
|
|
2973
|
+
reservation;
|
|
2605
2974
|
#transport;
|
|
2606
2975
|
#pcc;
|
|
2607
2976
|
#conversationId;
|
|
@@ -2629,6 +2998,7 @@ var SabreSession = class {
|
|
|
2629
2998
|
pcc: internal.pcc,
|
|
2630
2999
|
log: internal.transport.log
|
|
2631
3000
|
});
|
|
3001
|
+
this.reservation = new SessionReservation({ send: (request) => this[sessionSend](request) });
|
|
2632
3002
|
if (internal.keepAlive) this.#scheduleKeepAlive(KEEP_ALIVE_IDLE_MS);
|
|
2633
3003
|
}
|
|
2634
3004
|
/**
|
|
@@ -2684,11 +3054,11 @@ var SabreSession = class {
|
|
|
2684
3054
|
return this.#send(request);
|
|
2685
3055
|
}
|
|
2686
3056
|
async #send(request) {
|
|
2687
|
-
if (this.#closing !== void 0)
|
|
3057
|
+
if (this.#closing !== void 0) fail6("session closed");
|
|
2688
3058
|
if (this.#unusable !== void 0) {
|
|
2689
3059
|
throw new TypeError(`sabre-client: session unusable (${this.#unusable.kind})`, { cause: this.#unusable });
|
|
2690
3060
|
}
|
|
2691
|
-
if (this.#busy)
|
|
3061
|
+
if (this.#busy) fail6("session busy");
|
|
2692
3062
|
this.#busy = true;
|
|
2693
3063
|
try {
|
|
2694
3064
|
const response = await this.#transport.request({ ...request, ...this.#envelopeFields() });
|
|
@@ -2703,6 +3073,7 @@ var SabreSession = class {
|
|
|
2703
3073
|
}
|
|
2704
3074
|
async #sendClose(options) {
|
|
2705
3075
|
if (this.#unusable !== void 0) return;
|
|
3076
|
+
const { onRawRequest: _skipped, ...closeOptions } = options;
|
|
2706
3077
|
this.#busy = true;
|
|
2707
3078
|
try {
|
|
2708
3079
|
await this.#transport.request({
|
|
@@ -2713,7 +3084,7 @@ var SabreSession = class {
|
|
|
2713
3084
|
// Solo se reintenta ante `USG_*_BUSY`: tras un fallo de red no se sabe si ya se cerró.
|
|
2714
3085
|
idempotent: false,
|
|
2715
3086
|
operation: "close",
|
|
2716
|
-
...
|
|
3087
|
+
...closeOptions
|
|
2717
3088
|
});
|
|
2718
3089
|
} catch (error) {
|
|
2719
3090
|
throw this.#markUnusable(error);
|
|
@@ -2843,7 +3214,7 @@ function usernameToken(credentials) {
|
|
|
2843
3214
|
}
|
|
2844
3215
|
async function openSession(transport, credentials, options = {}) {
|
|
2845
3216
|
const { keepAlive = false, ...requestOptions } = options;
|
|
2846
|
-
if (typeof keepAlive !== "boolean")
|
|
3217
|
+
if (typeof keepAlive !== "boolean") fail6("keepAlive debe ser un boolean");
|
|
2847
3218
|
const conversationId = globalThis.crypto.randomUUID();
|
|
2848
3219
|
const request = {
|
|
2849
3220
|
service: "sessionCreate",
|
|
@@ -2948,34 +3319,35 @@ var SabreLegacy = class {
|
|
|
2948
3319
|
|
|
2949
3320
|
// src/auth/credentials.ts
|
|
2950
3321
|
var DEFAULT_DOMAIN = "AA";
|
|
2951
|
-
function
|
|
3322
|
+
function fail7(message) {
|
|
2952
3323
|
throw new TypeError(`sabre-client: ${message}`);
|
|
2953
3324
|
}
|
|
2954
3325
|
function requireString2(value, field) {
|
|
2955
|
-
if (typeof value !== "string" || value.trim() === "")
|
|
3326
|
+
if (typeof value !== "string" || value.trim() === "") fail7(`${field} es requerido y debe ser un string no vac\xEDo`);
|
|
2956
3327
|
}
|
|
2957
3328
|
function buildCredentials(options) {
|
|
2958
|
-
if (typeof options !== "object" || options === null)
|
|
3329
|
+
if (typeof options !== "object" || options === null) fail7("buildCredentials recibe un objeto");
|
|
2959
3330
|
const authVersion = options.authVersion ?? "v3";
|
|
2960
3331
|
if (authVersion !== "v3" && authVersion !== "v2") {
|
|
2961
|
-
|
|
3332
|
+
fail7(`authVersion debe ser 'v3' o 'v2' (recibido: ${JSON.stringify(authVersion)})`);
|
|
2962
3333
|
}
|
|
2963
3334
|
const { user, pcc, password, clientId, clientSecret } = options;
|
|
2964
3335
|
const domain = options.domain ?? DEFAULT_DOMAIN;
|
|
2965
3336
|
requireString2(user, "user");
|
|
2966
3337
|
requireString2(password, "password");
|
|
2967
|
-
if (!isPseudoCityCode(pcc))
|
|
3338
|
+
if (!isPseudoCityCode(pcc)) fail7("pcc debe tener 3 o 4 caracteres alfanum\xE9ricos");
|
|
2968
3339
|
requireString2(domain, "domain");
|
|
2969
|
-
if (/[-:]/.test(domain))
|
|
3340
|
+
if (/[-:]/.test(domain)) fail7("domain no puede contener '-' ni ':'");
|
|
2970
3341
|
if (clientId === void 0 !== (clientSecret === void 0)) {
|
|
2971
|
-
|
|
3342
|
+
fail7("clientId y clientSecret van juntos: pasa los dos o ninguno");
|
|
2972
3343
|
}
|
|
2973
3344
|
if (authVersion === "v3" || clientId !== void 0) {
|
|
2974
3345
|
requireString2(clientId, authVersion === "v3" ? "clientId (requerido con authVersion v3)" : "clientId");
|
|
2975
3346
|
requireString2(clientSecret, authVersion === "v3" ? "clientSecret (requerido con authVersion v3)" : "clientSecret");
|
|
2976
3347
|
}
|
|
2977
3348
|
const username = authVersion === "v3" ? `${user}-${pcc}-${domain}` : `V1:${user}:${pcc}:${domain}`;
|
|
2978
|
-
|
|
3349
|
+
if (authVersion === "v3") return { authVersion, clientId, clientSecret, username, password };
|
|
3350
|
+
return clientId === void 0 ? { authVersion, username, password } : { authVersion, clientId, clientSecret, username, password };
|
|
2979
3351
|
}
|
|
2980
3352
|
|
|
2981
3353
|
// src/queue/host-date.ts
|
|
@@ -3276,6 +3648,22 @@ function hostTime(text) {
|
|
|
3276
3648
|
return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`;
|
|
3277
3649
|
}
|
|
3278
3650
|
|
|
3651
|
+
// src/queue/queue-place-remark.ts
|
|
3652
|
+
var MONTHS2 = /* @__PURE__ */ new Set(["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"]);
|
|
3653
|
+
var REMARK = /^([A-Z0-9]{3,4})(\d{2})([A-Z]{3})(\d{1,3})\/(\d{1,3})$/;
|
|
3654
|
+
function parseQueuePlaceRemark(text) {
|
|
3655
|
+
if (typeof text !== "string") return null;
|
|
3656
|
+
const match = REMARK.exec(text.trim());
|
|
3657
|
+
if (match === null) return null;
|
|
3658
|
+
const [, pcc, day, month, queueText, picText] = match;
|
|
3659
|
+
const dayNumber2 = Number(day);
|
|
3660
|
+
if (dayNumber2 < 1 || dayNumber2 > 31 || !MONTHS2.has(month)) return null;
|
|
3661
|
+
const queue = Number(queueText);
|
|
3662
|
+
const pic = Number(picText);
|
|
3663
|
+
if (queue > MAX_QUEUE_NUMBER || !isInstructionCode(pic)) return null;
|
|
3664
|
+
return { pcc, date: `${day}${month}`, queue, pic };
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3279
3667
|
// src/air/seat-availability-request.ts
|
|
3280
3668
|
function buildSeatAvailabilityRequest(source) {
|
|
3281
3669
|
const { group, itinerary, segments: only, currencyCode } = source;
|
|
@@ -3360,7 +3748,7 @@ function fareComponent(fareComponentId, component) {
|
|
|
3360
3748
|
}
|
|
3361
3749
|
|
|
3362
3750
|
// src/index.ts
|
|
3363
|
-
var VERSION = "0.
|
|
3751
|
+
var VERSION = "0.5.0";
|
|
3364
3752
|
export {
|
|
3365
3753
|
Sabre,
|
|
3366
3754
|
SabreBusinessError,
|
|
@@ -3376,6 +3764,7 @@ export {
|
|
|
3376
3764
|
buildRevalidateItineraryRequest,
|
|
3377
3765
|
buildSeatAvailabilityRequest,
|
|
3378
3766
|
parseQueueParagraph,
|
|
3767
|
+
parseQueuePlaceRemark,
|
|
3379
3768
|
resolveHostDate
|
|
3380
3769
|
};
|
|
3381
3770
|
//# sourceMappingURL=index.js.map
|