sabre-client 0.4.1 → 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 +202 -21
- package/README.md +202 -21
- package/dist/index.d.ts +2726 -693
- package/dist/index.js +495 -115
- 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
|
}
|
|
@@ -1227,7 +1243,6 @@ function resolveConfig(config) {
|
|
|
1227
1243
|
requireString(credentials.clientId, "credentials.clientId (requerido con authVersion v3)");
|
|
1228
1244
|
requireString(credentials.clientSecret, "credentials.clientSecret (requerido con authVersion v3)");
|
|
1229
1245
|
}
|
|
1230
|
-
if (config.pcc !== void 0) requireString(config.pcc, "pcc");
|
|
1231
1246
|
const { logger } = config;
|
|
1232
1247
|
if (logger !== void 0) {
|
|
1233
1248
|
if (!isObject3(logger)) fail("logger debe ser un objeto con debug/info/warn/error");
|
|
@@ -1248,12 +1263,12 @@ function resolveConfig(config) {
|
|
|
1248
1263
|
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
1249
1264
|
if (typeof fetchImpl !== "function") fail("no hay fetch disponible: pasa config.fetch");
|
|
1250
1265
|
return {
|
|
1266
|
+
// Ya validado arriba: con v3 trae clientId/clientSecret; la marca de versión ya se resolvió.
|
|
1251
1267
|
credentials: Object.freeze({ ...credentials }),
|
|
1252
1268
|
environment,
|
|
1253
1269
|
baseUrl: BASE_URLS[environment],
|
|
1254
1270
|
soapBaseUrl: SOAP_BASE_URLS[environment],
|
|
1255
1271
|
authVersion,
|
|
1256
|
-
pcc: config.pcc,
|
|
1257
1272
|
logger,
|
|
1258
1273
|
retry: Object.freeze({ ...DEFAULT_RETRY, ...definedOnly(config.retry ?? {}) }),
|
|
1259
1274
|
// Enlazado al global para evitar "Illegal invocation" en algunos runtimes.
|
|
@@ -1290,10 +1305,12 @@ var RestTransport = class {
|
|
|
1290
1305
|
}
|
|
1291
1306
|
async request(request) {
|
|
1292
1307
|
if (request.retry !== void 0) validateRetryOptions(request.retry, "retry");
|
|
1293
|
-
const
|
|
1308
|
+
const wantsRequest = request.onRawRequest !== void 0 && request.auth !== false;
|
|
1309
|
+
const capture = request.onRawResponse === void 0 && !wantsRequest ? void 0 : { attempts: 0 };
|
|
1294
1310
|
try {
|
|
1295
1311
|
return await this.#attempts(request, capture);
|
|
1296
1312
|
} finally {
|
|
1313
|
+
if (wantsRequest && capture?.request !== void 0) this.#deliverRawRequest(request, capture.request, capture.attempts);
|
|
1297
1314
|
if (capture?.last !== void 0) this.#deliverRaw(request, { ...capture.last, attempts: capture.attempts });
|
|
1298
1315
|
}
|
|
1299
1316
|
}
|
|
@@ -1347,6 +1364,19 @@ var RestTransport = class {
|
|
|
1347
1364
|
});
|
|
1348
1365
|
}
|
|
1349
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
|
+
}
|
|
1350
1380
|
async #authorizationFor(request) {
|
|
1351
1381
|
if (request.auth === false || this.#authorization === void 0) return void 0;
|
|
1352
1382
|
return this.#authorization.get();
|
|
@@ -1363,12 +1393,18 @@ var RestTransport = class {
|
|
|
1363
1393
|
this.#log.emit("debug", "sabre.request.start", logContext);
|
|
1364
1394
|
const startedAt = Date.now();
|
|
1365
1395
|
let response;
|
|
1366
|
-
|
|
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
|
+
}
|
|
1367
1403
|
try {
|
|
1368
1404
|
response = await this.#config.fetch(url, {
|
|
1369
1405
|
method: request.method,
|
|
1370
1406
|
headers,
|
|
1371
|
-
...
|
|
1407
|
+
...body === void 0 ? {} : { body },
|
|
1372
1408
|
...request.signal === void 0 ? {} : { signal: request.signal }
|
|
1373
1409
|
});
|
|
1374
1410
|
} catch (cause) {
|
|
@@ -1383,20 +1419,20 @@ var RestTransport = class {
|
|
|
1383
1419
|
}
|
|
1384
1420
|
const text = await response.text();
|
|
1385
1421
|
if (capture !== void 0) capture.last = { status: response.status, headers: response.headers, body: text };
|
|
1386
|
-
const
|
|
1422
|
+
const parsed = parseBody(text);
|
|
1387
1423
|
const endContext = { ...logContext, status: response.status, durationMs: Date.now() - startedAt };
|
|
1388
1424
|
if (!response.ok) {
|
|
1389
|
-
const error = gatewayError(request.service, response.status,
|
|
1425
|
+
const error = gatewayError(request.service, response.status, parsed);
|
|
1390
1426
|
this.#log.emit("error", "sabre.request.end", { ...endContext, error });
|
|
1391
1427
|
throw error;
|
|
1392
1428
|
}
|
|
1393
|
-
const serviceError = request.classify?.(
|
|
1429
|
+
const serviceError = request.classify?.(parsed, response);
|
|
1394
1430
|
if (serviceError !== void 0) {
|
|
1395
1431
|
this.#log.emit("warn", "sabre.request.end", { ...endContext, error: serviceError });
|
|
1396
1432
|
throw serviceError;
|
|
1397
1433
|
}
|
|
1398
1434
|
this.#log.emit("info", "sabre.request.end", endContext);
|
|
1399
|
-
return
|
|
1435
|
+
return parsed;
|
|
1400
1436
|
}
|
|
1401
1437
|
#url(request) {
|
|
1402
1438
|
const path = request.path.startsWith("/") ? request.path : `/${request.path}`;
|
|
@@ -1499,6 +1535,7 @@ var TRANSIENT_CODES = /* @__PURE__ */ new Set([
|
|
|
1499
1535
|
var PREVIOUS_ENTRY_ACTIVE_RETRIES = 3;
|
|
1500
1536
|
var PREVIOUS_ENTRY_ACTIVE_DELAY_MS = 1e3;
|
|
1501
1537
|
var PREVIOUS_ENTRY_ACTIVE = /PREVIOUS ENTRY ACTIVE/i;
|
|
1538
|
+
var SOAP_HEADERS = { "Content-Type": "text/xml; charset=utf-8", Accept: "text/xml" };
|
|
1502
1539
|
var PARSER_OPTIONS = {
|
|
1503
1540
|
ignoreAttributes: false,
|
|
1504
1541
|
attributeNamePrefix: "@",
|
|
@@ -1534,6 +1571,7 @@ var SoapTransport = class {
|
|
|
1534
1571
|
try {
|
|
1535
1572
|
return await this.#attempts(request, capture);
|
|
1536
1573
|
} finally {
|
|
1574
|
+
if (capture.request !== void 0) this.#deliverRawRequest(request, capture.request, capture.attempts);
|
|
1537
1575
|
if (capture.last !== void 0) this.#deliverRaw(request, { ...capture.last, attempts: capture.attempts });
|
|
1538
1576
|
}
|
|
1539
1577
|
}
|
|
@@ -1580,10 +1618,18 @@ var SoapTransport = class {
|
|
|
1580
1618
|
const startedAt = Date.now();
|
|
1581
1619
|
let response;
|
|
1582
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
|
+
}
|
|
1583
1629
|
try {
|
|
1584
1630
|
response = await this.#config.fetch(this.#config.soapBaseUrl, {
|
|
1585
1631
|
method: "POST",
|
|
1586
|
-
headers:
|
|
1632
|
+
headers: SOAP_HEADERS,
|
|
1587
1633
|
body: envelope,
|
|
1588
1634
|
...request.signal === void 0 ? {} : { signal: request.signal }
|
|
1589
1635
|
});
|
|
@@ -1614,6 +1660,7 @@ var SoapTransport = class {
|
|
|
1614
1660
|
return {
|
|
1615
1661
|
...result,
|
|
1616
1662
|
messageId,
|
|
1663
|
+
respondedAt: respondedAtOf(text),
|
|
1617
1664
|
raw: text,
|
|
1618
1665
|
securityToken: request.readSecurityToken === true ? readSecurityToken(original) : void 0,
|
|
1619
1666
|
status: response.status
|
|
@@ -1656,12 +1703,12 @@ var SoapTransport = class {
|
|
|
1656
1703
|
});
|
|
1657
1704
|
}
|
|
1658
1705
|
const results = asNode(payload.ApplicationResults);
|
|
1659
|
-
const warnings = asArray(results?.Warning).map((
|
|
1660
|
-
const entry = resultEntry(
|
|
1706
|
+
const warnings = asArray(results?.Warning).map((node3) => {
|
|
1707
|
+
const entry = resultEntry(node3);
|
|
1661
1708
|
return new SabreWarning(`${service}: ${entry.description}`, { service, code: entry.code, raw: text });
|
|
1662
1709
|
});
|
|
1663
|
-
const errors = asArray(results?.Error).map((
|
|
1664
|
-
const entry = resultEntry(
|
|
1710
|
+
const errors = asArray(results?.Error).map((node3) => {
|
|
1711
|
+
const entry = resultEntry(node3);
|
|
1665
1712
|
return new SabreBusinessError(`${service}: ${entry.description}`, {
|
|
1666
1713
|
service,
|
|
1667
1714
|
code: entry.code,
|
|
@@ -1707,6 +1754,19 @@ var SoapTransport = class {
|
|
|
1707
1754
|
].join("");
|
|
1708
1755
|
}
|
|
1709
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). */
|
|
1710
1770
|
#deliverRaw(request, raw) {
|
|
1711
1771
|
if (request.onRawResponse === void 0) return;
|
|
1712
1772
|
const operation = request.operation ?? request.service;
|
|
@@ -1721,6 +1781,18 @@ var SoapTransport = class {
|
|
|
1721
1781
|
}
|
|
1722
1782
|
}
|
|
1723
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
|
+
}
|
|
1724
1796
|
function xmlEscape(value) {
|
|
1725
1797
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1726
1798
|
}
|
|
@@ -1743,6 +1815,9 @@ function securityHeader(security) {
|
|
|
1743
1815
|
"</wsse:Security>"
|
|
1744
1816
|
].join("");
|
|
1745
1817
|
}
|
|
1818
|
+
function wantsRawRequest(request) {
|
|
1819
|
+
return request.onRawRequest !== void 0 && "binarySecurityToken" in request.security;
|
|
1820
|
+
}
|
|
1746
1821
|
function scrubToken(text, security) {
|
|
1747
1822
|
if (!("binarySecurityToken" in security) || security.binarySecurityToken === "") return text;
|
|
1748
1823
|
const token = security.binarySecurityToken;
|
|
@@ -1782,8 +1857,8 @@ function faultError(service, status, fault, text) {
|
|
|
1782
1857
|
...code === void 0 || code === "" ? {} : { code }
|
|
1783
1858
|
});
|
|
1784
1859
|
}
|
|
1785
|
-
function resultEntry(
|
|
1786
|
-
const entry = asNode(
|
|
1860
|
+
function resultEntry(node3) {
|
|
1861
|
+
const entry = asNode(node3) ?? {};
|
|
1787
1862
|
const messages = [];
|
|
1788
1863
|
const shortTexts = [];
|
|
1789
1864
|
let code;
|
|
@@ -1821,8 +1896,8 @@ function untrimmedPayload(raw) {
|
|
|
1821
1896
|
const document = parseEnvelope(raw, untrimmedParser);
|
|
1822
1897
|
return document === void 0 ? void 0 : asNode(firstElement(document.Body)[1]);
|
|
1823
1898
|
}
|
|
1824
|
-
function firstElement(
|
|
1825
|
-
for (const [key, value] of Object.entries(
|
|
1899
|
+
function firstElement(node3) {
|
|
1900
|
+
for (const [key, value] of Object.entries(node3)) {
|
|
1826
1901
|
if (!key.startsWith("@") && key !== "#text") return [key, Array.isArray(value) ? value[0] : value];
|
|
1827
1902
|
}
|
|
1828
1903
|
return [void 0, void 0];
|
|
@@ -1837,14 +1912,14 @@ function asArray(value) {
|
|
|
1837
1912
|
}
|
|
1838
1913
|
function textOf(value) {
|
|
1839
1914
|
if (typeof value === "string") return value;
|
|
1840
|
-
const
|
|
1841
|
-
const text =
|
|
1842
|
-
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 : "";
|
|
1843
1918
|
}
|
|
1844
1919
|
function stringAt(root2, ...path) {
|
|
1845
|
-
let
|
|
1846
|
-
for (const key of path)
|
|
1847
|
-
return nonEmpty2(textOf(
|
|
1920
|
+
let node3 = root2;
|
|
1921
|
+
for (const key of path) node3 = asNode(node3)?.[key];
|
|
1922
|
+
return nonEmpty2(textOf(node3));
|
|
1848
1923
|
}
|
|
1849
1924
|
function nonEmpty2(value) {
|
|
1850
1925
|
return typeof value === "string" && value !== "" ? value : void 0;
|
|
@@ -1901,27 +1976,41 @@ import { XMLParser as XMLParser2 } from "fast-xml-parser";
|
|
|
1901
1976
|
// src/queue/queue-ref.ts
|
|
1902
1977
|
var MIN_QUEUE_NUMBER = 0;
|
|
1903
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
|
+
}
|
|
1904
1984
|
function validateQueueRef(value, label) {
|
|
1905
1985
|
if (typeof value === "number") {
|
|
1906
1986
|
if (Number.isInteger(value) && value >= MIN_QUEUE_NUMBER && value <= MAX_QUEUE_NUMBER) return;
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
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')`);
|
|
1910
1993
|
}
|
|
1911
1994
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1912
1995
|
const { name } = value;
|
|
1913
|
-
if (typeof name
|
|
1914
|
-
|
|
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;
|
|
1915
1999
|
}
|
|
1916
|
-
|
|
2000
|
+
fail3(`${label} debe ser un n\xFAmero de cola (0\u2013511), una cola alfab\xE9tica ('S') o { name }`);
|
|
1917
2001
|
}
|
|
1918
2002
|
function queueRefAttribute(queue) {
|
|
1919
|
-
|
|
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;
|
|
1920
2009
|
}
|
|
1921
2010
|
function toQueueRef(value) {
|
|
1922
2011
|
const text = value?.trim();
|
|
1923
2012
|
if (text === void 0 || text === "") return void 0;
|
|
1924
|
-
return /^\d+$/.test(text) ? Number(text) :
|
|
2013
|
+
return /^\d+$/.test(text) ? Number(text) : text;
|
|
1925
2014
|
}
|
|
1926
2015
|
|
|
1927
2016
|
// src/queue/queue-access.ts
|
|
@@ -1939,9 +2028,11 @@ function root(body) {
|
|
|
1939
2028
|
function identifier(queue, pcc) {
|
|
1940
2029
|
return `<QueueIdentifier ${queueRefAttribute(queue)} PseudoCityCode="${xmlEscape(pcc)}"/>`;
|
|
1941
2030
|
}
|
|
2031
|
+
function selectionXml(selection) {
|
|
2032
|
+
return selection.map((filter) => `<Selection Function="${xmlEscape(filter.function)}" Criteria="${xmlEscape(filter.criteria)}"/>`).join("");
|
|
2033
|
+
}
|
|
1942
2034
|
function queueAccessPayload(queue, pcc, selection = []) {
|
|
1943
|
-
|
|
1944
|
-
return root(`${identifier(queue, pcc)}${filters}`);
|
|
2035
|
+
return root(`${identifier(queue, pcc)}${selectionXml(selection)}`);
|
|
1945
2036
|
}
|
|
1946
2037
|
function queueNavigationPayload(action, from = []) {
|
|
1947
2038
|
return root(`<Navigation Action="${action}"/>${from.map((target) => identifier(target.queue, target.pcc)).join("")}`);
|
|
@@ -2007,17 +2098,21 @@ function queueEndError(error, service) {
|
|
|
2007
2098
|
function isOffQueueError(error) {
|
|
2008
2099
|
return error instanceof SabreBusinessError && [error, ...error.errors].some((result) => resultHasText(result, OFF_QUEUE));
|
|
2009
2100
|
}
|
|
2010
|
-
function endStep(warnings, reason = "end-of-display") {
|
|
2011
|
-
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) };
|
|
2012
2103
|
}
|
|
2013
|
-
function parseQueueStep(response) {
|
|
2104
|
+
function parseQueueStep(response, { messages = false } = {}) {
|
|
2014
2105
|
const { warnings } = response;
|
|
2015
2106
|
const paragraph = paragraphLines(response.raw);
|
|
2016
2107
|
const firstLine = paragraph.find((line) => line.trim() !== "");
|
|
2017
|
-
if (isEndOfDisplay(firstLine) || warnings.some(isEndOfDisplayResult)) return endStep(warnings);
|
|
2018
|
-
|
|
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);
|
|
2019
2113
|
if (fields === void 0) {
|
|
2020
|
-
|
|
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})`, {
|
|
2021
2116
|
protocol: "soap",
|
|
2022
2117
|
status: response.status,
|
|
2023
2118
|
retryable: false,
|
|
@@ -2025,7 +2120,7 @@ function parseQueueStep(response) {
|
|
|
2025
2120
|
});
|
|
2026
2121
|
}
|
|
2027
2122
|
const item = { ...fields, paragraph };
|
|
2028
|
-
return { done: false, item, warnings };
|
|
2123
|
+
return { done: false, kind: "pnr", item, warnings, ...respondedAtField(response.raw) };
|
|
2029
2124
|
}
|
|
2030
2125
|
function lineFields(value) {
|
|
2031
2126
|
const line = node(value);
|
|
@@ -2072,8 +2167,8 @@ function parseHostCount(value) {
|
|
|
2072
2167
|
const match = /^[.\s]*(\d+)\s*$/.exec(value ?? "");
|
|
2073
2168
|
return match === null ? void 0 : Number(match[1]);
|
|
2074
2169
|
}
|
|
2075
|
-
function attribute2(
|
|
2076
|
-
const value =
|
|
2170
|
+
function attribute2(node3, name) {
|
|
2171
|
+
const value = node3?.[`@${name}`];
|
|
2077
2172
|
return typeof value === "string" ? value : void 0;
|
|
2078
2173
|
}
|
|
2079
2174
|
function parseQueueCountResponse(response) {
|
|
@@ -2083,7 +2178,8 @@ function parseQueueCountResponse(response) {
|
|
|
2083
2178
|
countedAt ??= nonEmpty3(attribute2(info, "DateTime"));
|
|
2084
2179
|
for (const identifier2 of asArray(info.QueueIdentifier)) {
|
|
2085
2180
|
const number = attribute2(identifier2, "Number");
|
|
2086
|
-
const
|
|
2181
|
+
const name = nonEmpty3(attribute2(identifier2, "Name"));
|
|
2182
|
+
const queue = number === void 0 && name !== void 0 ? { name } : toQueueRef(number);
|
|
2087
2183
|
const count = parseHostCount(attribute2(identifier2, "Count"));
|
|
2088
2184
|
if (queue === void 0 || count === void 0) {
|
|
2089
2185
|
throw malformed(`QueueIdentifier con Number="${number ?? ""}" y Count="${attribute2(identifier2, "Count") ?? ""}"`, response);
|
|
@@ -2099,7 +2195,13 @@ function parseQueueCountResponse(response) {
|
|
|
2099
2195
|
if (count === void 0) throw malformed(`Totals ${key} con Count="${attribute2(total, "Count") ?? ""}"`, response);
|
|
2100
2196
|
totals[key] = count;
|
|
2101
2197
|
}
|
|
2102
|
-
return {
|
|
2198
|
+
return {
|
|
2199
|
+
queues,
|
|
2200
|
+
totals,
|
|
2201
|
+
...countedAt === void 0 ? {} : { countedAt },
|
|
2202
|
+
warnings: response.warnings,
|
|
2203
|
+
...respondedAtField(response.raw)
|
|
2204
|
+
};
|
|
2103
2205
|
}
|
|
2104
2206
|
function malformed(detail, response) {
|
|
2105
2207
|
return new SabreGatewayError(`${QUEUE_COUNT_SERVICE}: respuesta no reconocida (${detail})`, {
|
|
@@ -2116,9 +2218,10 @@ function nonEmpty3(value) {
|
|
|
2116
2218
|
|
|
2117
2219
|
// src/queue/queue-list.ts
|
|
2118
2220
|
var NAMESPACE3 = "http://webservices.sabre.com/sabreXML/2011/10";
|
|
2119
|
-
|
|
2221
|
+
var LIST_SELECTION_FUNCTIONS = ["QD", "AK"];
|
|
2222
|
+
function queueListPayload(queue, pcc, primaryPassenger, selection = []) {
|
|
2120
2223
|
const list = primaryPassenger ? '<List Ind="true" PrimaryPassenger="true"/>' : '<List Ind="true"/>';
|
|
2121
|
-
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>`;
|
|
2122
2225
|
}
|
|
2123
2226
|
function queueListRequest(payload, options) {
|
|
2124
2227
|
return {
|
|
@@ -2130,14 +2233,14 @@ function queueListRequest(payload, options) {
|
|
|
2130
2233
|
...options
|
|
2131
2234
|
};
|
|
2132
2235
|
}
|
|
2133
|
-
function emptyList(warnings, paragraph = []) {
|
|
2134
|
-
return { locators: [], entries: [], paragraph, warnings };
|
|
2236
|
+
function emptyList(warnings, paragraph = [], raw) {
|
|
2237
|
+
return { locators: [], entries: [], paragraph, warnings, ...respondedAtField(raw) };
|
|
2135
2238
|
}
|
|
2136
2239
|
function parseQueueList(response) {
|
|
2137
2240
|
const { warnings } = response;
|
|
2138
2241
|
const paragraph = paragraphLines(response.raw);
|
|
2139
2242
|
const firstLine = paragraph.find((line) => line.trim() !== "");
|
|
2140
|
-
if (isEndOfDisplay(firstLine) || warnings.some(isEndOfDisplayResult)) return emptyList(warnings, paragraph);
|
|
2243
|
+
if (isEndOfDisplay(firstLine) || warnings.some(isEndOfDisplayResult)) return emptyList(warnings, paragraph, response.raw);
|
|
2141
2244
|
const entries = asArray(response.payload.Line).map((line) => {
|
|
2142
2245
|
const fields = lineFields(line);
|
|
2143
2246
|
if (fields === void 0) {
|
|
@@ -2152,12 +2255,88 @@ function parseQueueList(response) {
|
|
|
2152
2255
|
const position = number !== void 0 && /^\d+$/.test(number) ? Number(number) : void 0;
|
|
2153
2256
|
return { ...position === void 0 ? {} : { position }, ...fields };
|
|
2154
2257
|
});
|
|
2155
|
-
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
|
+
});
|
|
2156
2335
|
}
|
|
2157
2336
|
|
|
2158
2337
|
// src/queue/queue-place.ts
|
|
2159
2338
|
var QUEUE_PLACE_SERVICE = "queuePlace";
|
|
2160
|
-
var
|
|
2339
|
+
var NAMESPACE5 = "http://webservices.sabre.com/sabreXML/2011/10";
|
|
2161
2340
|
var MAX_PLACE_TO = 10;
|
|
2162
2341
|
var MAX_PLACE_LOCATORS = 6;
|
|
2163
2342
|
var INSTRUCTION_CODE_RANGES = [
|
|
@@ -2173,7 +2352,7 @@ function queuePlacePayload(to, locators = []) {
|
|
|
2173
2352
|
return `<QueueIdentifier ${queueRefAttribute(target.queue)}${pic} PseudoCityCode="${xmlEscape(target.pcc)}"/>`;
|
|
2174
2353
|
}).join("");
|
|
2175
2354
|
const ids = locators.map((locator) => `<UniqueID ID="${xmlEscape(locator)}"/>`).join("");
|
|
2176
|
-
return `<QueuePlaceRQ Version="2.0.4" xmlns="${
|
|
2355
|
+
return `<QueuePlaceRQ Version="2.0.4" xmlns="${NAMESPACE5}"><QueueInfo>${identifiers}${ids}</QueueInfo></QueuePlaceRQ>`;
|
|
2177
2356
|
}
|
|
2178
2357
|
function queuePlaceRequest(payload, options) {
|
|
2179
2358
|
return {
|
|
@@ -2198,7 +2377,7 @@ function isEnd(response, text) {
|
|
|
2198
2377
|
}
|
|
2199
2378
|
function parsePlaceStep(response) {
|
|
2200
2379
|
const text = textLines(response.raw, "QueuePlaceRS");
|
|
2201
|
-
if (isEnd(response, text)) return endStep(response.warnings);
|
|
2380
|
+
if (isEnd(response, text)) return endStep(response.warnings, "end-of-display", response.raw);
|
|
2202
2381
|
const locator = signatureLocator(text);
|
|
2203
2382
|
if (locator === void 0) {
|
|
2204
2383
|
throw new SabreGatewayError(`${QUEUE_PLACE_SERVICE}: respuesta no reconocida (sin l\xEDnea de firma del PNR ni fin de cola)`, {
|
|
@@ -2208,7 +2387,7 @@ function parsePlaceStep(response) {
|
|
|
2208
2387
|
raw: response.raw
|
|
2209
2388
|
});
|
|
2210
2389
|
}
|
|
2211
|
-
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) };
|
|
2212
2391
|
}
|
|
2213
2392
|
function parsePlaceUnknown(response) {
|
|
2214
2393
|
const text = textLines(response.raw, "QueuePlaceRS");
|
|
@@ -2231,11 +2410,16 @@ function parsePlaceResult(response) {
|
|
|
2231
2410
|
...placedAt === void 0 ? {} : { placedAt }
|
|
2232
2411
|
};
|
|
2233
2412
|
});
|
|
2234
|
-
return {
|
|
2413
|
+
return {
|
|
2414
|
+
placements,
|
|
2415
|
+
text: textLines(response.raw, "QueuePlaceRS"),
|
|
2416
|
+
warnings: response.warnings,
|
|
2417
|
+
...respondedAtField(response.raw)
|
|
2418
|
+
};
|
|
2235
2419
|
}
|
|
2236
2420
|
|
|
2237
2421
|
// src/queue/queue-management.ts
|
|
2238
|
-
function
|
|
2422
|
+
function fail4(message) {
|
|
2239
2423
|
throw new TypeError(`sabre-client: ${message}`);
|
|
2240
2424
|
}
|
|
2241
2425
|
function rejectedBySabre(error) {
|
|
@@ -2244,6 +2428,8 @@ function rejectedBySabre(error) {
|
|
|
2244
2428
|
var QueueManagement = class {
|
|
2245
2429
|
#context;
|
|
2246
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;
|
|
2247
2433
|
/** @internal Lo construye `SabreSession`. */
|
|
2248
2434
|
constructor(context) {
|
|
2249
2435
|
this.#context = context;
|
|
@@ -2269,31 +2455,61 @@ var QueueManagement = class {
|
|
|
2269
2455
|
const response = await this.#context.send(queueCountRequest(pcc, options));
|
|
2270
2456
|
return parseQueueCountResponse(response);
|
|
2271
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
|
+
}
|
|
2272
2475
|
/**
|
|
2273
2476
|
* La lista de localizadores de una cola (`QueueAccessLLSRQ` con `List`), sin recorrerla. Con
|
|
2274
2477
|
* `primaryPassenger: true`, cada entrada trae también el pasajero principal. Una cola vacía da
|
|
2275
2478
|
* una lista vacía.
|
|
2276
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
|
+
*
|
|
2277
2483
|
* No cambia `state`. Es una lectura: se reintenta ante fallos transitorios. Lanza `TypeError`
|
|
2278
|
-
* 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`.
|
|
2279
2486
|
*/
|
|
2280
2487
|
async list(request, options = {}) {
|
|
2281
|
-
if (typeof request !== "object" || request === null)
|
|
2488
|
+
if (typeof request !== "object" || request === null) fail4("queue.list recibe { queue, pcc?, primaryPassenger?, selection? }");
|
|
2282
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");
|
|
2283
2491
|
const pcc = this.#pcc(request.pcc, "pcc");
|
|
2284
2492
|
const { primaryPassenger = false } = request;
|
|
2285
|
-
if (typeof primaryPassenger !== "boolean")
|
|
2286
|
-
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);
|
|
2287
2501
|
try {
|
|
2288
2502
|
return parseQueueList(await this.#context.send(send2));
|
|
2289
2503
|
} catch (error) {
|
|
2290
2504
|
const end = queueEndError(error, send2.service);
|
|
2291
|
-
if (end !== void 0) return emptyList([...end.error.warnings]);
|
|
2505
|
+
if (end !== void 0) return emptyList([...end.error.warnings], [], end.error.raw);
|
|
2292
2506
|
throw error;
|
|
2293
2507
|
}
|
|
2294
2508
|
}
|
|
2295
2509
|
/**
|
|
2296
|
-
* 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
|
|
2297
2513
|
* vacía, o sin PNR que cumplan `selection`, devuelve `{ done: true }` y la sesión sigue afuera.
|
|
2298
2514
|
*
|
|
2299
2515
|
* Lanza `TypeError` sin red si la sesión ya está dentro de una cola (o en `'unknown'`: hay que
|
|
@@ -2302,12 +2518,13 @@ var QueueManagement = class {
|
|
|
2302
2518
|
* `'unknown'`.
|
|
2303
2519
|
*/
|
|
2304
2520
|
async access(request, options = {}) {
|
|
2305
|
-
if (this.#state === "inside")
|
|
2306
|
-
if (this.#state === "unknown")
|
|
2307
|
-
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? }");
|
|
2308
2524
|
validateQueueRef(request.queue, "queue");
|
|
2309
2525
|
const pcc = this.#pcc(request.pcc, "pcc");
|
|
2310
2526
|
const selection = validateSelection(request.selection);
|
|
2527
|
+
this.#messages = isMessageQueue(request.queue);
|
|
2311
2528
|
return this.#step(queueAccessRequest(queueAccessPayload(request.queue, pcc, selection), "queue.access", options));
|
|
2312
2529
|
}
|
|
2313
2530
|
/**
|
|
@@ -2320,7 +2537,8 @@ var QueueManagement = class {
|
|
|
2320
2537
|
}
|
|
2321
2538
|
/**
|
|
2322
2539
|
* Saca el PNR actual de la cola y pasa al siguiente (`QR`). Con `from` (de 1 a 3 colas), lo saca
|
|
2323
|
-
* 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.
|
|
2324
2542
|
*/
|
|
2325
2543
|
async remove(request = {}, options = {}) {
|
|
2326
2544
|
this.#requireQueue("remove");
|
|
@@ -2367,10 +2585,11 @@ var QueueManagement = class {
|
|
|
2367
2585
|
* `options` se aplica a cada llamada a Sabre del recorrido.
|
|
2368
2586
|
*/
|
|
2369
2587
|
async process(request, callback, options = {}) {
|
|
2370
|
-
if (typeof request !== "object" || request === null)
|
|
2371
|
-
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");
|
|
2372
2590
|
const { limit, ...access } = request;
|
|
2373
|
-
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");
|
|
2374
2593
|
const startedOutside = this.#state === "outside";
|
|
2375
2594
|
let visited = 0;
|
|
2376
2595
|
try {
|
|
@@ -2380,6 +2599,7 @@ var QueueManagement = class {
|
|
|
2380
2599
|
await this.exit(options);
|
|
2381
2600
|
return { visited, ended: "limit" };
|
|
2382
2601
|
}
|
|
2602
|
+
if (step.kind !== "pnr") throw new TypeError("sabre-client: queue.process recibi\xF3 un mensaje en vez de un PNR");
|
|
2383
2603
|
const action = await callback(step.item);
|
|
2384
2604
|
visited++;
|
|
2385
2605
|
step = await this.#processAction(action, options);
|
|
@@ -2411,17 +2631,20 @@ var QueueManagement = class {
|
|
|
2411
2631
|
}
|
|
2412
2632
|
}
|
|
2413
2633
|
async place(request, options = {}) {
|
|
2414
|
-
if (typeof request !== "object" || request === null)
|
|
2634
|
+
if (typeof request !== "object" || request === null) fail4("queue.place recibe { to, locators? }");
|
|
2415
2635
|
const to = this.#placeTo(request.to);
|
|
2416
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
|
+
}
|
|
2417
2640
|
if (locators.length > 0 && this.#state === "inside") {
|
|
2418
|
-
|
|
2641
|
+
fail4("queue.place con locators no se puede dentro de una cola, donde coloca el PNR actual: sal con queue.exit() antes");
|
|
2419
2642
|
}
|
|
2420
2643
|
if (locators.length > 0 && this.#state === "unknown") {
|
|
2421
|
-
|
|
2644
|
+
fail4("no se sabe si la sesi\xF3n sigue dentro de una cola: llama a queue.exit() antes de colocar por localizador");
|
|
2422
2645
|
}
|
|
2423
2646
|
if (to.some((target) => target.instructionCode === void 0)) {
|
|
2424
|
-
|
|
2647
|
+
fail4("queue.place necesita instructionCode en cada destino: Sabre lo exige, dentro y fuera de una cola");
|
|
2425
2648
|
}
|
|
2426
2649
|
const send2 = queuePlaceRequest(queuePlacePayload(to, locators), options);
|
|
2427
2650
|
if (this.#state === "outside") return parsePlaceResult(await this.#context.send(send2));
|
|
@@ -2429,7 +2652,8 @@ var QueueManagement = class {
|
|
|
2429
2652
|
}
|
|
2430
2653
|
/** Manda una operación que avanza y actualiza `state` con el resultado. */
|
|
2431
2654
|
#step(request) {
|
|
2432
|
-
|
|
2655
|
+
const messages = this.#messages;
|
|
2656
|
+
return this.#advance(request, (response) => parseQueueStep(response, { messages }));
|
|
2433
2657
|
}
|
|
2434
2658
|
/**
|
|
2435
2659
|
* Manda una operación que puede mover el cursor. Un `QueueStep` fija el estado: `'outside'` con
|
|
@@ -2443,7 +2667,7 @@ var QueueManagement = class {
|
|
|
2443
2667
|
const end = queueEndError(error, request.service);
|
|
2444
2668
|
if (end !== void 0) {
|
|
2445
2669
|
this.#state = "outside";
|
|
2446
|
-
return endStep([...end.error.warnings], end.reason);
|
|
2670
|
+
return endStep([...end.error.warnings], end.reason, end.error.raw);
|
|
2447
2671
|
}
|
|
2448
2672
|
this.#onError(error);
|
|
2449
2673
|
throw error;
|
|
@@ -2464,14 +2688,14 @@ var QueueManagement = class {
|
|
|
2464
2688
|
}
|
|
2465
2689
|
#placeTo(to) {
|
|
2466
2690
|
if (!Array.isArray(to) || to.length === 0 || to.length > MAX_PLACE_TO) {
|
|
2467
|
-
|
|
2691
|
+
fail4(`to debe ser una lista de 1 a ${MAX_PLACE_TO} colas`);
|
|
2468
2692
|
}
|
|
2469
2693
|
return to.map((target, index) => {
|
|
2470
|
-
if (typeof target !== "object" || target === null)
|
|
2694
|
+
if (typeof target !== "object" || target === null) fail4(`to[${index}] debe ser { queue, pcc?, instructionCode }`);
|
|
2471
2695
|
const { queue, pcc, instructionCode } = target;
|
|
2472
2696
|
validateQueueRef(queue, `to[${index}].queue`);
|
|
2473
2697
|
if (instructionCode !== void 0 && !isInstructionCode(instructionCode)) {
|
|
2474
|
-
|
|
2698
|
+
fail4(`to[${index}].instructionCode debe ser un entero de 0 a 191 o de 200 a 254`);
|
|
2475
2699
|
}
|
|
2476
2700
|
return {
|
|
2477
2701
|
queue,
|
|
@@ -2481,20 +2705,20 @@ var QueueManagement = class {
|
|
|
2481
2705
|
});
|
|
2482
2706
|
}
|
|
2483
2707
|
#requireQueue(operation) {
|
|
2484
|
-
if (this.#state === "outside")
|
|
2708
|
+
if (this.#state === "outside") fail4(`queue.${operation} necesita estar dentro de una cola: entra con queue.access()`);
|
|
2485
2709
|
}
|
|
2486
2710
|
#pcc(value, label) {
|
|
2487
2711
|
if (value === void 0) return this.#context.pcc;
|
|
2488
|
-
if (!isPseudoCityCode(value))
|
|
2712
|
+
if (!isPseudoCityCode(value)) fail4(`${label} debe tener 3 o 4 caracteres alfanum\xE9ricos`);
|
|
2489
2713
|
return value;
|
|
2490
2714
|
}
|
|
2491
2715
|
#removeFrom(from) {
|
|
2492
2716
|
if (from === void 0) return [];
|
|
2493
2717
|
if (!Array.isArray(from) || from.length === 0 || from.length > MAX_REMOVE_FROM) {
|
|
2494
|
-
|
|
2718
|
+
fail4(`from debe ser una lista de 1 a ${MAX_REMOVE_FROM} colas`);
|
|
2495
2719
|
}
|
|
2496
2720
|
return from.map((target, index) => {
|
|
2497
|
-
if (typeof target !== "object" || target === null)
|
|
2721
|
+
if (typeof target !== "object" || target === null) fail4(`from[${index}] debe ser { queue, pcc? }`);
|
|
2498
2722
|
const { queue, pcc } = target;
|
|
2499
2723
|
validateQueueRef(queue, `from[${index}].queue`);
|
|
2500
2724
|
return { queue, pcc: this.#pcc(pcc, `from[${index}].pcc`) };
|
|
@@ -2504,37 +2728,171 @@ var QueueManagement = class {
|
|
|
2504
2728
|
function validateLocators(locators) {
|
|
2505
2729
|
if (locators === void 0) return [];
|
|
2506
2730
|
if (!Array.isArray(locators) || locators.length === 0 || locators.length > MAX_PLACE_LOCATORS) {
|
|
2507
|
-
|
|
2731
|
+
fail4(`locators debe ser una lista de 1 a ${MAX_PLACE_LOCATORS} localizadores`);
|
|
2508
2732
|
}
|
|
2509
2733
|
return locators.map((locator, index) => {
|
|
2510
|
-
if (typeof locator !== "string" || locator.trim() === "")
|
|
2734
|
+
if (typeof locator !== "string" || locator.trim() === "") fail4(`locators[${index}] debe ser un string no vac\xEDo`);
|
|
2511
2735
|
return locator;
|
|
2512
2736
|
});
|
|
2513
2737
|
}
|
|
2514
2738
|
function validateSelection(selection) {
|
|
2515
2739
|
if (selection === void 0) return [];
|
|
2516
|
-
if (!Array.isArray(selection))
|
|
2517
|
-
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`);
|
|
2518
2742
|
return selection.map((filter, index) => {
|
|
2519
2743
|
const { function: fn, criteria } = typeof filter === "object" && filter !== null ? filter : {};
|
|
2520
|
-
if (typeof fn !== "string" || fn.trim() === "")
|
|
2744
|
+
if (typeof fn !== "string" || fn.trim() === "") fail4(`selection[${index}].function debe ser un string no vac\xEDo`);
|
|
2521
2745
|
if (typeof criteria !== "string" || criteria.trim() === "") {
|
|
2522
|
-
|
|
2746
|
+
fail4(`selection[${index}].criteria debe ser un string no vac\xEDo`);
|
|
2523
2747
|
}
|
|
2524
2748
|
return { function: fn, criteria };
|
|
2525
2749
|
});
|
|
2526
2750
|
}
|
|
2527
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
|
+
|
|
2528
2886
|
// src/session/command.ts
|
|
2529
2887
|
var SABRE_COMMAND_SERVICE = "sabreCommand";
|
|
2530
|
-
var
|
|
2888
|
+
var NAMESPACE7 = "http://webservices.sabre.com/sabreXML/2011/10";
|
|
2531
2889
|
var MAX_COMMAND_LENGTH = 1023;
|
|
2532
2890
|
var DOUBLE_WIDTH = /* @__PURE__ */ new Set(["!", "%", "$", "&", "\xA5", "\xA7"]);
|
|
2533
|
-
function
|
|
2891
|
+
function fail5(message) {
|
|
2534
2892
|
throw new TypeError(`sabre-client: ${message}`);
|
|
2535
2893
|
}
|
|
2536
2894
|
function sabreCommandPayload(command) {
|
|
2537
|
-
return `<SabreCommandLLSRQ xmlns="${
|
|
2895
|
+
return `<SabreCommandLLSRQ xmlns="${NAMESPACE7}" Version="2.0.0" ReturnHostCommand="true"><Request Output="SCREEN"><HostCommand>${xmlEscape(command)}</HostCommand></Request></SabreCommandLLSRQ>`;
|
|
2538
2896
|
}
|
|
2539
2897
|
function commandToken(command) {
|
|
2540
2898
|
return (command.trim().split(/[¥‡\s]/u)[0] ?? "").slice(0, 4);
|
|
@@ -2545,13 +2903,13 @@ function sabreCommandLength(command) {
|
|
|
2545
2903
|
return length;
|
|
2546
2904
|
}
|
|
2547
2905
|
function sabreCommandRequest(command, options) {
|
|
2548
|
-
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");
|
|
2549
2907
|
const length = sabreCommandLength(command);
|
|
2550
2908
|
if (length > MAX_COMMAND_LENGTH) {
|
|
2551
|
-
|
|
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`);
|
|
2552
2910
|
}
|
|
2553
2911
|
const { idempotent = false, ...requestOptions } = options;
|
|
2554
|
-
if (typeof idempotent !== "boolean")
|
|
2912
|
+
if (typeof idempotent !== "boolean") fail5("idempotent debe ser un boolean");
|
|
2555
2913
|
return {
|
|
2556
2914
|
service: SABRE_COMMAND_SERVICE,
|
|
2557
2915
|
action: "SabreCommandLLSRQ",
|
|
@@ -2573,7 +2931,8 @@ function parseSabreCommandResponse(response) {
|
|
|
2573
2931
|
return {
|
|
2574
2932
|
text: textOf((untrimmedPayload(response.raw) ?? response.payload).Response) ?? "",
|
|
2575
2933
|
warnings: response.warnings,
|
|
2576
|
-
messageId: response.messageId
|
|
2934
|
+
messageId: response.messageId,
|
|
2935
|
+
...respondedAtField(response.raw)
|
|
2577
2936
|
};
|
|
2578
2937
|
}
|
|
2579
2938
|
|
|
@@ -2581,7 +2940,7 @@ function parseSabreCommandResponse(response) {
|
|
|
2581
2940
|
var OTA_NAMESPACE = "http://www.opentravel.org/OTA/2002/11";
|
|
2582
2941
|
var SWS_NAMESPACE = "http://webservices.sabre.com";
|
|
2583
2942
|
var sessionSend = /* @__PURE__ */ Symbol("sabre-client.session.send");
|
|
2584
|
-
function
|
|
2943
|
+
function fail6(message) {
|
|
2585
2944
|
throw new TypeError(`sabre-client: ${message}`);
|
|
2586
2945
|
}
|
|
2587
2946
|
var SESSION_ERROR_KINDS = /* @__PURE__ */ new Map([
|
|
@@ -2610,6 +2969,8 @@ var KEEP_ALIVE_IDLE_MS = 13 * 6e4;
|
|
|
2610
2969
|
var SabreSession = class {
|
|
2611
2970
|
/** Queue Management: contar, recorrer y colocar en colas, dentro de esta sesión. */
|
|
2612
2971
|
queue;
|
|
2972
|
+
/** La reserva: el historial de un PNR, dentro de esta sesión. */
|
|
2973
|
+
reservation;
|
|
2613
2974
|
#transport;
|
|
2614
2975
|
#pcc;
|
|
2615
2976
|
#conversationId;
|
|
@@ -2637,6 +2998,7 @@ var SabreSession = class {
|
|
|
2637
2998
|
pcc: internal.pcc,
|
|
2638
2999
|
log: internal.transport.log
|
|
2639
3000
|
});
|
|
3001
|
+
this.reservation = new SessionReservation({ send: (request) => this[sessionSend](request) });
|
|
2640
3002
|
if (internal.keepAlive) this.#scheduleKeepAlive(KEEP_ALIVE_IDLE_MS);
|
|
2641
3003
|
}
|
|
2642
3004
|
/**
|
|
@@ -2692,11 +3054,11 @@ var SabreSession = class {
|
|
|
2692
3054
|
return this.#send(request);
|
|
2693
3055
|
}
|
|
2694
3056
|
async #send(request) {
|
|
2695
|
-
if (this.#closing !== void 0)
|
|
3057
|
+
if (this.#closing !== void 0) fail6("session closed");
|
|
2696
3058
|
if (this.#unusable !== void 0) {
|
|
2697
3059
|
throw new TypeError(`sabre-client: session unusable (${this.#unusable.kind})`, { cause: this.#unusable });
|
|
2698
3060
|
}
|
|
2699
|
-
if (this.#busy)
|
|
3061
|
+
if (this.#busy) fail6("session busy");
|
|
2700
3062
|
this.#busy = true;
|
|
2701
3063
|
try {
|
|
2702
3064
|
const response = await this.#transport.request({ ...request, ...this.#envelopeFields() });
|
|
@@ -2711,6 +3073,7 @@ var SabreSession = class {
|
|
|
2711
3073
|
}
|
|
2712
3074
|
async #sendClose(options) {
|
|
2713
3075
|
if (this.#unusable !== void 0) return;
|
|
3076
|
+
const { onRawRequest: _skipped, ...closeOptions } = options;
|
|
2714
3077
|
this.#busy = true;
|
|
2715
3078
|
try {
|
|
2716
3079
|
await this.#transport.request({
|
|
@@ -2721,7 +3084,7 @@ var SabreSession = class {
|
|
|
2721
3084
|
// Solo se reintenta ante `USG_*_BUSY`: tras un fallo de red no se sabe si ya se cerró.
|
|
2722
3085
|
idempotent: false,
|
|
2723
3086
|
operation: "close",
|
|
2724
|
-
...
|
|
3087
|
+
...closeOptions
|
|
2725
3088
|
});
|
|
2726
3089
|
} catch (error) {
|
|
2727
3090
|
throw this.#markUnusable(error);
|
|
@@ -2851,7 +3214,7 @@ function usernameToken(credentials) {
|
|
|
2851
3214
|
}
|
|
2852
3215
|
async function openSession(transport, credentials, options = {}) {
|
|
2853
3216
|
const { keepAlive = false, ...requestOptions } = options;
|
|
2854
|
-
if (typeof keepAlive !== "boolean")
|
|
3217
|
+
if (typeof keepAlive !== "boolean") fail6("keepAlive debe ser un boolean");
|
|
2855
3218
|
const conversationId = globalThis.crypto.randomUUID();
|
|
2856
3219
|
const request = {
|
|
2857
3220
|
service: "sessionCreate",
|
|
@@ -2956,27 +3319,27 @@ var SabreLegacy = class {
|
|
|
2956
3319
|
|
|
2957
3320
|
// src/auth/credentials.ts
|
|
2958
3321
|
var DEFAULT_DOMAIN = "AA";
|
|
2959
|
-
function
|
|
3322
|
+
function fail7(message) {
|
|
2960
3323
|
throw new TypeError(`sabre-client: ${message}`);
|
|
2961
3324
|
}
|
|
2962
3325
|
function requireString2(value, field) {
|
|
2963
|
-
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`);
|
|
2964
3327
|
}
|
|
2965
3328
|
function buildCredentials(options) {
|
|
2966
|
-
if (typeof options !== "object" || options === null)
|
|
3329
|
+
if (typeof options !== "object" || options === null) fail7("buildCredentials recibe un objeto");
|
|
2967
3330
|
const authVersion = options.authVersion ?? "v3";
|
|
2968
3331
|
if (authVersion !== "v3" && authVersion !== "v2") {
|
|
2969
|
-
|
|
3332
|
+
fail7(`authVersion debe ser 'v3' o 'v2' (recibido: ${JSON.stringify(authVersion)})`);
|
|
2970
3333
|
}
|
|
2971
3334
|
const { user, pcc, password, clientId, clientSecret } = options;
|
|
2972
3335
|
const domain = options.domain ?? DEFAULT_DOMAIN;
|
|
2973
3336
|
requireString2(user, "user");
|
|
2974
3337
|
requireString2(password, "password");
|
|
2975
|
-
if (!isPseudoCityCode(pcc))
|
|
3338
|
+
if (!isPseudoCityCode(pcc)) fail7("pcc debe tener 3 o 4 caracteres alfanum\xE9ricos");
|
|
2976
3339
|
requireString2(domain, "domain");
|
|
2977
|
-
if (/[-:]/.test(domain))
|
|
3340
|
+
if (/[-:]/.test(domain)) fail7("domain no puede contener '-' ni ':'");
|
|
2978
3341
|
if (clientId === void 0 !== (clientSecret === void 0)) {
|
|
2979
|
-
|
|
3342
|
+
fail7("clientId y clientSecret van juntos: pasa los dos o ninguno");
|
|
2980
3343
|
}
|
|
2981
3344
|
if (authVersion === "v3" || clientId !== void 0) {
|
|
2982
3345
|
requireString2(clientId, authVersion === "v3" ? "clientId (requerido con authVersion v3)" : "clientId");
|
|
@@ -3285,6 +3648,22 @@ function hostTime(text) {
|
|
|
3285
3648
|
return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`;
|
|
3286
3649
|
}
|
|
3287
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
|
+
|
|
3288
3667
|
// src/air/seat-availability-request.ts
|
|
3289
3668
|
function buildSeatAvailabilityRequest(source) {
|
|
3290
3669
|
const { group, itinerary, segments: only, currencyCode } = source;
|
|
@@ -3369,7 +3748,7 @@ function fareComponent(fareComponentId, component) {
|
|
|
3369
3748
|
}
|
|
3370
3749
|
|
|
3371
3750
|
// src/index.ts
|
|
3372
|
-
var VERSION = "0.
|
|
3751
|
+
var VERSION = "0.5.0";
|
|
3373
3752
|
export {
|
|
3374
3753
|
Sabre,
|
|
3375
3754
|
SabreBusinessError,
|
|
@@ -3385,6 +3764,7 @@ export {
|
|
|
3385
3764
|
buildRevalidateItineraryRequest,
|
|
3386
3765
|
buildSeatAvailabilityRequest,
|
|
3387
3766
|
parseQueueParagraph,
|
|
3767
|
+
parseQueuePlaceRemark,
|
|
3388
3768
|
resolveHostDate
|
|
3389
3769
|
};
|
|
3390
3770
|
//# sourceMappingURL=index.js.map
|